| /* |
| * Copyright (c) 1997, 2022 Oracle and/or its affiliates and others. |
| * All rights reserved. |
| * Copyright 2004 The Apache Software Foundation |
| * |
| * Licensed 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 jakarta.el; |
| |
| import java.lang.reflect.AccessibleObject; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.MissingResourceException; |
| import java.util.ResourceBundle; |
| |
| /** |
| * Utility methods for this portion of the Jakarta Expression Language implementation |
| * |
| * <p> |
| * Methods on this class use a Map instance stored in ThreadLocal storage to minimize the performance impact on |
| * operations that take place multiple times on a single Thread. The keys and values of the Map are implementation |
| * private. |
| * |
| * @author edburns |
| * @author Kin-man Chung |
| * @author Dongbin Nie |
| */ |
| class ELUtil { |
| |
| /** |
| * This class may not be constructed. |
| */ |
| private ELUtil() { |
| } |
| |
| /** |
| * <p> |
| * The <code>ThreadLocal</code> variable used to record the <code>jakarta.faces.context.FacesContext</code> instance for |
| * each processing thread. |
| * </p> |
| */ |
| private static ThreadLocal<Map<String, ResourceBundle>> instance = new ThreadLocal<>() { |
| @Override |
| protected Map<String, ResourceBundle> initialValue() { |
| return (null); |
| } |
| }; |
| |
| /** |
| * @return a Map stored in ThreadLocal storage. This may be used by methods of this class to minimize the performance |
| * impact for operations that may take place multiple times on a given Thread instance. |
| */ |
| private static Map<String, ResourceBundle> getCurrentInstance() { |
| Map<String, ResourceBundle> result = instance.get(); |
| if (result == null) { |
| result = new HashMap<>(); |
| setCurrentInstance(result); |
| } |
| |
| return result; |
| |
| } |
| |
| /** |
| * Replace the Map with the argument context. |
| * |
| * @param context the Map to be stored in ThreadLocal storage. |
| */ |
| private static void setCurrentInstance(Map<String, ResourceBundle> context) { |
| instance.set(context); |
| } |
| |
| /** |
| * Convenience method, calls through to getExceptionMessageString(ELContext,java.lang.String,Object []). |
| * |
| * @param context the ELContext from which the Locale for this message is extracted. |
| * @param messageId the messageId String in the ResourceBundle |
| * |
| * @return a localized String for the argument messageId |
| */ |
| public static String getExceptionMessageString(ELContext context, String messageId) { |
| return getExceptionMessageString(context, messageId, null); |
| } |
| |
| /* |
| * <p>Return a Localized message String suitable for use as an Exception message. Examine the argument |
| * <code>context</code> for a <code>Locale</code>. If not present, use <code>Locale.getDefault()</code>. Load the |
| * <code>ResourceBundle</code> "jakarta.el.Messages" using that locale. Get the message string for argument |
| * <code>messageId</code>. If not found return "Missing Resource in Jakarta Expression Language implementation ??? messageId ???" with messageId |
| * substituted with the runtime value of argument <code>messageId</code>. If found, and argument <code>params</code> is |
| * non-null, format the message using the params. If formatting fails, return a sensible message including the |
| * <code>messageId</code>. If argument <code>params</code> is <code>null</code>, skip formatting and return the message |
| * directly, otherwise return the formatted message.</p> |
| * |
| * @param context the ELContext from which the Locale for this message is extracted. |
| * |
| * @param messageId the messageId String in the ResourceBundle |
| * |
| * @param params parameters to the message |
| * |
| * @return a localized String for the argument messageId |
| */ |
| public static String getExceptionMessageString(ELContext context, String messageId, Object[] params) { |
| String result = ""; |
| Locale locale = null; |
| |
| if (null == context || null == messageId) { |
| return result; |
| } |
| |
| if (null == (locale = context.getLocale())) { |
| locale = Locale.getDefault(); |
| } |
| |
| if (locale != null) { |
| Map<String, ResourceBundle> threadMap = getCurrentInstance(); |
| ResourceBundle resourceBundle = null; |
| if (null == (resourceBundle = threadMap.get(locale.toString()))) { |
| resourceBundle = ResourceBundle.getBundle("jakarta.el.PrivateMessages", locale); |
| threadMap.put(locale.toString(), resourceBundle); |
| } |
| |
| if (null != resourceBundle) { |
| try { |
| result = resourceBundle.getString(messageId); |
| if (null != params) { |
| result = MessageFormat.format(result, params); |
| } |
| } catch (IllegalArgumentException iae) { |
| result = "Can't get localized message: parameters to message appear to be incorrect. Message to format: " + messageId; |
| } catch (MissingResourceException mre) { |
| result = "Missing Resource in Jakarta Expression Language implementation: ???" + messageId + "???"; |
| } catch (Exception e) { |
| result = "Exception resolving message in Jakarta Expression Language implementation: ???" + messageId + "???"; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| static Constructor<?> findConstructor(Class<?> klass, Class<?>[] paramTypes, Object[] params) { |
| String methodName = "<init>"; |
| |
| if (klass == null) { |
| throw new MethodNotFoundException("Method not found: " + klass + "." + methodName + "(" + paramString(paramTypes) + ")"); |
| } |
| |
| if (paramTypes == null) { |
| paramTypes = getTypesFromValues(params); |
| } |
| |
| Constructor<?>[] constructors = klass.getConstructors(); |
| |
| List<Wrapper> wrappers = Wrapper.wrap(constructors); |
| |
| Wrapper result = findWrapper(klass, wrappers, methodName, paramTypes, params); |
| |
| if (result == null) { |
| return null; |
| } |
| |
| return getConstructor(klass, (Constructor<?>) result.unWrap()); |
| } |
| |
| static Object invokeConstructor(ELContext context, Constructor<?> constructor, Object[] params) { |
| Object[] parameters = buildParameters(context, constructor.getParameterTypes(), constructor.isVarArgs(), params); |
| try { |
| return constructor.newInstance(parameters); |
| } catch (IllegalAccessException iae) { |
| throw new ELException(iae); |
| } catch (IllegalArgumentException iae) { |
| throw new ELException(iae); |
| } catch (InvocationTargetException ite) { |
| throw new ELException(ite.getCause()); |
| } catch (InstantiationException ie) { |
| throw new ELException(ie.getCause()); |
| } |
| } |
| |
| static Method findMethod(Class<?> klass, Object base, String methodName, Class<?>[] paramTypes, Object[] params, boolean staticOnly) { |
| Method method = findMethod(klass, base, methodName, paramTypes, params); |
| if (staticOnly && !Modifier.isStatic(method.getModifiers())) { |
| throw new MethodNotFoundException("Method " + methodName + "for class " + klass + " not found or accessible"); |
| } |
| |
| return method; |
| } |
| |
| static Object invokeMethod(ELContext context, Method method, Object base, Object[] params) { |
| |
| Object[] parameters = buildParameters(context, method.getParameterTypes(), method.isVarArgs(), params); |
| try { |
| return method.invoke(base, parameters); |
| } catch (IllegalAccessException iae) { |
| throw new ELException(iae); |
| } catch (IllegalArgumentException iae) { |
| throw new ELException(iae); |
| } catch (InvocationTargetException ite) { |
| throw new ELException(ite.getCause()); |
| } |
| } |
| |
| static Method findMethod(Class<?> clazz, Object base, String methodName, Class<?>[] paramTypes, Object[] paramValues) { |
| if (clazz == null || methodName == null) { |
| throw new MethodNotFoundException("Method not found: " + clazz + "." + methodName + "(" + paramString(paramTypes) + ")"); |
| } |
| |
| if (paramTypes == null) { |
| paramTypes = getTypesFromValues(paramValues); |
| } |
| |
| Method[] methods = clazz.getMethods(); |
| |
| List<Wrapper> wrappers = Wrapper.wrap(methods, methodName); |
| |
| Wrapper result = findWrapper(clazz, wrappers, methodName, paramTypes, paramValues); |
| |
| if (result == null) { |
| return null; |
| } |
| |
| return getMethod(clazz, base, (Method) result.unWrap()); |
| } |
| |
| @SuppressWarnings("null") |
| private static Wrapper findWrapper(Class<?> clazz, List<Wrapper> wrappers, String name, Class<?>[] paramTypes, Object[] paramValues) { |
| List<Wrapper> assignableCandidates = new ArrayList<>(); |
| List<Wrapper> coercibleCandidates = new ArrayList<>(); |
| List<Wrapper> varArgsCandidates = new ArrayList<>(); |
| |
| int paramCount; |
| if (paramTypes == null) { |
| paramCount = 0; |
| } else { |
| paramCount = paramTypes.length; |
| } |
| |
| for (Wrapper w : wrappers) { |
| Class<?>[] mParamTypes = w.getParameterTypes(); |
| int mParamCount; |
| if (mParamTypes == null) { |
| mParamCount = 0; |
| } else { |
| mParamCount = mParamTypes.length; |
| } |
| |
| // Check the number of parameters |
| if (!(paramCount == mParamCount || (w.isVarArgs() && paramCount >= mParamCount - 1))) { |
| // Method has wrong number of parameters |
| continue; |
| } |
| |
| // Check the parameters match |
| boolean assignable = false; |
| boolean coercible = false; |
| boolean varArgs = false; |
| boolean noMatch = false; |
| for (int i = 0; i < mParamCount; i++) { |
| if (i == (mParamCount - 1) && w.isVarArgs()) { |
| varArgs = true; |
| // exact var array type match |
| if (mParamCount == paramCount) { |
| if (mParamTypes[i] == paramTypes[i]) { |
| continue; |
| } |
| } |
| |
| // unwrap the array's component type |
| Class<?> varType = mParamTypes[i].getComponentType(); |
| for (int j = i; j < paramCount; j++) { |
| if (!isAssignableFrom(paramTypes[j], varType) |
| && !(paramValues != null && j < paramValues.length && isCoercibleFrom(paramValues[j], varType))) { |
| noMatch = true; |
| break; |
| } |
| } |
| } else if (mParamTypes[i].equals(paramTypes[i])) { |
| } else if (isAssignableFrom(paramTypes[i], mParamTypes[i])) { |
| assignable = true; |
| } else { |
| if (paramValues == null || i >= paramValues.length) { |
| noMatch = true; |
| break; |
| } else { |
| if (isCoercibleFrom(paramValues[i], mParamTypes[i])) { |
| coercible = true; |
| } else { |
| noMatch = true; |
| break; |
| } |
| } |
| } |
| } |
| if (noMatch) { |
| continue; |
| } |
| |
| if (varArgs) { |
| varArgsCandidates.add(w); |
| } else if (coercible) { |
| coercibleCandidates.add(w); |
| } else if (assignable) { |
| assignableCandidates.add(w); |
| } else { |
| // If a method is found where every parameter matches exactly, |
| // return it |
| return w; |
| } |
| |
| } |
| |
| String errorMsg = "Unable to find unambiguous method: " + clazz + "." + name + "(" + paramString(paramTypes) + ")"; |
| if (!assignableCandidates.isEmpty()) { |
| return findMostSpecificWrapper(assignableCandidates, paramTypes, false, errorMsg); |
| } else if (!coercibleCandidates.isEmpty()) { |
| return findMostSpecificWrapper(coercibleCandidates, paramTypes, true, errorMsg); |
| } else if (!varArgsCandidates.isEmpty()) { |
| return findMostSpecificWrapper(varArgsCandidates, paramTypes, true, errorMsg); |
| } else { |
| throw new MethodNotFoundException("Method not found: " + clazz + "." + name + "(" + paramString(paramTypes) + ")"); |
| } |
| } |
| |
| private static Wrapper findMostSpecificWrapper(List<Wrapper> candidates, Class<?>[] matchingTypes, boolean elSpecific, String errorMsg) { |
| List<Wrapper> ambiguouses = new ArrayList<>(); |
| for (Wrapper candidate : candidates) { |
| boolean lessSpecific = false; |
| |
| Iterator<Wrapper> it = ambiguouses.iterator(); |
| while (it.hasNext()) { |
| int result = isMoreSpecific(candidate, it.next(), matchingTypes, elSpecific); |
| if (result == 1) { |
| it.remove(); |
| } else if (result == -1) { |
| lessSpecific = true; |
| } |
| } |
| |
| if (!lessSpecific) { |
| ambiguouses.add(candidate); |
| } |
| } |
| |
| if (ambiguouses.size() > 1) { |
| throw new MethodNotFoundException(errorMsg); |
| } |
| |
| return ambiguouses.get(0); |
| } |
| |
| private static int isMoreSpecific(Wrapper wrapper1, Wrapper wrapper2, Class<?>[] matchingTypes, boolean elSpecific) { |
| Class<?>[] paramTypes1 = wrapper1.getParameterTypes(); |
| Class<?>[] paramTypes2 = wrapper2.getParameterTypes(); |
| |
| if (wrapper1.isVarArgs()) { |
| // JLS8 15.12.2.5 Choosing the Most Specific Method |
| int length = Math.max(Math.max(paramTypes1.length, paramTypes2.length), matchingTypes.length); |
| paramTypes1 = getComparingParamTypesForVarArgsMethod(paramTypes1, length); |
| paramTypes2 = getComparingParamTypesForVarArgsMethod(paramTypes2, length); |
| |
| if (length > matchingTypes.length) { |
| Class<?>[] matchingTypes2 = new Class<?>[length]; |
| System.arraycopy(matchingTypes, 0, matchingTypes2, 0, matchingTypes.length); |
| matchingTypes = matchingTypes2; |
| } |
| } |
| |
| int result = 0; |
| for (int i = 0; i < paramTypes1.length; i++) { |
| if (paramTypes1[i] != paramTypes2[i]) { |
| int r2 = isMoreSpecific(paramTypes1[i], paramTypes2[i], matchingTypes[i], elSpecific); |
| if (r2 == 1) { |
| if (result == -1) { |
| return 0; |
| } |
| result = 1; |
| } else if (r2 == -1) { |
| if (result == 1) { |
| return 0; |
| } |
| result = -1; |
| } else { |
| return 0; |
| } |
| } |
| } |
| |
| if (result == 0) { |
| // The nature of bridge methods is such that it actually |
| // doesn't matter which one we pick as long as we pick |
| // one. That said, pick the 'right' one (the non-bridge |
| // one) anyway. |
| result = Boolean.compare(wrapper1.isBridge(), wrapper2.isBridge()); |
| } |
| |
| return result; |
| } |
| |
| private static int isMoreSpecific(Class<?> type1, Class<?> type2, Class<?> matchingType, boolean elSpecific) { |
| type1 = getBoxingTypeIfPrimitive(type1); |
| type2 = getBoxingTypeIfPrimitive(type2); |
| if (type2.isAssignableFrom(type1)) { |
| return 1; |
| } else if (type1.isAssignableFrom(type2)) { |
| return -1; |
| } else { |
| if (elSpecific) { |
| /* |
| * Number will be treated as more specific |
| * |
| * ASTInteger only return Long or BigInteger, no Byte / Short / Integer. ASTFloatingPoint also. |
| * |
| */ |
| if (matchingType != null && Number.class.isAssignableFrom(matchingType)) { |
| boolean b1 = Number.class.isAssignableFrom(type1) || type1.isPrimitive(); |
| boolean b2 = Number.class.isAssignableFrom(type2) || type2.isPrimitive(); |
| if (b1 && !b2) { |
| return 1; |
| } else if (b2 && !b1) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| |
| return 0; |
| } else { |
| return 0; |
| } |
| } |
| } |
| |
| private static Class<?> getBoxingTypeIfPrimitive(Class<?> clazz) { |
| if (clazz.isPrimitive()) { |
| if (clazz == Boolean.TYPE) { |
| return Boolean.class; |
| } |
| if (clazz == Character.TYPE) { |
| return Character.class; |
| } |
| if (clazz == Byte.TYPE) { |
| return Byte.class; |
| } |
| if (clazz == Short.TYPE) { |
| return Short.class; |
| } |
| if (clazz == Integer.TYPE) { |
| return Integer.class; |
| } |
| if (clazz == Long.TYPE) { |
| return Long.class; |
| } |
| if (clazz == Float.TYPE) { |
| return Float.class; |
| } |
| |
| return Double.class; |
| } else { |
| return clazz; |
| } |
| } |
| |
| private static Class<?>[] getComparingParamTypesForVarArgsMethod(Class<?>[] paramTypes, int length) { |
| Class<?>[] result = new Class<?>[length]; |
| System.arraycopy(paramTypes, 0, result, 0, paramTypes.length - 1); |
| Class<?> type = paramTypes[paramTypes.length - 1].getComponentType(); |
| for (int i = paramTypes.length - 1; i < length; i++) { |
| result[i] = type; |
| } |
| |
| return result; |
| } |
| |
| private static final String paramString(Class<?>[] types) { |
| if (types != null) { |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < types.length; i++) { |
| if (types[i] == null) { |
| sb.append("null, "); |
| } else { |
| sb.append(types[i].getName()).append(", "); |
| } |
| } |
| if (sb.length() > 2) { |
| sb.setLength(sb.length() - 2); |
| } |
| return sb.toString(); |
| } |
| return null; |
| } |
| |
| static boolean isAssignableFrom(Class<?> src, Class<?> target) { |
| // src will always be an object |
| // Short-cut. null is always assignable to an object and in Jakarta Expression Language null |
| // can always be coerced to a valid value for a primitive |
| if (src == null) { |
| return true; |
| } |
| |
| target = getBoxingTypeIfPrimitive(target); |
| |
| return target.isAssignableFrom(src); |
| } |
| |
| private static boolean isCoercibleFrom(Object src, Class<?> target) { |
| // TODO: This isn't pretty but it works. Significant refactoring would |
| // be required to avoid the exception. |
| try { |
| ELManager.getExpressionFactory().coerceToType(src, target); |
| } catch (Exception e) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| private static Class<?>[] getTypesFromValues(Object[] values) { |
| if (values == null) { |
| return null; |
| } |
| |
| Class<?> result[] = new Class<?>[values.length]; |
| for (int i = 0; i < values.length; i++) { |
| if (values[i] == null) { |
| result[i] = null; |
| } else { |
| result[i] = values[i].getClass(); |
| } |
| } |
| |
| return result; |
| } |
| |
| /* |
| * Get a public method form a public class or interface of a given method. Note that if a PropertyDescriptor is obtained |
| * for a non-public class that implements a public interface, the read/write methods will be for the class, and |
| * therefore inaccessible. To correct this, a version of the same method must be found in a superclass or interface. |
| */ |
| static Method getMethod(Class<?> type, Object base, Method m) { |
| // If base is null, method MUST be static |
| // If base is non-null, method may be static or non-static |
| if (m == null || |
| (Modifier.isPublic(type.getModifiers()) && |
| (canAccess(base, m) || base != null && canAccess(null, m)))) { |
| return m; |
| } |
| Class<?>[] inf = type.getInterfaces(); |
| Method mp = null; |
| for (int i = 0; i < inf.length; i++) { |
| try { |
| mp = inf[i].getMethod(m.getName(), m.getParameterTypes()); |
| mp = getMethod(mp.getDeclaringClass(), base, mp); |
| if (mp != null) { |
| return mp; |
| } |
| } catch (NoSuchMethodException e) { |
| // Ignore |
| } |
| } |
| Class<?> sup = type.getSuperclass(); |
| if (sup != null) { |
| try { |
| mp = sup.getMethod(m.getName(), m.getParameterTypes()); |
| mp = getMethod(mp.getDeclaringClass(), base, mp); |
| if (mp != null) { |
| return mp; |
| } |
| } catch (NoSuchMethodException e) { |
| // Ignore |
| } |
| } |
| return null; |
| } |
| |
| static Constructor<?> getConstructor(Class<?> type, Constructor<?> c) { |
| if (c == null || Modifier.isPublic(type.getModifiers())) { |
| return c; |
| } |
| Constructor<?> cp = null; |
| Class<?> sup = type.getSuperclass(); |
| if (sup != null) { |
| try { |
| cp = sup.getConstructor(c.getParameterTypes()); |
| cp = getConstructor(cp.getDeclaringClass(), cp); |
| if (cp != null) { |
| return cp; |
| } |
| } catch (NoSuchMethodException e) { |
| // Ignore |
| } |
| } |
| return null; |
| } |
| |
| |
| static boolean canAccess(Object base, AccessibleObject accessibleObject) { |
| try { |
| return accessibleObject.canAccess(base); |
| } catch (IllegalArgumentException iae) { |
| return false; |
| } |
| } |
| |
| |
| @SuppressWarnings("null") // params cannot be null when used |
| static Object[] buildParameters(ELContext context, Class<?>[] parameterTypes, boolean isVarArgs, Object[] params) { |
| Object[] parameters = null; |
| if (parameterTypes.length > 0) { |
| parameters = new Object[parameterTypes.length]; |
| int paramCount = params == null ? 0 : params.length; |
| if (isVarArgs) { |
| int varArgIndex = parameterTypes.length - 1; |
| // First argCount-1 parameters are standard |
| for (int i = 0; (i < varArgIndex && i < paramCount); i++) { |
| parameters[i] = context.convertToType(params[i], parameterTypes[i]); |
| } |
| // Last parameter is the varargs |
| if (parameterTypes.length == paramCount && params[varArgIndex] != null && |
| parameterTypes[varArgIndex] == params[varArgIndex].getClass()) { |
| parameters[varArgIndex] = params[varArgIndex]; |
| } else { |
| Class<?> varArgClass = parameterTypes[varArgIndex].getComponentType(); |
| final Object varargs = Array.newInstance(varArgClass, (paramCount - varArgIndex)); |
| for (int i = (varArgIndex); i < paramCount; i++) { |
| Array.set(varargs, i - varArgIndex, context.convertToType(params[i], varArgClass)); |
| } |
| parameters[varArgIndex] = varargs; |
| } |
| } else { |
| for (int i = 0; i < parameterTypes.length && i < paramCount; i++) { |
| parameters[i] = context.convertToType(params[i], parameterTypes[i]); |
| } |
| } |
| } |
| return parameters; |
| } |
| |
| private abstract static class Wrapper { |
| |
| public static List<Wrapper> wrap(Method[] methods, String name) { |
| List<Wrapper> result = new ArrayList<>(); |
| for (Method method : methods) { |
| if (method.getName().equals(name)) { |
| result.add(new MethodWrapper(method)); |
| } |
| } |
| return result; |
| } |
| |
| public static List<Wrapper> wrap(Constructor<?>[] constructors) { |
| List<Wrapper> result = new ArrayList<>(); |
| for (Constructor<?> constructor : constructors) { |
| result.add(new ConstructorWrapper(constructor)); |
| } |
| return result; |
| } |
| |
| public abstract Object unWrap(); |
| |
| public abstract Class<?>[] getParameterTypes(); |
| |
| public abstract boolean isVarArgs(); |
| |
| public abstract boolean isBridge(); |
| } |
| |
| private static class MethodWrapper extends Wrapper { |
| private final Method m; |
| |
| public MethodWrapper(Method m) { |
| this.m = m; |
| } |
| |
| @Override |
| public Object unWrap() { |
| return m; |
| } |
| |
| @Override |
| public Class<?>[] getParameterTypes() { |
| return m.getParameterTypes(); |
| } |
| |
| @Override |
| public boolean isVarArgs() { |
| return m.isVarArgs(); |
| } |
| |
| @Override |
| public boolean isBridge() { |
| return m.isBridge(); |
| } |
| } |
| |
| private static class ConstructorWrapper extends Wrapper { |
| private final Constructor<?> c; |
| |
| public ConstructorWrapper(Constructor<?> c) { |
| this.c = c; |
| } |
| |
| @Override |
| public Object unWrap() { |
| return c; |
| } |
| |
| @Override |
| public Class<?>[] getParameterTypes() { |
| return c.getParameterTypes(); |
| } |
| |
| @Override |
| public boolean isVarArgs() { |
| return c.isVarArgs(); |
| } |
| |
| @Override |
| public boolean isBridge() { |
| return false; |
| } |
| } |
| } |