commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hen...@apache.org
Subject svn commit: r1153476 - in /commons/proper/jexl/trunk/src: main/java/org/apache/commons/jexl2/ main/java/org/apache/commons/jexl2/internal/ site/xdoc/ site/xdoc/reference/ test/java/org/apache/commons/jexl2/
Date Wed, 03 Aug 2011 13:17:46 GMT
Author: henrib
Date: Wed Aug  3 13:17:44 2011
New Revision: 1153476

URL: http://svn.apache.org/viewvc?rev=1153476&view=rev
Log:
JEXL-118
* Extended =~ and  !~ operator to behave as IN and NOT IN on sets, collections, maps (on keys),
arrays and "duck-typed" collections (classes exposing "contains" method)
* Updated ArrayListWrapper to allow better control over its usage as method declaring class
* Added specific tests
* Updated syntax reference
* Updated changes

Modified:
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlArithmetic.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/ArrayListWrapper.java
    commons/proper/jexl/trunk/src/site/xdoc/changes.xml
    commons/proper/jexl/trunk/src/site/xdoc/reference/syntax.xml
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/ArithmeticTest.java

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java?rev=1153476&r1=1153475&r2=1153476&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java Wed
Aug  3 13:17:44 2011
@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.commons.jexl2.parser.SimpleNode;
 import org.apache.commons.logging.Log;
@@ -727,7 +728,7 @@ public class Interpreter implements Pars
             JexlNode statement = node.jjtGetChild(2);
             // get an iterator for the collection/array etc via the
             // introspector.
-            Iterator<?> itemsIterator = getUberspect().getIterator(iterableValue, node);
+            Iterator<?> itemsIterator = uberspect.getIterator(iterableValue, node);
             if (itemsIterator != null) {
                 while (itemsIterator.hasNext()) {
                     if (isCancelled()) {
@@ -775,7 +776,53 @@ public class Interpreter implements Pars
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE;
+            // use arithmetic / pattern matching ?
+            if (right instanceof java.util.regex.Pattern || right instanceof String) {
+                return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE;
+            }
+            // left in right ? <=> right.contains(left) ?
+            // try contains on collection
+            if (right instanceof Set<?>) {
+                return ((Set<?>) right).contains(left) ? Boolean.TRUE : Boolean.FALSE;
+            }
+            // try contains on map key
+            if (right instanceof Map<?,?>) {
+                return ((Map<?,?>) right).containsKey(left) ? Boolean.TRUE : Boolean.FALSE;
+            }
+            // try contains on collection
+            if (right instanceof Collection<?>) {
+                return ((Collection<?>) right).contains(left) ? Boolean.TRUE : Boolean.FALSE;
+            }
+            // try a contains method (duck type set)
+            try {
+                Object[] argv = {left};
+                JexlMethod vm = uberspect.getMethod(right, "contains", argv, node);
+                if (vm != null) {
+                    return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.TRUE :
Boolean.FALSE;
+                } else if (arithmetic.narrowArguments(argv)) {
+                    vm = uberspect.getMethod(right, "contains", argv, node);
+                    if (vm != null) {
+                        return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.TRUE
: Boolean.FALSE;
+                    }
+                }
+            } catch (InvocationTargetException e) {
+                throw new JexlException(node, "=~ invocation error", e.getCause());
+            } catch (Exception e) {
+                throw new JexlException(node, "=~ error", e);
+            }
+            // try iterative comparison
+            Iterator<?> it = uberspect.getIterator(right, node);
+            if (it != null) {
+                while (it.hasNext()) {
+                    Object next = it.next();
+                    if (next == left || (next != null && next.equals(left))) {
+                        return Boolean.TRUE;
+                    }
+                }
+                return Boolean.FALSE;
+            }
+            // defaults to equal
+            return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
         } catch (RuntimeException xrt) {
             throw new JexlException(node, "=~ error", xrt);
         }
@@ -1107,7 +1154,52 @@ public class Interpreter implements Pars
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE;
+            if (right instanceof java.util.regex.Pattern || right instanceof String) {
+                // use arithmetic / pattern matching
+                return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE;
+            }
+            // try contains on collection
+            if (right instanceof Set<?>) {
+                return ((Set<?>) right).contains(left) ? Boolean.FALSE : Boolean.TRUE;
+            }
+            // try contains on map key
+            if (right instanceof Map<?,?>) {
+                return ((Map<?,?>) right).containsKey(left) ? Boolean.FALSE : Boolean.TRUE;
+            }
+            // try contains on collection
+            if (right instanceof Collection<?>) {
+                return ((Collection<?>) right).contains(left) ? Boolean.FALSE : Boolean.TRUE;
+            }
+            // try a contains method (duck type set)
+            try {
+                Object[] argv = {right};
+                JexlMethod vm = uberspect.getMethod(left, "contains", argv, node);
+                if (vm != null) {
+                    return arithmetic.toBoolean(vm.invoke(left, argv)) ? Boolean.FALSE :
Boolean.TRUE;
+                } else if (arithmetic.narrowArguments(argv)) {
+                    vm = uberspect.getMethod(left, "contains", argv, node);
+                    if (vm != null) {
+                        return arithmetic.toBoolean(vm.invoke(left, argv)) ? Boolean.FALSE
: Boolean.TRUE;
+                    }
+                }
+            } catch (InvocationTargetException e) {
+                throw new JexlException(node, "!~ invocation error", e.getCause());
+            } catch (Exception e) {
+                throw new JexlException(node, "!~ error", e);
+            }
+            // try iterative comparison
+            Iterator<?> it = uberspect.getIterator(right, node.jjtGetChild(1));
+            if (it != null) {
+                while (it.hasNext()) {
+                    Object next = it.next();
+                    if (next == left || (next != null && next.equals(left))) {
+                        return Boolean.FALSE;
+                    }
+                }
+                return Boolean.TRUE;
+            }
+            // defaults to not equal
+            return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
         } catch (RuntimeException xrt) {
             throw new JexlException(node, "!~ error", xrt);
         }

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlArithmetic.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlArithmetic.java?rev=1153476&r1=1153475&r2=1153476&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlArithmetic.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlArithmetic.java Wed
Aug  3 13:17:44 2011
@@ -21,6 +21,8 @@ import java.lang.reflect.Field;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.MathContext;
+import java.util.Collection;
+import java.util.Iterator;
 
 /**
  * Perform arithmetic.
@@ -599,8 +601,8 @@ public class JexlArithmetic {
             return left.equals(right);
         } else if (isFloatingPointType(left, right)) {
             return toDouble(left) == toDouble(right);
-        } else if (left instanceof Number || right instanceof Number || left instanceof Character
-                || right instanceof Character) {
+        } else if ((left instanceof Number || left instanceof Character)
+                && (right instanceof Character || right instanceof Number)) {
             return toLong(left) == toLong(right);
         } else if (left instanceof Boolean || right instanceof Boolean) {
             return toBoolean(left) == toBoolean(right);

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/ArrayListWrapper.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/ArrayListWrapper.java?rev=1153476&r1=1153475&r2=1153476&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/ArrayListWrapper.java
(original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/internal/ArrayListWrapper.java
Wed Aug  3 13:17:44 2011
@@ -18,9 +18,18 @@ package org.apache.commons.jexl2.interna
 
 import java.lang.reflect.Array;
 import java.util.AbstractList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
 
 /**
- * A class that wraps an array with a List interface.
+ * A class that wraps an array within an AbstractList.
+ * <p>
+ * It overrides all methods because introspection uses this class a a marker for wrapped
arrays; the declared class
+ * for any method is thus always ArrayListWrapper.
+ * </p>
  */
 public class ArrayListWrapper extends AbstractList<Object> {
     /** the array to wrap. */
@@ -56,4 +65,136 @@ public class ArrayListWrapper extends Ab
     public int size() {
         return Array.getLength(array);
     }
+
+    @Override
+    public Object[] toArray() {
+        final int size = size();
+        Object[] a = new Object[size];
+        for(int i = 0; i < size; ++i) {
+            a[i] = get(i);
+        }
+        return a;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T[] toArray(T[] a) {
+        int size = size();
+        if (a.length < size) {
+            T[] x = (T[]) Array.newInstance(a.getClass().getComponentType(), size);
+            System.arraycopy(a, a.length, x, 0, a.length);
+        }
+        for(int i = 0; i < size; ++i) {
+            a[i] = (T) get(i);
+        }
+        if (a.length > size) {
+            a[size] = null;
+        }
+        return a;
+    }
+
+    @Override
+    public int indexOf(Object o) {
+        final int size = size();
+        if (o == null) {
+            for (int i = 0; i < size; i++) {
+                if (get(i) == null) {
+                    return i;
+                }
+            }
+        } else {
+            for (int i = 0; i < size; i++) {
+                if (o.equals(get(i))) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return indexOf(o) != -1;
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        return super.isEmpty();
+    }
+
+    @Override
+    public Iterator<Object> iterator() {
+        return super.iterator();
+    }
+    
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return super.containsAll(c);
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+        return super.lastIndexOf(o);
+    }
+
+    @Override
+    public ListIterator<Object> listIterator() {
+        return super.listIterator();
+    }
+
+    @Override
+    public ListIterator<Object> listIterator(int index) {
+        return super.listIterator(index);
+    }
+
+    @Override
+    public List<Object> subList(int fromIndex, int toIndex) {
+        return super.subList(fromIndex, toIndex);
+    }
+    
+    @Override
+    public boolean add(Object o) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends Object> c) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public boolean addAll(int index, Collection<? extends Object> c) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void clear() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void add(int index, Object element) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public Object remove(int index) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
 }
\ No newline at end of file

Modified: commons/proper/jexl/trunk/src/site/xdoc/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/site/xdoc/changes.xml?rev=1153476&r1=1153475&r2=1153476&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/site/xdoc/changes.xml (original)
+++ commons/proper/jexl/trunk/src/site/xdoc/changes.xml Wed Aug  3 13:17:44 2011
@@ -26,6 +26,9 @@
   </properties>
   <body>
     <release version="2.1" date="unreleased">
+        <action dev="henrib" type="add" issue="JEXL-118" due-to="Max Tardiveau">
+            Provide an IN operator: =~ / match operator extended to provide IN behavior (!~
as NOT IN)
+        </action>
         <action dev="henrib" type="add" issue="JEXL-116" due-to="Sarel Botha">
             Add control over classes, methods, constructors and properties allowed in scripts
         </action>

Modified: commons/proper/jexl/trunk/src/site/xdoc/reference/syntax.xml
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/site/xdoc/reference/syntax.xml?rev=1153476&r1=1153475&r2=1153476&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/site/xdoc/reference/syntax.xml (original)
+++ commons/proper/jexl/trunk/src/site/xdoc/reference/syntax.xml Wed Aug  3 13:17:44 2011
@@ -450,21 +450,29 @@
           </td>
         </tr>
         <tr>
-          <td>Regex match <code>=~</code></td>
+          <td>In or Match<code>=~</code></td>
           <td>
-            The Perl inspired <code>=~</code> operator can be used to check that
a <code>string</code> matches
+            The syntactically Perl inspired <code>=~</code> operator can be used
to check that a <code>string</code> matches
             a regular expression (expressed either a Java String or a java.util.regex.Pattern).
             For example
             <code>"abcdef" =~ "abc.*</code> returns <code>true</code>.
+            It also checks whether any collection, set or map (on keys) contains a value
or not; in that case, it behaves
+            as an "in" operator. Note that it also applies to arrays as well as "duck-typed"
collection, ie classes exposing a "contains"
+            method.
+            <code> "a" =~ ["a","b","c","d","e",f"]</code> returns <code>true</code>.
           </td>
         </tr>
         <tr>
-          <td>Regex no-match <code>!~</code></td>
+          <td>Not-In or Not-Match<code>!~</code></td>
           <td>
-            The Perl inspired <code>!~</code> operator can be used to check that
a <code>string</code> does not
+            The syntactically Perl inspired <code>!~</code> operator can be used
to check that a <code>string</code> does not
             match a regular expression (expressed either a Java String or a java.util.regex.Pattern).
             For example
             <code>"abcdef" !~ "abc.*</code> returns <code>false</code>.
+            It also checks whether any collection, set or map (on keys) does not contain
a value; in that case, it behaves
+            as "not in" operator. Note that it also applies to arrays as well as "duck-typed"
collection, ie classes exposing a "contains"
+            method.
+            <code> "a" !~ ["a","b","c","d","e",f"]</code> returns <code>true</code>.
           </td>
         </tr>
         <tr>

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/ArithmeticTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/ArithmeticTest.java?rev=1153476&r1=1153475&r2=1153476&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/ArithmeticTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/ArithmeticTest.java Wed
Aug  3 13:17:44 2011
@@ -19,11 +19,14 @@ package org.apache.commons.jexl2;
 import java.util.Map;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.ArrayList;
 
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import org.apache.commons.jexl2.junit.Asserter;
 
-
 public class ArithmeticTest extends JexlTestCase {
     private Asserter asserter;
 
@@ -39,7 +42,7 @@ public class ArithmeticTest extends Jexl
     public void testUndefinedVar() throws Exception {
         asserter.failExpression("objects[1].status", ".* undefined variable objects.*");
     }
-    
+
     public void testLeftNullOperand() throws Exception {
         asserter.setVariable("left", null);
         asserter.setVariable("right", Integer.valueOf(8));
@@ -177,18 +180,18 @@ public class ArithmeticTest extends Jexl
     public void testCoercions() throws Exception {
         asserter.assertExpression("1", new Integer(1)); // numerics default to Integer
 //        asserter.assertExpression("5L", new Long(5)); // TODO when implemented
-        
+
         asserter.setVariable("I2", new Integer(2));
         asserter.setVariable("L2", new Long(2));
         asserter.setVariable("L3", new Long(3));
         asserter.setVariable("B10", BigInteger.TEN);
-        
+
         // Integer & Integer => Integer
         asserter.assertExpression("I2 + 2", new Integer(4));
         asserter.assertExpression("I2 * 2", new Integer(4));
         asserter.assertExpression("I2 - 2", new Integer(0));
         asserter.assertExpression("I2 / 2", new Integer(1));
-        
+
         // Integer & Long => Long
         asserter.assertExpression("I2 * L2", new Long(4));
         asserter.assertExpression("I2 / L2", new Long(1));
@@ -198,13 +201,27 @@ public class ArithmeticTest extends Jexl
         asserter.assertExpression("L2 + L3", new Long(5));
         asserter.assertExpression("L2 / L2", new Long(1));
         asserter.assertExpression("L2 / 2", new Long(1));
-        
+
         // BigInteger
         asserter.assertExpression("B10 / 10", BigInteger.ONE);
         asserter.assertExpression("B10 / I2", new BigInteger("5"));
         asserter.assertExpression("B10 / L2", new BigInteger("5"));
     }
 
+    public static class MatchingContainer {
+        private final Set<Integer> values;
+
+        public MatchingContainer(int[] is) {
+            values = new HashSet<Integer>();
+            for (int value : is) {
+                values.add(value);
+            }
+        }
+
+        public boolean contains(int value) {
+            return values.contains(value);
+        }
+    }
 
     public void testRegexp() throws Exception {
         asserter.setVariable("str", "abc456");
@@ -222,6 +239,36 @@ public class ArithmeticTest extends Jexl
         asserter.assertExpression("str !~ match", Boolean.FALSE);
         asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
         asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
+        // check the in/not-in variant
+        asserter.assertExpression("'a' =~ ['a','b','c','d','e','f']", Boolean.TRUE);
+        asserter.assertExpression("'a' !~ ['a','b','c','d','e','f']", Boolean.FALSE);
+        asserter.assertExpression("'z' =~ ['a','b','c','d','e','f']", Boolean.FALSE);
+        asserter.assertExpression("'z' !~ ['a','b','c','d','e','f']", Boolean.TRUE);
+        // check in/not-in on array, list, map, set and duck-type collection
+        int[] ai = {2, 4, 42, 54};
+        List<Integer> al = new ArrayList<Integer>();
+        for(int i : ai) {
+            al.add(i);
+        }
+        Map<Integer, String> am = new HashMap<Integer, String>();
+        am.put(2, "two");
+        am.put(4, "four");
+        am.put(42, "forty-two");
+        am.put(54, "fifty-four");
+        MatchingContainer ad = new MatchingContainer(ai);
+        Set<Integer> as = ad.values;
+        Object[] vars = { ai, al, am, ad, as };
+      
+        for(Object var : vars) {
+            asserter.setVariable("container", var);
+            for(int x : ai) {
+                asserter.setVariable("x", x);
+                asserter.assertExpression("x =~ container", Boolean.TRUE);
+            }
+            asserter.setVariable("x", 169);
+            asserter.assertExpression("x !~ container", Boolean.TRUE);
+        }
+
     }
 
     /**
@@ -231,7 +278,7 @@ public class ArithmeticTest extends Jexl
      * @throws Exception
      */
     public void testDivideByZero() throws Exception {
-        Map<String,Object> vars = new HashMap<String,Object>();
+        Map<String, Object> vars = new HashMap<String, Object>();
         JexlContext context = new MapContext(vars);
         vars.put("aByte", new Byte((byte) 1));
         vars.put("aShort", new Short((short) 2));
@@ -280,11 +327,11 @@ public class ArithmeticTest extends Jexl
                         // check we have a zero & incremement zero count
                         if (nan instanceof Number) {
                             double zero = ((Number) nan).doubleValue();
-                            if (zero == 0.0)
+                            if (zero == 0.0) {
                                 zeval += 1;
+                            }
                         }
-                    }
-                    catch (Exception any) {
+                    } catch (Exception any) {
                         // increment the exception count
                         zthrow += 1;
                     }
@@ -293,8 +340,7 @@ public class ArithmeticTest extends Jexl
             if (!jexl.isLenient()) {
                 assertTrue("All expressions should have thrown " + zthrow + "/" + PERMS,
                         zthrow == PERMS);
-            }
-            else {
+            } else {
                 assertTrue("All expressions should have zeroed " + zeval + "/" + PERMS,
                         zeval == PERMS);
             }



Mime
View raw message