You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2022/11/05 17:38:17 UTC

[commons-jexl] 01/02: JEXL-384: refine strictness by introducing strict-cast flag in arithmetic only used by cast operators and set as false if strict is false or isStrict method is overridden; - added 'condition' operator to use in if/while/for; - simplified test accordingly;

This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch JEXL-384
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git

commit 992c8463f52880a9cb733c4ffac609ab5caff232
Author: henrib <he...@apache.org>
AuthorDate: Sat Nov 5 18:36:29 2022 +0100

    JEXL-384: refine strictness by introducing strict-cast flag in arithmetic only used by cast operators and set as false if strict is false or isStrict method is overridden;
    - added 'condition' operator to use in if/while/for;
    - simplified test accordingly;
---
 .../org/apache/commons/jexl3/JexlArithmetic.java   | 92 +++++++++++++++-------
 .../org/apache/commons/jexl3/JexlFeatures.java     |  7 +-
 .../org/apache/commons/jexl3/JexlOperator.java     |  9 ++-
 .../apache/commons/jexl3/internal/Interpreter.java | 16 ++--
 .../org/apache/commons/jexl3/Issues300Test.java    | 14 +---
 5 files changed, 91 insertions(+), 47 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index e813151a..9f6eca41 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -22,6 +22,7 @@ import org.apache.commons.jexl3.introspection.JexlMethod;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.MathContext;
@@ -80,6 +81,9 @@ public class JexlArithmetic {
     /** Whether this JexlArithmetic instance behaves in strict or lenient mode. */
     private final boolean strict;
 
+    /** Whether this JexlArithmetic instance allows null as argument to cast methods - toXXX(). */
+    private final boolean strictCast;
+
     /** The big decimal math context. */
     private final MathContext mathContext;
 
@@ -119,6 +123,17 @@ public class JexlArithmetic {
             // ignore
         }
         this.ctor = actor;
+        boolean cast = strict;
+        // if isStrict is not overridden, we are in strict-cast mode
+        if (cast) {
+            try {
+                Method istrict = getClass().getMethod("isStrict", JexlOperator.class);
+                cast = JexlArithmetic.class == istrict.getDeclaringClass();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        this.strictCast = cast;
     }
 
     /**
@@ -371,7 +386,17 @@ public class JexlArithmetic {
      * @return true if strict, false if lenient
      */
     public boolean isStrict() {
-        return this.strict;
+        return strict;
+    }
+
+    /**
+     * Checks whether this JexlArithmetic instance
+     * strictly considers null as an error when used as operand of a cast method (toXXX())..
+     *
+     * @return true if strict-cast, false if lenient
+     */
+    public boolean isStrictCast() {
+        return strictCast;
     }
 
     /**
@@ -380,21 +405,32 @@ public class JexlArithmetic {
      * If null-safe (ie not-strict), the operator does accept null arguments even if the arithmetic itself is strict.</p>
      * <p>The default implementation considers equal/not-equal operators as null-safe so one can check for null as in
      * <code>if (myvar == null) {...}</code>. Note that this operator is used for equal and not-equal syntax. The complete
-     * list of operators that are not strict are (==, [], []=, ., .=). </p>
-     * <p>An arithmetic refining its strict behavior handling for more operators must declare which by overriding
-     * this method.</p>
+     * list of operators that are not strict are (==, [], []=, ., .=, empty, size, contains). </p>
+     * <p>
+     *     An arithmetic refining its strict behavior handling for more operators must declare which by overriding
+     * this method.
+     * </p>
+     * <p>
+     *     If this method is overridden, the arithmetic instance is <em>NOT</em> in strict-cast mode. Tp restore the
+     *     strict-cast behavior, override the {@link #isStrictCast()} method/
+     * </p>
      * @param operator the operator to check for null-argument(s) handling
      * @return true if operator considers null arguments as errors, false if operator has appropriate semantics
      * for null argument(s)
      */
     public boolean isStrict(JexlOperator operator) {
-        switch(operator) {
-            case EQ:
-            case ARRAY_GET:
-            case ARRAY_SET:
-            case PROPERTY_GET:
-            case PROPERTY_SET:
-                return false;
+        if (operator != null) {
+            switch (operator) {
+                case EQ:
+                case ARRAY_GET:
+                case ARRAY_SET:
+                case PROPERTY_GET:
+                case PROPERTY_SET:
+                case EMPTY:
+                case SIZE:
+                case CONTAINS:
+                    return false;
+            }
         }
         return isStrict();
     }
@@ -435,22 +471,25 @@ public class JexlArithmetic {
      * The result of +,/,-,*,% when both operands are null.
      *
      * @return Integer(0) if lenient
-     * @throws ArithmeticException if strict
+     * @throws ArithmeticException if strict-cast
      */
     protected Object controlNullNullOperands() {
-        if (isStrict()) {
+        if (isStrictCast()) {
             throw new NullOperand();
         }
         return 0;
     }
 
     /**
-     * Throw a NPE if arithmetic is strict.
+     * Throw a NPE if arithmetic is strict-cast.
+     * <p>This method is called by the cast methods ({@link #toBoolean(Object)}, {@link #toInteger(Object)},
+     * {@link #toDouble(Object)}, {@link #toString(Object)}, {@link #toBigInteger(Object)}, {@link #toBigDecimal(Object)})
+     * when they encounter a null argument.</p>
      *
      * @throws ArithmeticException if strict
      */
     protected void controlNullOperand() {
-        if (isStrict()) {
+        if (isStrictCast()) {
             throw new NullOperand();
         }
     }
@@ -556,7 +595,7 @@ public class JexlArithmetic {
         Number result = original;
         if (original instanceof BigDecimal) {
             final BigDecimal bigd = (BigDecimal) original;
-            // if it's bigger than a double it can't be narrowed
+            // if it is bigger than a double, it can not be narrowed
             if (bigd.compareTo(BIGD_DOUBLE_MAX_VALUE) > 0
                 || bigd.compareTo(BIGD_DOUBLE_MIN_VALUE) < 0) {
                 return original;
@@ -587,7 +626,7 @@ public class JexlArithmetic {
         } else {
             if (original instanceof BigInteger) {
                 final BigInteger bigi = (BigInteger) original;
-                // if it's bigger than a Long it can't be narrowed
+                // if it is bigger than a Long, it can not be narrowed
                 if (bigi.compareTo(BIGI_LONG_MAX_VALUE) > 0
                         || bigi.compareTo(BIGI_LONG_MIN_VALUE) < 0) {
                     return original;
@@ -751,7 +790,6 @@ public class JexlArithmetic {
      */
     protected Object increment(Object val, int incr) {
         if (val == null) {
-            controlNullOperand();
             return incr;
         }
         if (val instanceof Integer) {
@@ -803,7 +841,7 @@ public class JexlArithmetic {
                             : left instanceof String && right instanceof String;
         if (!strconcat) {
             try {
-                // if both (non null) args fit as long
+                // if both (non-null) args fit as long
                 final Number ln = asLongNumber(left);
                 final Number rn = asLongNumber(right);
                 if (ln != null && rn != null) {
@@ -835,9 +873,7 @@ public class JexlArithmetic {
                 final BigInteger result = l.add(r);
                 return narrowBigInteger(left, right, result);
             } catch (final ArithmeticException nfe) {
-                if (left == null || right == null) {
-                    controlNullOperand();
-                }
+                // ignore and continue in sequence
             }
         }
         return (left == null? "" : toString(left)).concat(right == null ? "" : toString(right));
@@ -855,7 +891,7 @@ public class JexlArithmetic {
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
-        // if both (non null) args fit as long
+        // if both (non-null) args fit as long
         final Number ln = asLongNumber(left);
         final Number rn = asLongNumber(right);
         if (ln != null && rn != null) {
@@ -908,7 +944,7 @@ public class JexlArithmetic {
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
-        // if both (non null) args fit as long
+        // if both (non-null) args fit as long
         final Number ln = asLongNumber(left);
         final Number rn = asLongNumber(right);
         if (ln != null && rn != null) {
@@ -977,7 +1013,7 @@ public class JexlArithmetic {
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
-        // if both (non null) args fit as int
+        // if both (non-null) args fit as int
         final Number ln = asLongNumber(left);
         final Number rn = asLongNumber(right);
         if (ln != null && rn != null) {
@@ -1021,7 +1057,7 @@ public class JexlArithmetic {
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
-        // if both (non null) args fit as long
+        // if both (non-null) args fit as long
         final Number ln = asLongNumber(left);
         final Number rn = asLongNumber(right);
         if (ln != null && rn != null) {
@@ -1063,7 +1099,6 @@ public class JexlArithmetic {
      */
     public Object negate(final Object val) {
         if (val == null) {
-            controlNullOperand();
             return null;
         }
         if (val instanceof Integer) {
@@ -1120,7 +1155,6 @@ public class JexlArithmetic {
      */
     public Object positivize(final Object val) {
         if (val == null) {
-            controlNullOperand();
             return null;
         }
         if (val instanceof Short) {
@@ -1598,7 +1632,7 @@ public class JexlArithmetic {
             final String strval = val.toString();
             return !strval.isEmpty() && !"false".equals(strval);
         }
-        // non null value is true
+        // non-null value is true
         return true;
     }
 
diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index 124468fb..58100769 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -60,7 +60,7 @@ public final class JexlFeatures {
         "register", "reserved variable", "local variable", "assign/modify",
         "global assign/modify", "array reference", "create instance", "loop", "function",
         "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade",
-        "thin-arrow", "fat-arrow"
+        "thin-arrow", "fat-arrow", "namespace pragma"
     };
     /** Registers feature ordinal. */
     private static final int REGISTER = 0;
@@ -98,6 +98,8 @@ public final class JexlFeatures {
     public static final int THIN_ARROW = 16;
     /** Fat-arrow lambda syntax. */
     public static final int FAT_ARROW = 17;
+    /** Allow jexl.namespace pragma syntax. */
+    public static final int NAMESPACE_PRAGMA = 18;
 
     /**
      * Creates an all-features-enabled instance.
@@ -115,7 +117,8 @@ public final class JexlFeatures {
                 | (1L << PRAGMA)
                 | (1L << ANNOTATION)
                 | (1L << SCRIPT)
-                | (1L << THIN_ARROW);
+                | (1L << THIN_ARROW)
+                | (1L << NAMESPACE_PRAGMA);
         reservedNames = Collections.emptySet();
         nameSpaces = TEST_STR_FALSE;
     }
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOperator.java b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
index 2b93eb24..905f92a9 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOperator.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
@@ -409,7 +409,14 @@ public enum JexlOperator {
      * <br><strong>Method:</strong> <code>Iterator&lt;Object&gt; forEach(R y);</code>.
      * @since 3.1
      */
-    FOR_EACH("for(...)", "forEach", 1);
+    FOR_EACH("for(...)", "forEach", 1),
+
+    /**
+     * Test condition in if, for, while.
+     * <br><strong>Method:</strong> <code>boolean testCondition(R y);</code>.
+     * @since 3.3
+     */
+    CONDITION("?", "testCondition", 1);
 
     /**
      * The operator symbol.
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index 433440c0..d3433e1b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -536,6 +536,11 @@ public class Interpreter extends InterpreterBase {
         }
     }
 
+    private boolean testPredicate(JexlNode node, Object condition) {
+        final Object predicate = operators.tryOverload(node, JexlOperator.CONDITION, condition);
+        return  arithmetic.toBoolean(predicate != JexlEngine.TRY_FAILED? predicate : condition);
+    }
+
     @Override
     protected Object visit(final ASTIfStatement node, final Object data) {
         final int n = 0;
@@ -544,8 +549,9 @@ public class Interpreter extends InterpreterBase {
             Object result = null;
             // pairs of { conditions , 'then' statement }
             for(int ifElse = 0; ifElse < (numChildren - 1); ifElse += 2) {
-                final Object condition = node.jjtGetChild(ifElse).jjtAccept(this, null);
-                if (arithmetic.toBoolean(condition)) {
+                final JexlNode testNode = node.jjtGetChild(ifElse);
+                final Object condition = testNode.jjtAccept(this, null);
+                if (testPredicate(testNode, condition)) {
                     // first objectNode is true statement
                     return node.jjtGetChild(ifElse + 1).jjtAccept(this, null);
                 }
@@ -753,7 +759,7 @@ public class Interpreter extends InterpreterBase {
             // last child is body
             final JexlNode statement = (form & 8) != 0 ? node.jjtGetChild(nc) : null;
             // while(predicate())...
-            while (predicate == null || arithmetic.toBoolean(predicate.jjtAccept(this, data))) {
+            while (predicate == null || testPredicate(predicate, predicate.jjtAccept(this, data))) {
                 cancelCheck(node);
                 // the body
                 if (statement != null) {
@@ -785,7 +791,7 @@ public class Interpreter extends InterpreterBase {
         Object result = null;
         /* first objectNode is the condition */
         final JexlNode condition = node.jjtGetChild(0);
-        while (arithmetic.toBoolean(condition.jjtAccept(this, data))) {
+        while (testPredicate(condition, condition.jjtAccept(this, data))) {
             cancelCheck(node);
             if (node.jjtGetNumChildren() > 1) {
                 try {
@@ -819,7 +825,7 @@ public class Interpreter extends InterpreterBase {
                     //continue;
                 }
             }
-        } while (arithmetic.toBoolean(condition.jjtAccept(this, data)));
+        } while (testPredicate(condition, condition.jjtAccept(this, data)));
         return result;
     }
 
diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
index f80ac72d..cab32c2e 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
@@ -1016,17 +1016,11 @@ public class Issues300Test {
             super(astrict);
         }
         @Override
-        public boolean toBoolean(final Object val) {
-            return val == null? false : super.toBoolean(val);
-        }
-        @Override
-        public Object not(final Object val) {
-            return val == null? true : super.not(val);
-        }
-        @Override
         public boolean isStrict(JexlOperator op) {
-            if (JexlOperator.NOT == op) {
-                return false;
+            switch(op) {
+                case NOT:
+                case CONDITION:
+                    return false;
             }
             return super.isStrict(op);
         }