/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* KIND, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
*/ | |
package org.codehaus.groovy.reflection; | |
import org.codehaus.groovy.GroovyBugError; | |
import org.codehaus.groovy.runtime.MetaClassHelper; | |
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; | |
import org.codehaus.groovy.runtime.wrappers.Wrapper; | |
import java.lang.reflect.Array; | |
public class ParameterTypes | |
{ | |
protected volatile Class [] nativeParamTypes; | |
protected volatile CachedClass [] parameterTypes; | |
protected boolean isVargsMethod; | |
public ParameterTypes () { | |
} | |
public ParameterTypes(Class pt []) { | |
nativeParamTypes = pt; | |
} | |
public ParameterTypes(String pt[]) { | |
nativeParamTypes = new Class[pt.length]; | |
for (int i = 0; i != pt.length; ++i) { | |
try { | |
nativeParamTypes[i] = Class.forName(pt[i]); | |
} | |
catch (ClassNotFoundException e){ | |
NoClassDefFoundError err = new NoClassDefFoundError(); | |
err.initCause(e); | |
throw err; | |
} | |
} | |
} | |
public ParameterTypes(CachedClass[] parameterTypes) { | |
setParametersTypes(parameterTypes); | |
} | |
protected final void setParametersTypes(CachedClass[] pt) { | |
this.parameterTypes = pt; | |
isVargsMethod = pt.length > 0 && pt [pt.length-1].isArray; | |
} | |
public CachedClass[] getParameterTypes() { | |
if (parameterTypes == null) { | |
getParametersTypes0(); | |
} | |
return parameterTypes; | |
} | |
private synchronized void getParametersTypes0() { | |
if (parameterTypes != null) | |
return; | |
Class [] npt = nativeParamTypes == null ? getPT() : nativeParamTypes; | |
CachedClass[] pt = new CachedClass [npt.length]; | |
for (int i = 0; i != npt.length; ++i) | |
pt[i] = ReflectionCache.getCachedClass(npt[i]); | |
nativeParamTypes = npt; | |
setParametersTypes(pt); | |
} | |
public Class[] getNativeParameterTypes() { | |
if (nativeParamTypes == null) { | |
getNativeParameterTypes0(); | |
} | |
return nativeParamTypes; | |
} | |
private synchronized void getNativeParameterTypes0() { | |
if (nativeParamTypes != null) | |
return; | |
Class [] npt; | |
if (parameterTypes != null) { | |
npt = new Class [parameterTypes.length]; | |
for (int i = 0; i != parameterTypes.length; ++i) { | |
npt[i] = parameterTypes[i].getTheClass(); | |
} | |
} | |
else | |
npt = getPT (); | |
nativeParamTypes = npt; | |
} | |
protected Class[] getPT() { throw new UnsupportedOperationException(getClass().getName()); } | |
public boolean isVargsMethod() { | |
return isVargsMethod; | |
} | |
public boolean isVargsMethod(Object[] arguments) { | |
// Uncomment if at some point this method can be called before parameterTypes initialized | |
// getParameterTypes(); | |
if(!isVargsMethod) | |
return false; | |
final int lenMinus1 = parameterTypes.length - 1; | |
// -1 because the varg part is optional | |
if (lenMinus1 == arguments.length) return true; | |
if (lenMinus1 > arguments.length) return false; | |
if (arguments.length > parameterTypes.length) return true; | |
// only case left is arguments.length == parameterTypes.length | |
Object last = arguments[arguments.length - 1]; | |
if (last == null) return true; | |
Class clazz = last.getClass(); | |
return !clazz.equals(parameterTypes[lenMinus1].getTheClass()); | |
} | |
public final Object[] coerceArgumentsToClasses(Object[] argumentArray) { | |
// Uncomment if at some point this method can be called before parameterTypes initialized | |
// getParameterTypes(); | |
argumentArray = correctArguments(argumentArray); | |
final CachedClass[] pt = parameterTypes; | |
final int len = argumentArray.length; | |
for (int i = 0; i < len; i++) { | |
final Object argument = argumentArray[i]; | |
if (argument != null) { | |
argumentArray[i] = pt[i].coerceArgument(argument); | |
} | |
} | |
return argumentArray; | |
} | |
public Object[] correctArguments(Object[] argumentArray) { | |
// correct argumentArray's length | |
if (argumentArray == null) { | |
return MetaClassHelper.EMPTY_ARRAY; | |
} | |
final CachedClass[] pt = getParameterTypes(); | |
if (pt.length == 1 && argumentArray.length == 0) { | |
if (isVargsMethod) | |
return new Object[]{Array.newInstance(pt[0].getTheClass().getComponentType(), 0)}; | |
else | |
return MetaClassHelper.ARRAY_WITH_NULL; | |
} | |
if (isVargsMethod && isVargsMethod(argumentArray)) { | |
return fitToVargs(argumentArray, pt); | |
} | |
return argumentArray; | |
} | |
/** | |
* this method is called when the number of arguments to a method is greater than 1 | |
* and if the method is a vargs method. This method will then transform the given | |
* arguments to make the method callable | |
* | |
* @param argumentArrayOrig the arguments used to call the method | |
* @param paramTypes the types of the parameters the method takes | |
*/ | |
private static Object[] fitToVargs(Object[] argumentArrayOrig, CachedClass[] paramTypes) { | |
Class vargsClassOrig = paramTypes[paramTypes.length - 1].getTheClass().getComponentType(); | |
Class vargsClass = ReflectionCache.autoboxType(vargsClassOrig); | |
Object[] argumentArray = argumentArrayOrig.clone(); | |
MetaClassHelper.unwrap(argumentArray); | |
if (argumentArray.length == paramTypes.length - 1) { | |
// the vargs argument is missing, so fill it with an empty array | |
Object[] newArgs = new Object[paramTypes.length]; | |
System.arraycopy(argumentArray, 0, newArgs, 0, argumentArray.length); | |
Object vargs = Array.newInstance(vargsClass, 0); | |
newArgs[newArgs.length - 1] = vargs; | |
return newArgs; | |
} else if (argumentArray.length == paramTypes.length) { | |
// the number of arguments is correct, but if the last argument | |
// is no array we have to wrap it in a array. If the last argument | |
// is null, then we don't have to do anything | |
Object lastArgument = argumentArray[argumentArray.length - 1]; | |
if (lastArgument != null && !lastArgument.getClass().isArray()) { | |
// no array so wrap it | |
Object wrapped = makeCommonArray(argumentArray, paramTypes.length - 1, vargsClass); | |
Object[] newArgs = new Object[paramTypes.length]; | |
System.arraycopy(argumentArray, 0, newArgs, 0, paramTypes.length - 1); | |
newArgs[newArgs.length - 1] = wrapped; | |
return newArgs; | |
} else { | |
// we may have to box the argument! | |
return argumentArray; | |
} | |
} else if (argumentArray.length > paramTypes.length) { | |
// the number of arguments is too big, wrap all exceeding elements | |
// in an array, but keep the old elements that are no vargs | |
Object[] newArgs = new Object[paramTypes.length]; | |
// copy arguments that are not a varg | |
System.arraycopy(argumentArray, 0, newArgs, 0, paramTypes.length - 1); | |
// create a new array for the vargs and copy them | |
Object vargs = makeCommonArray(argumentArray, paramTypes.length - 1, vargsClass); | |
newArgs[newArgs.length - 1] = vargs; | |
return newArgs; | |
} else { | |
throw new GroovyBugError("trying to call a vargs method without enough arguments"); | |
} | |
} | |
private static Object makeCommonArray(Object[] arguments, int offset, Class baseClass) { | |
Object[] result = (Object[]) Array.newInstance(baseClass, arguments.length - offset); | |
for (int i=offset; i<arguments.length; i++) { | |
Object v = arguments[i]; | |
v = DefaultTypeTransformation.castToType(v,baseClass); | |
result[i-offset] = v; | |
} | |
return result; | |
} | |
public boolean isValidMethod(Class[] arguments) { | |
if (arguments == null) return true; | |
final int size = arguments.length; | |
CachedClass[] pt = getParameterTypes(); | |
final int paramMinus1 = pt.length-1; | |
if (isVargsMethod && size >= paramMinus1) | |
return isValidVarargsMethod(arguments, size, pt, paramMinus1); | |
else | |
if (pt.length == size) | |
return isValidExactMethod(arguments, pt); | |
else | |
if (pt.length == 1 && size == 0 && !pt[0].isPrimitive) | |
return true; | |
return false; | |
} | |
private static boolean isValidExactMethod(Class[] arguments, CachedClass[] pt) { | |
// lets check the parameter types match | |
int size = pt.length; | |
for (int i = 0; i < size; i++) { | |
if (!pt[i].isAssignableFrom(arguments[i])) { | |
return false; | |
} | |
} | |
return true; | |
} | |
public boolean isValidExactMethod(Object [] args) { | |
// lets check the parameter types match | |
getParametersTypes0(); | |
int size = args.length; | |
if (size != parameterTypes.length) | |
return false; | |
for (int i = 0; i < size; i++) { | |
if (args[i] != null && !parameterTypes[i].isAssignableFrom(args[i].getClass())) { | |
return false; | |
} | |
} | |
return true; | |
} | |
public boolean isValidExactMethod(Class [] args) { | |
// lets check the parameter types match | |
getParametersTypes0(); | |
int size = args.length; | |
if (size != parameterTypes.length) | |
return false; | |
for (int i = 0; i < size; i++) { | |
if (args[i] != null && !parameterTypes[i].isAssignableFrom(args[i])) { | |
return false; | |
} | |
} | |
return true; | |
} | |
private static boolean testComponentAssignable(Class toTestAgainst, Class toTest) { | |
Class component = toTest.getComponentType(); | |
if (component==null) return false; | |
return MetaClassHelper.isAssignableFrom(toTestAgainst, component); | |
} | |
private static boolean isValidVarargsMethod(Class[] arguments, int size, CachedClass[] pt, int paramMinus1) { | |
// first check normal number of parameters | |
for (int i = 0; i < paramMinus1; i++) { | |
if (pt[i].isAssignableFrom(arguments[i])) continue; | |
return false; | |
} | |
// check direct match | |
CachedClass varg = pt[paramMinus1]; | |
Class clazz = varg.getTheClass().getComponentType(); | |
if ( size==pt.length && | |
(varg.isAssignableFrom(arguments[paramMinus1]) || | |
testComponentAssignable(clazz, arguments[paramMinus1]))) | |
{ | |
return true; | |
} | |
// check varged | |
for (int i = paramMinus1; i < size; i++) { | |
if (MetaClassHelper.isAssignableFrom(clazz, arguments[i])) continue; | |
return false; | |
} | |
return true; | |
} | |
public boolean isValidMethod(Object[] arguments) { | |
if (arguments == null) return true; | |
final int size = arguments.length; | |
CachedClass[] paramTypes = getParameterTypes(); | |
final int paramMinus1 = paramTypes.length-1; | |
if ( size >= paramMinus1 && paramTypes.length > 0 && | |
paramTypes[(paramMinus1)].isArray) | |
{ | |
// first check normal number of parameters | |
for (int i = 0; i < paramMinus1; i++) { | |
if (paramTypes[i].isAssignableFrom(getArgClass(arguments[i]))) continue; | |
return false; | |
} | |
// check direct match | |
CachedClass varg = paramTypes[paramMinus1]; | |
Class clazz = varg.getTheClass().getComponentType(); | |
if ( size==paramTypes.length && | |
(varg.isAssignableFrom(getArgClass(arguments[paramMinus1])) || | |
testComponentAssignable(clazz, getArgClass(arguments[paramMinus1])))) | |
{ | |
return true; | |
} | |
// check varged | |
for (int i = paramMinus1; i < size; i++) { | |
if (MetaClassHelper.isAssignableFrom(clazz, getArgClass(arguments[i]))) continue; | |
return false; | |
} | |
return true; | |
} else if (paramTypes.length == size) { | |
// lets check the parameter types match | |
for (int i = 0; i < size; i++) { | |
if (paramTypes[i].isAssignableFrom(getArgClass(arguments[i]))) continue; | |
return false; | |
} | |
return true; | |
} else if (paramTypes.length == 1 && size == 0 && !paramTypes[0].isPrimitive) { | |
return true; | |
} | |
return false; | |
} | |
private static Class getArgClass(Object arg) { | |
Class cls; | |
if (arg == null) { | |
cls = null; | |
} else { | |
if (arg instanceof Wrapper) { | |
cls = ((Wrapper)arg).getType(); | |
} | |
else | |
cls = arg.getClass(); | |
} | |
return cls; | |
} | |
} |