From 1a2718741d516ddef14530b83786a8daa28d7317 Mon Sep 17 00:00:00 2001 From: Christian Niessner Date: Thu, 10 Mar 2016 11:41:34 +0100 Subject: [PATCH] This is the initial version of an 'java like' method matching for OGNL. This fixes: https://github.com/jkuhnert/ognl/issues/16 https://github.com/jkuhnert/ognl/issues/17 https://issues.apache.org/jira/browse/OGNL-250 and maybe even more issues related to wrong methods. But it's a huge change in OGNL method matching so it also might break some things. All unit tests pass for Java 7 and Java 8. --- pom.xml | 4 +- src/java/ognl/ASTCtor.java | 5 + src/java/ognl/ASTMethod.java | 75 ++- src/java/ognl/OgnlRuntime.java | 484 +++++++++++++----- src/test/java/org/ognl/test/MethodTest.java | 24 +- src/test/java/org/ognl/test/PropertyTest.java | 6 +- .../ognl/test/objects/MethodTestMethods.java | 59 +++ src/test/java/org/ognl/test/objects/Root.java | 2 +- .../java/org/ognl/test/objects/Simple.java | 5 + 9 files changed, 531 insertions(+), 133 deletions(-) create mode 100644 src/test/java/org/ognl/test/objects/MethodTestMethods.java diff --git a/pom.xml b/pom.xml index 93985d24..7c83b795 100644 --- a/pom.xml +++ b/pom.xml @@ -66,9 +66,9 @@ test - javassist + org.javassist javassist - 3.11.0.GA + 3.20.0-GA diff --git a/src/java/ognl/ASTCtor.java b/src/java/ognl/ASTCtor.java index 4435d34c..7650c32c 100644 --- a/src/java/ognl/ASTCtor.java +++ b/src/java/ognl/ASTCtor.java @@ -62,6 +62,11 @@ void setClassName(String className) this.className = className; } + Class getCreatedClass(OgnlContext context) throws ClassNotFoundException { + return OgnlRuntime.classForName(context, className); + } + + void setArray(boolean value) { isArray = value; diff --git a/src/java/ognl/ASTMethod.java b/src/java/ognl/ASTMethod.java index b9112205..627ef34e 100644 --- a/src/java/ognl/ASTMethod.java +++ b/src/java/ognl/ASTMethod.java @@ -35,6 +35,7 @@ import ognl.enhance.UnsupportedCompilationException; import java.lang.reflect.Method; +import java.util.List; /** * @author Luke Blanshard (blanshlu@netscape.net) @@ -154,13 +155,13 @@ public String toGetSourceString(OgnlContext context, Object target) Method m = null; try { - m = OgnlRuntime.getMethod(context, context.getCurrentType() != null ? context.getCurrentType() : target.getClass(), _methodName, _children, false); + Class[] argumentClasses = getChildrenClasses(context, _children); if (m == null) - m = OgnlRuntime.getReadMethod(target.getClass(), _methodName, _children != null ? _children.length : -1); + m = OgnlRuntime.getReadMethod(target.getClass(), _methodName, argumentClasses); if (m == null) { - m = OgnlRuntime.getWriteMethod(target.getClass(), _methodName, _children != null ? _children.length : -1); + m = OgnlRuntime.getWriteMethod(target.getClass(), _methodName, argumentClasses); if (m != null) { @@ -327,7 +328,7 @@ public String toSetSourceString(OgnlContext context, Object target) + " last child? " + lastChild(context));*/ Method m = OgnlRuntime.getWriteMethod(context.getCurrentType() != null ? context.getCurrentType() : target.getClass(), - _methodName, _children != null ? _children.length : -1); + _methodName, getChildrenClasses(context, _children)); if (m == null) { throw new UnsupportedCompilationException("Unable to determine setter method generation for " + _methodName); @@ -486,4 +487,70 @@ public String toSetSourceString(OgnlContext context, Object target) return result + ")" + post; } + + private static Class getClassMatchingAllChildren(OgnlContext context, Node[] _children) { + Class[] cc = getChildrenClasses(context, _children); + Class componentType = null; + for (int j = 0; j < cc.length; j++) { + Class ic = cc[j]; + if (ic == null) { + componentType = Object.class; // fall back to object... + break; + } else { + if (componentType == null) { + componentType = ic; + } else { + if (!componentType.isAssignableFrom(ic)) { + if (ic.isAssignableFrom(componentType)) { + componentType = ic; // just swap... ic is more generic... + } else { + Class pc; + while ((pc = componentType.getSuperclass()) != null) { // TODO hmm - it could also be that an interface matches... + if (pc.isAssignableFrom(ic)) { + componentType = pc; // use this matching parent class + break; + } + } + if (!componentType.isAssignableFrom(ic)) { + // parents didn't match. the types might be primitives. Fall back to object. + componentType = Object.class; + break; + } + } + } + } + } + } + if (componentType == null) + componentType = Object.class; + return componentType; + } + + private static Class[] getChildrenClasses(OgnlContext context, Node[] _children) { + if (_children == null) + return null; + Class[] argumentClasses = new Class[_children.length]; + for (int i = 0; i < _children.length; i++) { + Node child = _children[i]; + if (child instanceof ASTList) { // special handling for ASTList - it creates a List + //Class componentType = getClassMatchingAllChildren(context, ((ASTList)child)._children); + //argumentClasses[i] = Array.newInstance(componentType, 0).getClass(); + argumentClasses[i] = List.class; + } else if (child instanceof NodeType) { + argumentClasses[i] = ((NodeType)child).getGetterClass(); + } else if (child instanceof ASTCtor) { + try { + argumentClasses[i] = ((ASTCtor)child).getCreatedClass(context); + } catch (ClassNotFoundException nfe) { + throw OgnlOps.castToRuntime(nfe); + } + } else if (child instanceof ASTTest) { + argumentClasses[i] = getClassMatchingAllChildren(context, ((ASTTest)child)._children); + } else { + throw new UnsupportedOperationException("Don't know how to handle child: "+child); + } + } + return argumentClasses; + } + } diff --git a/src/java/ognl/OgnlRuntime.java b/src/java/ognl/OgnlRuntime.java index 25d33f24..299efb0c 100644 --- a/src/java/ognl/OgnlRuntime.java +++ b/src/java/ognl/OgnlRuntime.java @@ -928,26 +928,109 @@ else if (c.getSuperclass() == Number.class) { return c; } + public static Class[] getArgClasses(Object[] args) { + if (args == null) + return null; + Class[] argClasses = new Class[args.length]; + for (int i=0; i= classes.length){ - break; - } + /* + * varArg's start with a penalty of 1000. + * There are some java compiler rules that are hopefully reflectet by this penalty: + * * Legacy beats Varargs + * * Widening beats Varargs + * * Boxing beats Varargs + */ + ArgsCompatbilityReport report = new ArgsCompatbilityReport(1000, new boolean[args.length]); + /* + * varargs signature is: method(type1, type2, typeN, typeV ...) + * This means: All arguments up to typeN needs exact matching, all varargs need to match typeV + */ + if (classes.length - 1 > args.length) + // we don't have enough arguments to provide the required 'fixed' arguments + return null; - result = isTypeCompatible(args[index], classes[index]); + // type check on fixed arguments + for (int index = 0, count = classes.length - 1; index < count; ++index) + if (!isTypeCompatible(args[index], classes[index], index, report)) + return null; - if (!result && classes[index].isArray()) { - result = isTypeCompatible(args[index], classes[index].getComponentType()); - } - } + // type check on varargs + Class varArgsType = classes[classes.length - 1].getComponentType(); + for (int index = classes.length - 1, count = args.length; index < count; ++index) + if (!isTypeCompatible(args[index], varArgsType, index, report)) + return null; + + return report; } else { - for (int index = 0, count = args.length; result && (index < count); ++index) { - result = isTypeCompatible(args[index], classes[index]); - } + ArgsCompatbilityReport report = new ArgsCompatbilityReport(0, new boolean[args.length]); + for (int index = 0, count = args.length; index < count; ++index) + if (!isTypeCompatible(args[index], classes[index], index, report)) + return null; + return report; } - return result; } /** @@ -1108,28 +1231,6 @@ public static boolean getConvertedTypes(OgnlContext context, Object target, Memb return result; } - public static Method getConvertedMethodAndArgs(OgnlContext context, Object target, String propertyName, - List methods, Object[] args, Object[] newArgs) - { - Method result = null; - TypeConverter converter = context.getTypeConverter(); - - if ((converter != null) && (methods != null)) - { - for (int i = 0, icount = methods.size(); (result == null) && (i < icount); i++) - { - Method m = (Method) methods.get(i); - Class[] parameterTypes = findParameterTypes(target != null ? target.getClass() : null, m);//getParameterTypes(m); - - if (getConvertedTypes(context, target, m, propertyName, parameterTypes, args, newArgs)) - { - result = m; - } - } - } - return result; - } - public static Constructor getConvertedConstructorAndArgs(OgnlContext context, Object target, List constructors, Object[] args, Object[] newArgs) { @@ -1166,40 +1267,32 @@ public static Constructor getConvertedConstructorAndArgs(OgnlContext context, Ob * @return Best method match or null if none could be found. */ public static Method getAppropriateMethod(OgnlContext context, Object source, Object target, String propertyName, - List methods, Object[] args, Object[] actualArgs) + String methodName, List methods, Object[] args, Object[] actualArgs) { Method result = null; - Class[] resultParameterTypes = null; if (methods != null) { - for (int i = 0, icount = methods.size(); i < icount; i++) + Class typeClass = target != null ? target.getClass() : null; + if (typeClass == null && source != null && Class.class.isInstance(source)) { - Method m = (Method) methods.get(i); - - Class typeClass = target != null ? target.getClass() : null; - if (typeClass == null && source != null && Class.class.isInstance(source)) - { - typeClass = (Class)source; - } + typeClass = (Class)source; + } + Class[] argClasses = getArgClasses(args); - Class[] mParameterTypes = findParameterTypes(typeClass, m); + MatchingMethod mm = findBestMethod(methods, typeClass, methodName, argClasses); + if (mm != null) { + result = mm.mMethod; + Class[] mParameterTypes = mm.mParameterTypes; + System.arraycopy(args, 0, actualArgs, 0, args.length); - if (areArgsCompatible(args, mParameterTypes, m) - && ((result == null) || isMoreSpecific(mParameterTypes, resultParameterTypes))) + for (int j = 0; j < mParameterTypes.length; j++) { - result = m; - resultParameterTypes = mParameterTypes; - System.arraycopy(args, 0, actualArgs, 0, args.length); + Class type = mParameterTypes[j]; - for (int j = 0; j < mParameterTypes.length; j++) + if (mm.report.conversionNeeded[j] || (type.isPrimitive() && (actualArgs[j] == null))) { - Class type = mParameterTypes[j]; - - if (type.isPrimitive() && (actualArgs[j] == null)) - { - actualArgs[j] = getConvertedType(context, source, result, propertyName, null, type); - } + actualArgs[j] = getConvertedType(context, source, result, propertyName, args[j], type); } } } @@ -1213,7 +1306,142 @@ public static Method getAppropriateMethod(OgnlContext context, Object source, Ob return result; } - public static Object callAppropriateMethod(OgnlContext context, Object source, Object target, String methodName, + public static Method getConvertedMethodAndArgs(OgnlContext context, Object target, String propertyName, + List methods, Object[] args, Object[] newArgs) { + Method result = null; + TypeConverter converter = context.getTypeConverter(); + + if ((converter != null) && (methods != null)) { + for (int i = 0, icount = methods.size(); (result == null) && (i < icount); i++) { + Method m = (Method) methods.get(i); + Class[] parameterTypes = findParameterTypes(target != null ? target.getClass() : null, m);//getParameterTypes(m); + + if (getConvertedTypes(context, target, m, propertyName, parameterTypes, args, newArgs)) { + result = m; + } + } + } + return result; + } + + private static class MatchingMethod { + Method mMethod; + int score; + ArgsCompatbilityReport report; + Class[] mParameterTypes; + + private MatchingMethod(Method method, int score, ArgsCompatbilityReport report, Class[] mParameterTypes) { + this.mMethod = method; + this.score = score; + this.report = report; + this.mParameterTypes = mParameterTypes; + } + } + + private static MatchingMethod findBestMethod(List methods, Class typeClass, String name, Class[] argClasses) { + MatchingMethod mm = null; + for (int i = 0, icount = methods.size(); i < icount; i++) { + Method m = (Method) methods.get(i); + + Class[] mParameterTypes = findParameterTypes(typeClass, m); + ArgsCompatbilityReport report = areArgsCompatible(argClasses, mParameterTypes, m); + if (report == null) + continue; + + String methodName = m.getName(); + int score = report.score; + if (name.equals(methodName)) { + // exact match - no additinal score... + } else if (name.equalsIgnoreCase(methodName)) { + // minimal penalty.. + score += 200; + } else if (methodName.toLowerCase().endsWith(name.toLowerCase())) { + // has a prefix... + score += 500; + } else { + // just in case... + score += 5000; + } + if (mm == null || mm.score > score) { + mm = new MatchingMethod(m, score, report, mParameterTypes); + } else if (mm.score == score) { + // it happens that we see the same method signature multiple times - for the current class or interfaces ... + // TODO why are all of them on the list and not only the most specific one? + // check for same signature + if (Arrays.equals(mm.mMethod.getParameterTypes(), m.getParameterTypes()) && mm.mMethod.getName().equals(m.getName())) { + boolean retsAreEqual = mm.mMethod.getReturnType().equals(m.getReturnType()); + // it is the same method. we use the most specific one... + if (mm.mMethod.getDeclaringClass().isAssignableFrom(m.getDeclaringClass())) { + if (!retsAreEqual && !mm.mMethod.getReturnType().isAssignableFrom(m.getReturnType())) + System.err.println("Two methods with same method signature but return types conflict? \""+mm.mMethod+"\" and \""+m+"\" please report!"); + + mm = new MatchingMethod(m, score, report, mParameterTypes); + } else if (!m.getDeclaringClass().isAssignableFrom(mm.mMethod.getDeclaringClass())) { + // this should't happen + System.err.println("Two methods with same method signature but not providing classes assignable? \""+mm.mMethod+"\" and \""+m+"\" please report!"); + } else if (!retsAreEqual && !m.getReturnType().isAssignableFrom(mm.mMethod.getReturnType())) + System.err.println("Two methods with same method signature but return types conflict? \""+mm.mMethod+"\" and \""+m+"\" please report!"); + } else { + // two methods with same score - direct compare to find the better one... + // legacy wins over varargs + if (isJdk15() && (m.isVarArgs() || mm.mMethod.isVarArgs())) { + if (m.isVarArgs() && !mm.mMethod.isVarArgs()) { + // keep with current + } else if (!m.isVarArgs() && mm.mMethod.isVarArgs()) { + // legacy wins... + mm = new MatchingMethod(m, score, report, mParameterTypes); + } else { + // both arguments are varargs... + System.err.println("Two vararg methods with same score("+score+"): \""+mm.mMethod+"\" and \""+m+"\" please report!"); + } + } else { + int scoreCurr = 0; + int scoreOther = 0; + for (int j=0; j scoreOther) { + // other wins... + mm = new MatchingMethod(m, score, report, mParameterTypes); + } // else current one wins... + } + } + } + } + return mm; + } + + public static Object callAppropriateMethod(OgnlContext context, Object source, Object target, String methodName, String propertyName, List methods, Object[] args) throws MethodFailedException { @@ -1221,7 +1449,7 @@ public static Object callAppropriateMethod(OgnlContext context, Object source, O Object[] actualArgs = _objectArrayPool.create(args.length); try { - Method method = getAppropriateMethod(context, source, target, propertyName, methods, args, actualArgs); + Method method = getAppropriateMethod(context, source, target, propertyName, methodName, methods, args, actualArgs); if ((method == null) || !isMethodAccessible(context, source, method, propertyName)) { @@ -1444,7 +1672,7 @@ public static final Object getMethodValue(OgnlContext context, Object target, St Object result = null; Method m = getGetMethod(context, (target == null) ? null : target.getClass() , propertyName); if (m == null) - m = getReadMethod((target == null) ? null : target.getClass(), propertyName, 0); + m = getReadMethod((target == null) ? null : target.getClass(), propertyName, null); if (checkAccessAndExistence) { @@ -2655,10 +2883,10 @@ public static Method getMethod(OgnlContext context, Class target, String name, */ public static Method getReadMethod(Class target, String name) { - return getReadMethod(target, name, -1); + return getReadMethod(target, name, null); } - public static Method getReadMethod(Class target, String name, int numParms) + public static Method getReadMethod(Class target, String name, Class[] argClasses) { try { if (name.indexOf('"') >= 0) @@ -2670,8 +2898,7 @@ public static Method getReadMethod(Class target, String name, int numParms) MethodDescriptor[] methods = info.getMethodDescriptors(); // exact matches first - - Method m = null; + ArrayList candidates = new ArrayList(); for (int i = 0; i < methods.length; i++) { @@ -2685,26 +2912,14 @@ public static Method getReadMethod(Class target, String name, int numParms) || methods[i].getName().toLowerCase().equals("is" + name)) && !methods[i].getName().startsWith("set")) { - if (numParms > 0 && methods[i].getMethod().getParameterTypes().length == numParms) - return methods[i].getMethod(); - else if (numParms < 0) - { - if ( methods[i].getName().equals( name ) ) - { - return methods[i].getMethod(); - } - else if ( ( m != null - && m.getParameterTypes().length > methods[i].getMethod().getParameterTypes().length ) - || m == null) - { - m = methods[i].getMethod(); - } - } + candidates.add(methods[i].getMethod()); } } - - if (m != null) - return m; + if (!candidates.isEmpty()) { + MatchingMethod mm = findBestMethod(candidates, target, name, argClasses); + if (mm != null) + return mm.mMethod; + } for (int i = 0; i < methods.length; i++) { @@ -2715,26 +2930,35 @@ else if ( ( m != null && !methods[i].getName().startsWith("set") && methods[i].getMethod().getReturnType() != Void.TYPE) { - if (numParms > 0 && methods[i].getMethod().getParameterTypes().length == numParms) - return methods[i].getMethod(); - else if (numParms < 0) - { - if ((m != null && m.getParameterTypes().length > methods[i].getMethod().getParameterTypes().length) - || m == null) - { - m = methods[i].getMethod(); - } - } + Method m = methods[i].getMethod(); + if (!candidates.contains(m)) + candidates.add(m); } } - if (m != null) - return m; + if (!candidates.isEmpty()) { + MatchingMethod mm = findBestMethod(candidates, target, name, argClasses); + if (mm != null) + return mm.mMethod; + } // try one last time adding a get to beginning - if (!name.startsWith("get")) - return OgnlRuntime.getReadMethod(target, "get" + name, numParms); + if (!name.startsWith("get")) { + Method ret = OgnlRuntime.getReadMethod(target, "get" + name, argClasses); + if (ret != null) + return ret; + } + + if (!candidates.isEmpty()) { + // we need to do conversions. + // TODO we have to find out which conversions are possible! + int reqArgCount = argClasses==null?0:argClasses.length; + for (Method m : candidates) { + if (m.getParameterTypes().length == reqArgCount) + return m; + } + } } catch (Throwable t) { @@ -2746,10 +2970,10 @@ else if (numParms < 0) public static Method getWriteMethod(Class target, String name) { - return getWriteMethod(target, name, -1); + return getWriteMethod(target, name, null); } - public static Method getWriteMethod(Class target, String name, int numParms) + public static Method getWriteMethod(Class target, String name, Class[] argClasses) { try { if (name.indexOf('"') >= 0) @@ -2758,6 +2982,8 @@ public static Method getWriteMethod(Class target, String name, int numParms) BeanInfo info = Introspector.getBeanInfo(target); MethodDescriptor[] methods = info.getMethodDescriptors(); + ArrayList candidates = new ArrayList(); + for (int i = 0; i < methods.length; i++) { if (!isMethodCallable(methods[i].getMethod())) continue; @@ -2767,13 +2993,15 @@ public static Method getWriteMethod(Class target, String name, int numParms) || methods[i].getName().toLowerCase().equals("set" + name.toLowerCase())) && !methods[i].getName().startsWith("get")) { - if (numParms > 0 && methods[i].getMethod().getParameterTypes().length == numParms) - return methods[i].getMethod(); - else if (numParms < 0) - return methods[i].getMethod(); + candidates.add(methods[i].getMethod()); } } + if (!candidates.isEmpty()) { + MatchingMethod mm = findBestMethod(candidates, target, name, argClasses); + if (mm != null) + return mm.mMethod; + } // try again on pure class Method[] cmethods = target.getClass().getMethods(); @@ -2786,19 +3014,39 @@ else if (numParms < 0) || cmethods[i].getName().toLowerCase().equals("set" + name.toLowerCase())) && !cmethods[i].getName().startsWith("get")) { - if (numParms > 0 && cmethods[i].getParameterTypes().length == numParms) - return cmethods[i]; - else if (numParms < 0) - return cmethods[i]; + Method m = methods[i].getMethod(); + if (!candidates.contains(m)) + candidates.add(m); } } + if (!candidates.isEmpty()) { + MatchingMethod mm = findBestMethod(candidates, target, name, argClasses); + if (mm != null) + return mm.mMethod; + } // try one last time adding a set to beginning if (!name.startsWith("set")) { - return OgnlRuntime.getReadMethod(target, "set" + name, numParms); - } + Method ret = OgnlRuntime.getReadMethod(target, "set" + name, argClasses); + if (ret != null) + return ret; + } + + if (!candidates.isEmpty()) { + // we need to do conversions. + // TODO we have to find out which conversions are possible! + int reqArgCount = argClasses==null?0:argClasses.length; + for (Method m : candidates) { + if (m.getParameterTypes().length == reqArgCount) + return m; + } + if ( argClasses == null && candidates.size() == 1 ) { + // this seems to be the TestCase TestOgnlRuntime.test_Complicated_Inheritance() - is this a real world use case? + return candidates.get(0); + } + } } catch (Throwable t) { throw OgnlOps.castToRuntime(t); diff --git a/src/test/java/org/ognl/test/MethodTest.java b/src/test/java/org/ognl/test/MethodTest.java index 499842f0..8719b87e 100644 --- a/src/test/java/org/ognl/test/MethodTest.java +++ b/src/test/java/org/ognl/test/MethodTest.java @@ -44,10 +44,10 @@ public class MethodTest extends OgnlTestCase { "hashCode()", new Integer(ROOT.hashCode()) } , { "getBooleanValue() ? \"here\" : \"\"", ""}, { "getValueIsTrue(!false) ? \"\" : \"here\" ", ""}, - { "messages.format('ShowAllCount', one)", "foo"}, // FIXME lukaszlenart: OGNL converts this one parameter into Object[] :\ - { "messages.format('ShowAllCount', {one})", "foo"}, - { "messages.format('ShowAllCount', {one, two})", "foo"}, - { "messages.format('ShowAllCount', one, two)", "haha"}, + { "messages.format('ShowAllCount', one)", ROOT.getMessages().format("ShowAllCount", ROOT.getOne()) }, + { "messages.format('ShowAllCount', {one})", ROOT.getMessages().format("ShowAllCount", new Object[] { ROOT.getOne() }) }, + { "messages.format('ShowAllCount', {one, two})", ROOT.getMessages().format("ShowAllCount", new Object[] { ROOT.getOne(), ROOT.getTwo() }) }, + { "messages.format('ShowAllCount', one, two)", ROOT.getMessages().format("ShowAllCount", ROOT.getOne(), ROOT.getTwo()) }, { "getTestValue(@org.ognl.test.objects.SimpleEnum@ONE.value)", new Integer(2)}, { "@org.ognl.test.MethodTest@getA().isProperty()", Boolean.FALSE}, { "isDisabled()", Boolean.TRUE}, @@ -56,7 +56,21 @@ public class MethodTest extends OgnlTestCase { LIST, "addValue(name)", Boolean.TRUE}, { "getDisplayValue(methodsTest.allowDisplay)", "test"}, { "isThisVarArgsWorking(three, rootValue)", Boolean.TRUE}, - { GENERIC, "service.getFullMessageFor(value, null)", "Halo 3"} + { GENERIC, "service.getFullMessageFor(value, null)", "Halo 3"}, + // TestCase for https://github.com/jkuhnert/ognl/issues/17 - ArrayIndexOutOfBoundsException when trying to access BeanFactory + { "testMethods.getBean('TestBean')", ROOT.getTestMethods().getBean("TestBean") } , + // https://issues.apache.org/jira/browse/OGNL-250 - OnglRuntime getMethodValue fails to find method matching propertyName + { "testMethods.testProperty", ROOT.getTestMethods().testProperty() } , + // TODO: some further java <> OGNL inconsistencies: - related to https://github.com/jkuhnert/ognl/issues/16 + // Java 'ROOT.getTestMethods().argsTest1(Arrays.asList( ROOT.getOne() )' doesn't compile: + // --> The method argsTest1(Object[]) in the type MethodTestMethods is not applicable for the arguments (List) + { "testMethods.argsTest1({one})", "Array: [[1]]" }, // "Array: [[1]]" means dual-cast is done. + // Java: ROOT.getTestMethods().argsTest(Arrays.asList( ROOT.getOne() )) + // --> The method argsTest2(List) in the type MethodTestMethods is not applicable for the arguments (List) + { "testMethods.argsTest2({one})", "List: [1]" }, + // Java 'ROOT.getTestMethods().argsTest1(Arrays.asList( ROOT.getOne() )' doesn't compile: + // --> The method argsTest(Object[]) in the type MethodTestMethods is not applicable for the arguments (List) + { "testMethods.argsTest3({one})", "List: [1]" }, }; public static class A diff --git a/src/test/java/org/ognl/test/PropertyTest.java b/src/test/java/org/ognl/test/PropertyTest.java index d5a2fab9..610df1ac 100644 --- a/src/test/java/org/ognl/test/PropertyTest.java +++ b/src/test/java/org/ognl/test/PropertyTest.java @@ -89,9 +89,9 @@ public class PropertyTest extends OgnlTestCase { ROOT, "property.bean3.value != null", Boolean.TRUE}, { ROOT, "\"background-color:blue; width:\" + (currentLocaleVerbosity / 2) + \"px\"", "background-color:blue; width:43px"}, { ROOT, "renderNavigation ? '' : 'noborder'", "noborder" }, - { ROOT, "format('key', array)", "formatted" }, - { ROOT, "format('key', intValue)", "formatted" }, - { ROOT, "format('key', map.size)", "formatted" }, + { ROOT, "format('key', array)", ROOT.format("key", ROOT.getArray()) }, + { ROOT, "format('key', intValue)", ROOT.format("key", /*ROOT.getIntValue()*/ 2) }, // getIntValue() is 0 during startup, but set to 2 during tests! + { ROOT, "format('key', map.size)", ROOT.format("key", ROOT.getMap().size()) }, { ROOT, "'disableButton(this,\"' + map.get('button-testing') + '\");clearElement("testFtpMessage")'", "disableButton(this,'null');clearElement('testFtpMessage')" }, { ROOT.getMap(), "!disableWarning", Boolean.TRUE}, diff --git a/src/test/java/org/ognl/test/objects/MethodTestMethods.java b/src/test/java/org/ognl/test/objects/MethodTestMethods.java new file mode 100644 index 00000000..77e77ccc --- /dev/null +++ b/src/test/java/org/ognl/test/objects/MethodTestMethods.java @@ -0,0 +1,59 @@ +package org.ognl.test.objects; + +import java.util.Arrays; +import java.util.List; + +public class MethodTestMethods { + //--------------------------------------------------------------------- + // TestCase for https://github.com/jkuhnert/ognl/issues/17 - ArrayIndexOutOfBoundsException when trying to access BeanFactory + // Implementation of BeanFactory interface + //--------------------------------------------------------------------- + + public Object getBean(String name) { + return "NamedBean: "+name; + } + + public T getBean(String name, Class requiredType) { + return (T) ("NamedTypedBean: "+name+" "+requiredType.getSimpleName()); + } + + public T getBean(Class requiredType) { + return (T) ("TypedBean: "+requiredType.getSimpleName()); + } + + public Object getBean(String name, Object... args) { + return "NamedBeanWithArgs: "+name+" "+Arrays.toString(args); + } + + //--------------------------------------------------------------------- + // https://issues.apache.org/jira/browse/OGNL-250 - OnglRuntime getMethodValue fails to find method matching propertyName + //--------------------------------------------------------------------- + + private String testProperty = "Hello World!"; + + public String testProperty() { + return testProperty; + } + + //--------------------------------------------------------------------- + // Tests related to https://github.com/jkuhnert/ognl/issues/16 + // Argument matching tests + //--------------------------------------------------------------------- + + public String argsTest1(Object[] data) { + return "Array: "+Arrays.toString(data); + } + + public String argsTest2(List data) { + return "List: "+data; + } + + public String argsTest3(Object[] data) { + return "Array: "+Arrays.toString(data); + } + + public String argsTest3(List data) { + return "List: "+data; + } + +} diff --git a/src/test/java/org/ognl/test/objects/Root.java b/src/test/java/org/ognl/test/objects/Root.java index 5ee2eb15..d63c8576 100644 --- a/src/test/java/org/ognl/test/objects/Root.java +++ b/src/test/java/org/ognl/test/objects/Root.java @@ -210,7 +210,7 @@ public String format(String key, Object value) public String format(String key, Object[] value) { - return "formatted"; + return "formatted: "+key+" "+Arrays.toString(value); } public String getCurrentClass(String value) diff --git a/src/test/java/org/ognl/test/objects/Simple.java b/src/test/java/org/ognl/test/objects/Simple.java index f269c501..18731658 100644 --- a/src/test/java/org/ognl/test/objects/Simple.java +++ b/src/test/java/org/ognl/test/objects/Simple.java @@ -173,6 +173,11 @@ public Root getRootValue() return root; } + public MethodTestMethods getTestMethods() + { + return new MethodTestMethods(); + } + public Messages getMessages() { return _messages;