Merge branch 'master' of https://gitbox.apache.org/repos/asf/commons-jexl.git master
authorGary Gregory <garydgregory@gmail.com>
Fri, 5 Aug 2022 12:39:01 +0000 (08:39 -0400)
committerGary Gregory <garydgregory@gmail.com>
Fri, 5 Aug 2022 12:39:01 +0000 (08:39 -0400)
37 files changed:
.github/workflows/maven.yml
RELEASE-NOTES.txt
pom.xml
src/changes/changes.xml
src/main/config/findbugs-exclude-filter.xml
src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
src/main/java/org/apache/commons/jexl3/JexlException.java
src/main/java/org/apache/commons/jexl3/JexlOperator.java
src/main/java/org/apache/commons/jexl3/internal/Debugger.java
src/main/java/org/apache/commons/jexl3/internal/Engine.java
src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
src/main/java/org/apache/commons/jexl3/internal/Operators.java
src/main/java/org/apache/commons/jexl3/internal/Scope.java
src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
src/main/java/org/apache/commons/jexl3/internal/introspection/ClassTool.java [new file with mode: 0644]
src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java
src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
src/test/java/org/apache/commons/jexl3/ForEachTest.java
src/test/java/org/apache/commons/jexl3/Issues300Test.java
src/test/java/org/apache/commons/jexl3/JexlTestCase.java
src/test/java/org/apache/commons/jexl3/LexicalTest.java
src/test/java/org/apache/commons/jexl3/ScriptTest.java
src/test/java/org/apache/commons/jexl3/SideEffectTest.java
src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
src/test/java/org/apache/commons/jexl3/jexl342/OptionalArithmetic.java
src/test/java/org/apache/commons/jexl3/junit/Asserter.java
src/test/scripts/httpPost.jexl [new file with mode: 0644]

index cfcc4bf7448c3512ea5d194bac7ea8daef9fb1d0..fc2aa3810a9ee62b15f433272f74041fb4d41845 100644 (file)
@@ -38,7 +38,7 @@ jobs:
         \r
     steps:\r
     - uses: actions/checkout@v3\r
-    - uses: actions/cache@v3.0.4\r
+    - uses: actions/cache@v3.0.6\r
       with:\r
         path: ~/.m2/repository\r
         key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}\r
index 29e6ddee682ceb8c8094d8096da568f24dcec51d..ac14d259318502b5286bfcda7daaeca7293ac2cb 100644 (file)
@@ -35,12 +35,16 @@ with the @NoJexl annotation on source code. This is achieved through a crude but
 a security manager that controls what JEXL can introspect and thus expose to scripts.
 Used in conjunction with options (JexlOptions) and features (JexlFeatures), the permissions (JexlPermissions)
 allow fine-tuning the scripting integration into any project.
+JEXL 3.3 also adds some syntactic (ECMAScript) features (let, const, =>, for, ...) to further reduce
+the skill set required to write scripts.
 
 New Features in 3.3:
 ====================
 * JEXL-373:     Add support for prefix/postfix increment/decrement operators
 * JEXL-372:     Add support for 'standard' for loop
 * JEXL-369:     Add 'let' and 'const' variable declarations
+* JEXL-367:     Named function and fat-arrow (=>) lambda syntax
+* JEXL-366:     Fail to evaluate string and number comparison
 * JEXL-365:     Lambda expressions
 * JEXL-363:     Allow retrieving captured variables in script
 * JEXL-360:     Add missing bitshift operators ( >>, >>>, <<)
@@ -49,8 +53,12 @@ New Features in 3.3:
 
 Bugs Fixed in 3.3:
 ==================
+* JEXL-376:     Introspector captures methods on non-exported classes (modules, java9+)
+* JEXL-375:     Cannot access enums by their name when using sandbox
+* JEXL-374:     No exception if dereferencing null object using safe(false) and antish(false)
 * JEXL-371:     Override of a protected method with public visibility is not callable
 * JEXL-370:     Cannot check if variable is defined using ObjectContext if the value is null
+* JEXL-368:     Namespace functor resolution is not cached
 * JEXL-364:     Evaluator options not propagated in closures
 * JEXL-362:     JexlInfo position reporting is off
 * JEXL-361:     Null may be used as operand silently even in arithmetic strict(true) mode
diff --git a/pom.xml b/pom.xml
index f58a3a2a401da0f4a119a959f020eef74bd21779..15628a88116419d4b0ad7bbbbfd8e2e2caa6c912 100644 (file)
--- a/pom.xml
+++ b/pom.xml
         <commons.jira.id>JEXL</commons.jira.id>
         <commons.jira.pid>12310479</commons.jira.pid>
         <checkstyle.plugin.version>3.1.2</checkstyle.plugin.version>
-        <checkstyle.version>10.3</checkstyle.version>
+        <checkstyle.version>10.3.2</checkstyle.version>
         <japicmp.skip>false</japicmp.skip>
+        <commons.rat.version>0.14</commons.rat.version>
         <commons.japicmp.version>0.15.7</commons.japicmp.version>
         <commons.pmd.version>3.17.0</commons.pmd.version>
-        <commons.spotbugs.version>4.7.0.0</commons.spotbugs.version>
+        <commons.pmd-impl.version>6.46.0</commons.pmd-impl.version>
+        <commons.spotbugs.version>4.7.1.1</commons.spotbugs.version>
 
         <!-- override of Jacoco properties defined in CP52 -->
         <commons.jacoco.version>0.8.8</commons.jacoco.version>
                         <exclude>org/apache/commons/jexl3/parser/*Provider.java</exclude>
                     </excludes>
                 </configuration>
-                <dependencies>
-                    <dependency>
-                        <groupId>org.ow2.asm</groupId>
-                        <artifactId>asm</artifactId>
-                        <version>9.3</version>
-                    </dependency>
-                </dependencies>
             </plugin>
 
             <!-- japicmp -->
                 </configuration>
             </plugin>
 
+            <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>animal-sniffer-maven-plugin</artifactId>
+            <version>1.21</version>
+                <configuration><ignores>java.lang.invoke.*</ignores></configuration>
+            </plugin>
+
         </plugins>
     </build>
 
index af5c95462b2f13a8fb737cfc49c911ae22ce6fc8..4e37b286353f168776cd73d559bbcb7a3c52d30a 100644 (file)
     <body>\r
         <release version="3.3" date="YYYY-MM-DD">\r
             <!-- ADD -->\r
-            <action dev="henrib" type="add" issue="JEXL-357" >\r
+            <action dev="henrib" type="add" issue="JEXL-373" due-to="Dmitri Blinov">\r
+                Add support for prefix/postfix increment/decrement operators\r
+            </action>\r
+            <action dev="henrib" type="add" issue="JEXL-372" due-to="Dmitri Blinov">\r
+                Add support for 'standard' for loop\r
+            </action>\r
+            <action dev="henrib" type="add" issue="JEXL-369" due-to="Dmitri Blinov">\r
+            Add 'let' and 'const' variable declarations\r
+            </action>\r
+            <action dev="henrib" type="add" issue="JEXL-367" due-to="Hussachai Puripunpinyo">\r
+                Named function and fat-arrow (=>) lambda syntax\r
+            </action>\r
+            <action dev="henrib" type="add" issue="JEXL-366" due-to="Hussachai Puripunpinyo">\r
+            Fail to evaluate string and number comparison\r
+            </action>\r
+            <action dev="henrib" type="add" issue="JEXL-365" due-to="Dmitri Blinov">\r
+                Lambda expressions\r
+            </action>\r
+            <action dev="henrib" type="add" issue="JEXL-363">\r
+                Allow retrieving captured variables in script\r
+            </action>\r
+            <action dev="henrib" type="add" issue="JEXL-360"  due-to="Ian Hawkins">\r
+                Add missing bitshift operators (&lt;&lt;, &gt;&gt;&gt;, &gt;&gt;)\r
+            </action>\r
+            <action dev="henrib" type="add" issue="JEXL-359">\r
+                Allow per-operator arithmetic handling of null arguments\r
+            </action>\r
+            <action dev="henrib" type="add" issue="JEXL-357">\r
                 Configure accessible packages/classes/methods/fields\r
             </action>\r
             <!--  FIX -->\r
+            <action dev="henrib" type="fix" issue="JEXL-376">\r
+                Introspector captures methods on non-exported classes (modules, java9+)\r
+            </action>\r
+            <action dev="henrib" type="fix" issue="JEXL-375" due-to="Jan Klicka">\r
+                Cannot access enums by their name when using sandbox\r
+            </action>\r
+            <action dev="henrib" type="fix" issue="JEXL-374" due-to="Alex Hutton">\r
+                No exception if dereferencing null object using safe(false) and antish(false)\r
+            </action>\r
             <action dev="henrib" type="fix" issue="JEXL-371">\r
                 Override of a protected method with public visibility is not callable\r
             </action>\r
+            <action dev="henrib" type="fix" issue="JEXL-370" due-to="Alex Hutton">\r
+                Cannot check if variable is defined using ObjectContext if the value is null\r
+            </action>\r
+            <action dev="henrib" type="add" issue="JEXL-368">\r
+                Namespace functor resolution is not cached\r
+            </action>\r
+            <action dev="henrib" type="fix" issue="JEXL-364">\r
+                Evaluator options not propagated in closures\r
+            </action>\r
+            <action dev="henrib" type="fix" issue="JEXL-362">\r
+                JexlInfo position reporting is off\r
+            </action>\r
+            <action dev="henrib" type="fix" issue="JEXL-361">\r
+                Null may be used as operand silently even in arithmetic strict(true) mode\r
+            </action>\r
+            <action dev="henrib" type="fix" issue="JEXL-358">\r
+                JexlScript.curry(...) resulting scripts don't evaluate correctly\r
+            </action>\r
             <action dev="henrib" type="fix" issue="JEXL-354"  due-to="William Price">\r
                 #pragma does not handle negative integer or real literals\r
             </action>\r
@@ -42,7 +96,7 @@
             </action>\r
             <!-- UPDATE -->\r
             <action dev="ggregory" type="update" due-to="Gary Gregory">\r
-                Bump actions/cache from 3 to 3.0.4.\r
+                Bump actions/cache from 3 to 3.0.6.\r
             </action>\r
             <action dev="ggregory" type="update" due-to="Dependabot">\r
                 Bump actions/checkout from 2 to 3 #79.\r
index 18f97c3d9cb5d93e883754177f95a0cb5607d830..7f46eb6bf866563c52646f717d8ca1bfc7abe31a 100644 (file)
@@ -1,70 +1,74 @@
-<?xml version="1.0"?>
-<!--
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
--->
-
-<!--
-  This file removes JavaCC generated classes from being analyzed by findbugs.
-  Having no way to influence their generation, instructing findbugs to ignore them reduces clutter.
--->
-<FindBugsFilter>
-    <Match>
-        <Class name="org.apache.commons.jexl3.parser.AbstractCharStream"/>
-    </Match>
-    <Match>
-        <Class name="org.apache.commons.jexl3.parser.JexlLexicalNode"/>
-    </Match>
-    <Match>
-        <Class name="org.apache.commons.jexl3.parser.JexlParser"/>
-    </Match>
-    <Match>
-        <Class name="org.apache.commons.jexl3.parser.ParseException"/>
-    </Match>
-    <Match>
-        <Class name="org.apache.commons.jexl3.parser.Parser"/>
-    </Match>
-    <Match>
-        <Class name="org.apache.commons.jexl3.parser.ParserConstants"/>
-    </Match>
-    <Match>
-        <Class name="org.apache.commons.jexl3.parser.ParserTokenManager"/>
-    </Match>
-    <Match>
-        <Class name="org.apache.commons.jexl3.parser.ParserTreeConstants"/>
-    </Match>
-    <Match>
-        <Class name="org.apache.commons.jexl3.parser.TokenMgrError"/>
-    </Match>
-    <Match>
-        <Class name="org.apache.commons.jexl3.parser.SimpleNode"/>
-    </Match>
-    <Match>
-        <Class name="org.apache.commons.jexl3.JexlBuilder"/>
-        <Bug code="EI2,EI"></Bug>
-    </Match>
-    <Match>
-        <Package name="org.apache.commons.jexl3.internal"/>
-        <Bug code="EI2,EI"></Bug>
-    </Match>
-    <Match>
-        <Package name="org.apache.commons.jexl3.introspection.internal"/>
-        <Bug code="EI2,EI"></Bug>
-    </Match>
-    <Match>
-        <Package name="org.apache.commons.jexl3.parser"/>
-        <Bug code="EI2,EI"></Bug>
-    </Match>
-</FindBugsFilter>
+<?xml version="1.0"?>\r
+<!--\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+-->\r
+\r
+<!--\r
+  This file removes JavaCC generated classes from being analyzed by findbugs.\r
+  Having no way to influence their generation, instructing findbugs to ignore them reduces clutter.\r
+-->\r
+<FindBugsFilter\r
+    xmlns="https://github.com/spotbugs/filter/3.0.0"\r
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+    xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">\r
+\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.parser.AbstractCharStream"/>\r
+    </Match>\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.parser.JexlLexicalNode"/>\r
+    </Match>\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.parser.JexlParser"/>\r
+    </Match>\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.parser.ParseException"/>\r
+    </Match>\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.parser.Parser"/>\r
+    </Match>\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.parser.ParserConstants"/>\r
+    </Match>\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.parser.ParserTokenManager"/>\r
+    </Match>\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.parser.ParserTreeConstants"/>\r
+    </Match>\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.parser.TokenMgrError"/>\r
+    </Match>\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.parser.SimpleNode"/>\r
+    </Match>\r
+    <Match>\r
+        <Class name="org.apache.commons.jexl3.JexlBuilder"/>\r
+        <Bug code="EI2,EI"></Bug>\r
+    </Match>\r
+    <Match>\r
+        <Package name="org.apache.commons.jexl3.internal"/>\r
+        <Bug code="EI2,EI"></Bug>\r
+    </Match>\r
+    <Match>\r
+        <Package name="org.apache.commons.jexl3.introspection.internal"/>\r
+        <Bug code="EI2,EI"></Bug>\r
+    </Match>\r
+    <Match>\r
+        <Package name="org.apache.commons.jexl3.parser"/>\r
+        <Bug code="EI2,EI"></Bug>\r
+    </Match>\r
+</FindBugsFilter>\r
index 1ca2bd42ca06237772dd89b7579f14309f487e94..4610d2270f695110f08008b488c1096c77a70b9b 100644 (file)
@@ -534,7 +534,7 @@ public class JexlArithmetic {
     }
 
     /**
-     * Given a Number, return back the value attempting to narrow it to a target class.
+     * Given a Number, return the value attempting to narrow it to a target class.
      *
      * @param original the original number
      * @param narrow   the attempted target class
@@ -613,14 +613,14 @@ public class JexlArithmetic {
      * if either arguments is a Long, no narrowing to Integer will occur
      * </p>
      *
-     * @param lhs  the left hand side operand that lead to the bigi result
-     * @param rhs  the right hand side operand that lead to the bigi result
+     * @param lhs  the left-hand side operand that lead to the bigi result
+     * @param rhs  the right-hand side operand that lead to the bigi result
      * @param bigi the BigInteger to narrow
      * @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
      */
     protected Number narrowBigInteger(final Object lhs, final Object rhs, final BigInteger bigi) {
         //coerce to long if possible
-        if (!(lhs instanceof BigInteger || rhs instanceof BigInteger)
+        if ((isNumberable(lhs) || isNumberable(rhs))
                 && bigi.compareTo(BIGI_LONG_MAX_VALUE) <= 0
                 && bigi.compareTo(BIGI_LONG_MIN_VALUE) >= 0) {
             // coerce to int if possible
@@ -637,13 +637,13 @@ public class JexlArithmetic {
     }
 
     /**
-     * Given a BigDecimal, attempt to narrow it to an Integer or Long if it fits if
+     * Given a BigDecimal, attempt to narrow it to an Integer or Long if it fits and
      * one of the arguments is a numberable.
      *
-     * @param lhs  the left hand side operand that lead to the bigd result
-     * @param rhs  the right hand side operand that lead to the bigd result
+     * @param lhs  the left-hand side operand that lead to the bigd result
+     * @param rhs  the right-hand side operand that lead to the bigd result
      * @param bigd the BigDecimal to narrow
-     * @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
+     * @return an Integer or Long if narrowing is possible, the original BigDecimal otherwise
      */
     protected Number narrowBigDecimal(final Object lhs, final Object rhs, final BigDecimal bigd) {
         if (isNumberable(lhs) || isNumberable(rhs)) {
@@ -755,19 +755,21 @@ public class JexlArithmetic {
             return ((Long) val) + incr;
         }
         if (val instanceof BigDecimal) {
-            return ((BigDecimal) val).add(BigDecimal.valueOf(incr));
+            BigDecimal bd = (BigDecimal) val;
+            return bd.add(BigDecimal.valueOf(incr), this.mathContext);
         }
         if (val instanceof BigInteger) {
-            return ((BigInteger) val).add(BigInteger.valueOf(incr));
+            BigInteger bi = (BigInteger) val;
+            return bi.add(BigInteger.valueOf(incr));
         }
         if (val instanceof Float) {
             return ((Float) val) + incr;
         }
         if (val instanceof Short) {
-            return (short) ((Short) val) + incr;
+            return (short) (((Short) val) + incr);
         }
         if (val instanceof Byte) {
-            return (byte) ((Byte) val) + incr;
+            return (byte) (((Byte) val) + incr);
         }
         throw new ArithmeticException("Object "+(incr < 0? "decrement":"increment")+":(" + val + ")");
     }
@@ -1046,6 +1048,7 @@ public class JexlArithmetic {
     /**
      * Negates a value (unary minus for numbers).
      *
+     * @see #isNegateStable()
      * @param val the value to negate
      * @return the negated value
      */
@@ -1092,7 +1095,6 @@ public class JexlArithmetic {
      * <p>This is used to determine whether negate results on number literals can be cached.
      * If the result on calling negate with the same constant argument may change between calls,
      * which means the function is not deterministic, this method must return false.
-     * @see #isNegateStable()
      * @return true if negate is idempotent, false otherwise
      */
     public boolean isNegateStable() {
@@ -1676,7 +1678,7 @@ public class JexlArithmetic {
      */
     private double parseDouble(String arg) throws ArithmeticException {
         try {
-            return arg.isEmpty()? Double.NaN : Double.parseDouble((String) arg);
+            return arg.isEmpty()? Double.NaN : Double.parseDouble(arg);
         } catch(NumberFormatException xformat) {
             throw new ArithmeticException("Double coercion: ("+ arg +")");
         }
@@ -1862,8 +1864,7 @@ public class JexlArithmetic {
             return parseDouble((String) val);
         }
         if (val instanceof Character) {
-            final int i = ((Character) val);
-            return i;
+            return ((Character) val);
         }
         throw new ArithmeticException("Double coercion: "
                 + val.getClass().getName() + ":(" + val + ")");
index 573bc755a36f545d8fe3885ea699463c7651a9ae..a683596f6e4345a38c5a271644fb4bf8db01fcfe 100644 (file)
@@ -193,7 +193,7 @@ public class JexlException extends RuntimeException {
                     stackJexl.add(se);
                 }
             }
-            xthrow.setStackTrace(stackJexl.toArray(new StackTraceElement[stackJexl.size()]));
+            xthrow.setStackTrace(stackJexl.toArray(new StackTraceElement[0]));
         }
         return xthrow;
     }
@@ -631,6 +631,7 @@ public class JexlException extends RuntimeException {
         public Property(final JexlNode node, final String pty) {
             this(node, pty, true, null);
         }
+
         /**
          * Creates a new Property exception instance.
          *
index 820ad0d0f6dc0f23b9b12ceac10c7c5184a1d843..2b93eb2437eca0ac1b13b08f0af9dc86b4dedaf2 100644 (file)
@@ -28,7 +28,7 @@ package org.apache.commons.jexl3;
  * <p>The default JexlArithmetic implements generic versions of these methods using Object as arguments.
  * You can use your own derived JexlArithmetic that override and/or overload those operator methods.
  * Note that these are overloads by convention, not actual Java overloads.
- * The following rules apply to operator methods:</p>
+ * The following rules apply to all operator methods:</p>
  * <ul>
  * <li>Operator methods should be public</li>
  * <li>Operators return type should be respected when primitive (int, boolean,...)</li>
@@ -36,6 +36,14 @@ package org.apache.commons.jexl3;
  * <li>Operators may return JexlEngine.TRY_AGAIN to fallback on default JEXL implementation</li>
  * </ul>
  *
+ * For side effect operators, operators that modify the left-hand size value (+=, -=, etc), the user implemented
+ * overload methods may return:
+ * <ul>
+ *     <li>JexlEngine.TRY_FAIL to let the default fallback behavior be executed.</li>
+ *     <li>Any other value will be used as the new value to be assigned to the left-hand-side.</li>
+ * </ul>
+ * Note that side effect operators always return the left-hand side value (with an exception for postfix ++ and --).
+ *
  * @since 3.0
  */
 public enum JexlOperator {
@@ -44,7 +52,7 @@ public enum JexlOperator {
      * Add operator.
      * <br><strong>Syntax:</strong> <code>x + y</code>
      * <br><strong>Method:</strong> <code>T add(L x, R y);</code>.
-     * @see JexlArithmetic#add
+     * @see JexlArithmetic#add(Object, Object)
      */
     ADD("+", "add", 2),
 
@@ -52,7 +60,7 @@ public enum JexlOperator {
      * Subtract operator.
      * <br><strong>Syntax:</strong> <code>x - y</code>
      * <br><strong>Method:</strong> <code>T subtract(L x, R y);</code>.
-     * @see JexlArithmetic#subtract
+     * @see JexlArithmetic#subtract(Object, Object)
      */
     SUBTRACT("-", "subtract", 2),
 
@@ -60,7 +68,7 @@ public enum JexlOperator {
      * Multiply operator.
      * <br><strong>Syntax:</strong> <code>x * y</code>
      * <br><strong>Method:</strong> <code>T multiply(L x, R y);</code>.
-     * @see JexlArithmetic#multiply
+     * @see JexlArithmetic#multiply(Object, Object)
      */
     MULTIPLY("*", "multiply", 2),
 
@@ -68,7 +76,7 @@ public enum JexlOperator {
      * Divide operator.
      * <br><strong>Syntax:</strong> <code>x / y</code>
      * <br><strong>Method:</strong> <code>T divide(L x, R y);</code>.
-     * @see JexlArithmetic#divide
+     * @see JexlArithmetic#divide(Object, Object)
      */
     DIVIDE("/", "divide", 2),
 
@@ -76,7 +84,7 @@ public enum JexlOperator {
      * Modulo operator.
      * <br><strong>Syntax:</strong> <code>x % y</code>
      * <br><strong>Method:</strong> <code>T mod(L x, R y);</code>.
-     * @see JexlArithmetic#mod
+     * @see JexlArithmetic#mod(Object, Object)
      */
     MOD("%", "mod", 2),
 
@@ -84,7 +92,7 @@ public enum JexlOperator {
      * Bitwise-and operator.
      * <br><strong>Syntax:</strong> <code>x &amp; y</code>
      * <br><strong>Method:</strong> <code>T and(L x, R y);</code>.
-     * @see JexlArithmetic#and
+     * @see JexlArithmetic#and(Object, Object)
      */
     AND("&", "and", 2),
 
@@ -92,7 +100,7 @@ public enum JexlOperator {
      * Bitwise-or operator.
      * <br><strong>Syntax:</strong> <code>x | y</code>
      * <br><strong>Method:</strong> <code>T or(L x, R y);</code>.
-     * @see JexlArithmetic#or
+     * @see JexlArithmetic#or(Object, Object)
      */
     OR("|", "or", 2),
 
@@ -100,7 +108,7 @@ public enum JexlOperator {
      * Bitwise-xor operator.
      * <br><strong>Syntax:</strong> <code>x ^ y</code>
      * <br><strong>Method:</strong> <code>T xor(L x, R y);</code>.
-     * @see JexlArithmetic#xor
+     * @see JexlArithmetic#xor(Object, Object)
      */
     XOR("^", "xor", 2),
 
@@ -132,7 +140,7 @@ public enum JexlOperator {
      * Equals operator.
      * <br><strong>Syntax:</strong> <code>x == y</code>
      * <br><strong>Method:</strong> <code>boolean equals(L x, R y);</code>.
-     * @see JexlArithmetic#equals
+     * @see JexlArithmetic#equals(Object, Object)
      */
     EQ("==", "equals", 2),
 
@@ -140,7 +148,7 @@ public enum JexlOperator {
      * Less-than operator.
      * <br><strong>Syntax:</strong> <code>x &lt; y</code>
      * <br><strong>Method:</strong> <code>boolean lessThan(L x, R y);</code>.
-     * @see JexlArithmetic#lessThan
+     * @see JexlArithmetic#lessThan(Object, Object)
      */
     LT("<", "lessThan", 2),
 
@@ -148,7 +156,7 @@ public enum JexlOperator {
      * Less-than-or-equal operator.
      * <br><strong>Syntax:</strong> <code>x &lt;= y</code>
      * <br><strong>Method:</strong> <code>boolean lessThanOrEqual(L x, R y);</code>.
-     * @see JexlArithmetic#lessThanOrEqual
+     * @see JexlArithmetic#lessThanOrEqual(Object, Object)
      */
     LTE("<=", "lessThanOrEqual", 2),
 
@@ -156,7 +164,7 @@ public enum JexlOperator {
      * Greater-than operator.
      * <br><strong>Syntax:</strong> <code>x &gt; y</code>
      * <br><strong>Method:</strong> <code>boolean greaterThan(L x, R y);</code>.
-     * @see JexlArithmetic#greaterThan
+     * @see JexlArithmetic#greaterThan(Object, Object)
      */
     GT(">", "greaterThan", 2),
 
@@ -164,7 +172,7 @@ public enum JexlOperator {
      * Greater-than-or-equal operator.
      * <br><strong>Syntax:</strong> <code>x &gt;= y</code>
      * <br><strong>Method:</strong> <code>boolean greaterThanOrEqual(L x, R y);</code>.
-     * @see JexlArithmetic#greaterThanOrEqual
+     * @see JexlArithmetic#greaterThanOrEqual(Object, Object)
      */
     GTE(">=", "greaterThanOrEqual", 2),
 
@@ -172,7 +180,7 @@ public enum JexlOperator {
      * Contains operator.
      * <br><strong>Syntax:</strong> <code>x =~ y</code>
      * <br><strong>Method:</strong> <code>boolean contains(L x, R y);</code>.
-     * @see JexlArithmetic#contains
+     * @see JexlArithmetic#contains(Object, Object)
      */
     CONTAINS("=~", "contains", 2),
 
@@ -180,7 +188,7 @@ public enum JexlOperator {
      * Starts-with operator.
      * <br><strong>Syntax:</strong> <code>x =^ y</code>
      * <br><strong>Method:</strong> <code>boolean startsWith(L x, R y);</code>.
-     * @see JexlArithmetic#startsWith
+     * @see JexlArithmetic#startsWith(Object, Object)
      */
     STARTSWITH("=^", "startsWith", 2),
 
@@ -188,7 +196,7 @@ public enum JexlOperator {
      * Ends-with operator.
      * <br><strong>Syntax:</strong> <code>x =$ y</code>
      * <br><strong>Method:</strong> <code>boolean endsWith(L x, R y);</code>.
-     * @see JexlArithmetic#endsWith
+     * @see JexlArithmetic#endsWith(Object, Object)
      */
     ENDSWITH("=$", "endsWith", 2),
 
@@ -196,7 +204,7 @@ public enum JexlOperator {
      * Not operator.
      * <br><strong>Syntax:</strong> <code>!x</code>
      * <br><strong>Method:</strong> <code>T not(L x);</code>.
-     * @see JexlArithmetic#not
+     * @see JexlArithmetic#not(Object)
      */
     NOT("!", "not", 1),
 
@@ -204,7 +212,7 @@ public enum JexlOperator {
      * Complement operator.
      * <br><strong>Syntax:</strong> <code>~x</code>
      * <br><strong>Method:</strong> <code>T complement(L x);</code>.
-     * @see JexlArithmetic#complement
+     * @see JexlArithmetic#complement(Object)
      */
     COMPLEMENT("~", "complement", 1),
 
@@ -212,7 +220,7 @@ public enum JexlOperator {
      * Negate operator.
      * <br><strong>Syntax:</strong> <code>-x</code>
      * <br><strong>Method:</strong> <code>T negate(L x);</code>.
-     * @see JexlArithmetic#negate
+     * @see JexlArithmetic#negate(Object)
      */
     NEGATE("-", "negate", 1),
 
@@ -220,7 +228,7 @@ public enum JexlOperator {
      * Positivize operator.
      * <br><strong>Syntax:</strong> <code>+x</code>
      * <br><strong>Method:</strong> <code>T positivize(L x);</code>.
-     * @see JexlArithmetic#positivize
+     * @see JexlArithmetic#positivize(Object)
      */
     POSITIVIZE("+", "positivize", 1),
 
@@ -228,7 +236,7 @@ public enum JexlOperator {
      * Empty operator.
      * <br><strong>Syntax:</strong> <code>empty x</code> or <code>empty(x)</code>
      * <br><strong>Method:</strong> <code>boolean empty(L x);</code>.
-     * @see JexlArithmetic#empty
+     * @see JexlArithmetic#empty(Object)
      */
     EMPTY("empty", "empty", 1),
 
@@ -236,7 +244,7 @@ public enum JexlOperator {
      * Size operator.
      * <br><strong>Syntax:</strong> <code>size x</code> or <code>size(x)</code>
      * <br><strong>Method:</strong> <code>int size(L x);</code>.
-     * @see JexlArithmetic#size
+     * @see JexlArithmetic#size(Object)
      */
     SIZE("size", "size", 1),
 
@@ -319,15 +327,15 @@ public enum JexlOperator {
 
     /**
      * Increment pseudo-operator.
-     * <br>No syntax, used as helper for <code>++</code>.
-     * @see JexlArithmetic#increment
+     * <br>No syntax, used as helper for the prefix and postfix versions of <code>++</code>.
+     * @see JexlArithmetic#increment(Object)
      */
     INCREMENT("+1", "increment", 1),
 
     /**
      * Decrement pseudo-operator.
-     * <br>No syntax, used as helper for <code>--</code>.
-     * @see JexlArithmetic#decrement
+     * <br>No syntax, used as helper for the prefix and postfix versions of <code>--</code>.
+     * @see JexlArithmetic#decrement(Object)
      */
     DECREMENT("-1", "decrement", 1),
 
@@ -336,28 +344,28 @@ public enum JexlOperator {
      * <br><strong>Syntax:</strong> <code>++x</code>
      * <br><strong>Method:</strong> <code>T incrementAndGet(L x);</code>.
      */
-    INCREMENT_AND_GET("++.", "incrementAndGet", INCREMENT),
+    INCREMENT_AND_GET("++.", "incrementAndGet", INCREMENT, 1),
 
     /**
      * Postfix ++, increments and returns the value before incrementing.
      * <br><strong>Syntax:</strong> <code>x++</code>
      * <br><strong>Method:</strong> <code>T getAndIncrement(L x);</code>.
      */
-    GET_AND_INCREMENT(".++", "getAndIncrement", INCREMENT),
+    GET_AND_INCREMENT(".++", "getAndIncrement", INCREMENT, 1),
 
     /**
      * Prefix --, decrements and returns the value after decrementing.
      * <br><strong>Syntax:</strong> <code>--x</code>
      * <br><strong>Method:</strong> <code>T decrementAndGet(L x);</code>.
      */
-    DECREMENT_AND_GET("--.", "decrementAndGet", DECREMENT),
+    DECREMENT_AND_GET("--.", "decrementAndGet", DECREMENT, 1),
 
     /**
      * Postfix --, decrements and returns the value before decrementing.
      * <br><strong>Syntax:</strong> <code>x--</code>
      * <br><strong>Method:</strong> <code>T getAndDecrement(L x);</code>.
      */
-    GET_AND_DECREMENT(".--", "getAndDecrement", DECREMENT),
+    GET_AND_DECREMENT(".--", "getAndDecrement", DECREMENT, 1),
 
     /**
      * Marker for side effect.
@@ -431,23 +439,32 @@ public enum JexlOperator {
      * @param argc the number of parameters for the method
      */
     JexlOperator(final String o, final String m, final int argc) {
-        this.operator = o;
-        this.methodName = m;
-        this.arity = argc;
-        this.base = null;
+        this(o, m, null, argc);
     }
 
     /**
-     * Creates a side-effect operator.
+     * Creates a side effect operator with arity == 2.
      *
      * @param o the operator name
      * @param m the method name associated to this operator in a JexlArithmetic
      * @param b the base operator, ie + for +=
      */
     JexlOperator(final String o, final String m, final JexlOperator b) {
+        this(o, m, b, 2);
+    }
+
+    /**
+     * Creates a side effect operator.
+     *
+     * @param o the operator name
+     * @param m the method name associated to this operator in a JexlArithmetic
+     * @param b the base operator, ie + for +=
+     * @param a the operator arity
+     */
+    JexlOperator(final String o, final String m, final JexlOperator b, final int a) {
         this.operator = o;
         this.methodName = m;
-        this.arity = 2;
+        this.arity = a;
         this.base = b;
     }
 
index e24bf8f13147827e420f8c99df97ce899befc308..196c0fcf4c482339a1abfb44ae86fed37d44ee32 100644 (file)
@@ -50,6 +50,8 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
     protected int depth = Integer.MAX_VALUE;
     /** Arrow symbol. */
     protected String arrow = "->";
+    /** EOL. */
+    protected String lf = "\n";
 
     /**
      * Creates a Debugger.
@@ -234,6 +236,16 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
         return this;
     }
 
+    /**
+     * Sets this debugger line-feed string.
+     * @param lf the string used to delineate lines (usually "\" or "")
+     * @return this debugger instance
+     */
+    public Debugger lineFeed(final String lf) {
+        this.lf = lf;
+        return this;
+    }
+
     /**
      * Checks if a child node is the cause to debug &amp; adds its representation to the rebuilt expression.
      * @param node the child node
@@ -264,7 +276,6 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
      */
     private static boolean isStatement(JexlNode child) {
         return child instanceof ASTJexlScript
-                || child instanceof ASTJexlLambda
                 || child instanceof ASTBlock
                 || child instanceof ASTIfStatement
                 || child instanceof ASTForeachStatement
@@ -313,7 +324,7 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
         if (!isStatement(child) && !semicolTerminated(builder)) {
             builder.append(';');
             if (indent > 0) {
-                builder.append('\n');
+                builder.append(lf);
             } else {
                 builder.append(' ');
             }
@@ -537,7 +548,7 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
         builder.append('{');
         if (indent > 0) {
             indentLevel += 1;
-            builder.append('\n');
+            builder.append(lf);
         } else {
             builder.append(' ');
         }
@@ -554,6 +565,9 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
                 }
             }
         }
+        if (!Character.isSpaceChar(builder.charAt(builder.length() - 1))) {
+            builder.append(' ');
+        }
         builder.append('}');
         return data;
     }
@@ -615,15 +629,45 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
         return check(node, "break", data);
     }
 
+
     @Override
     protected Object visit(final ASTForeachStatement node, final Object data) {
+        final int form = node.getLoopForm();
         builder.append("for(");
-        accept(node.jjtGetChild(0), data);
-        builder.append(" : ");
-        accept(node.jjtGetChild(1), data);
-        builder.append(") ");
-        if (node.jjtGetNumChildren() > 2) {
-            acceptStatement(node.jjtGetChild(2), data);
+        final JexlNode body;
+        if (form == 0) {
+            // for( .. : ...)
+            accept(node.jjtGetChild(0), data);
+            builder.append(" : ");
+            accept(node.jjtGetChild(1), data);
+            builder.append(") ");
+            body = node.jjtGetNumChildren() > 2? node.jjtGetChild(2) : null;
+        } else {
+            // for( .. ; ... ; ..)
+            int nc = 0;
+            // first child is var declaration(s)
+            final JexlNode vars = (form & 1) != 0 ? node.jjtGetChild(nc++) : null;
+            final JexlNode predicate = (form & 2) != 0 ? node.jjtGetChild(nc++) : null;
+            // the loop step
+            final JexlNode step = (form & 4) != 0 ? node.jjtGetChild(nc++) : null;
+            // last child is body
+            body = (form & 8) != 0 ? node.jjtGetChild(nc) : null;
+            if (vars != null) {
+                accept(vars, data);
+            }
+            builder.append("; ");
+            if (predicate != null) {
+                accept(predicate, data);
+            }
+            builder.append("; ");
+            if (step != null) {
+                accept(step, data);
+            }
+            builder.append(") ");
+        }
+        // the body
+        if (body != null) {
+            accept(body, data);
         } else {
             builder.append(';');
         }
@@ -1047,6 +1091,36 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
         return data;
     }
 
+    @Override
+    protected Object visit(final ASTDefineVars node, final Object data) {
+        final int num = node.jjtGetNumChildren();
+        if (num > 0) {
+            // var, let, const
+            accept(node.jjtGetChild(0), data);
+            for (int i = 1; i < num; ++i) {
+                builder.append(", ");
+                JexlNode child = node.jjtGetChild(i);
+                if (child instanceof ASTAssignment) {
+                    ASTAssignment assign = (ASTAssignment) child;
+                    int nc = assign.jjtGetNumChildren();
+                    ASTVar var = (ASTVar) assign.jjtGetChild(0);
+                    builder.append(var.getName());
+                    if (nc > 1) {
+                        builder.append(" = ");
+                        accept(assign.jjtGetChild(1), data);
+                    }
+                } else if (child instanceof ASTVar) {
+                    ASTVar var = (ASTVar) child;
+                    builder.append(var.getName());
+                } else {
+                    // that's odd
+                    accept(child, data);
+                }
+            }
+        }
+        return data;
+    }
+
     @Override
     protected Object visit(final ASTWhileStatement node, final Object data) {
         builder.append("while (");
@@ -1132,7 +1206,7 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
 
     @Override
     protected Object visit(final ASTGetDecrementNode node, final Object data) {
-        return postfixChild(node, "++", data);
+        return postfixChild(node, "--", data);
     }
 
     @Override
@@ -1147,7 +1221,7 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail {
 
     @Override
     protected Object visit(final ASTIncrementGetNode node, final Object data) {
-        return prefixChild(node, "--", data);
+        return prefixChild(node, "++", data);
     }
 
     @Override
index c701b6fd000744e6524e7d5f3ba0637df68e9836..cf3c8d4918cb5017559ccadf6e49b629551e10ed 100644 (file)
@@ -74,7 +74,7 @@ public class Engine extends JexlEngine {
      */
     private static final class UberspectHolder {
         /** The default uberspector that handles all introspection patterns. */
-        private static final Uberspect UBERSPECT =
+        static final Uberspect UBERSPECT =
                 new Uberspect(LogFactory.getLog(JexlEngine.class),
                         JexlUberspect.JEXL_STRATEGY,
                         JexlPermissions.parse());
index 7b9861e95f66734460cc6d034e2bad5188e3e763..4ba5113b4bf3506953192f3dc5a4265050c6f904 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.commons.jexl3.internal;
 
 import java.util.Iterator;
 import java.util.concurrent.Callable;
+import java.util.function.Consumer;
 
 import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlContext;
@@ -173,6 +174,7 @@ public class Interpreter extends InterpreterBase {
     public Object getAttribute(final Object object, final Object attribute) {
         return getAttribute(object, attribute, null);
     }
+
     /**
      * Sets an attribute of an object.
      *
@@ -627,7 +629,7 @@ public class Interpreter extends InterpreterBase {
         return node.getLoopForm() == 0 ? forIterator(node, data) : forLoop(node, data);
     }
 
-    protected Object forIterator(final ASTForeachStatement node, final Object data) {
+    private Object forIterator(final ASTForeachStatement node, final Object data) {
         Object result = null;
         /* first objectNode is the loop variable */
         final ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
@@ -704,7 +706,7 @@ public class Interpreter extends InterpreterBase {
         return result;
     }
 
-    protected Object forLoop(final ASTForeachStatement node, final Object data) {
+    private Object forLoop(final ASTForeachStatement node, final Object data) {
         Object result = null;
         int nc;
         int form = node.getLoopForm();
@@ -733,22 +735,24 @@ public class Interpreter extends InterpreterBase {
             }
             // initialize after eventual creation of local lexical frame
             init.jjtAccept(this, data);
-            nc = 1;
+            // other inits
+            for (JexlNode moreAssignment = node.jjtGetChild(nc);
+                 moreAssignment instanceof ASTAssignment;
+                 moreAssignment = node.jjtGetChild(++nc)) {
+                moreAssignment.jjtAccept(this, data);
+            }
         } else {
             locals = null;
             nc = 0;
         }
-        Object forEach = null;
         try {
             // the loop condition
             final JexlNode predicate = (form & 2) != 0? node.jjtGetChild(nc++) : null;
             // the loop step
             final JexlNode step = (form & 4) != 0? node.jjtGetChild(nc++) : null;
             // last child is body
-            final JexlNode statement = (form & 8) != 0 ? node.jjtGetChild(nc++) : null;
-            // get an iterator for the collection/array etc via the introspector.
-            final Iterator<?> itemsIterator = null;
-            int cnt = 0;
+            final JexlNode statement = (form & 8) != 0 ? node.jjtGetChild(nc) : null;
+            // while(predicate())...
             while (predicate == null || arithmetic.toBoolean(predicate.jjtAccept(this, data))) {
                 cancelCheck(node);
                 // the body
@@ -768,8 +772,6 @@ public class Interpreter extends InterpreterBase {
                 }
             }
         } finally {
-            //  closeable iterator handling
-            closeIfSupported(forEach);
             // restore lexical frame
             if (locals != null) {
                 block = block.pop();
@@ -1175,7 +1177,7 @@ public class Interpreter extends InterpreterBase {
         JexlNode objectNode = null;
         JexlNode ptyNode = null;
         StringBuilder ant = null;
-        boolean antish = !(parent instanceof ASTReference);
+        boolean antish = !(parent instanceof ASTReference) && options.isAntish();
         int v = 1;
         main:
         for (int c = 0; c < numChildren; c++) {
@@ -1227,11 +1229,6 @@ public class Interpreter extends InterpreterBase {
                     }
                     final ASTIdentifier afirst = (ASTIdentifier) first;
                     ant = new StringBuilder(afirst.getName());
-                    // skip the else...*
-                    // *... and continue
-                    if (!options.isAntish()) {
-                        antish = false;
-                    }
                     continue;
                     // skip the first node case since it was trialed in jjtAccept above and returned null
                 }
@@ -1254,7 +1251,7 @@ public class Interpreter extends InterpreterBase {
                 object = context.get(ant.toString());
             } else if (c != numChildren - 1) {
                 // only the last one may be null
-                ptyNode = objectNode;
+                ptyNode = c == 0 && numChildren > 1 ? node.jjtGetChild(1) : objectNode;
                 break; //
             }
         }
@@ -1368,15 +1365,6 @@ public class Interpreter extends InterpreterBase {
         return executeAssign(node, JexlOperator.INCREMENT_AND_GET, data);
     }
 
-    /**
-     * Helper for postfix assignment operators.
-     * @param operator the operator
-     * @return true if operator is a postfix operator (x++, y--)
-     */
-    private static boolean isPostfix(JexlOperator operator) {
-        return operator == JexlOperator.GET_AND_INCREMENT || operator == JexlOperator.GET_AND_DECREMENT;
-    }
-
     /**
      * Executes an assignment with an optional side-effect operator.
      * @param node     the node
@@ -1388,9 +1376,9 @@ public class Interpreter extends InterpreterBase {
         cancelCheck(node);
         // left contains the reference to assign to
         final JexlNode left = node.jjtGetChild(0);
-        ASTIdentifier var = null;
+        final ASTIdentifier var;
         Object object = null;
-        int symbol = -1;
+        final int symbol;
         // check var decl with assign is ok
         if (left instanceof ASTIdentifier) {
             var = (ASTIdentifier) left;
@@ -1404,50 +1392,54 @@ public class Interpreter extends InterpreterBase {
                     return undefinedVariable(var, var.getName());
                 }
             }
+        } else {
+            var = null;
+            symbol = -1;
         }
         boolean antish = options.isAntish();
         // 0: determine initial object & property:
         final int last = left.jjtGetNumChildren() - 1;
         // right is the value expression to assign
-        Object right = node.jjtGetNumChildren() < 2? null: node.jjtGetChild(1).jjtAccept(this, data);
-        // previous value for postfix assignment operators (x++, x--)
-        Object actual = null;
+       final  Object right = node.jjtGetNumChildren() < 2? null: node.jjtGetChild(1).jjtAccept(this, data);
+        // actual value to return, right in most cases
+        Object actual = right;
         // a (var?) v = ... expression
         if (var != null) {
             if (symbol >= 0) {
                 // check we are not assigning a symbol itself
                 if (last < 0) {
-                    if (assignop != null) {
-                        final Object self = actual = getVariable(frame, block, var);
-                        right = operators.tryAssignOverload(node, assignop, self, right);
-                        if (right == JexlOperator.ASSIGN) {
-                            return self;
+                    if (assignop == null) {
+                        // make the closure accessible to itself, ie capture the currently set variable after frame creation
+                        if (right instanceof Closure) {
+                            ((Closure) right).setCaptured(symbol, right);
                         }
+                        frame.set(symbol, right);
+                    } else {
+                        // go through potential overload
+                        final Object self = getVariable(frame, block, var);
+                        final Consumer<Object> f = r -> frame.set(symbol, r);
+                        actual = operators.tryAssignOverload(node, assignop, f, self, right);
                     }
-                    frame.set(symbol, right);
-                    // make the closure accessible to itself, ie capture the currently set variable after frame creation
-                    if (right instanceof Closure) {
-                        ((Closure) right).setCaptured(symbol, right);
-                    }
-                    return isPostfix(assignop)? actual : right; // 1
+                    return actual; // 1
                 }
                 object = getVariable(frame, block, var);
                 // top level is a symbol, can not be an antish var
                 antish = false;
             } else {
                 // check we are not assigning direct global
+                final String name = var.getName();
                 if (last < 0) {
-                    if (assignop != null) {
-                        final Object self = actual = context.get(var.getName());
-                        right = operators.tryAssignOverload(node, assignop, self, right);
-                        if (right == JexlOperator.ASSIGN) {
-                            return self;
-                        }
+                    if (assignop == null) {
+                        setContextVariable(node, name, right);
+                    } else {
+                        // go through potential overload
+                        final Object self = context.get(name);
+                        final Consumer<Object> f = r ->  setContextVariable(node, name, r);
+                        actual = operators.tryAssignOverload(node, assignop, f, self, right);
                     }
-                    setContextVariable(node, var.getName(), right);
-                    return isPostfix(assignop)? actual : right; // 2
+                    return actual; // 2
                 }
-                object = context.get(var.getName());
+                object = context.get(name);
                 // top level accesses object, can not be an antish var
                 if (object != null) {
                     antish = false;
@@ -1510,19 +1502,18 @@ public class Interpreter extends InterpreterBase {
         if (propertyId != null) {
             // deal with creating/assignining antish variable
             if (antish && ant != null && object == null && !propertyId.isSafe() && !propertyId.isExpression()) {
-                if (last > 0) {
-                    ant.append('.');
-                }
+                ant.append('.');
                 ant.append(propertyId.getName());
-                if (assignop != null) {
+                final String name = ant.toString();
+                if (assignop == null) {
+                    setContextVariable(propertyNode, name, right);
+                } else {
                     final Object self = actual = context.get(ant.toString());
-                    right = operators.tryAssignOverload(node, assignop, self, right);
-                    if (right == JexlOperator.ASSIGN) {
-                        return self;
-                    }
+                    final JexlNode pnode = propertyNode;
+                    final Consumer<Object> assign = r -> setContextVariable(pnode, name, r);
+                    actual = operators.tryAssignOverload(node, assignop, assign, self, right);
                 }
-                setContextVariable(propertyNode, ant.toString(), right);
-                return isPostfix(assignop)? actual : right; // 3
+                return actual; // 3
             }
             // property of an object ?
             property = evalIdentifier(propertyId);
@@ -1546,15 +1537,26 @@ public class Interpreter extends InterpreterBase {
             return unsolvableProperty(objectNode, "<null>.<?>", true, null);
         }
         // 3: one before last, assign
-        if (assignop != null) {
-            final Object self = actual = getAttribute(object, property, propertyNode);
-            right = operators.tryAssignOverload(node, assignop, self, right);
-            if (right == JexlOperator.ASSIGN) {
-                return self;
-            }
+        if (assignop == null) {
+            setAttribute(object, property, right, propertyNode);
+        } else {
+            final Object self = getAttribute(object, property, propertyNode);
+            final Object o = object;
+            final JexlNode n = propertyNode;
+            final Consumer<Object> assign = r ->  setAttribute(o, property, r, n);
+            actual = operators.tryAssignOverload(node, assignop, assign, self, right);
         }
-        setAttribute(object, property, right, propertyNode);
-        return isPostfix(assignop)? actual : right; // 4
+        return actual;
+    }
+
+    @Override
+    protected Object visit(final ASTDefineVars node, final Object data) {
+        final int argc = node.jjtGetNumChildren();
+        Object result = null;
+        for (int i = 0; i < argc; i++) {
+            result = node.jjtGetChild(i).jjtAccept(this, data);
+        }
+        return result;
     }
 
     @Override
@@ -1756,7 +1758,7 @@ public class Interpreter extends InterpreterBase {
                         return call.eval(mCALL);
                     }
                     // functor is a var, may be method is a global one ?
-                    if (isavar && target == context) {
+                    if (isavar) {
                         if (call.isContextMethod(methodName, argv)) {
                             return call.eval(methodName);
                         }
@@ -1824,8 +1826,8 @@ public class Interpreter extends InterpreterBase {
                 }
             }
             boolean narrow = false;
-            JexlMethod ctor = null;
             Funcall funcall = null;
+            JexlMethod ctor;
             while (true) {
                 // try as stated
                 ctor = uberspect.getConstructor(target, argv);
index aa548e0e656be0b46dfc7c2907f496dfbaa03557..f0da705e84fc10bb991da6d5999287716519ea88 100644 (file)
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.jexl3.internal;
 
-import java.lang.reflect.Method;
 import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
@@ -26,6 +25,9 @@ import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlUberspect;
 import org.apache.commons.jexl3.parser.JexlNode;
 
+import java.lang.reflect.Method;
+import java.util.function.Consumer;
+
 /**
  * Helper class to deal with operator overloading and specifics.
  * @since 3.0
@@ -95,7 +97,7 @@ public class Operators {
      * @param args     the arguments
      * @return the result of the operator evaluation or TRY_FAILED
      */
-    protected Object tryOverload(final JexlNode node, final JexlOperator operator, final Object... args) {
+    protected Object tryOverload(final JexlNode node, final JexlOperator operator, Object... args) {
         if (operators != null && operators.overloads(operator)) {
             final JexlArithmetic arithmetic = interpreter.arithmetic;
             final boolean cache = interpreter.cache;
@@ -113,18 +115,39 @@ public class Operators {
                 final JexlMethod vm = operators.getOperator(operator, args);
                 if (vm != null && !isArithmetic(vm)) {
                     final Object result = vm.invoke(arithmetic, args);
-                    if (cache) {
+                    if (cache && !vm.tryFailed(result)) {
                         node.jjtSetValue(vm);
                     }
                     return result;
                 }
             } catch (final Exception xany) {
-                return interpreter.operatorError(node, operator, xany);
+                // ignore return if lenient, will return try_failed
+                interpreter.operatorError(node, operator, xany);
             }
         }
         return JexlEngine.TRY_FAILED;
     }
 
+    /**
+     * Helper for postfix assignment operators.
+     * @param operator the operator
+     * @return true if operator is a postfix operator (x++, y--)
+     */
+    private static boolean isPostfix(JexlOperator operator) {
+        return operator == JexlOperator.GET_AND_INCREMENT || operator == JexlOperator.GET_AND_DECREMENT;
+    }
+
+    /**
+     * Tidy arguments based on operator arity.
+     * <p>The interpreter may add a null to the arguments of operator expecting only one parameter.</p>
+     * @param operator the operator
+     * @param args the arguements (as seen by the interpreter)
+     * @return the tidied arguments
+     */
+    private Object[] arguments(JexlOperator operator, Object...args) {
+        return operator.getArity() == 1 && args.length > 1 ? new Object[]{args[0]} : args;
+    }
+
     /**
      * Evaluates an assign operator.
      * <p>
@@ -139,72 +162,91 @@ public class Operators {
      *         JexlEngine.TRY_FAILED if no operation was performed,
      *         the value to use as the side effect argument otherwise
      */
-    protected Object tryAssignOverload(final JexlNode node, final JexlOperator operator, final Object...args) {
+    protected Object tryAssignOverload(final JexlNode node,
+                                       final JexlOperator operator,
+                                       final Consumer<Object> assignFun,
+                                       final Object...args) {
         final JexlArithmetic arithmetic = interpreter.arithmetic;
         if (args.length < operator.getArity()) {
             return JexlEngine.TRY_FAILED;
         }
-        // try to call overload with side effect
-        Object result = tryOverload(node, operator, args);
-        if (result != JexlEngine.TRY_FAILED) {
-            return result;
-        }
-        // call base operator
-        final JexlOperator base = operator.getBaseOperator();
-        if (base == null) {
-            throw new IllegalArgumentException("must be called with a side-effect operator");
-        }
-        if (operators != null && operators.overloads(base)) {
-            // in case there is an overload on the base operator
-            try {
-                final JexlMethod vm = operators.getOperator(base, args);
-                if (vm != null) {
-                    result = vm.invoke(arithmetic, args);
-                    if (result != JexlEngine.TRY_FAILED) {
-                        return result;
-                    }
+        Object result;
+        try {
+        // if some overloads exist...
+        if (operators != null) {
+            // try to call overload with side effect; the object is modified
+            result = tryOverload(node, operator, arguments(operator, args));
+            if (result != JexlEngine.TRY_FAILED) {
+                return result; // 1
+            }
+            // try to call base overload (ie + for +=)
+            final JexlOperator base = operator.getBaseOperator();
+            if (base != null && operators.overloads(base)) {
+                result = tryOverload(node, base, arguments(base, args));
+                if (result != JexlEngine.TRY_FAILED) {
+                    assignFun.accept(result);
+                    return isPostfix(operator) ? args[0] : result; // 2
                 }
-            } catch (final Exception xany) {
-                interpreter.operatorError(node, base, xany);
             }
         }
         // base eval
-        try {
-            switch (operator) {
-                case SELF_ADD:
-                    return arithmetic.add(args[0], args[1]);
-                case SELF_SUBTRACT:
-                    return arithmetic.subtract(args[0], args[1]);
-                case SELF_MULTIPLY:
-                    return arithmetic.multiply(args[0], args[1]);
-                case SELF_DIVIDE:
-                    return arithmetic.divide(args[0], args[1]);
-                case SELF_MOD:
-                    return arithmetic.mod(args[0], args[1]);
-                case SELF_AND:
-                    return arithmetic.and(args[0], args[1]);
-                case SELF_OR:
-                    return arithmetic.or(args[0], args[1]);
-                case SELF_XOR:
-                    return arithmetic.xor(args[0], args[1]);
-                case SELF_SHIFTLEFT:
-                    return arithmetic.shiftLeft(args[0], args[1]);
-                case SELF_SHIFTRIGHT:
-                    return arithmetic.shiftRight(args[0], args[1]);
-                case SELF_SHIFTRIGHTU:
-                    return arithmetic.shiftRightUnsigned(args[0], args[1]);
-                case INCREMENT_AND_GET:
-                case GET_AND_INCREMENT:
-                    return arithmetic.increment(args[0]);
-                case DECREMENT_AND_GET:
-                case GET_AND_DECREMENT:
-                    return arithmetic.decrement(args[0]);
-                default:
-                    // unexpected, new operator added?
-                    throw new UnsupportedOperationException(operator.getOperatorSymbol());
+        switch (operator) {
+            case SELF_ADD:
+                result = arithmetic.add(args[0], args[1]);
+                break;
+            case SELF_SUBTRACT:
+                result = arithmetic.subtract(args[0], args[1]);
+                break;
+            case SELF_MULTIPLY:
+                result = arithmetic.multiply(args[0], args[1]);
+                break;
+            case SELF_DIVIDE:
+                result = arithmetic.divide(args[0], args[1]);
+                break;
+            case SELF_MOD:
+                result = arithmetic.mod(args[0], args[1]);
+                break;
+            case SELF_AND:
+                result = arithmetic.and(args[0], args[1]);
+                break;
+            case SELF_OR:
+                result = arithmetic.or(args[0], args[1]);
+                break;
+            case SELF_XOR:
+                result = arithmetic.xor(args[0], args[1]);
+                break;
+            case SELF_SHIFTLEFT:
+                result = arithmetic.shiftLeft(args[0], args[1]);
+                break;
+            case SELF_SHIFTRIGHT:
+                result = arithmetic.shiftRight(args[0], args[1]);
+                break;
+            case SELF_SHIFTRIGHTU:
+                result = arithmetic.shiftRightUnsigned(args[0], args[1]);
+                break;
+            case INCREMENT_AND_GET:
+                result = arithmetic.increment(args[0]);
+                break;
+            case DECREMENT_AND_GET:
+                result = arithmetic.decrement(args[0]);
+                break;
+            case GET_AND_INCREMENT:
+                result = args[0];
+                assignFun.accept(arithmetic.increment(result));
+                return result; // 3
+            case GET_AND_DECREMENT: {
+                result = args[0];
+                assignFun.accept(arithmetic.decrement(result));
+                return result; // 4
+            }
+            default:
+                // unexpected, new operator added?
+                throw new UnsupportedOperationException(operator.getOperatorSymbol());
             }
+            assignFun.accept(result);
+            return result; // 5
         } catch (final Exception xany) {
-            interpreter.operatorError(node, base, xany);
+            interpreter.operatorError(node, operator, xany);
         }
         return JexlEngine.TRY_FAILED;
     }
index ee14bbd823af1a8460aee25590f992cb0f30ba97..2951ff6046f19a8e5e45e85323ba9c3836cca99e 100644 (file)
@@ -84,7 +84,7 @@ public final class Scope {
     public Scope(final Scope scope, final String... parameters) {
         if (parameters != null) {
             parms = parameters.length;
-            namedVariables = new LinkedHashMap<String, Integer>();
+            namedVariables = new LinkedHashMap<>();
             for (int p = 0; p < parms; ++p) {
                 namedVariables.put(parameters[p], p);
             }
@@ -140,10 +140,10 @@ public final class Scope {
             final Integer pr = parent.getSymbol(name, true);
             if (pr != null) {
                 if (capturedVariables == null) {
-                    capturedVariables = new LinkedHashMap<Integer, Integer>();
+                    capturedVariables = new LinkedHashMap<>();
                 }
                 if (namedVariables == null) {
-                    namedVariables = new LinkedHashMap<String, Integer>();
+                    namedVariables = new LinkedHashMap<>();
                 }
                 register = namedVariables.size();
                 namedVariables.put(name, register);
@@ -193,7 +193,7 @@ public final class Scope {
      */
     public int declareParameter(final String name) {
         if (namedVariables == null) {
-            namedVariables = new LinkedHashMap<String, Integer>();
+            namedVariables = new LinkedHashMap<>();
         } else if (vars > 0) {
             throw new IllegalStateException("cant declare parameters after variables");
         }
@@ -214,9 +214,9 @@ public final class Scope {
      * @param name the variable name
      * @return the register index storing this variable
      */
-    public int declareVariable(final String name, boolean lexical, boolean constant) {
+    public int declareVariable(final String name) {
         if (namedVariables == null) {
-            namedVariables = new LinkedHashMap<String, Integer>();
+            namedVariables = new LinkedHashMap<>();
         }
         Integer register = namedVariables.get(name);
         if (register == null) {
@@ -228,7 +228,7 @@ public final class Scope {
                 final Integer pr = parent.getSymbol(name, true);
                 if (pr != null) {
                     if (capturedVariables == null) {
-                        capturedVariables = new LinkedHashMap<Integer, Integer>();
+                        capturedVariables = new LinkedHashMap<>();
                     }
                     capturedVariables.put(register, pr);
                 }
@@ -285,7 +285,7 @@ public final class Scope {
      */
     public String[] getCapturedVariables() {
         if (capturedVariables != null) {
-            final List<String> captured = new ArrayList<String>(vars);
+            final List<String> captured = new ArrayList<>(vars);
             for (final Map.Entry<String, Integer> entry : namedVariables.entrySet()) {
                 final int symnum = entry.getValue();
                 if (symnum >= parms && capturedVariables.containsKey(symnum)) {
@@ -293,7 +293,7 @@ public final class Scope {
                 }
             }
             if (!captured.isEmpty()) {
-                return captured.toArray(new String[captured.size()]);
+                return captured.toArray(new String[0]);
             }
         }
         return EMPTY_STRS;
@@ -328,7 +328,7 @@ public final class Scope {
      * @param bound number of known bound parameters (curry)
      * @return the parameter names
      */
-    protected String[] getParameters(final int bound) {
+     String[] getParameters(final int bound) {
         final int unbound = parms - bound;
         if ((namedVariables == null) || (unbound <= 0)) {
             return EMPTY_STRS;
@@ -352,14 +352,14 @@ public final class Scope {
         if ((namedVariables == null) || (vars <= 0)) {
             return EMPTY_STRS;
         }
-        final List<String> locals = new ArrayList<String>(vars);
+        final List<String> locals = new ArrayList<>(vars);
         for (final Map.Entry<String, Integer> entry : namedVariables.entrySet()) {
             final int symnum = entry.getValue();
             if (symnum >= parms && (capturedVariables == null || !capturedVariables.containsKey(symnum))) {
                 locals.add(entry.getKey());
             }
         }
-        return locals.toArray(new String[locals.size()]);
+        return locals.toArray(new String[0]);
     }
 
 }
index 5964d34840859ea2c6a889656dbb744098b64c28..9da2182d7d06b673c646d279f938886ed1ac1c9f 100644 (file)
@@ -21,7 +21,7 @@ import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.parser.*;
 
 /**
- * Fully abstract to avoid public interface exposition.
+ * Concrete visitor base, used for feature and operator controllers.
  */
 public class ScriptVisitor extends ParserVisitor {
     /**
@@ -116,6 +116,11 @@ public class ScriptVisitor extends ParserVisitor {
         return visitNode(node, data);
     }
 
+    @Override
+    protected Object visit(final ASTDefineVars node, final Object data) {
+        return visitNode(node, data);
+    }
+
     @Override
     protected Object visit(final ASTReference node, final Object data) {
         return visitNode(node, data);
index 3c9bf998798d87860cb4a604511d612b3209e921..ca0e8e3de57ac9e6335026003941283596907dc1 100644 (file)
@@ -605,7 +605,7 @@ public final class TemplateEngine extends JxltEngine {
          */
         CompositeExpression(final int[] counters, final List<TemplateExpression> list, final TemplateExpression src) {
             super(src);
-            this.exprs = list.toArray(new TemplateExpression[list.size()]);
+            this.exprs = list.toArray(new TemplateExpression[0]);
             this.meta = (counters[ExpressionType.DEFERRED.getIndex()] > 0 ? 2 : 0)
                     | (counters[ExpressionType.IMMEDIATE.getIndex()] > 0 ? 1 : 0);
         }
index f71ca518245363b39151edad5bc2cc1b79ee386c..9c8b9ea37fb8d6441f377054849ce8548ca3922d 100644 (file)
@@ -146,8 +146,8 @@ public final class TemplateScript implements JxltEngine.Template {
                 jpe += 1;
             }
         }
-        source = blocks.toArray(new Block[blocks.size()]);
-        exprs = uexprs.toArray(new TemplateExpression[uexprs.size()]);
+        source = blocks.toArray(new Block[0]);
+        exprs = uexprs.toArray(new TemplateExpression[0]);
     }
 
     /**
index 7e36ce9a8253eac06234f5562dd3c57dab5b57c0..ba68aaf0cecf6891a2d98487e1ba429f6547366e 100644 (file)
@@ -110,7 +110,7 @@ final class ClassMap {
             }
             @Override
             public Set<Entry<MethodKey, Method>> entrySet() {
-                return null;
+                return Collections.emptySet();
             }
             @Override public Method get(Object name) {
                 return CACHE_MISS;
@@ -164,7 +164,7 @@ final class ClassMap {
      * @return the array of field names
      */
     String[] getFieldNames() {
-        return fieldCache.keySet().toArray(new String[fieldCache.size()]);
+        return fieldCache.keySet().toArray(new String[0]);
     }
 
     /**
@@ -173,7 +173,7 @@ final class ClassMap {
      * @return the array of method names
      */
     String[] getMethodNames() {
-        return byName.keySet().toArray(new String[byName.size()]);
+        return byName.keySet().toArray(new String[0]);
     }
 
     /**
@@ -221,11 +221,7 @@ final class ClassMap {
                 if (methodList != null) {
                     cacheEntry = methodKey.getMostSpecificMethod(methodList);
                 }
-                if (cacheEntry == null) {
-                    byKey.put(methodKey, CACHE_MISS);
-                } else {
-                    byKey.put(methodKey, cacheEntry);
-                }
+                byKey.put(methodKey, cacheEntry == null? CACHE_MISS : cacheEntry);
             } catch (final MethodKey.AmbiguousException ae) {
                 // that's a miss :-)
                 byKey.put(methodKey, CACHE_MISS);
@@ -248,7 +244,7 @@ final class ClassMap {
      */
     private static void create(final ClassMap cache, final JexlPermissions permissions, Class<?> clazz, final Log log) {
         //
-        // Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
+        // Build a list of all elements in the class hierarchy. This one is bottom-first; we start
         // with the actual declaring class and its interfaces and then move up (superclass etc.) until we
         // hit java.lang.Object. That is important because it will give us the methods of the declaring class
         // which might in turn be abstract further up the tree.
@@ -256,7 +252,7 @@ final class ClassMap {
         // We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions.
         //
         for (Class<?> classToReflect = clazz; classToReflect != null; classToReflect = classToReflect.getSuperclass()) {
-            if (Modifier.isPublic(classToReflect.getModifiers())) {
+            if (Modifier.isPublic(classToReflect.getModifiers()) && ClassTool.isExported(classToReflect)) {
                 populateWithClass(cache, permissions, classToReflect, log);
             }
             final Class<?>[] interfaces = classToReflect.getInterfaces();
@@ -282,7 +278,7 @@ final class ClassMap {
                     }
                     end += 1;
                 }
-                final Method[] lmn = lm.subList(start, end).toArray(new Method[end - start]);
+                final Method[] lmn = lm.subList(start, end).toArray(new Method[0]);
                 cache.byName.put(name, lmn);
                 start = end;
             }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassTool.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassTool.java
new file mode 100644 (file)
index 0000000..fb531b3
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+/**
+ * Utility for Java9+ backport in Java8 of class and module related methods.
+ */
+class ClassTool {
+    /** The Class.getModule() method. */
+    private static final MethodHandle GET_MODULE;
+    /** The Class.getPackageName() method. */
+    private static final MethodHandle GET_PKGNAME;
+    /** The Module.isExported(String packageName) method. */
+    private static final MethodHandle IS_EXPORTED;
+    static {
+        final MethodHandles.Lookup LOOKUP= MethodHandles.lookup();
+        MethodHandle getModule = null;
+        MethodHandle getPackageName = null;
+        MethodHandle isExported = null;
+        try {
+            Class<?> modulec = ClassTool.class.getClassLoader().loadClass("java.lang.Module");
+            if (modulec != null) {
+                getModule = LOOKUP.findVirtual(Class.class, "getModule", MethodType.methodType(modulec));
+                if (getModule != null) {
+                    getPackageName = LOOKUP.findVirtual(Class.class, "getPackageName", MethodType.methodType(String.class));
+                    if (getPackageName != null) {
+                        isExported = LOOKUP.findVirtual(modulec, "isExported", MethodType.methodType(boolean.class, String.class));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // ignore all
+        }
+        GET_MODULE = getModule;
+        GET_PKGNAME = getPackageName;
+        IS_EXPORTED = isExported;
+    }
+
+    /**
+     * Checks whether a class is exported by its module (java 9+).
+     * The code performs the following sequence through reflection (since the same jar can run
+     * on a Java8 or Java9+ runtime and the module features does not exist on 8).
+     * <code>
+     * Module module = declarator.getModule();
+     * return module.isExported(declarator.getPackageName());
+     * </code>
+     * This is required since some classes and methods may not be exported thus not callable through
+     * reflection.
+     * @param declarator the class
+     * @return true if class is exported or no module support exists
+     */
+    static boolean isExported(Class<?> declarator) {
+        if (IS_EXPORTED != null)  {
+            try {
+                final Object module = GET_MODULE.invoke(declarator);
+                if (module != null) {
+                    final String pkgName = (String) GET_PKGNAME.invoke(declarator);
+                    return (Boolean) IS_EXPORTED.invoke(module, pkgName);
+                }
+            } catch (Throwable e) {
+                // ignore
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Gets the package name of a class (class.getPackage() may return null).
+     * @param clz the class
+     * @return the class package name
+     */
+    static String getPackageName(Class<?> clz) {
+        String pkgName = "";
+        if (clz != null) {
+            // use native if we can
+            if (GET_PKGNAME != null) {
+                try {
+                    return (String) GET_PKGNAME.invoke(clz);
+                } catch(Throwable xany) {
+                    return "";
+                }
+            }
+            // remove array
+            Class<?> clazz = clz;
+            while(clazz.isArray()) {
+                clazz = clazz.getComponentType();
+            }
+            // mimic getPackageName()
+            if (clazz.isPrimitive()) {
+                return "java.lang";
+            }
+            // remove enclosing
+            Class<?> walk = clazz.getEnclosingClass();
+            while(walk != null) {
+                clazz = walk;
+                walk = walk.getEnclosingClass();
+            }
+            Package pkg = clazz.getPackage();
+            // pkg may be null for unobvious reasons
+            if (pkg == null) {
+                String name = clazz.getName();
+                int dot = name.lastIndexOf('.');
+                if (dot > 0) {
+                    pkgName = name.substring(0, dot);
+                }
+            } else {
+                pkgName = pkg.getName();
+            }
+        }
+        return pkgName;
+    }
+
+}
index 08b5290de00803ed28caa7a8752ebeb7262a1ca7..ba7a5bc5aeb36225cfa8139e0072f9764db9486f 100644 (file)
@@ -267,7 +267,7 @@ public final class Introspector {
                     }
                 }
                 // try to find one
-                ctor = key.getMostSpecificConstructor(l.toArray(new Constructor<?>[l.size()]));
+                ctor = key.getMostSpecificConstructor(l.toArray(new Constructor<?>[0]));
                 if (ctor != null) {
                     constructorsMap.put(key, ctor);
                 } else {
index 1134257126a2ff9111029b66651a0e69a3b1a065..582bce3e2063487287b9320849810af020f31007 100644 (file)
@@ -17,8 +17,6 @@
 
 package org.apache.commons.jexl3.internal.introspection;
 
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
@@ -52,21 +50,6 @@ import org.apache.commons.jexl3.introspection.JexlPermissions;
  * not be altered using an instance of permissions using {@link JexlPermissions#parse(String...)}.</p>
  */
 public class Permissions implements JexlPermissions {
-    /**
-     * Java9 introduced Class.getPackageName(), use it if it exists.
-     */
-    private static final MethodHandle GETPKGNAME = getPackageNameHandle();
-    static MethodHandle getPackageNameHandle() {
-        MethodHandle mh;
-        try {
-            Method m = Class.class.getMethod("getPackageName");
-            mh = MethodHandles.lookup().unreflect(m);
-        } catch(Exception xm) {
-            mh = null;
-        }
-        return mh;
-    }
-
     /**
      * Equivalent of @NoJexl on a class in a package.
      */
@@ -260,53 +243,6 @@ public class Permissions implements JexlPermissions {
         return allowed == null? Collections.emptySet() : Collections.unmodifiableSet(allowed);
     }
 
-
-    /**
-     * Gets the package name of a class (class.getPackage() may return null).
-     * @param clz the class
-     * @return the class package name
-     */
-    static String getPackageName(Class<?> clz) {
-        String pkgName = "";
-        if (clz != null) {
-            // use native if we can
-            if (GETPKGNAME != null) {
-                try {
-                    return (String) GETPKGNAME.invokeWithArguments(clz);
-                } catch(Throwable xany) {
-                    return "";
-                }
-            }
-            // remove array
-            Class<?> clazz = clz;
-            while(clazz.isArray()) {
-                clazz = clazz.getComponentType();
-            }
-            // mimic getPackageName()
-            if (clazz.isPrimitive()) {
-                return "java.lang";
-            }
-            // remove enclosing
-            Class<?> walk = clazz.getEnclosingClass();
-            while(walk != null) {
-                clazz = walk;
-                walk = walk.getEnclosingClass();
-            }
-            Package pkg = clazz.getPackage();
-            // pkg may be null for unobvious reasons
-            if (pkg == null) {
-                String name = clazz.getName();
-                int dot = name.lastIndexOf('.');
-                if (dot > 0) {
-                    pkgName = name.substring(0, dot);
-                }
-            } else {
-                pkgName = pkg.getName();
-            }
-        }
-        return pkgName;
-    }
-
     /**
      * Gets the package constraints.
      * @param packageName the package name
@@ -324,7 +260,7 @@ public class Permissions implements JexlPermissions {
      * @return the class constraints instance, not-null.
      */
     private NoJexlClass getNoJexl(Class<?> clazz) {
-        String pkgName = getPackageName(clazz);
+        String pkgName = ClassTool.getPackageName(clazz);
         NoJexlPackage njp = getNoJexlPackage(pkgName);
         if (njp != null) {
             NoJexlClass njc = njp.getNoJexl(clazz);
@@ -341,7 +277,7 @@ public class Permissions implements JexlPermissions {
      * @return true if allowed, false otherwise
      */
     private boolean wildcardAllow(Class<?> clazz) {
-        return wildcardAllow(allowed, getPackageName(clazz));
+        return wildcardAllow(allowed, ClassTool.getPackageName(clazz));
     }
 
     /**
@@ -393,7 +329,7 @@ public class Permissions implements JexlPermissions {
         if (nojexl != null) {
             return true;
         }
-        NoJexlPackage njp = packages.get(getPackageName(clazz));
+        NoJexlPackage njp = packages.get(ClassTool.getPackageName(clazz));
         return njp != null && Objects.equals(NOJEXL_CLASS, njp.getNoJexl(clazz));
     }
 
index 16e80b677f7b6e43a0207575561d31892924a1ef..c2a98ce560b8a1e8a71d8a8f26360114c3cdaba9 100644 (file)
@@ -119,16 +119,17 @@ public final class SandboxUberspect implements JexlUberspect {
                                           final Object obj,
                                           final Object identifier) {
         if (obj != null) {
+            final Class<?> clazz = obj instanceof Class<?>? (Class<?>) obj : obj.getClass();
             if (identifier != null) {
                 final String property = identifier.toString();
-                final String actual = sandbox.read(obj.getClass(), property);
+                final String actual = sandbox.read(clazz, property);
                 if (actual != null) {
                     // no transformation, strict equality: use identifier before string conversion
                     final Object pty = eq(actual, property) ? identifier : actual;
                     return uberspect.getPropertyGet(resolvers, obj, pty);
                 }
             } else {
-                final String actual = sandbox.read(obj.getClass(), null);
+                final String actual = sandbox.read(clazz, null);
                 if (actual != JexlSandbox.NULL) {
                      return uberspect.getPropertyGet(resolvers, obj, null);
                 }
@@ -148,16 +149,17 @@ public final class SandboxUberspect implements JexlUberspect {
                                           final Object identifier,
                                           final Object arg) {
         if (obj != null) {
+            final Class<?> clazz = obj instanceof Class<?>? (Class<?>) obj : obj.getClass();
             if (identifier != null) {
                 final String property = identifier.toString();
-                final String actual = sandbox.write(obj.getClass(), property);
+                final String actual = sandbox.write(clazz, property);
                 if (actual != null) {
                     // no transformation, strict equality: use identifier before string conversion
                     final Object pty = eq(actual, property) ? identifier : actual;
                     return uberspect.getPropertySet(resolvers, obj, pty, arg);
                 }
             } else {
-                final String actual = sandbox.write(obj.getClass(), null);
+                final String actual = sandbox.write(clazz, null);
                 if (actual != JexlSandbox.NULL) {
                     return uberspect.getPropertySet(resolvers, obj, null, arg);
                 }
index 589a84d7e4edd2603be4179093cbcc12f27849f7..8e8c3e37456e05b0f862b75212644f60f0a9af97 100644 (file)
@@ -69,7 +69,7 @@ public class Uberspect implements JexlUberspect {
     /**
      * The map from arithmetic classes to overloaded operator sets.
      * <p>
-     * This keeps track of which operator methods are overloaded per JexlArithemtic classes
+     * This map keeps track of which operator methods are overloaded per JexlArithmetic classes
      * allowing a fail fast test during interpretation by avoiding seeking a method when there is none.
      */
     private final Map<Class<? extends JexlArithmetic>, Set<JexlOperator>> operatorMap;
index b5db9195c2c3827ecc649ab2fa956df755035b26..2edef12281f5612e1d5d4ad4084249b4c97a102a 100644 (file)
@@ -237,7 +237,7 @@ public abstract class JexlParser extends StringParser {
     }
 
     /**
-     * Gets the lexical unit currently used by this parser.
+     * Gets the lexical unit used by this parser.
      * @return the named register map
      */
     protected LexicalUnit getUnit() {
@@ -399,7 +399,7 @@ public abstract class JexlParser extends StringParser {
         if (scope == null) {
             scope = new Scope(null);
         }
-        final int symbol = scope.declareVariable(name, true, true);
+        final int symbol = scope.declareVariable(name);
         variable.setSymbol(symbol, name);
         variable.setLexical(true);
         if (scope.isCapturedSymbol(symbol)) {
@@ -433,7 +433,7 @@ public abstract class JexlParser extends StringParser {
         if (scope == null) {
             scope = new Scope(null);
         }
-        final int symbol = scope.declareVariable(name, lexical, constant);
+        final int symbol = scope.declareVariable(name);
         variable.setSymbol(symbol, name);
         variable.setLexical(lexical);
         variable.setConstant(constant);
@@ -701,7 +701,6 @@ public abstract class JexlParser extends StringParser {
     protected void throwParsingException(final Token parsed) {
         JexlInfo xinfo  = null;
         String msg = "unrecoverable state";
-        JexlException.Parsing xparse = null;
         Token token = parsed;
         if (token == null) {
             token = this.getToken(0);
index 18ad35099298f38ad62e41c248d1abd51a42b844..074c8471c292944845c25a7f8b6bfad97d1f82b0 100644 (file)
@@ -285,9 +285,11 @@ TOKEN_MGR_DECLS : {
 <*> TOKEN :
 {
   < STRING_LITERAL:
-    "\"" (~["\"","\\","\n","\r","\u2028","\u2029"] | "\\" ~["\n","\r","\u2028","\u2029"])* "\""
+    "\"" (~["\"","\\","\n","\r","\t","\f","\b","\u2028","\u2029"]
+          | "\\" ~["\n","\r","\t","\f","\b","\u2028","\u2029"])* "\""
   |
-    "'" (~["'","\\","\n","\r","\u2028","\u2029"] | "\\" ~["\n","\r","\u2028","\u2029"])* "'"
+    "'" (~["'","\\","\n","\r","\t","\f","\b","\u2028","\u2029"]
+         | "\\" ~["\n","\r","\t","\f","\b","\u2028","\u2029"])* "'"
   > { popDot(); } /* Revert state to default if was DOT_ID. */
 }
 
@@ -301,7 +303,7 @@ TOKEN_MGR_DECLS : {
 <*> TOKEN :
 {
   < REGEX_LITERAL:
-    "~" "/" (~["/","\n","\r","\u2028","\u2029"] | "\\" "/" )* "/"
+    "~" "/" (~["/","\n","\r","\t","\f","\b","\u2028","\u2029"] | "\\" "/" )* "/"
   > { popDot(); } /* Revert state to default if was DOT_ID. */
 }
 
@@ -466,11 +468,11 @@ void ForEachVar() #Reference : {}
 
 void Var() #void : {}
 {
-    <VAR> DefineVar() (<COMMA> DefineVar())*
+    (<VAR> DefineVar() (<COMMA> DefineVar())*) #DefineVars(>1)
     |
-    <LET> DefineLet() (<COMMA> DefineLet())*
+    (<LET> DefineLet() (<COMMA> DefineLet())*) #DefineVars(>1)
     |
-    <CONST> DefineConst() (<COMMA> DefineConst())*
+    (<CONST> DefineConst() (<COMMA> DefineConst())*) #DefineVars(>1)
 }
 
  void DefineVar() #void : {}
index f03d4a99b93a323c3139f022d75f964bed990482..e457c4179b41e072fd5da93dee4581a4b3cffb9a 100644 (file)
@@ -16,8 +16,6 @@
  */
 package org.apache.commons.jexl3.parser;
 
-import org.apache.commons.jexl3.JexlOperator;
-
 /**
  * Fully abstract to avoid public interface exposition.
  */
@@ -64,6 +62,8 @@ public abstract class ParserVisitor {
 
     protected abstract Object visit(ASTVar node, Object data);
 
+    protected abstract Object visit(ASTDefineVars node, Object data);
+
     protected abstract Object visit(ASTReference node, Object data);
 
     protected abstract Object visit(ASTTernaryNode node, Object data);
@@ -194,13 +194,13 @@ public abstract class ParserVisitor {
 
     protected abstract Object visit(ASTSetShiftRightUnsignedNode node, final Object data);
 
-    protected abstract Object visit(final ASTGetDecrementNode node, final Object data);
+    protected abstract Object visit(ASTGetDecrementNode node, final Object data);
 
-    protected abstract Object visit(final ASTGetIncrementNode node, final Object data);
+    protected abstract Object visit(ASTGetIncrementNode node, final Object data);
 
-    protected abstract Object visit(final ASTDecrementGetNode node, final Object data);
+    protected abstract Object visit(ASTDecrementGetNode node, final Object data);
 
-    protected abstract Object visit(final ASTIncrementGetNode node, final Object data);
+    protected abstract Object visit(ASTIncrementGetNode node, final Object data);
 
     protected abstract Object visit(ASTJxltLiteral node, Object data);
 
index a8eaa426b74a5b0bf36ef4670cae07b26e42b4e7..e18459748027cdbe1875f503594a217123553c24 100644 (file)
@@ -307,7 +307,7 @@ public class JexlScriptEngine extends AbstractScriptEngine implements Compilable
         private FactorySingletonHolder() {}
 
         /** The engine factory singleton instance. */
-        private static final JexlScriptEngineFactory DEFAULT_FACTORY = new JexlScriptEngineFactory();
+        static final JexlScriptEngineFactory DEFAULT_FACTORY = new JexlScriptEngineFactory();
     }
 
     /**
@@ -319,7 +319,7 @@ public class JexlScriptEngine extends AbstractScriptEngine implements Compilable
         private EngineSingletonHolder() {}
 
         /** The JEXL engine singleton instance. */
-        private static final JexlEngine DEFAULT_ENGINE = new JexlBuilder().logger(LOG).cache(CACHE_SIZE).create();
+        static final JexlEngine DEFAULT_ENGINE = new JexlBuilder().logger(LOG).cache(CACHE_SIZE).create();
     }
 
     /**
index 8c1ca5800d5f4bda334b3fb070cabfcdb0a3386d..61778347159ac424a842cd1f00db97c6a13c0cda 100644 (file)
@@ -33,8 +33,10 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.MathContext;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -384,6 +386,143 @@ public class ArithmeticTest extends JexlTestCase {
         }
     }
 
+    @Test
+    public void testMinusMinusPrefix() throws Exception {
+        asserter.setVariable("aByte", new Byte((byte) 2));
+        asserter.setVariable("aShort", new Short((short) 3));
+        asserter.setVariable("anInteger", new Integer(4));
+        asserter.setVariable("aLong", new Long(5));
+        asserter.setVariable("aFloat", new Float(6.6));
+        asserter.setVariable("aDouble", new Double(7.7));
+        asserter.setVariable("aBigInteger", new BigInteger("8"));
+        asserter.setVariable("aBigDecimal", new BigDecimal("9.9"));
+        asserter.setVariable("aString", "forty-two");
+
+        asserter.assertExpression("--aByte", new Byte((byte) 1));
+        asserter.assertExpression("--aShort", new Short((short) 2));
+        asserter.assertExpression("--anInteger", new Integer(3));
+        asserter.assertExpression("--aLong", new Long(4));
+        asserter.assertExpression("--aFloat", new Float(5.6));
+        asserter.assertExpression("--aDouble", new Double(6.7));
+        asserter.assertExpression("--aBigInteger", new BigInteger("7"));
+        asserter.assertExpression("--aBigDecimal", new BigDecimal("8.9"));
+
+        asserter.failExpression("aString--", "--", String::contains);
+    }
+
+    @Test
+    public void testMinusMinusPostfix() throws Exception {
+        asserter.setVariable("aByte", new Byte((byte) 2));
+        asserter.setVariable("aShort", new Short((short) 3));
+        asserter.setVariable("anInteger", new Integer(4));
+        asserter.setVariable("aLong", new Long(5));
+        asserter.setVariable("aFloat", new Float(6.6));
+        asserter.setVariable("aDouble", new Double(7.7));
+        asserter.setVariable("aBigInteger", new BigInteger("8"));
+        asserter.setVariable("aBigDecimal", new BigDecimal("9.9"));
+        asserter.setVariable("aString", "forty-two");
+
+        asserter.assertExpression("aByte--",new Byte((byte) 2));
+        asserter.assertExpression("aShort--", new Short((short) 3));
+        asserter.assertExpression("anInteger--", new Integer(4));
+        asserter.assertExpression("aLong--", new Long(5));
+        asserter.assertExpression("aFloat--", new Float(6.6));
+        asserter.assertExpression("aDouble--", new Double(7.7));
+        asserter.assertExpression("aBigInteger--", new BigInteger("8"));
+        asserter.assertExpression("aBigDecimal--", new BigDecimal("9.9"));
+
+        asserter.assertExpression("aByte", new Byte((byte) 1));
+        asserter.assertExpression("aShort", new Short((short) 2));
+        asserter.assertExpression("anInteger", new Integer(3));
+        asserter.assertExpression("aLong", new Long(4));
+        asserter.assertExpression("aFloat", new Float(5.6));
+        asserter.assertExpression("aDouble", new Double(6.7));
+        asserter.assertExpression("aBigInteger", new BigInteger("7"));
+        asserter.assertExpression("aBigDecimal", new BigDecimal("8.9"));
+
+        asserter.failExpression("aString--", "--", String::contains);
+    }
+
+    @Test
+    public void testPlusPlusPrefix() throws Exception {
+        asserter.setVariable("aByte", new Byte((byte) 0));
+        asserter.setVariable("aShort", new Short((short) 1));
+        asserter.setVariable("anInteger", new Integer(2));
+        asserter.setVariable("aLong", new Long(3));
+        asserter.setVariable("aFloat", new Float(4.4));
+        asserter.setVariable("aDouble", new Double(5.5));
+        asserter.setVariable("aBigInteger", new BigInteger("6"));
+        asserter.setVariable("aBigDecimal", new BigDecimal("7.7"));
+        asserter.setVariable("aString", "forty-two");
+
+        asserter.assertExpression("++aByte", new Byte((byte) 1));
+        asserter.assertExpression("++aShort", new Short((short) 2));
+        asserter.assertExpression("++anInteger", new Integer(3));
+        asserter.assertExpression("++aLong", new Long(4));
+        asserter.assertExpression("++aFloat", new Float(5.4));
+        asserter.assertExpression("++aDouble", new Double(6.5));
+        asserter.assertExpression("++aBigInteger", new BigInteger("7"));
+        asserter.assertExpression("++aBigDecimal", new BigDecimal("8.7"));
+
+        asserter.failExpression("++aString", "++", String::contains);
+    }
+
+    @Test
+    public void testPlusPlusPostfix() throws Exception {
+        asserter.setVariable("aByte", new Byte((byte) 0));
+        asserter.setVariable("aShort", new Short((short) 1));
+        asserter.setVariable("anInteger", new Integer(2));
+        asserter.setVariable("aLong", new Long(3));
+        asserter.setVariable("aFloat", new Float(4.4));
+        asserter.setVariable("aDouble", new Double(5.5));
+        asserter.setVariable("aBigInteger", new BigInteger("6"));
+        asserter.setVariable("aBigDecimal", new BigDecimal("7.7"));
+        asserter.setVariable("aString", "forty-two");
+
+        asserter.assertExpression("aByte++", new Byte((byte) 0));
+        asserter.assertExpression("aShort++", new Short((short) 1));
+        asserter.assertExpression("anInteger++", new Integer(2));
+        asserter.assertExpression("aLong++", new Long(3));
+        asserter.assertExpression("aFloat++", new Float(4.4));
+        asserter.assertExpression("aDouble++", new Double(5.5));
+        asserter.assertExpression("aBigInteger++", new BigInteger("6"));
+        asserter.assertExpression("aBigDecimal++", new BigDecimal("7.7"));
+
+        asserter.assertExpression("aByte", new Byte((byte) 1));
+        asserter.assertExpression("aShort", new Short((short) 2));
+        asserter.assertExpression("anInteger", new Integer(3));
+        asserter.assertExpression("aLong", new Long(4));
+        asserter.assertExpression("aFloat", new Float(5.4));
+        asserter.assertExpression("aDouble", new Double(6.5));
+        asserter.assertExpression("aBigInteger", new BigInteger("7"));
+        asserter.assertExpression("aBigDecimal", new BigDecimal("8.7"));
+
+        asserter.failExpression("aString++", "++", String::contains);
+    }
+
+    @Test
+    public void testNarrowBig() throws Exception {
+        List<String> ls = Arrays.asList("zero", "one", "two");
+        asserter.setVariable("list",ls);
+        asserter.setVariable("aBigDecimal", new BigDecimal("1"));
+        asserter.setVariable("aBigInteger", new BigDecimal("1"));
+        asserter.assertExpression("list.get(aBigDecimal)", "one");
+        asserter.assertExpression("list.get(aBigInteger)", "one");
+    }
+
+    @Test
+    public void testNarrowBigDecimal() throws Exception {
+        asserter.setVariable("bi420", BigInteger.valueOf(420));
+        asserter.setVariable("bi10", BigInteger.valueOf(10));
+        asserter.setVariable("bd420", new BigDecimal("420"));
+        asserter.setVariable("bd10", new BigDecimal("10"));
+        asserter.assertExpression("420 / bi10", 42);
+        asserter.assertExpression("420l / bi10", 42L);
+        asserter.assertExpression("bi420 / 420", 1);
+        asserter.assertExpression("bi420 / 420l", 1L);
+        asserter.assertExpression("bd420 / 10", new BigDecimal("42"));
+    }
+
     /**
      * test some simple mathematical calculations
      */
index 48308f0ad7fc60a7dd82b4157f252125be4cdeb6..0207cfbcaa0c0e43c7d080bcf383058bc5ac7e77 100644 (file)
@@ -19,8 +19,11 @@ package org.apache.commons.jexl3;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
+
+import org.apache.commons.jexl3.internal.Debugger;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -36,6 +39,30 @@ public class ForEachTest extends JexlTestCase {
         super("ForEachTest");
     }
 
+    @Test public void testForLoop0b0() {
+        String src = "(l)->{ for(let x = 0, y = 0; x < 4; ++x) l.add(x) }";
+        JexlEngine jexl = new JexlBuilder().safe(false).create();
+        JexlScript script = jexl.createScript(src);
+        List<Integer> l = new ArrayList<>();
+        Object result = script.execute(null, l);
+        Assert.assertNotNull(result);
+        Assert.assertEquals(Arrays.asList(0, 1, 2, 3), l);
+        String resrc = toString(script);
+        Assert.assertEquals(src, resrc);
+    }
+
+    @Test public void testForLoop0a() {
+        String src = "(l)->{ for(let x = 0; x < 4; ++x) { l.add(x); } }";
+        JexlEngine jexl = new JexlBuilder().safe(false).create();
+        JexlScript script = jexl.createScript(src);
+        List<Integer> l = new ArrayList<>();
+        Object result = script.execute(null, l);
+        Assert.assertNotNull(result);
+        Assert.assertEquals(Arrays.asList(0, 1, 2, 3), l);
+        String resrc = toString(script);
+        Assert.assertEquals(src, resrc);
+    }
+
     @Test
     public void testForEachWithEmptyStatement() throws Exception {
         final JexlScript e = JEXL.createScript("for(item : list) ;");
index 4c1eb00f222ae3c89eb80278ea8b97e95f344dfb..12187785a1b06e135f8676b7626074389937aa53 100644 (file)
  */
 package org.apache.commons.jexl3;
 
+import org.apache.commons.jexl3.internal.Engine32;
+import org.apache.commons.jexl3.internal.OptionsContext;
+import org.apache.commons.jexl3.introspection.JexlSandbox;
+import org.junit.Assert;
+import org.junit.Test;
+
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -26,11 +32,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.commons.jexl3.internal.Engine32;
-import org.apache.commons.jexl3.internal.OptionsContext;
-import org.junit.Assert;
 import static org.junit.Assert.assertEquals;
-import org.junit.Test;
 
 /**
  * Test cases for reported issue between JEXL-300 and JEXL-399.
@@ -814,4 +816,62 @@ public class Issues300Test {
             Assert.assertEquals("phone", xvar.getVariable());
         }
     }
+
+    public static class TestObject374 {
+        private String name;
+        private TestObject374 nested = null;
+        public String getName() {
+            return name;
+        }
+        public void setName(String pName) {
+            this.name = pName;
+        }
+        public TestObject374 getNested() {
+            return nested;
+        }
+        public void setNested(TestObject374 pNested) {
+            nested = pNested;
+        }
+    }
+
+    @Test
+    public void test374() {
+        JexlEngine engine = new JexlBuilder().cache(512).strict(true).silent(false).antish(false).safe(false).create();
+        // Create expression to evaluate 'name'
+        JexlExpression expr = engine.createExpression("nested.name");
+        // Create an object with getter for name
+        TestObject374 myObject = new TestObject374();
+        myObject.setName("John");
+        JexlContext context = new ObjectContext<TestObject374>(engine, myObject);
+        // Expect an exception because nested is null, so we are doing null.name
+        try {
+            Object result = expr.evaluate(context);
+            Assert.fail("An exception expected, but got: " + result);
+        } catch (JexlException ex) {
+            // Expected
+            //ex.printStackTrace();
+        }
+    }
+
+    @Test
+    public void test375() {
+        JexlSandbox jexlSandbox = new JexlSandbox(false);
+        jexlSandbox.allow(Type375.class.getName());
+        JexlEngine engine = new JexlBuilder().sandbox(jexlSandbox).create();
+
+        JexlContext context = new MapContext();
+        context.set("Type", Type375.class);
+
+        Object result = engine.createScript("Type.valueOf('DOMICILE')").execute(context);
+        Assert.assertEquals(Type375.DOMICILE, result);
+
+        result = engine.createScript("Type.DOMICILE").execute(context);
+        Assert.assertEquals(Type375.DOMICILE, result);
+    }
+
+    public enum Type375 {
+        DELIVERY_ADDRESS,
+        DOMICILE
+    }
+
 }
index a8249556677b6adea121ef7f51e5037f41ed0538..efa0573514d5fd2b18ae3573c9aec67c90cfaebc 100644 (file)
@@ -20,6 +20,7 @@ package org.apache.commons.jexl3;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 
+import org.apache.commons.jexl3.internal.Debugger;
 import org.apache.commons.jexl3.internal.OptionsContext;
 import org.apache.commons.jexl3.internal.Util;
 import org.apache.commons.jexl3.internal.introspection.Uberspect;
@@ -33,7 +34,7 @@ import org.junit.Assert;
  * Eases the implementation of main methods to debug.
  */
 public class JexlTestCase {
-    // The default options: all tests where engine must lexicality is
+    // The default options: all tests where engine lexicality is
     // important can be identified by the builder  calling lexical(...).
     static {
         JexlOptions.setDefaultFlags("-safe", "+lexical");
@@ -47,7 +48,7 @@ public class JexlTestCase {
     protected final JexlEngine JEXL;
 
     public JexlTestCase(final String name) {
-        this(name, new JexlBuilder().cache(128).create());
+        this(name, new JexlBuilder().permissions(null).cache(128).create());
     }
 
     protected JexlTestCase(final String name, final JexlEngine jexl) {
@@ -125,6 +126,12 @@ public class JexlTestCase {
         return arg.trim().replaceAll("\\s+", " ");
     }
 
+    public static String toString(JexlScript script) {
+        Debugger d = new Debugger().lineFeed("").indentation(0);
+        d.debug(script);
+        return  d.toString();
+    }
+
     /**
      * A very secure singleton.
      */
index e5faaef945a87b7336a9a4dc2445d68482da5ae3..43d0fd9f2015468dc4be9160d3f4d09ddf15b095 100644 (file)
@@ -520,7 +520,7 @@ public class LexicalTest {
         final JexlEngine jexl = new JexlBuilder().strict(true).create();
         final JexlScript script = jexl.createScript("let x = 32; (()->{ for(let x : null) { let c = 0; { return x; } } } )(); ");
         Assert.assertNotNull(script);
-        String dbg = script.getParsedText();
+        String dbg = JexlTestCase.toString(script);
         String src = script.getSourceText();
         Assert.assertTrue(JexlTestCase.equalsIgnoreWhiteSpace(src, dbg));
     }
index 6c7ed1b33269349a52c5e385a236607e1e316147..fb9f0e651cff4dc7cf1596394ee69156dfae616a 100644 (file)
  */
 package org.apache.commons.jexl3;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
 import java.net.URL;
+
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -25,10 +31,11 @@ import org.junit.Test;
  * Tests for JexlScript
  * @since 1.1
  */
-@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+@SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"})
 public class ScriptTest extends JexlTestCase {
     static final String TEST1 =  "src/test/scripts/test1.jexl";
     static final String TEST_ADD =  "src/test/scripts/testAdd.jexl";
+    static final String TEST_JSON =  "src/test/scripts/httpPost.jexl";
 
     // test class for testScriptUpdatesContext
     // making this class private static will cause the test to fail.
@@ -55,7 +62,7 @@ public class ScriptTest extends JexlTestCase {
      * Test creating a script from spaces.
      */
     @Test
-    public void testSpacesScript() throws Exception {
+    public void testSpacesScript() {
         final String code = " ";
         final JexlScript s = JEXL.createScript(code);
         Assert.assertNotNull(s);
@@ -65,37 +72,106 @@ public class ScriptTest extends JexlTestCase {
      * Test creating a script from a string.
      */
     @Test
-    public void testSimpleScript() throws Exception {
+    public void testSimpleScript() {
         final String code = "while (x < 10) x = x + 1;";
         final JexlScript s = JEXL.createScript(code);
         final JexlContext jc = new MapContext();
-        jc.set("x", new Integer(1));
+        jc.set("x",1);
 
         final Object o = s.execute(jc);
-        Assert.assertEquals("Result is wrong", new Integer(10), o);
+        Assert.assertEquals("Result is wrong", 10, o);
         Assert.assertEquals("getText is wrong", code, s.getSourceText());
     }
 
     @Test
-    public void testScriptFromFile() throws Exception {
+    public void testScriptJsonFromFileJexl() {
+        final File testScript = new File(TEST_JSON);
+        final JexlScript s = JEXL.createScript(testScript);
+        final JexlContext jc = new MapContext();
+        jc.set("httpr", new HttpPostRequest());
+        Object result = s.execute(jc);
+        Assert.assertNotNull(result);
+        Assert.assertEquals("{  \"id\": 101}", result);
+    }
+
+    @Test
+    public void testScriptJsonFromFileJava() {
+        final String testScript ="httpr.execute('https://jsonplaceholder.typicode.com/posts', null)";
+        final JexlScript s = JEXL.createScript(testScript);
+        final JexlContext jc = new MapContext();
+        jc.set("httpr", new HttpPostRequest());
+        Object result = s.execute(jc);
+        Assert.assertNotNull(result);
+        Assert.assertEquals("{  \"id\": 101}", result);
+    }
+
+    /**
+     * An object to call from.
+     */
+    public static class HttpPostRequest {
+        public static String execute(String url, String data) throws IOException {
+            return httpPostRequest(url, data);
+        }
+    }
+
+    /**
+     *  HTTP post.
+     * @param sURL the url
+     * @param jsonData some json data
+     * @return the result
+     * @throws IOException
+     */
+    private static String httpPostRequest(String sURL, String jsonData) throws IOException {
+        URL url = new java.net.URL(sURL);
+        HttpURLConnection con = (HttpURLConnection) url.openConnection();
+        con.setRequestMethod("POST");
+        con.setRequestProperty("Accept", "application/json");
+        // send data
+        if ( jsonData != null ) {
+            con.setRequestProperty("Content-Type", "application/json; utf-8");
+            con.setDoOutput(true);
+
+            OutputStream outputStream = con.getOutputStream();
+            byte[] input = jsonData.getBytes("utf-8");
+            outputStream.write(input, 0, input.length);
+        }
+        // read response
+        int responseCode = con.getResponseCode();
+        InputStream inputStream = null;
+        inputStream =  con.getInputStream();
+        StringBuffer response = new java.lang.StringBuffer();
+        if (inputStream != null) {
+            try (BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream))) {
+                String inputLine = "";
+                while ((inputLine = in.readLine()) != null) {
+                    response.append(inputLine);
+                }
+            }
+        }
+        return response.toString();
+    }
+
+
+    @Test
+    public void testScriptFromFile() {
         final File testScript = new File(TEST1);
         final JexlScript s = JEXL.createScript(testScript);
         final JexlContext jc = new MapContext();
         jc.set("out", System.out);
         final Object result = s.execute(jc);
         Assert.assertNotNull("No result", result);
-        Assert.assertEquals("Wrong result", new Integer(7), result);
+        Assert.assertEquals("Wrong result", 7, result);
     }
 
     @Test
-    public void testArgScriptFromFile() throws Exception {
+    public void testArgScriptFromFile() {
         final File testScript = new File(TEST_ADD);
         final JexlScript s = JEXL.createScript(testScript,"x", "y");
         final JexlContext jc = new MapContext();
         jc.set("out", System.out);
         final Object result = s.execute(jc, 13, 29);
         Assert.assertNotNull("No result", result);
-        Assert.assertEquals("Wrong result", new Integer(42), result);
+        Assert.assertEquals("Wrong result", 42, result);
     }
 
     @Test
@@ -106,7 +182,7 @@ public class ScriptTest extends JexlTestCase {
         jc.set("out", System.out);
         final Object result = s.execute(jc);
         Assert.assertNotNull("No result", result);
-        Assert.assertEquals("Wrong result", new Integer(7), result);
+        Assert.assertEquals("Wrong result", 7, result);
     }
 
     @Test
@@ -117,11 +193,11 @@ public class ScriptTest extends JexlTestCase {
         jc.set("out", System.out);
         final Object result = s.execute(jc, 13, 29);
         Assert.assertNotNull("No result", result);
-        Assert.assertEquals("Wrong result", new Integer(42), result);
+        Assert.assertEquals("Wrong result", 42, result);
     }
 
     @Test
-    public void testScriptUpdatesContext() throws Exception {
+    public void testScriptUpdatesContext() {
         final String jexlCode = "resultat.setCode('OK')";
         final JexlExpression e = JEXL.createExpression(jexlCode);
         final JexlScript s = JEXL.createScript(jexlCode);
index a198165a029bc1285ad00f9a5dcdcc3e111493a3..022f22d31a22f024266b2edfe7cb6c04133b67e3 100644 (file)
@@ -23,12 +23,16 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.commons.jexl3.internal.introspection.NoJexlTest;
+import org.apache.commons.jexl3.jexl342.OptionalArithmetic;
 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.Ignore;
 import org.junit.Test;
 
 
@@ -363,70 +367,157 @@ public class SideEffectTest extends JexlTestCase {
         Object result;
         script = jexl.createScript("(x, y)->{ x += y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115 + 15,  result);
+        Assert.assertEquals(3115 + 15, result);
         final Var v0 = new Var(3115);
         result = script.execute(jc, v0, new Var(15));
         Assert.assertEquals(result, v0);
-        Assert.assertEquals(3115 + 15,  v0.value);
+        Assert.assertEquals(3115 + 15, v0.value);
 
         script = jexl.createScript("(x, y)->{ x -= y}");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115 - 15,  result);
+        Assert.assertEquals(3115 - 15, result);
         final Var v1 = new Var(3115);
         result = script.execute(jc, v1, new Var(15));
         Assert.assertNotEquals(result, v1); // not a real side effect
-        Assert.assertEquals(3115 - 15,  ((Var) result).value);
+        Assert.assertEquals(3115 - 15, ((Var) result).value);
 
         script = jexl.createScript("(x, y)->{ x *= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115 * 15,  result);
+        Assert.assertEquals(3115 * 15, result);
         final Var v2 = new Var(3115);
         result = script.execute(jc, v2, new Var(15));
         Assert.assertEquals(result, v2);
-        Assert.assertEquals(3115 * 15,  v2.value);
+        Assert.assertEquals(3115 * 15, v2.value);
 
         script = jexl.createScript("(x, y)->{ x /= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115 / 15,  result);
+        Assert.assertEquals(3115 / 15, result);
         final Var v3 = new Var(3115);
         result = script.execute(jc, v3, new Var(15));
         Assert.assertEquals(result, v3);
-        Assert.assertEquals(3115 / 15,  v3.value);
+        Assert.assertEquals(3115 / 15, v3.value);
 
         script = jexl.createScript("(x, y)->{ x %= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115 % 15,  result);
+        Assert.assertEquals(3115 % 15, result);
         final Var v4 = new Var(3115);
         result = script.execute(jc, v4, new Var(15));
         Assert.assertEquals(result, v4);
-        Assert.assertEquals(3115 % 15,  v4.value);
+        Assert.assertEquals(3115 % 15, v4.value);
 
         script = jexl.createScript("(x, y)->{ x &= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115L & 15,  result);
+        Assert.assertEquals(3115L & 15, result);
         final Var v5 = new Var(3115);
         result = script.execute(jc, v5, new Var(15));
         Assert.assertEquals(result, v5);
-        Assert.assertEquals(3115 & 15,  v5.value);
+        Assert.assertEquals(3115 & 15, v5.value);
 
         script = jexl.createScript("(x, y)->{ x |= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115L | 15,  result);
+        Assert.assertEquals(3115L | 15, result);
         final Var v6 = new Var(3115);
         result = script.execute(jc, v6, new Var(15));
         Assert.assertEquals(result, v6);
-        Assert.assertEquals(3115L | 15,  v6.value);
+        Assert.assertEquals(3115L | 15, v6.value);
 
         script = jexl.createScript("(x, y)->{ x ^= y }");
         result = script.execute(jc, 3115, 15);
-        Assert.assertEquals(3115L ^ 15,  result);
+        Assert.assertEquals(3115L ^ 15, result);
         final Var v7 = new Var(3115);
         result = script.execute(jc, v7, new Var(15));
         Assert.assertEquals(result, v7);
-        Assert.assertEquals(3115L ^ 15,  v7.value);
+        Assert.assertEquals(3115L ^ 15, v7.value);
+
+        script = jexl.createScript("(x, y)->{ x >>>= y }");
+        result = script.execute(jc, 234453115, 5);
+        Assert.assertEquals(234453115L >>> 5, result);
+        final Var v8 = new Var(234453115);
+        result = script.execute(jc, v8, 5);
+        Assert.assertEquals(result, v8);
+        Assert.assertEquals(234453115L >>> 5, v8.value);
+
+        script = jexl.createScript("(x, y)->{ x >>= y }");
+        result = script.execute(jc, 435566788L, 7);
+        Assert.assertEquals(435566788L >> 7, result);
+        final Var v9 = new Var(435566788);
+        result = script.execute(jc, v9, 7);
+        Assert.assertEquals(result, v9);
+        Assert.assertEquals(435566788L >> 7, v9.value);
+
+        script = jexl.createScript("(x, y)->{ x <<= y }");
+        result = script.execute(jc, 3115, 2);
+        Assert.assertEquals(3115L << 2, result);
+        final Var v10 = new Var(3115);
+        result = script.execute(jc, v10, 2);
+        Assert.assertEquals(result, v10);
+        Assert.assertEquals(3115L << 2, v10.value);
     }
 
 
+    @Test
+    public void testIncrementSelf() throws Exception {
+        final JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new SelfArithmetic(false)).create();
+        final JexlContext jc = null;
+        runSelfIncrement(jexl, jc);
+        runSelfIncrement(jexl, jc);
+    }
+
+    @Test
+    public void testIncrementSelfNoCache() throws Exception {
+        final JexlEngine jexl = new JexlBuilder().cache(0).arithmetic(new SelfArithmetic(false)).create();
+        final JexlContext jc = null;
+        runSelfIncrement(jexl, jc);
+    }
+
+    protected void runSelfIncrement(final JexlEngine jexl, final JexlContext jc) {
+        JexlScript script = jexl.createScript("x -> [+x, +(x++), +x]");
+        final Var v11 = new Var(3115);
+        final AtomicInteger i11 = new AtomicInteger(3115);
+        for(Object v : Arrays.asList(v11, i11)) {
+            Object result = script.execute(jc, v);
+            Assert.assertTrue(result instanceof int[]);
+            int[] r = (int[]) result;
+            Assert.assertEquals(3115, r[0]);
+            Assert.assertEquals(3115, r[1]);
+            Assert.assertEquals(3116, r[2]);
+        }
+
+        script = jexl.createScript("x -> [+x, +(++x), +x]");
+        final Var v12 = new Var(3189);
+        final AtomicInteger i12 = new AtomicInteger(3189);
+        for(Object v : Arrays.asList(v12, i12)) {
+            Object result = script.execute(jc, v);
+            Assert.assertTrue(result instanceof int[]);
+            int[] r = (int[]) result;
+            Assert.assertEquals(3189, r[0]);
+            Assert.assertEquals(3190, r[1]);
+            Assert.assertEquals(3190, r[2]);
+        }
+
+        script = jexl.createScript("x -> [+x, +(x--), +x]");
+        final Var v13 = new Var(3115);
+        for(Object v : Arrays.asList(v13)) {
+            Object result = script.execute(jc, v13);
+            Assert.assertTrue(result instanceof int[]);
+            int[] r = (int[]) result;
+            Assert.assertEquals(3115, r[0]);
+            Assert.assertEquals(3115, r[1]);
+            Assert.assertEquals(3114, r[2]);
+        }
+
+        script = jexl.createScript("x -> [+x, +(--x), +x]");
+        final Var v14 = new Var(3189);
+        for(Object v : Arrays.asList(v14)) {
+            Object result = script.execute(jc, v);
+            Assert.assertTrue(result instanceof int[]);
+            int[] r = (int[]) result;
+            Assert.assertEquals(3189, r[0]);
+            Assert.assertEquals(3188, r[1]);
+            Assert.assertEquals(3188, r[2]);
+        }
+    }
+
     @Test
     public void testOverrideGetSet() throws Exception {
         final JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new SelfArithmetic(false)).create();
@@ -456,14 +547,13 @@ public class SideEffectTest extends JexlTestCase {
             value = v;
         }
 
-        @Override
-        public String toString() {
+        @Override public String toString() {
             return Integer.toString(value);
         }
     }
 
     // an arithmetic that performs side effects
-    public static class SelfArithmetic extends JexlArithmetic {
+    public static class SelfArithmetic extends OptionalArithmetic {
         public SelfArithmetic(final boolean strict) {
             super(strict);
         }
@@ -484,9 +574,9 @@ public class SideEffectTest extends JexlTestCase {
             return "VALUE".equals(property)? var.value = v : JexlEngine.TRY_FAILED;
         }
 
-        public JexlOperator selfAdd(final Var lhs, final Var rhs) {
+        public Var selfAdd(final Var lhs, final Var rhs) {
             lhs.value += rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
         // for kicks, this one does not side effect but overloads nevertheless
@@ -494,46 +584,97 @@ public class SideEffectTest extends JexlTestCase {
             return new Var(lhs.value - rhs.value);
         }
 
-        public JexlOperator selfDivide(final Var lhs, final Var rhs) {
+        public Var selfDivide(final Var lhs, final Var rhs) {
             lhs.value /= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
-        public JexlOperator selfMultiply(final Var lhs, final Var rhs) {
+        public Var selfMultiply(final Var lhs, final Var rhs) {
             lhs.value *= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
-        public JexlOperator selfMod(final Var lhs, final Var rhs) {
+        public Var selfMod(final Var lhs, final Var rhs) {
             lhs.value %= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
         public Var and(final Var lhs, final Var rhs) {
             return new Var(lhs.value & rhs.value);
         }
 
-        public JexlOperator selfAnd(final Var lhs, final Var rhs) {
+        public Var selfAnd(final Var lhs, final Var rhs) {
             lhs.value &= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
         public Var or(final Var lhs, final Var rhs) {
             return new Var(lhs.value | rhs.value);
         }
 
-        public JexlOperator selfOr(final Var lhs, final Var rhs) {
+        public Var selfOr(final Var lhs, final Var rhs) {
             lhs.value |= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
         }
 
         public Var xor(final Var lhs, final Var rhs) {
             return new Var(lhs.value ^ rhs.value);
         }
 
-        public JexlOperator selfXor(final Var lhs, final Var rhs) {
+        public Var selfXor(final Var lhs, final Var rhs) {
             lhs.value ^= rhs.value;
-            return JexlOperator.ASSIGN;
+            return lhs;
+        }
+
+        public Var shiftLeft(final Var lhs, final int rhs) {
+            return new Var(lhs.value << rhs);
+        }
+
+        public Var selfShiftLeft(final Var lhs, final int rhs) {
+            lhs.value <<= rhs;
+            return lhs;
+        }
+
+        public Var shiftRight(final Var lhs, final int rhs) {
+            return new Var(lhs.value >> rhs);
+        }
+
+        public Var selfShiftRight(final Var lhs, final int rhs) {
+            lhs.value >>= rhs;
+            return lhs;
+        }
+
+        public Var shiftRightUnsigned(final Var lhs, final int rhs) {
+            return new Var(lhs.value >>> rhs);
+        }
+
+        public Var selfShiftRightUnsigned(final Var lhs, final int rhs) {
+            lhs.value >>>= rhs;
+            return lhs;
+        }
+
+        public int increment(final Var lhs) {
+            return lhs.value + 1;
+        }
+
+        public int decrement(final Var lhs) {
+            return lhs.value - 1;
+        }
+
+        public int incrementAndGet(AtomicInteger i) {
+            return i.incrementAndGet();
+        }
+
+        public int getAndIncrement(AtomicInteger i) {
+            return i.getAndIncrement();
+        }
+
+        public int positivize(Var n) {
+            return n.value;
+        }
+
+        public int positivize(Number n) {
+            return n.intValue();
         }
     }
 
@@ -545,14 +686,14 @@ public class SideEffectTest extends JexlTestCase {
             super(astrict);
         }
 
-        public JexlOperator selfAdd(final Collection<String> c, final String item) throws IOException {
+        public Object selfAdd(final Collection<String> c, final String item) throws IOException {
             c.add(item);
-            return JexlOperator.ASSIGN;
+            return c;
         }
 
-        public JexlOperator selfAdd(final Appendable c, final String item) throws IOException {
+        public Object selfAdd(final Appendable c, final String item) throws IOException {
             c.append(item);
-            return JexlOperator.ASSIGN;
+            return c;
         }
 
         @Override
@@ -572,7 +713,7 @@ public class SideEffectTest extends JexlTestCase {
             }
             if (c instanceof Appendable) {
                 ((Appendable) c).append(item);
-                return JexlOperator.ASSIGN;
+                return c;
             }
             return JexlEngine.TRY_FAILED;
         }
index fe8d9c8bae7ff33fb4d8acd244c772a66824c84d..d516601c1ff96d37b5386a7b8dfb0b26eeef1244 100644 (file)
@@ -188,19 +188,19 @@ public class PermissionsTest {
     @Test
     public void testGetPackageName() {
         final String PKG = "org.apache.commons.jexl3.internal.introspection";
-        String pkg = Permissions.getPackageName(Outer.class);
+        String pkg = ClassTool.getPackageName(Outer.class);
         Assert.assertEquals(PKG, pkg);
-        pkg = Permissions.getPackageName(Outer.Inner.class);
+        pkg = ClassTool.getPackageName(Outer.Inner.class);
         Assert.assertEquals(PKG, pkg);
         Outer[] oo = new Outer[0];
-        pkg = Permissions.getPackageName(oo.getClass());
+        pkg = ClassTool.getPackageName(oo.getClass());
         Assert.assertEquals(PKG, pkg);
         Outer.Inner[] ii = new Outer.Inner[0];
-        pkg = Permissions.getPackageName(ii.getClass());
+        pkg = ClassTool.getPackageName(ii.getClass());
         Assert.assertEquals(PKG, pkg);
-        pkg = Permissions.getPackageName(Process.class);
+        pkg = ClassTool.getPackageName(Process.class);
         Assert.assertEquals("java.lang", pkg);
-        pkg = Permissions.getPackageName(Integer.TYPE);
+        pkg = ClassTool.getPackageName(Integer.TYPE);
         Assert.assertEquals("java.lang", pkg);
     }
 
index dcb119147e50d6f0fae2dd92f119d7378a7ad8f9..9bce162cea59f0a3aee9f6ec58f2887f9e39b5e5 100644 (file)
@@ -228,4 +228,34 @@ public class OptionalArithmetic extends JexlArithmetic {
         }
         return narrowed;
     }
+
+    @Override
+    public ArrayBuilder arrayBuilder(final int size) {
+        return new org.apache.commons.jexl3.internal.ArrayBuilder(size) {
+            @Override
+            public void add(Object value) {
+                super.add(star(value));
+            }
+        };
+    }
+
+    @Override
+    public SetBuilder setBuilder(final int size) {
+        return new org.apache.commons.jexl3.internal.SetBuilder(size) {
+            @Override
+            public void add(Object value) {
+                super.add(star(value));
+            }
+        };
+    }
+
+    @Override
+    public MapBuilder mapBuilder(final int size) {
+        return new org.apache.commons.jexl3.internal.MapBuilder(size) {
+            @Override
+            public void put(Object key, Object value) {
+                super.put(key, star(value));
+            }
+        };
+    }
 }
index 2aaf29a94e81317aa2f2bd20cff3af15bc800db6..8d5db9a50b5bfd7bb355418a4b79d3c9fb025987 100644 (file)
@@ -18,8 +18,10 @@ package org.apache.commons.jexl3.junit;
 
 import java.lang.reflect.Array;
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.BiPredicate;
 
 
 import org.apache.commons.jexl3.JexlEvalContext;
@@ -99,8 +101,11 @@ public class Asserter extends Assert {
             final JexlArithmetic jexla = engine.getArithmetic();
             Assert.assertEquals("expression: " + expression, 0,
                     ((BigDecimal) expected).compareTo(jexla.toBigDecimal(value)));
-        }
-        if (expected != null && value != null) {
+        } else if (expected instanceof BigInteger) {
+            final JexlArithmetic jexla = engine.getArithmetic();
+            Assert.assertEquals("expression: " + expression, 0,
+                    ((BigInteger) expected).compareTo(jexla.toBigInteger(value)));
+        } else if (expected != null && value != null) {
             if (expected.getClass().isArray() && value.getClass().isArray()) {
                 final int esz = Array.getLength(expected);
                 final int vsz = Array.getLength(value);
@@ -130,12 +135,15 @@ public class Asserter extends Assert {
      * @throws Exception if the expression did not fail or the exception did not match the expected pattern
      */
     public void failExpression(final String expression, final String matchException) throws Exception {
+         failExpression(expression, matchException, String::matches);
+    }
+    public void failExpression(final String expression, final String matchException, BiPredicate<String,String> predicate) throws Exception {
         try {
             final JexlScript exp = engine.createScript(expression);
             exp.execute(context);
             fail("expression: " + expression);
         } catch (final JexlException xjexl) {
-            if (matchException != null && !xjexl.getMessage().matches(matchException)) {
+            if (matchException != null && !predicate.test(xjexl.getMessage(), matchException)) {
                 fail("expression: " + expression + ", expected: " + matchException + ", got " + xjexl.getMessage());
             }
         }
diff --git a/src/test/scripts/httpPost.jexl b/src/test/scripts/httpPost.jexl
new file mode 100644 (file)
index 0000000..5f10620
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//-------------------------------------------------------------------
+// send a POST Request
+//-------------------------------------------------------------------
+
+var httpPostRequest = (sURL, jsonData) -> {
+  var url = new("java.net.URL", sURL);
+  var con = url.openConnection();
+  con.setRequestMethod("POST");
+  con.setRequestProperty("Accept", "application/json");
+
+  // send data
+  if ( jsonData != null ) {
+    con.setRequestProperty("Content-Type", "application/json; utf-8");
+    con.setDoOutput(true);
+
+    var outputStream = con.getOutputStream();
+    var input = jsonData.getBytes("utf-8");
+    outputStream.write(input, 0, size(input));
+  }
+
+  // read response
+  var responseCode = con.getResponseCode();
+  var inputStream = null;
+  inputStream =  con.getInputStream();
+  var response = new("java.lang.StringBuffer");
+  if (inputStream != null) {
+    var in = new("java.io.BufferedReader", new("java.io.InputStreamReader", inputStream));
+    var inputLine = "";
+    while ((inputLine = in.readLine()) != null) {
+      response.append(inputLine);
+    }
+    in.close();
+  }
+  response.toString();
+}
+
+httpPostRequest("https://jsonplaceholder.typicode.com/posts");