Author: henrib Date: Tue Jan 16 19:34:30 2018 New Revision: 1821295 URL: http://svn.apache.org/viewvc?rev=1821295&view=rev Log: JEXL-246: 3rd times a charm... relaxing ambiguity rules wrt null / object; properly detect JexlArithmetic operator methods
Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest200.java commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java?rev=1821295&r1=1821294&r2=1821295&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java (original) +++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java Tue Jan 16 19:34:30 2018 @@ -20,7 +20,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Arrays; -import java.util.IdentityHashMap; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -298,34 +298,43 @@ public final class MethodKey { static Class<?> primitiveClass(Class<?> parm) { // it was marginally faster to get from the map than call isPrimitive... //if (!parm.isPrimitive()) return parm; - List<Class<?>> prim = CONVERTIBLES.get(parm); - return prim == null ? parm : prim.get(0); + Class<?>[] prim = CONVERTIBLES.get(parm); + return prim == null ? parm : prim[0]; + } + + /** + * Helper to build class arrays. + * @param args the classes + * @return the array + */ + private static Class<?>[] asArray(Class<?>... args) { + return args; } /** * Maps from primitive types to invocation compatible classes. * <p>Considering the key as a parameter type, the value is the list of argument classes that are invocation - * compatible with the parameter. Example is Long is invocation convertible to long. + * compatible with the parameter. Example is Long is invocation convertible to long. */ - private static final Map<Class<?>, List<Class<?>>> CONVERTIBLES; + private static final Map<Class<?>, Class<?>[]> CONVERTIBLES; static { - CONVERTIBLES = new IdentityHashMap<Class<?>, List<Class<?>>>(PRIMITIVE_SIZE); + CONVERTIBLES = new HashMap<Class<?>, Class<?>[]>(PRIMITIVE_SIZE); CONVERTIBLES.put(Boolean.TYPE, - Arrays.<Class<?>>asList(Boolean.class)); + asArray(Boolean.class)); CONVERTIBLES.put(Character.TYPE, - Arrays.<Class<?>>asList(Character.class)); + asArray(Character.class)); CONVERTIBLES.put(Byte.TYPE, - Arrays.<Class<?>>asList(Byte.class)); + asArray(Byte.class)); CONVERTIBLES.put(Short.TYPE, - Arrays.<Class<?>>asList(Short.class, Byte.class)); + asArray(Short.class, Byte.class)); CONVERTIBLES.put(Integer.TYPE, - Arrays.<Class<?>>asList(Integer.class, Short.class, Byte.class)); + asArray(Integer.class, Short.class, Byte.class)); CONVERTIBLES.put(Long.TYPE, - Arrays.<Class<?>>asList(Long.class, Integer.class, Short.class, Byte.class)); + asArray(Long.class, Integer.class, Short.class, Byte.class)); CONVERTIBLES.put(Float.TYPE, - Arrays.<Class<?>>asList(Float.class, Long.class, Integer.class, Short.class, Byte.class)); + asArray(Float.class, Long.class, Integer.class, Short.class, Byte.class)); CONVERTIBLES.put(Double.TYPE, - Arrays.<Class<?>>asList(Double.class, Float.class, Long.class, Integer.class, Short.class, Byte.class)); + asArray(Double.class, Float.class, Long.class, Integer.class, Short.class, Byte.class)); } /** @@ -333,19 +342,19 @@ public final class MethodKey { * <p>Considering the key as a parameter type, the value is the list of argument types that are invocation * compatible with the parameter. Example is 'int' is invocation convertible to 'long'. */ - private static final Map<Class<?>, List<Class<?>>> STRICT_CONVERTIBLES; + private static final Map<Class<?>, Class<?>[]> STRICT_CONVERTIBLES; static { - STRICT_CONVERTIBLES = new IdentityHashMap<Class<?>, List<Class<?>>>(PRIMITIVE_SIZE); + STRICT_CONVERTIBLES = new HashMap<Class<?>, Class<?>[]>(PRIMITIVE_SIZE); STRICT_CONVERTIBLES.put(Short.TYPE, - Arrays.<Class<?>>asList(Byte.TYPE)); + asArray(Byte.TYPE)); STRICT_CONVERTIBLES.put(Integer.TYPE, - Arrays.<Class<?>>asList(Short.TYPE, Byte.TYPE)); + asArray(Short.TYPE, Byte.TYPE)); STRICT_CONVERTIBLES.put(Long.TYPE, - Arrays.<Class<?>>asList(Integer.TYPE, Short.TYPE, Byte.TYPE)); + asArray(Integer.TYPE, Short.TYPE, Byte.TYPE)); STRICT_CONVERTIBLES.put(Float.TYPE, - Arrays.<Class<?>>asList(Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE)); + asArray(Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE)); STRICT_CONVERTIBLES.put(Double.TYPE, - Arrays.<Class<?>>asList(Float.TYPE, Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE)); + asArray(Float.TYPE, Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE)); } /** @@ -373,8 +382,13 @@ public final class MethodKey { } /* Primitive conversion check. */ if (formal.isPrimitive()) { - List<Class<?>> clist = strict ? STRICT_CONVERTIBLES.get(formal) : CONVERTIBLES.get(formal); - return clist.contains(actual); + Class<?>[] clist = strict ? STRICT_CONVERTIBLES.get(formal) : CONVERTIBLES.get(formal); + for(int c = 0; c < clist.length; ++c) { + if (actual == clist[c]) { + return true; + } + } + return false; } /* Check for vararg conversion. */ if (possibleVarArg && formal.isArray()) { @@ -469,8 +483,8 @@ public final class MethodKey { * @throws MethodKey.AmbiguousException if there is more than one. */ private T getMostSpecific(MethodKey key, T[] methods) { - final Class<?>[] classes = key.params; - LinkedList<T> applicables = getApplicables(methods, classes); + final Class<?>[] args = key.params; + LinkedList<T> applicables = getApplicables(methods, args); if (applicables.isEmpty()) { return null; } @@ -486,12 +500,12 @@ public final class MethodKey { */ LinkedList<T> maximals = new LinkedList<T>(); for (T app : applicables) { - Class<?>[] appArgs = getParameterTypes(app); + final Class<?>[] parms = getParameterTypes(app); boolean lessSpecific = false; Iterator<T> maximal = maximals.iterator(); while(!lessSpecific && maximal.hasNext()) { T max = maximal.next(); - switch (moreSpecific(appArgs, getParameterTypes(max))) { + switch (moreSpecific(args, parms, getParameterTypes(max))) { case MORE_SPECIFIC: /* * This method is more specific than the previously @@ -521,7 +535,7 @@ public final class MethodKey { } // if we have more than one maximally specific method, this call is ambiguous... if (maximals.size() > 1) { - throw ambiguousException(classes, applicables); + throw ambiguousException(args, applicables); } return maximals.getFirst(); } // CSON: RedundantThrows @@ -542,7 +556,7 @@ public final class MethodKey { * corresponding parameter of class 'Object'.</li> * </ul> * - * @param classes the argument classes + * @param classes the argument args * @param applicables the list of applicable methods or constructors * @return an ambiguous exception */ @@ -575,12 +589,13 @@ public final class MethodKey { * Determines which method signature (represented by a class array) is more * specific. This defines a partial ordering on the method signatures. * - * @param c1 first signature to compare - * @param c2 second signature to compare + * @param a the arguments signature + * @param c1 first method signature to compare + * @param c2 second method signature to compare * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if * c1 is less specific than c2, INCOMPARABLE if they are incomparable. */ - private int moreSpecific(Class<?>[] c1, Class<?>[] c2) { + private int moreSpecific(final Class<?>[] a, final Class<?>[] c1, final Class<?>[] c2) { boolean c1MoreSpecific = false; boolean c2MoreSpecific = false; @@ -601,6 +616,16 @@ public final class MethodKey { for (int i = 0; i < length; ++i) { if (c1[i] != c2[i]) { boolean last = (i == ultimate); + if (a[i] == Void.class) { + if (c1[i] == Object.class && c2[i] != Object.class) { + c1MoreSpecific = true; + continue; + } + if (c1[i] != Object.class && c2[i] == Object.class) { + c2MoreSpecific = true; + continue; + } + } c1MoreSpecific = c1MoreSpecific || isStrictConvertible(c2[i], c1[i], last); c2MoreSpecific = c2MoreSpecific || isStrictConvertible(c1[i], c2[i], last); } Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java?rev=1821295&r1=1821294&r2=1821295&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java (original) +++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java Tue Jan 16 19:34:30 2018 @@ -431,19 +431,13 @@ public class Uberspect implements JexlUb for (JexlOperator op : JexlOperator.values()) { Method[] methods = getMethods(arithmetic.getClass(), op.getMethodName()); if (methods != null) { - for (Method method : methods) { + mloop: for (Method method : methods) { Class<?>[] parms = method.getParameterTypes(); if (parms.length != op.getArity()) { continue; } // eliminate method(Object) and method(Object, Object) - boolean root = true; - for (int p = 0; root && p < parms.length; ++p) { - if (!Object.class.equals(parms[p])) { - root = false; - } - } - if (!root) { + if (!JexlArithmetic.class.equals(method.getDeclaringClass())) { ops.add(op); } } Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest200.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest200.java?rev=1821295&r1=1821294&r2=1821295&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest200.java (original) +++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest200.java Tue Jan 16 19:34:30 2018 @@ -347,71 +347,4 @@ public class IssuesTest200 extends JexlT } } } - - /** - * An arithmetic that implements 2 selfAdd methods. - */ - public static class Arithmetic246 extends JexlArithmetic { - public Arithmetic246(boolean astrict) { - super(astrict); - } - - public JexlOperator selfAdd(Collection<String> c, String item) { - c.add(item); - return JexlOperator.ASSIGN; - } - - public JexlOperator selfAdd(Appendable c, String item) throws IOException { - c.append(item); - return JexlOperator.ASSIGN; - } - } - - @Test - public void test246() throws Exception { - Log log246 = LogFactory.getLog(IssuesTest200.class); - // quiesce the logger - java.util.logging.Logger ll246 = java.util.logging.LogManager.getLogManager().getLogger(IssuesTest200.class.getName()); - ll246.setLevel(Level.INFO); - JexlEngine jexl = new JexlBuilder().arithmetic(new Arithmetic246(true)).debug(true).logger(log246).create(); - JexlScript script = jexl.createScript("z += x", "x"); - MapContext ctx = new MapContext(); - List<String> z = new ArrayList<String>(1); - Object zz; - - // no ambiguous, std case - ctx.set("z", z); - zz = script.execute(ctx, "42"); - Assert.assertTrue(zz == z); - Assert.assertEquals(1, z.size()); - z.clear(); - ctx.clear(); - - // method discovery will fail due to ambiguity: first arg is null, no type, 2 potential methods - // create a cache-miss entry in method resolution - String expectNullOperand = null; - try { - script.execute(ctx, "42"); - Assert.fail("null z evaluating 'z += x'"); - } catch(JexlException xae) { - expectNullOperand = xae.toString(); - } - Assert.assertNotNull(expectNullOperand); - - // second call will not provoque ambiguity (since cache-miss is recalled) but operator will fail nevertheless - try { - // discovery will fail for null arg - script.execute(ctx, "42"); - Assert.fail("null z evaluating 'z += x'"); - } catch(JexlException xae) { - expectNullOperand = xae.toString(); - } - Assert.assertNotNull(expectNullOperand); - - // a non ambiguous call still succeeds - ctx.set("z", z); - zz = script.execute(ctx, "42"); - Assert.assertTrue(zz == z); - Assert.assertEquals(1, z.size()); - } } Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java?rev=1821295&r1=1821294&r2=1821295&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java (original) +++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java Tue Jan 16 19:34:30 2018 @@ -16,9 +16,17 @@ */ package org.apache.commons.jexl3; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.Map; +import java.util.logging.Level; import org.apache.commons.jexl3.junit.Asserter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -516,4 +524,93 @@ public class SideEffectTest extends Jexl return JexlOperator.ASSIGN; } } + + /** + * An arithmetic that implements 2 selfAdd methods. + */ + public static class Arithmetic246 extends JexlArithmetic { + public Arithmetic246(boolean astrict) { + super(astrict); + } + + public JexlOperator selfAdd(Collection<String> c, String item) throws IOException { + c.add(item); + return JexlOperator.ASSIGN; + } + + public JexlOperator selfAdd(Appendable c, String item) throws IOException { + c.append(item); + return JexlOperator.ASSIGN; + } + } + + public static class Arithmetic246b extends Arithmetic246 { + public Arithmetic246b(boolean astrict) { + super(astrict); + } + + public Object selfAdd(Object c, String item) throws IOException { + if (c == null) { + return new ArrayList<String>(Arrays.asList(item)); + } + if (c instanceof Appendable) { + ((Appendable) c).append(item); + return JexlOperator.ASSIGN; + } + return JexlEngine.TRY_FAILED; + } + } + + @Test + public void test246() throws Exception { + run246(new Arithmetic246(true)); + } + + @Test + public void test246b() throws Exception { + run246(new Arithmetic246b(true)); + } + + private void run246(JexlArithmetic j246) throws Exception { + Log log246 = LogFactory.getLog(SideEffectTest.class); + // quiesce the logger + java.util.logging.Logger ll246 = java.util.logging.LogManager.getLogManager().getLogger(SideEffectTest.class.getName()); + // ll246.setLevel(Level.WARNING); + JexlEngine jexl = new JexlBuilder().arithmetic(j246).cache(32).debug(true).logger(log246).create(); + JexlScript script = jexl.createScript("z += x", "x"); + MapContext ctx = new MapContext(); + List<String> z = new ArrayList<String>(1); + Object zz = null; + + // no ambiguous, std case + ctx.set("z", z); + zz = script.execute(ctx, "42"); + Assert.assertTrue(zz == z); + Assert.assertEquals(1, z.size()); + z.clear(); + ctx.clear(); + + boolean t246 = false; + // call with null + try { + script.execute(ctx, "42"); + zz = ctx.get("z"); + Assert.assertTrue(zz instanceof List<?>); + z = (List<String>) zz; + Assert.assertEquals(1, z.size()); + } catch(JexlException xjexl) { + t246 = true; + Assert.assertTrue(j246.getClass().equals(Arithmetic246.class)); + } catch(ArithmeticException xjexl) { + t246 = true; + Assert.assertTrue(j246.getClass().equals(Arithmetic246.class)); + } + ctx.clear(); + + // a non ambiguous call still succeeds + ctx.set("z", z); + zz = script.execute(ctx, "-42"); + Assert.assertTrue(zz == z); + Assert.assertEquals(t246? 1 : 2, z.size()); + } }