Author: markt Date: Tue Sep 17 12:51:17 2013 New Revision: 1524019 URL: http://svn.apache.org/r1524019 Log: Revert r1523988. Using java.beans.expression was a lot simpler in terms of code volume but it failed to handle varargs correctly and triggered some unit test failures.
Modified: tomcat/trunk/java/javax/el/BeanELResolver.java tomcat/trunk/java/javax/el/StaticFieldELResolver.java tomcat/trunk/java/javax/el/Util.java Modified: tomcat/trunk/java/javax/el/BeanELResolver.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/javax/el/BeanELResolver.java?rev=1524019&r1=1524018&r2=1524019&view=diff ============================================================================== --- tomcat/trunk/java/javax/el/BeanELResolver.java (original) +++ tomcat/trunk/java/javax/el/BeanELResolver.java Tue Sep 17 12:51:17 2013 @@ -166,16 +166,16 @@ public class BeanELResolver extends ELRe String methodName = (String) factory.coerceToType(method, String.class); - java.beans.Expression beanExpression = - new java.beans.Expression(base, methodName, params); + // Find the matching method + Method matchingMethod = + Util.findMethod(base.getClass(), methodName, paramTypes, params); + + Object[] parameters = Util.buildParameters( + matchingMethod.getParameterTypes(), matchingMethod.isVarArgs(), + params); - Object result; + Object result = null; try { - result = beanExpression.getValue(); - } catch (Exception e) { - throw new ELException(e); - } -/* try { result = matchingMethod.invoke(base, parameters); } catch (IllegalArgumentException | IllegalAccessException e) { throw new ELException(e); @@ -184,7 +184,7 @@ public class BeanELResolver extends ELRe Util.handleThrowable(cause); throw new ELException(cause); } -*/ + context.setPropertyResolved(base, method); return result; } Modified: tomcat/trunk/java/javax/el/StaticFieldELResolver.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/javax/el/StaticFieldELResolver.java?rev=1524019&r1=1524018&r2=1524019&view=diff ============================================================================== --- tomcat/trunk/java/javax/el/StaticFieldELResolver.java (original) +++ tomcat/trunk/java/javax/el/StaticFieldELResolver.java Tue Sep 17 12:51:17 2013 @@ -17,7 +17,10 @@ package javax.el; import java.beans.FeatureDescriptor; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Iterator; @@ -89,29 +92,61 @@ public class StaticFieldELResolver exten throw new NullPointerException(); } - Object result = null; - if (base instanceof ELClass && method instanceof String) { context.setPropertyResolved(base, method); + Class<?> clazz = ((ELClass) base).getKlass(); String methodName = (String) method; + if ("<init>".equals(methodName)) { - // java.beans.Expression uses 'new' for constructors - methodName = "new"; - } - Class<?> clazz = ((ELClass) base).getKlass(); + Constructor<?> match = + Util.findConstructor(clazz, paramTypes, params); - java.beans.Expression beanExpression = - new java.beans.Expression(clazz, methodName, params); + Object[] parameters = Util.buildParameters( + match.getParameterTypes(), match.isVarArgs(), params); - try { - result = beanExpression.getValue(); - } catch (Exception e) { - throw new ELException(e); + Object result = null; + + try { + result = match.newInstance(parameters); + } catch (IllegalArgumentException | IllegalAccessException | + InstantiationException e) { + throw new ELException(e); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + Util.handleThrowable(cause); + throw new ELException(cause); + } + return result; + + } else { + Method match = + Util.findMethod(clazz, methodName, paramTypes, params); + + int modifiers = match.getModifiers(); + if (!Modifier.isStatic(modifiers)) { + throw new MethodNotFoundException(Util.message(context, + "staticFieldELResolver.methodNotFound", methodName, + clazz.getName())); + } + + Object[] parameters = Util.buildParameters( + match.getParameterTypes(), match.isVarArgs(), params); + + Object result = null; + try { + result = match.invoke(null, parameters); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new ELException(e); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + Util.handleThrowable(cause); + throw new ELException(cause); + } + return result; } } - - return result; + return null; } @Override Modified: tomcat/trunk/java/javax/el/Util.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/javax/el/Util.java?rev=1524019&r1=1524018&r2=1524019&view=diff ============================================================================== --- tomcat/trunk/java/javax/el/Util.java (original) +++ tomcat/trunk/java/javax/el/Util.java Tue Sep 17 12:51:17 2013 @@ -17,12 +17,19 @@ package javax.el; import java.lang.ref.WeakReference; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; 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.List; import java.util.Locale; +import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; @@ -187,6 +194,321 @@ class Util { * This method duplicates code in org.apache.el.util.ReflectionUtil. When * making changes keep the code in sync. */ + static Method findMethod(Class<?> clazz, String methodName, + Class<?>[] paramTypes, Object[] paramValues) { + + if (clazz == null || methodName == null) { + throw new MethodNotFoundException( + message(null, "util.method.notfound", 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, (Method) result.unWrap()); + } + + /* + * This method duplicates code in org.apache.el.util.ReflectionUtil. When + * making changes keep the code in sync. + */ + @SuppressWarnings("null") + private static Wrapper findWrapper(Class<?> clazz, List<Wrapper> wrappers, + String name, Class<?>[] paramTypes, Object[] paramValues) { + + Map<Wrapper,Integer> candidates = new HashMap<>(); + + 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))) { + // Method has wrong number of parameters + continue; + } + + // Check the parameters match + int exactMatch = 0; + boolean noMatch = false; + for (int i = 0; i < mParamCount; i++) { + // Can't be null + if (mParamTypes[i].equals(paramTypes[i])) { + exactMatch++; + } else if (i == (mParamCount - 1) && w.isVarArgs()) { + Class<?> varType = mParamTypes[i].getComponentType(); + for (int j = i; j < paramCount; j++) { + if (!isAssignableFrom(paramTypes[j], varType)) { + if (paramValues == null) { + noMatch = true; + break; + } else { + if (!isCoercibleFrom(paramValues[j], varType)) { + noMatch = true; + break; + } + } + } + // Don't treat a varArgs match as an exact match, it can + // lead to a varArgs method matching when the result + // should be ambiguous + } + } else if (!isAssignableFrom(paramTypes[i], mParamTypes[i])) { + if (paramValues == null) { + noMatch = true; + break; + } else { + if (!isCoercibleFrom(paramValues[i], mParamTypes[i])) { + noMatch = true; + break; + } + } + } + } + if (noMatch) { + continue; + } + + // If a method is found where every parameter matches exactly, + // return it + if (exactMatch == paramCount) { + return w; + } + + candidates.put(w, Integer.valueOf(exactMatch)); + } + + // Look for the method that has the highest number of parameters where + // the type matches exactly + int bestMatch = 0; + Wrapper match = null; + boolean multiple = false; + for (Map.Entry<Wrapper, Integer> entry : candidates.entrySet()) { + if (entry.getValue().intValue() > bestMatch || + match == null) { + bestMatch = entry.getValue().intValue(); + match = entry.getKey(); + multiple = false; + } else if (entry.getValue().intValue() == bestMatch) { + multiple = true; + } + } + if (multiple) { + if (bestMatch == paramCount - 1) { + // Only one parameter is not an exact match - try using the + // super class + match = resolveAmbiguousWrapper(candidates.keySet(), paramTypes); + } else { + match = null; + } + + if (match == null) { + // If multiple methods have the same matching number of parameters + // the match is ambiguous so throw an exception + throw new MethodNotFoundException(message( + null, "util.method.ambiguous", clazz, name, + paramString(paramTypes))); + } + } + + // Handle case where no match at all was found + if (match == null) { + throw new MethodNotFoundException(message( + null, "util.method.notfound", clazz, name, + paramString(paramTypes))); + } + + return match; + } + + + 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; + } + + + /* + * This method duplicates code in org.apache.el.util.ReflectionUtil. When + * making changes keep the code in sync. + */ + private static Wrapper resolveAmbiguousWrapper(Set<Wrapper> candidates, + Class<?>[] paramTypes) { + // Identify which parameter isn't an exact match + Wrapper w = candidates.iterator().next(); + + int nonMatchIndex = 0; + Class<?> nonMatchClass = null; + + for (int i = 0; i < paramTypes.length; i++) { + if (w.getParameterTypes()[i] != paramTypes[i]) { + nonMatchIndex = i; + nonMatchClass = paramTypes[i]; + break; + } + } + + if (nonMatchClass == null) { + // Null will always be ambiguous + return null; + } + + for (Wrapper c : candidates) { + if (c.getParameterTypes()[nonMatchIndex] == + paramTypes[nonMatchIndex]) { + // Methods have different non-matching parameters + // Result is ambiguous + return null; + } + } + + // Can't be null + Class<?> superClass = nonMatchClass.getSuperclass(); + while (superClass != null) { + for (Wrapper c : candidates) { + if (c.getParameterTypes()[nonMatchIndex].equals(superClass)) { + // Found a match + return c; + } + } + superClass = superClass.getSuperclass(); + } + + // Treat instances of Number as a special case + Wrapper match = null; + if (Number.class.isAssignableFrom(nonMatchClass)) { + for (Wrapper c : candidates) { + Class<?> candidateType = c.getParameterTypes()[nonMatchIndex]; + if (Number.class.isAssignableFrom(candidateType) || + candidateType.isPrimitive()) { + if (match == null) { + match = c; + } else { + // Match still ambiguous + match = null; + break; + } + } + } + } + + return match; + } + + + /* + * This method duplicates code in org.apache.el.util.ReflectionUtil. When + * making changes keep the code in sync. + */ + private static boolean isAssignableFrom(Class<?> src, Class<?> target) { + // src will always be an object + // Short-cut. null is always assignable to an object and in EL null + // can always be coerced to a valid value for a primitive + if (src == null) { + return true; + } + + Class<?> targetClass; + if (target.isPrimitive()) { + if (target == Boolean.TYPE) { + targetClass = Boolean.class; + } else if (target == Character.TYPE) { + targetClass = Character.class; + } else if (target == Byte.TYPE) { + targetClass = Byte.class; + } else if (target == Short.TYPE) { + targetClass = Short.class; + } else if (target == Integer.TYPE) { + targetClass = Integer.class; + } else if (target == Long.TYPE) { + targetClass = Long.class; + } else if (target == Float.TYPE) { + targetClass = Float.class; + } else { + targetClass = Double.class; + } + } else { + targetClass = target; + } + return targetClass.isAssignableFrom(src); + } + + + /* + * This method duplicates code in org.apache.el.util.ReflectionUtil. When + * making changes keep the code in sync. + */ + 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 { + getExpressionFactory().coerceToType(src, target); + } catch (ELException 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; + } + + + /* + * This method duplicates code in org.apache.el.util.ReflectionUtil. When + * making changes keep the code in sync. + */ static Method getMethod(Class<?> type, Method m) { if (m == null || Modifier.isPublic(type.getModifiers())) { return m; @@ -218,4 +540,164 @@ class Util { } return null; } + + + static Constructor<?> findConstructor(Class<?> clazz, Class<?>[] paramTypes, + Object[] paramValues) { + + String methodName = "<init>"; + + if (clazz == null) { + throw new MethodNotFoundException( + message(null, "util.method.notfound", clazz, methodName, + paramString(paramTypes))); + } + + if (paramTypes == null) { + paramTypes = getTypesFromValues(paramValues); + } + + Constructor<?>[] constructors = clazz.getConstructors(); + + List<Wrapper> wrappers = Wrapper.wrap(constructors); + + Wrapper result = findWrapper( + clazz, wrappers, methodName, paramTypes, paramValues); + + if (result == null) { + return null; + } + return getConstructor(clazz, (Constructor<?>) result.unWrap()); + } + + + 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 Object[] buildParameters(Class<?>[] parameterTypes, + boolean isVarArgs,Object[] params) { + ExpressionFactory factory = getExpressionFactory(); + Object[] parameters = null; + if (parameterTypes.length > 0) { + parameters = new Object[parameterTypes.length]; + int paramCount = params.length; + if (isVarArgs) { + int varArgIndex = parameterTypes.length - 1; + // First argCount-1 parameters are standard + for (int i = 0; (i < varArgIndex); i++) { + parameters[i] = factory.coerceToType(params[i], + parameterTypes[i]); + } + // Last parameter is the varargs + 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, + factory.coerceToType(params[i], varArgClass)); + } + parameters[varArgIndex] = varargs; + } else { + parameters = new Object[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + parameters[i] = factory.coerceToType(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(); + } + + + 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(); + } + } + + 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(); + } + } } --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org