blob: 60690360b79f443e915b2a8b9658254915dd47d2 [file] [log] [blame]
/*
* 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 {
private static final Class[] NO_PARAMETERS = new Class[0];
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;
if (npt.length == 0) {
nativeParamTypes = NO_PARAMETERS;
setParametersTypes(CachedClass.EMPTY_ARRAY);
} else {
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;
}
}