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

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


The following commit(s) were added to refs/heads/master by this push:
     new b640ba68 JEXL: last updates before 3.4; - !==, === operators; - 
instanceof, !instanceof operator; - Exception cause unwrap in catch; - JEXL-235 
check;
b640ba68 is described below

commit b640ba6820eb07ffc23043f118a3497f64339df5
Author: Henri Biestro <hbies...@cloudera.com>
AuthorDate: Fri May 24 11:55:43 2024 +0200

    JEXL: last updates before 3.4;
    - !==, === operators;
    - instanceof, !instanceof operator;
    - Exception cause unwrap in catch;
    - JEXL-235 check;
---
 RELEASE-NOTES.txt                                  | 22 ++++---
 pom.xml                                            |  2 +-
 src/changes/changes.xml                            |  9 ++-
 .../org/apache/commons/jexl3/JexlArithmetic.java   | 22 +++++++
 .../org/apache/commons/jexl3/JexlFeatures.java     |  5 +-
 .../org/apache/commons/jexl3/JexlOperator.java     |  8 +++
 .../apache/commons/jexl3/internal/Debugger.java    | 22 ++++++-
 .../apache/commons/jexl3/internal/Interpreter.java | 71 +++++++++++++++++++++-
 .../commons/jexl3/internal/ScriptVisitor.java      | 18 +++++-
 .../org/apache/commons/jexl3/internal/Source.java  |  7 ++-
 .../commons/jexl3/parser/OperatorController.java   | 10 +++
 .../org/apache/commons/jexl3/parser/Parser.jjt     | 35 ++++++++---
 .../apache/commons/jexl3/parser/ParserVisitor.java |  8 +++
 .../jexl3/scripting/JexlScriptEngineFactory.java   |  4 +-
 src/site/site.xml                                  |  2 +-
 .../org/apache/commons/jexl3/ArithmeticTest.java   | 25 +++++++-
 .../apache/commons/jexl3/TryCatchFinallyTest.java  |  7 +++
 .../jexl3/scripting/JexlScriptEngineTest.java      |  4 +-
 src/test/scripts/httpPost.jexl                     |  2 +-
 19 files changed, 250 insertions(+), 33 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index dfae9e13..2492472d 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -1,6 +1,6 @@
 
                             Apache Commons JEXL
-                                Version 3.3
+                                Version 3.4
                                Release Notes
 
 
@@ -19,16 +19,24 @@ Its goal is to expose scripting features usable by 
technical operatives or consu
   https://commons.apache.org/jexl/
 
 
========================================================================================================================
-Release 3.3.1
+Release 3.4
 
========================================================================================================================
 
-Version 3.3.1 is a maintenance release.
+Version 3.4 is a minor release.
 
 Compatibility with previous releases
 ====================================
-Version 3.3.1 is source and binary compatible with 3.3.
+Version 3.4 is source and binary compatible with 3.3.
 
-New Features in 3.3.1:
+
+What's new in 3.4:
+==================
+Features and permissions are easier to define through new methods.
+Some new syntaxes are introduced (thus the new minor); try/catch/finally 
(including with resources),
+an array-access safe navigation ((x?[y]), strict equality/inequality operators 
(===, !==) and
+more permissive structured literals. See the JIRA tickets for more details.
+
+New Features in 3.4:
 ====================
 * JEXL-421:     ArrayBuilder: array type should reflect common class of its 
entries
 * JEXL-419:     Add permission syntax to allow class/method/field
@@ -38,8 +46,8 @@ New Features in 3.3.1:
 * JEXL-401:     Captured variables should be read-only
 * JEXL-398:     Allow 'trailing commas' or ellipsis while defining array, map 
and set literals
 
-Bugs Fixed in 3.3.1:
-===================
+Bugs Fixed in 3.4:
+==================
 * JEXL-420:     Error while comparing float and string value
 * JEXL-417:     JexlArithmetic looses precision during arithmetic operator 
execution
 * JEXL-416:     Null-valued pragma throws NPE in 3.3
diff --git a/pom.xml b/pom.xml
index e1fca938..f86e5ad3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,7 +58,7 @@
         <commons.jacoco.haltOnFailure>true</commons.jacoco.haltOnFailure>
         <commons.jacoco.classRatio>0.96</commons.jacoco.classRatio>
         <commons.jacoco.instructionRatio>0.89</commons.jacoco.instructionRatio>
-        <commons.jacoco.methodRatio>0.90</commons.jacoco.methodRatio>
+        <commons.jacoco.methodRatio>0.89</commons.jacoco.methodRatio>
         <commons.jacoco.branchRatio>0.80</commons.jacoco.branchRatio>
         <commons.jacoco.lineRatio>0.89</commons.jacoco.lineRatio>
         <commons.jacoco.complexityRatio>0.75</commons.jacoco.complexityRatio>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index e0de8faa..b6d65c71 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -27,7 +27,7 @@
         <author email="d...@commons.apache.org">Commons Developers</author>
     </properties>
     <body>
-        <release version="3.4.0" date="YYYY-MM-DD">
+        <release version="3.4" date="YYYY-MM-DD">
             <!-- ADD -->
             <action dev="henrib" type="add" issue="JEXL-421">
                 ArrayBuilder: array type should reflect common class of its 
entries
@@ -50,7 +50,9 @@
             <action dev="henrib" type="add" issue="JEXL-398" due-to="Xu 
Pengcheng">
                 Allow 'trailing commas' or ellipsis while defining array, map 
and set literals
             </action>
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add Maven 
property project.build.outputTimestamp for build reproducibility.</action>
+            <action type="add" dev="ggregory" due-to="Gary Gregory">
+                Add Maven property project.build.outputTimestamp for build 
reproducibility.
+            </action>
             <!-- FIX -->
             <action dev="henrib" type="fix" issue="JEXL-420" due-to="Xu 
Pengcheng">
                 Error while comparing float and string value
@@ -98,6 +100,9 @@
                 Deprecate JexlNode.JexlNode(Parser, int) in favor of 
JexlNode.JexlNode(int).
             </action>
             <!-- UPDATE -->
+            <action dev="henrib" type="update" due-to="dependabot">
+                Bump commons-parent from 67 to 69.
+            </action>
             <action dev="henrib" type="update" due-to="dependabot">
                 Bump github actions.
             </action>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java 
b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index cd74350c..cd499a19 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -828,6 +828,7 @@ public class JexlArithmetic {
         if (operator != null) {
             switch (operator) {
                 case EQ:
+                case EQSTRICT:
                 case ARRAY_GET:
                 case ARRAY_SET:
                 case PROPERTY_GET:
@@ -2070,6 +2071,27 @@ public class JexlArithmetic {
         return compare(left, right, EQ) == 0;
     }
 
+    /**
+     * Test if left and right are strictly equal.
+     * <p>They must have the same class, comparable and the comparison returns 
0.</p>
+     *
+     * @param left  left argument
+     * @param right right argument
+     * @return the test result
+     */
+    public boolean strictEquals(final Object left, final Object right) {
+        if (left == right) {
+            return true;
+        }
+        if (left == null || right == null) {
+            return false;
+        }
+        if (left.getClass().equals(right.getClass()) && left instanceof 
Comparable<?>) {
+            return ((Comparable) left).compareTo((Comparable) right) == 0;
+        }
+        return false;
+    }
+
     /**
      * Test if left &lt; right.
      *
diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java 
b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index be45f9b5..309ea329 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -233,7 +233,7 @@ public final class JexlFeatures {
     private static final Set<String> RESERVED_WORDS =
         Collections.unmodifiableSet(
             new HashSet<>((Arrays.asList(
-                "switch", "case", "default", "class", "instanceof", "jexl", 
"$jexl"))));
+                "switch", "case", "default", "class", "jexl", "$jexl"))));
 
     /**
      * The modern scripting features set.
@@ -309,6 +309,9 @@ public final class JexlFeatures {
         if (this.flags != other.flags) {
             return false;
         }
+        if (this.nameSpaces != other.nameSpaces) {
+            return false;
+        }
         if (!Objects.equals(this.reservedNames, other.reservedNames)) {
             return false;
         }
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOperator.java 
b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
index 905f92a9..5861200e 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOperator.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOperator.java
@@ -144,6 +144,14 @@ public enum JexlOperator {
      */
     EQ("==", "equals", 2),
 
+    /**
+     * Equal-strict operator.
+     * <br><strong>Syntax:</strong> <code>x === y</code>
+     * <br><strong>Method:</strong> <code>boolean strictEquals(L x, R 
y);</code>.
+     * @see JexlArithmetic#strictEquals(Object, Object)
+     */
+    EQSTRICT("===", "strictEquals", 2),
+
     /**
      * Less-than operator.
      * <br><strong>Syntax:</strong> <code>x &lt; y</code>
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java 
b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
index 2ebf8899..f5a530c7 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
@@ -615,6 +615,26 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         return infixChildren(node, " == ", false, data);
     }
 
+    @Override
+    protected Object visit(final ASTEQSNode node, final Object data) {
+        return infixChildren(node, " === ", false, data);
+    }
+
+    @Override
+    protected Object visit(final ASTNESNode node, final Object data) {
+        return infixChildren(node, " !== ", false, data);
+    }
+
+    @Override
+    protected Object visit(final ASTInstanceOf node, final Object data) {
+        return infixChildren(node, " instanceof ", false, data);
+    }
+
+    @Override
+    protected Object visit(final ASTNotInstanceOf node, final Object data) {
+        return infixChildren(node, " !instanceof ", false, data);
+    }
+
     @Override
     protected Object visit(final ASTERNode node, final Object data) {
         return infixChildren(node, " =~ ", false, data);
@@ -731,7 +751,7 @@ public class Debugger extends ParserVisitor implements 
JexlInfo.Detail {
         if (node.hasFinallyClause()) {
             builder.append(indent > 0? lf : ' ');
             builder.append("finally ");
-            accept(node.jjtGetChild(nc++), data);
+            accept(node.jjtGetChild(nc), data);
         }
         return data;
     }
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 55bc6e64..4cbfa331 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -315,6 +315,18 @@ public class Interpreter extends InterpreterBase {
         }
     }
 
+    @Override
+    protected Object visit(final ASTEQSNode node, final Object data) {
+        final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            final Object result = operators.tryOverload(node, 
JexlOperator.EQSTRICT, left, right);
+            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.strictEquals(left, right);
+        } catch (final ArithmeticException xrt) {
+            throw new JexlException(findNullOperand(node, left, right), "=== 
error", xrt);
+        }
+    }
+
     @Override
     protected Object visit(final ASTNENode node, final Object data) {
         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
@@ -329,6 +341,20 @@ public class Interpreter extends InterpreterBase {
         }
     }
 
+    @Override
+    protected Object visit(final ASTNESNode node, final Object data) {
+        final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            final Object result = operators.tryOverload(node, 
JexlOperator.EQSTRICT, left, right);
+            return result != JexlEngine.TRY_FAILED
+                ? !arithmetic.toBoolean(result)
+                : !arithmetic.strictEquals(left, right);
+        } catch (final ArithmeticException xrt) {
+            throw new JexlException(findNullOperand(node, left, right), "!== 
error", xrt);
+        }
+    }
+
     @Override
     protected Object visit(final ASTGENode node, final Object data) {
         final Object left = node.jjtGetChild(0).jjtAccept(this, data);
@@ -524,6 +550,36 @@ public class Interpreter extends InterpreterBase {
         return  arithmetic.testPredicate(predicate != JexlEngine.TRY_FAILED? 
predicate : condition);
     }
 
+    @Override
+    protected Object visit(final ASTInstanceOf node, final Object data) {
+        final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return isInstance(left, right);
+    }
+    @Override
+
+    protected Object visit(final ASTNotInstanceOf node, final Object data) {
+        final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return !isInstance(left, right);
+    }
+
+    /**
+     * Determines if the specified Object is assignment-compatible with the 
object represented by the Class.
+     * @param object the Object
+     * @param clazz the Class
+     * @return the result of isInstance call
+     */
+    private boolean isInstance(final Object object, final Object clazz) {
+        if (object == null || clazz == null) {
+            return false;
+        }
+        Class<?> c = clazz instanceof Class<?>
+            ? (Class<?>) clazz
+            : uberspect.getClassByName(resolveClassName(clazz.toString()));
+        return c != null && c.isInstance(object);
+    }
+
     @Override
     protected Object visit(final ASTIfStatement node, final Object data) {
         final int n = 0;
@@ -541,7 +597,7 @@ public class Interpreter extends InterpreterBase {
             }
             // if odd...
             if ((numChildren & 1) == 1) {
-                // if there is an else, there are an odd number of children in 
the statement and it is the last child,
+                // If there is an else, it is the last child of an odd number 
of children in the statement,
                 // execute it.
                 result = node.jjtGetChild(numChildren - 1).jjtAccept(this, 
null);
             }
@@ -751,7 +807,8 @@ public class Interpreter extends InterpreterBase {
         if (symbol < 0) {
             setContextVariable(catchVar.jjtGetParent(), 
catchVariable.getName(), caught);
         } else {
-            frame.set(symbol, caught);
+            Throwable cause  = caught.getCause();
+            frame.set(symbol, cause == null? caught : cause);
         }
         try {
             // evaluate body
@@ -1275,7 +1332,15 @@ public class Interpreter extends InterpreterBase {
 
     @Override
     protected Object visit(final ASTQualifiedIdentifier node, final Object 
data) {
-        final String name = node.getName();
+        return resolveClassName(node.getName());
+    }
+
+    /**
+     * Resolves a class name.
+     * @param name the simple class name
+     * @return the fully qualified class name or the name
+     */
+    private String resolveClassName(final String name) {
         // try with local solver
         String fqcn = fqcnSolver.resolveClassName(name);
         if (fqcn != null) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java 
b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
index 8ccd9261..7869ad50 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
@@ -115,12 +115,18 @@ public class ScriptVisitor extends ParserVisitor {
     protected Object visit(final ASTThrowStatement node, final Object data) {
         return visitNode(node, data);
     }
-    @Override
 
+    @Override
     protected Object visit(final ASTReturnStatement node, final Object data) {
         return visitNode(node, data);
     }
 
+    @Override
+    protected Object visit(final ASTInstanceOf node, final Object data) { 
return visitNode(node, data); }
+
+    @Override
+    protected Object visit(final ASTNotInstanceOf node, final Object data) { 
return visitNode(node, data); }
+
     @Override
     protected Object visit(final ASTAssignment node, final Object data) {
         return visitNode(node, data);
@@ -195,12 +201,20 @@ public class ScriptVisitor extends ParserVisitor {
     protected Object visit(final ASTEQNode node, final Object data) {
         return visitNode(node, data);
     }
-
+    @Override
+    protected Object visit(final ASTEQSNode node, final Object data) {
+        return visitNode(node, data);
+    }
     @Override
     protected Object visit(final ASTNENode node, final Object data) {
         return visitNode(node, data);
     }
 
+    @Override
+    protected Object visit(final ASTNESNode node, final Object data) {
+        return visitNode(node, data);
+    }
+
     @Override
     protected Object visit(final ASTLTNode node, final Object data) {
         return visitNode(node, data);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Source.java 
b/src/main/java/org/apache/commons/jexl3/internal/Source.java
index afe17571..255435b5 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Source.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Source.java
@@ -25,7 +25,7 @@ import org.apache.commons.jexl3.JexlFeatures;
  * <p>This is meant for caching scripts using their 'source' as key but still 
distinguishing
  * scripts with different features and prevent false sharing.
  */
-public final class Source {
+public final class Source implements Comparable<Source> {
     /** The hash code, pre-computed for fast op. */
     private final int hashCode;
     /** The set of features. */
@@ -59,6 +59,11 @@ public final class Source {
         return hashCode;
     }
 
+    @Override
+    public int compareTo(Source s) {
+        return str.compareTo(s.str);
+    }
+
     @Override
     public boolean equals(final Object obj) {
         if (this == obj) {
diff --git 
a/src/main/java/org/apache/commons/jexl3/parser/OperatorController.java 
b/src/main/java/org/apache/commons/jexl3/parser/OperatorController.java
index b9781b13..cd85d9f7 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/OperatorController.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/OperatorController.java
@@ -142,11 +142,21 @@ final class OperatorController extends ScriptVisitor {
         return JexlOperator.EQ;
     }
 
+    @Override
+    protected JexlOperator visit(final ASTEQSNode node, final Object data) {
+        return JexlOperator.EQSTRICT;
+    }
+
     @Override
     protected JexlOperator visit(final ASTNENode node, final Object data) {
         return JexlOperator.EQ;
     }
 
+    @Override
+    protected JexlOperator visit(final ASTNESNode node, final Object data) {
+        return JexlOperator.EQSTRICT;
+    }
+
     @Override
     protected JexlOperator visit(final ASTGTNode node, final Object data) {
         return JexlOperator.GT;
diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt 
b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
index 93e22d9e..2aa71f9c 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -102,6 +102,19 @@ TOKEN_MGR_DECLS : {
     | <"//" (~["\n","\r"])* ("\n" | "\r" | "\r\n")? >
 }
 
+<NEVER> TOKEN : /* Exception handling. */
+{
+      < THROW : "throw" >
+    | < TRY : "try" >
+    | < CATCH : "catch" >
+    | < FINALLY : "finally" >
+}
+
+<NEVER> TOKEN : /* Type check. */
+{
+      < ISA : "instanceof" >
+}
+
 <*> TOKEN : { /* SEPARATORS */
       < LPAREN : "(">
     | < RPAREN : ")">
@@ -124,6 +137,7 @@ TOKEN_MGR_DECLS : {
     | < NULLP : "??" >
     | < AND : "&&" >
     | < OR : "||" >
+    | < NISA : "!instanceof">
 }
 
 <DEFAULT> TOKEN : { /* CONDITIONALS */
@@ -144,6 +158,8 @@ TOKEN_MGR_DECLS : {
     | < eeq : "=$" > // ends equal
     | < sne : "!^" > // start not equal
     | < ene : "!$" > // ends not equal
+    | < eqstrict : "===" > // strict equal
+    | < neqstrict : "!==" > // strict not equal
 }
 
 <NEVER> TOKEN : { /* COMPARISONS */
@@ -223,13 +239,6 @@ TOKEN_MGR_DECLS : {
     | < FALSE : "false" >
 }
 
-<NEVER> TOKEN : /* Exception handling. */
-{
-      < THROW : "throw" >
-    | < TRY : "try" >
-    | < CATCH : "catch" >
-    | < FINALLY : "finally" >
-}
 
 /***************************************
  *     Identifier & String tokens
@@ -264,12 +273,14 @@ TOKEN_MGR_DECLS : {
              case "gt" : matchedToken.kind = GT; break;
              case "ge" : matchedToken.kind = GE; break;
           }
-      } else if (jexl331 && length >= 3 && length <= 7) {
+      } else if (jexl331 && length >= 3 && length <= 10) {
           switch (matchedToken.image) {
              case "try" : matchedToken.kind = TRY; break;
              case "catch" : matchedToken.kind = CATCH; break;
              case "finally" : matchedToken.kind = FINALLY; break;
              case "throw" : matchedToken.kind = THROW; break;
+             case "instanceof": matchedToken.kind = ISA; break;
+             case "!instanceof": matchedToken.kind = NISA; break;
           }
       }
   }
@@ -700,6 +711,10 @@ void EqualityExpression() #void : {}
      (<eq> | <EQ>) RelationalExpression() #EQNode(2)
    |
      (<ne> | <NE>) RelationalExpression() #NENode(2)
+   |
+     (<eqstrict>) RelationalExpression() #EQSNode(2)
+   |
+     (<neqstrict>) RelationalExpression() #NESNode(2)
    |
      <range> RelationalExpression() #RangeNode(2) // range
   ) )?
@@ -728,6 +743,10 @@ void RelationalExpression() #void : {}
     <eeq> ShiftExpression() #EWNode(2) // ends with
    |
     <ene> ShiftExpression() #NEWNode(2) // not ends with
+   |
+    <ISA> ShiftExpression() #InstanceOf(2) // instanceof
+   |
+    <NISA> ShiftExpression() #NotInstanceOf(2) // not instanceof
   ) )?
 }
 
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java 
b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
index 0beea51c..675e03fc 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
@@ -64,6 +64,10 @@ public abstract class ParserVisitor {
 
     protected abstract Object visit(ASTReturnStatement node, Object data);
 
+    protected abstract Object visit(final ASTInstanceOf node, final Object 
data);
+
+    protected abstract Object visit(final ASTNotInstanceOf node, final Object 
data);
+
     protected abstract Object visit(ASTAssignment node, Object data);
 
     protected abstract Object visit(ASTVar node, Object data);
@@ -94,8 +98,12 @@ public abstract class ParserVisitor {
 
     protected abstract Object visit(ASTEQNode node, Object data);
 
+    protected abstract Object visit(ASTEQSNode node, Object data);
+
     protected abstract Object visit(ASTNENode node, Object data);
 
+    protected abstract Object visit(ASTNESNode node, Object data);
+
     protected abstract Object visit(ASTLTNode node, Object data);
 
     protected abstract Object visit(ASTGTNode node, Object data);
diff --git 
a/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngineFactory.java 
b/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngineFactory.java
index debce368..72e08938 100644
--- 
a/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngineFactory.java
+++ 
b/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngineFactory.java
@@ -50,7 +50,7 @@ public class JexlScriptEngineFactory implements 
ScriptEngineFactory {
 
     @Override
     public String getEngineVersion() {
-        return "3.3"; // ensure this is updated if function changes are made 
to this class
+        return "3.4"; // ensure this is updated if function changes are made 
to this class
     }
 
     @Override
@@ -60,7 +60,7 @@ public class JexlScriptEngineFactory implements 
ScriptEngineFactory {
 
     @Override
     public String getLanguageVersion() {
-        return "3.3"; // this should be derived from the actual version
+        return "3.4"; // this should be derived from the actual version
     }
 
     @Override
diff --git a/src/site/site.xml b/src/site/site.xml
index d17a2f96..fe28e10c 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -26,7 +26,7 @@
         <menu name="JEXL">
             <item name="Overview"                href="index.html" />
             <item name="Release Notes"           href="relnotes33.html"/>
-            <item name="Javadoc 3.3"             href="apidocs/index.html"/>
+            <item name="Javadoc 3.4"             href="apidocs/index.html"/>
             <item name="Javadoc 2.1.1"           
href="javadocs/apidocs-2.1.1/index.html"/>
             <item name="Javadoc 1.1"             
href="javadocs/apidocs-1.1/index.html"/>
             <item name="Download"                href="download_jexl.cgi"/>
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java 
b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index 029d25ed..473cd2a8 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -1045,6 +1045,8 @@ public class ArithmeticTest extends JexlTestCase {
         final Object[] EXPRESSIONS = {
                 // Basic compare
                 "1 == 1", true,
+                "1 === 1", true,
+                "1.d === 1", false,
                 "1 != 1", false,
                 "1 != 2", true,
                 "1 > 2", false,
@@ -1058,11 +1060,13 @@ public class ArithmeticTest extends JexlTestCase {
                 "1.1 < 2", true,
                 // Big Decimal <-> Big Integer Coercion
                 "1.0b == 1h", true,
+                "1.0b !== 1h", true,
                 "1h == 1.0b", true,
                 "1.1b != 1h", true,
                 "1.1b < 2h", true,
                 // Mix all type of numbers
                 "1l == 1.0", true, // long and int
+                "1l !== 1.0", true, // long and int
                 "1.0d == 1.0f", true, // double and float
                 "1l == 1.0b", true,
                 "1l == 1h", true,
@@ -1183,7 +1187,7 @@ public class ArithmeticTest extends JexlTestCase {
                     try {
                         final JexlExpression zexpr = 
jexl.createExpression(expr);
                         final Object nan = zexpr.evaluate(context);
-                        // check we have a zero & incremement zero count
+                        // check we have a zero & increment zero count
                         if (nan instanceof Number) {
                             final double zero = ((Number) nan).doubleValue();
                             if (zero == 0.0) {
@@ -1930,6 +1934,25 @@ public class ArithmeticTest extends JexlTestCase {
         asserter.failExpression("objects[1].status", ".*variable 'objects' is 
undefined.*");
     }
 
+    @Test
+    public void testInstanceOf() throws Exception {
+        final JexlEngine jexl = new 
JexlBuilder().strict(true).safe(false).imports("java.lang").create();
+        Object r = jexl.createExpression("3.0 instanceof 
'Double'").evaluate(null);
+        Assert.assertTrue((Boolean) r);
+        r = jexl.createExpression("'3.0' !instanceof 'Double'").evaluate(null);
+        Assert.assertTrue((Boolean) r);
+        JexlScript script = jexl.createScript("x instanceof y", "x", "y");
+        r = script.execute(null, "foo", String.class);
+        Assert.assertTrue((Boolean) r);
+        r = script.execute(null, 42.0, Double.class);
+        Assert.assertTrue((Boolean) r);
+        script = jexl.createScript("x !instanceof y", "x", "y");
+        r = script.execute(null, "foo", Double.class);
+        Assert.assertTrue((Boolean) r);
+        r = script.execute(null, 42.0, String.class);
+        Assert.assertTrue((Boolean) r);
+    }
+
     /**
      * Inspired by JEXL-16{1,2}.
      */
diff --git a/src/test/java/org/apache/commons/jexl3/TryCatchFinallyTest.java 
b/src/test/java/org/apache/commons/jexl3/TryCatchFinallyTest.java
index 89df8dcd..ce9f5323 100644
--- a/src/test/java/org/apache/commons/jexl3/TryCatchFinallyTest.java
+++ b/src/test/java/org/apache/commons/jexl3/TryCatchFinallyTest.java
@@ -248,4 +248,11 @@ public class TryCatchFinallyTest extends JexlTestCase {
 
     }
   }
+  @Test
+  public void testExceptionType() throws Exception {
+    JexlScript e = JEXL.createScript("try { 
'asb'.getBytes('NoSuchCharacterSet'); } catch (let ex) { ex }");
+    JexlContext jc = new MapContext();
+    Object o = e.execute(jc);
+    Assert.assertTrue(o instanceof java.io.UnsupportedEncodingException);
+  }
 }
diff --git 
a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java 
b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
index 26975c2d..65357afa 100644
--- a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
+++ b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
@@ -221,9 +221,9 @@ public class JexlScriptEngineTest {
     public void testScriptEngineFactory() throws Exception {
         final JexlScriptEngineFactory factory = new JexlScriptEngineFactory();
         Assert.assertEquals("JEXL Engine", 
factory.getParameter(ScriptEngine.ENGINE));
-        Assert.assertEquals("3.3", 
factory.getParameter(ScriptEngine.ENGINE_VERSION));
+        Assert.assertEquals("3.4", 
factory.getParameter(ScriptEngine.ENGINE_VERSION));
         Assert.assertEquals("JEXL", 
factory.getParameter(ScriptEngine.LANGUAGE));
-        Assert.assertEquals("3.3", 
factory.getParameter(ScriptEngine.LANGUAGE_VERSION));
+        Assert.assertEquals("3.4", 
factory.getParameter(ScriptEngine.LANGUAGE_VERSION));
         Assert.assertNull(factory.getParameter("THREADING"));
         Assert.assertEquals(NAMES, factory.getParameter(ScriptEngine.NAME));
         Assert.assertEquals(EXTENSIONS, factory.getExtensions());
diff --git a/src/test/scripts/httpPost.jexl b/src/test/scripts/httpPost.jexl
index 083e4ada..322ef2e4 100644
--- a/src/test/scripts/httpPost.jexl
+++ b/src/test/scripts/httpPost.jexl
@@ -38,7 +38,7 @@
         // read response
         let responseCode = con.getResponseCode();
         let inputStream = con.getInputStream();
-        let response = new StringBuffer();
+        let response = new StringBuilder();
         if (inputStream != null) {
             let in = new BufferedReader(new InputStreamReader(inputStream));
             var inputLine = "";

Reply via email to