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 2017/06/28 06:50:26 UTC

svn commit: r1800127 - in /commons/proper/jexl/trunk/src: main/java/org/apache/commons/jexl3/internal/ main/java/org/apache/commons/jexl3/parser/ site/xdoc/ site/xdoc/reference/ test/java/org/apache/commons/jexl3/

Author: henrib
Date: Wed Jun 28 06:50:25 2017
New Revision: 1800127

URL: http://svn.apache.org/viewvc?rev=1800127&view=rev
Log:
JEXL-226: add ?? operator

Modified:
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.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/jexl3/IfTest.java

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java?rev=1800127&r1=1800126&r2=1800127&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java Wed Jun 28 06:50:25 2017
@@ -90,12 +90,14 @@ import org.apache.commons.jexl3.parser.A
 import org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
 import org.apache.commons.jexl3.parser.ASTVar;
 import org.apache.commons.jexl3.parser.ASTWhileStatement;
+import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
+import org.apache.commons.jexl3.parser.ASTAnnotation;
+import org.apache.commons.jexl3.parser.ASTNullpNode;
+
 import org.apache.commons.jexl3.parser.JexlNode;
 import org.apache.commons.jexl3.parser.ParserVisitor;
 
 import java.util.regex.Pattern;
-import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
-import org.apache.commons.jexl3.parser.ASTAnnotation;
 
 /**
  * Helps pinpoint the cause of problems in expressions that fail during evaluation.
@@ -578,13 +580,13 @@ public class Debugger extends ParserVisi
     protected Object visit(ASTGTNode node, Object data) {
         return infixChildren(node, " > ", false, data);
     }
-    
+
     /** Checks identifiers that contain spaces or punctuation
      * (but underscore, at-sign, sharp-sign and dollar).
      */
-    protected static final Pattern QUOTED_IDENTIFIER = 
+    protected static final Pattern QUOTED_IDENTIFIER =
             Pattern.compile("[\\s]|[\\p{Punct}&&[^@#\\$_]]");
-    
+
     /**
      * Checks whether an identifier should be quoted or not.
      * @param str the identifier
@@ -900,6 +902,14 @@ public class Debugger extends ParserVisi
         return data;
     }
 
+    @Override
+    protected Object visit(ASTNullpNode node, Object data) {
+        accept(node.jjtGetChild(0), data);
+        builder.append("??");
+        accept(node.jjtGetChild(1), data);
+        return data;
+    }
+
     @Override
     protected Object visit(ASTTrueNode node, Object data) {
         check(node, "true", data);

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java?rev=1800127&r1=1800126&r2=1800127&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java Wed Jun 28 06:50:25 2017
@@ -76,6 +76,7 @@ import org.apache.commons.jexl3.parser.A
 import org.apache.commons.jexl3.parser.ASTNSWNode;
 import org.apache.commons.jexl3.parser.ASTNotNode;
 import org.apache.commons.jexl3.parser.ASTNullLiteral;
+import org.apache.commons.jexl3.parser.ASTNullpNode;
 import org.apache.commons.jexl3.parser.ASTNumberLiteral;
 import org.apache.commons.jexl3.parser.ASTOrNode;
 import org.apache.commons.jexl3.parser.ASTRangeNode;
@@ -849,6 +850,12 @@ public class Interpreter extends Interpr
     }
 
     @Override
+    protected Object visit(ASTNullpNode node, Object data) {
+        Object lhs = node.jjtGetChild(0).jjtAccept(this, data);
+        return lhs != null? lhs : node.jjtGetChild(1).jjtAccept(this, data);
+    }
+
+    @Override
     protected Object visit(ASTSizeFunction node, Object data) {
         try {
             Object val = node.jjtGetChild(0).jjtAccept(this, data);
@@ -956,7 +963,7 @@ public class Interpreter extends Interpr
      * Check if a null evaluated expression is protected by a ternary expression.
      * <p>
      * The rationale is that the ternary / elvis expressions are meant for the user to explictly take control
-     * over the error generation; ie, ternaries can return null even if the engine in isStrict mode
+     * over the error generation; ie, ternaries can return null even if the engine in strict mode
      * would normally throw an exception.
      * </p>
      * @param node the expression node
@@ -966,7 +973,11 @@ public class Interpreter extends Interpr
         for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) {
             if (walk instanceof ASTTernaryNode) {
                 return true;
-            } else if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
+            }
+            if (walk instanceof ASTNullpNode) {
+                return true;
+            }
+            if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
                 break;
             }
         }

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt?rev=1800127&r1=1800126&r2=1800127&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt Wed Jun 28 06:50:25 2017
@@ -149,6 +149,7 @@ TOKEN_MGR_DECLS : {
 <*> TOKEN : { /* CONDITIONALS */
       < QMARK : "?" >
     | < ELVIS : "?:" >
+    | < NULLP : "??" >
     | < AND : "&&" | "and" >
     | < OR : "||" | "or" >
 }
@@ -452,6 +453,8 @@ void ConditionalExpression() #void : {}
     <QMARK> Expression() <COLON> Expression() #TernaryNode(3)
   |
     <ELVIS> Expression() #TernaryNode(2)
+  |
+    <NULLP> Expression() #NullpNode(2)
   )?
 }
 

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java?rev=1800127&r1=1800126&r2=1800127&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java Wed Jun 28 06:50:25 2017
@@ -64,6 +64,8 @@ public abstract class ParserVisitor {
 
     protected abstract Object visit(ASTTernaryNode node, Object data);
 
+    protected abstract Object visit(ASTNullpNode node, Object data);
+
     protected abstract Object visit(ASTOrNode node, Object data);
 
     protected abstract Object visit(ASTAndNode node, Object data);

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=1800127&r1=1800126&r2=1800127&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/site/xdoc/changes.xml (original)
+++ commons/proper/jexl/trunk/src/site/xdoc/changes.xml Wed Jun 28 06:50:25 2017
@@ -25,7 +25,13 @@
         <author email="dev@commons.apache.org">Commons Developers</author>
     </properties>
     <body>
-        <release version="3.2" date="unreleased">
+        <release version="3.1.1" date="unreleased">
+            <action dev="henrib" type="add" issue="JEXL-226" due-to="Min Wei">
+                add ?? operator support
+            </action>
+            <action dev="henrib" type="fix" issue="JEXL-230" due-to="Dmitri Blinov">
+                List literal is not mentioned in docs
+            </action>
         </release>
         <release version="3.1" date="2017-04-14">
             <action dev="henrib" type="add" issue="JEXL-222" due-to="Dmitri Blinov">

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=1800127&r1=1800126&r2=1800127&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/site/xdoc/reference/syntax.xml (original)
+++ commons/proper/jexl/trunk/src/site/xdoc/reference/syntax.xml Wed Jun 28 06:50:25 2017
@@ -501,6 +501,26 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>Null coalescing operator <code>??</code> </td>
+                    <td>
+                        The null coalescing operator returns the result of its first operand if it is defined and is not null.
+                        <p>When <code>x</code>and<code>y</code>are null or undefined,
+                            <code>x ?? 'unknown or null x'</code> evaluates as  <code>'unknown or null x'</code>
+                            <code>y ?? "default"</code> evaluates as  <code>"default"</code>.
+                        </p>
+                        <p>
+                            When <code>var x = 42</code> and  <code>var y = "forty-two"</code>,<code>x??"other"</code>
+                            evaluates as <code>42</code> and <code>y??"other"</code> evaluates as <code>"forty-two"</code>.
+                        </p>
+                        <p>
+                            <strong>NOTE:</strong> this operator does not behave like the ternary conditional since it
+                            does not coerce the first argument to a boolean to evaluate the condition.
+                            When <code>var x = false</code> and  <code>var y = 0</code>,<code>x??true</code>
+                            evaluates as <code>false</code> and <code>y??1</code> evaluates as <code>0</code>.
+                        </p>
+                    </td>
+                </tr>
+                <tr>
                     <td>Equality</td>
                     <td>
                         The usual <code>==</code> operator can be used as well as the abbreviation <code>eq</code>.

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IfTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IfTest.java?rev=1800127&r1=1800126&r2=1800127&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IfTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IfTest.java Wed Jun 28 06:50:25 2017
@@ -222,13 +222,14 @@ public class IfTest extends JexlTestCase
 
     /**
      * Ternary operator condition undefined or null evaluates to false
-     * independantly of engine flags.
+     * independently of engine flags; same for null coalescing operator.
      * @throws Exception
      */
     @Test
     public void testTernaryShorthand() throws Exception {
         JexlEvalContext jc = new JexlEvalContext();
         JexlExpression e = JEXL.createExpression("x.y.z = foo?:'quux'");
+        JexlExpression f = JEXL.createExpression("foo??'quux'");
         Object o;
 
         // undefined foo
@@ -239,6 +240,8 @@ public class IfTest extends JexlTestCase
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
             Assert.assertEquals("Should be quux", "quux", o);
+            o = f.evaluate(jc);
+            Assert.assertEquals("Should be quux", "quux", o);
         }
 
         jc.set("foo", null);
@@ -250,6 +253,8 @@ public class IfTest extends JexlTestCase
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
             Assert.assertEquals("Should be quux", "quux", o);
+            o = f.evaluate(jc);
+            Assert.assertEquals("Should be quux", "quux", o);
         }
 
         jc.set("foo", Boolean.FALSE);
@@ -261,6 +266,8 @@ public class IfTest extends JexlTestCase
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
             Assert.assertEquals("Should be quux", "quux", o);
+            o = f.evaluate(jc);
+            Assert.assertEquals("Should be false", false, o);
         }
 
         jc.set("foo", Double.NaN);
@@ -272,6 +279,8 @@ public class IfTest extends JexlTestCase
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
             Assert.assertEquals("Should be quux", "quux", o);
+            o = f.evaluate(jc);
+            Assert.assertTrue("Should be NaN", Double.isNaN((Double) o));
         }
 
         jc.set("foo", "");
@@ -283,6 +292,8 @@ public class IfTest extends JexlTestCase
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
             Assert.assertEquals("Should be quux", "quux", o);
+            o = f.evaluate(jc);
+            Assert.assertEquals("Should be empty string", "", o);
         }
 
         jc.set("foo", "false");
@@ -294,6 +305,8 @@ public class IfTest extends JexlTestCase
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
             Assert.assertEquals("Should be quux", "quux", o);
+            o = f.evaluate(jc);
+            Assert.assertEquals("Should be 'false'", "false", o);
         }
 
         jc.set("foo", 0d);
@@ -305,6 +318,8 @@ public class IfTest extends JexlTestCase
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
             Assert.assertEquals("Should be quux", "quux", o);
+            o = f.evaluate(jc);
+            Assert.assertEquals("Should be 0", 0.d, o);
         }
 
         jc.set("foo", 0);
@@ -316,6 +331,8 @@ public class IfTest extends JexlTestCase
             Assert.assertEquals("Should be quux", "quux", o);
             o = jc.get("x.y.z");
             Assert.assertEquals("Should be quux", "quux", o);
+            o = f.evaluate(jc);
+            Assert.assertEquals("Should be 0", 0, o);
         }
 
         jc.set("foo", "bar");
@@ -332,4 +349,22 @@ public class IfTest extends JexlTestCase
         debuggerCheck(JEXL);
     }
 
+    @Test
+    public void testNullCoaelescing() throws Exception {
+        Object o;
+        JexlEvalContext jc = new JexlEvalContext();
+        JexlExpression xtrue = JEXL.createExpression("x??true");
+        o = xtrue.evaluate(jc);
+        Assert.assertEquals("Should be true", true, o);
+        jc.set("x", false);
+        o = xtrue.evaluate(jc);
+        Assert.assertEquals("Should be false", false, o);
+        JexlExpression yone = JEXL.createExpression("y??1");
+        o = yone.evaluate(jc);
+        Assert.assertEquals("Should be 1", 1, o);
+        jc.set("y", 0);
+        o = yone.evaluate(jc);
+        Assert.assertEquals("Should be 0", 0, o);
+        debuggerCheck(JEXL);
+    }
 }