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());
+    }
 }


Reply via email to