/* * 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 final static 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; } }