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

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


The following commit(s) were added to refs/heads/master by this push:
     new 669ecfe  JEXL-278: added logic to better capture ambiguous statements 
and a method to attempt cleaning them from the source
669ecfe is described below

commit 669ecfed0b22031d6e617c408e168a859f802628
Author: henrib <hen...@apache.org>
AuthorDate: Thu Oct 25 22:15:12 2018 +0200

    JEXL-278: added logic to better capture ambiguous statements and a method 
to attempt cleaning them from the source
---
 RELEASE-NOTES.txt                                  |  1 +
 .../java/org/apache/commons/jexl3/JexlEngine.java  | 22 +-----
 .../org/apache/commons/jexl3/JexlException.java    | 75 ++++++++++++++++++-
 .../java/org/apache/commons/jexl3/JexlInfo.java    | 32 +++++++-
 .../org/apache/commons/jexl3/internal/Scope.java   | 22 ++++--
 .../apache/commons/jexl3/parser/ASTAmbiguous.java  | 34 +++++++++
 .../apache/commons/jexl3/parser/JexlParser.java    | 87 ++++++++++++++++------
 .../org/apache/commons/jexl3/parser/Parser.jjt     |  4 +-
 src/site/xdoc/changes.xml                          |  3 +
 .../org/apache/commons/jexl3/Issues200Test.java    | 35 ++++++++-
 .../java/org/apache/commons/jexl3/LambdaTest.java  | 12 ++-
 .../apache/commons/jexl3/parser/ParserTest.java    |  2 +
 12 files changed, 272 insertions(+), 57 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 334cab7..9ef3f8b 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -60,6 +60,7 @@ New Features in 3.2:
 Bugs Fixed in 3.2:
 ==================
 
+* JEXL-278:      Ambiguous exceptions should point to actual statement 
ambiguity
 * JEXL-272:      Dereferencing null property not reported on method call
 * JEXL-271:      Hoisted variable is lost when currying lambda
 * JEXL-270:      Wrong Script$Curried creation when script.curry() method is 
called inside script
diff --git a/src/main/java/org/apache/commons/jexl3/JexlEngine.java 
b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
index 06202f0..b796bb8 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
@@ -579,27 +579,7 @@ public abstract class JexlEngine {
      * @return a JexlInfo instance
      */
     public JexlInfo createInfo() {
-        JexlInfo info = null;
-        StackTraceElement[] stack = new Throwable().getStackTrace();
-        StackTraceElement se = null;
-        String name = getClass().getName();
-        for (int s = 1; s < stack.length; ++s) {
-            se = stack[s];
-            String className = se.getClassName();
-            if (!className.equals(name)) {
-                // go deeper if called from jexl implementation classes
-                if (className.startsWith("org.apache.commons.jexl3.internal.")
-                    || className.startsWith("org.apache.commons.jexl3.J")) {
-                    name = className;
-                } else {
-                    break;
-                }
-            }
-        }
-        if (se != null) {
-            info = createInfo(se.getClassName() + "." + se.getMethodName(), 
se.getLineNumber(), 0);
-        }
-        return info;
+        return new JexlInfo();
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/jexl3/JexlException.java 
b/src/main/java/org/apache/commons/jexl3/JexlException.java
index 856d154..f82d9cf 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlException.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlException.java
@@ -29,6 +29,9 @@ import java.lang.reflect.UndeclaredThrowableException;
 import java.util.ArrayList;
 import java.util.List;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
 /**
  * Wraps any error that might occur during interpretation of a script or 
expression.
  *
@@ -232,6 +235,14 @@ public class JexlException extends RuntimeException {
                     + expr.substring(begin, end > length ? length : end) + " 
...'";
         }
     }
+    
+    /**
+     * Pleasing checkstyle.
+     * @return the info
+     */
+    protected JexlInfo info() {
+        return info;
+    }
 
     /**
      * Thrown when tokenization fails.
@@ -306,19 +317,79 @@ public class JexlException extends RuntimeException {
      * @since 3.0
      */
     public static class Ambiguous extends Parsing {
+        /** The mark at which ambiguity might stop and recover. */
+        private JexlInfo recover = null;
         /**
          * Creates a new Ambiguous statement exception instance.
          * @param info  the location information
          * @param expr  the source expression line
          */
         public Ambiguous(JexlInfo info, String expr) {
-            super(info, expr);
+           this(info, null, expr);
         }
-
+                
+        /**
+         * Creates a new Ambiguous statement exception instance.
+         * @param begin  the start location information
+         * @param end the end location information
+         * @param expr  the source expression line
+         */
+        public Ambiguous(JexlInfo begin, JexlInfo end, String expr) {
+            super(begin, expr);
+            recover = end;
+        }
+        
         @Override
         protected String detailedMessage() {
             return parserError("ambiguous statement", getDetail());
         }
+
+        /**
+         * Tries to remove this ambiguity in the source.
+         * @param src the source that triggered this exception
+         * @return the source with the ambiguous statement removed 
+         *         or null if no recovery was possible
+         */
+        public String tryCleanSource(String src) {
+            JexlInfo ji = info();
+            return ji == null || recover == null
+                  ? src
+                  : sliceSource(src, ji.getLine(), ji.getColumn(), 
recover.getLine(), recover.getColumn());
+        }
+    }
+
+    /**
+     * Removes a slice from a source.
+     * @param src the source
+     * @param froml the begin line
+     * @param fromc the begin column
+     * @param tol the to line
+     * @param toc the to column
+     * @return the source with the (begin) to (to) zone removed
+     */
+    public static String sliceSource(String src, int froml, int fromc, int 
tol, int toc) {
+        BufferedReader reader = new BufferedReader(new StringReader(src));
+        StringBuilder buffer = new StringBuilder();
+        String line;
+        int cl = 1;
+        try {
+            while ((line = reader.readLine()) != null) {
+                if (cl < froml || cl > tol) {
+                    buffer.append(line).append('\n');
+                } else {
+                    if (cl == froml) {
+                        buffer.append(line.substring(0, fromc - 1));
+                    }
+                    if (cl == tol) {
+                        buffer.append(line.substring(toc));
+                    }
+                } // else ignore line
+                cl += 1;
+            }
+        } catch (IOException xignore) {
+            //damn the checked exceptions :-)
+        }
+        return buffer.toString();
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/jexl3/JexlInfo.java 
b/src/main/java/org/apache/commons/jexl3/JexlInfo.java
index b105c98..c0abe07 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlInfo.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlInfo.java
@@ -22,7 +22,7 @@ package org.apache.commons.jexl3;
  * debugging information reporting.
  */
 public class JexlInfo {
-
+    
     /** line number. */
     private final int line;
 
@@ -73,6 +73,36 @@ public class JexlInfo {
         line = l;
         column = c;
     }
+                
+    /**
+     * Create an information structure for dynamic set/get/invoke/new.
+     * <p>This gathers the class, method and line number of the first calling 
method
+     * outside of o.a.c.jexl3.</p>
+     */
+    public JexlInfo() {
+        StackTraceElement[] stack = new Throwable().getStackTrace();
+        String cname = getClass().getName();
+        String pkgname = getClass().getPackage().getName();
+        StackTraceElement se = null;
+        for (int s = 1; s < stack.length; ++s) {
+            se = stack[s];
+            String className = se.getClassName();
+            if (!className.equals(cname)) {
+                // go deeper if called from jexl implementation classes
+                if (className.startsWith(pkgname + ".internal.")
+                    || className.startsWith(pkgname + ".Jexl")) {
+                    cname = className;
+                } else {
+                    break;
+                }
+            }
+        }
+        this.name = se != null
+             ? se.getClassName() + "." + se.getMethodName()  +":" + 
se.getLineNumber()
+                    : "?";
+        this.line = 0;
+        this.column = 0;
+    }
 
     /**
      * Creates info reusing the name.
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Scope.java 
b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
index a0d52ab..380cd4c 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
@@ -121,7 +121,7 @@ public final class Scope {
                 if (namedVariables == null) {
                     namedVariables = new LinkedHashMap<String, Integer>();
                 }
-                register = Integer.valueOf(namedVariables.size());
+                register = namedVariables.size();
                 namedVariables.put(name, register);
                 hoistedVariables.put(register, pr);
             }
@@ -153,7 +153,7 @@ public final class Scope {
         }
         Integer register = namedVariables.get(name);
         if (register == null) {
-            register = Integer.valueOf(namedVariables.size());
+            register = namedVariables.size();
             namedVariables.put(name, register);
             parms += 1;
         }
@@ -173,9 +173,19 @@ public final class Scope {
         }
         Integer register = namedVariables.get(name);
         if (register == null) {
-            register = Integer.valueOf(namedVariables.size());
+            register = namedVariables.size();
             namedVariables.put(name, register);
             vars += 1;
+//            // check if local is redefining hoisted
+//            if (parent != null) {
+//                Integer pr = parent.getSymbol(name, true);
+//                if (pr != null) {
+//                    if (hoistedVariables == null) {
+//                        hoistedVariables = new LinkedHashMap<Integer, 
Integer>();
+//                    }
+//                    hoistedVariables.put(register, pr);
+//                }
+//            }
         }
         return register;
     }
@@ -193,8 +203,8 @@ public final class Scope {
                 for (Map.Entry<Integer, Integer> hoist : 
hoistedVariables.entrySet()) {
                     Integer target = hoist.getKey();
                     Integer source = hoist.getValue();
-                    Object arg = frame.get(source.intValue());
-                    arguments[target.intValue()] = arg;
+                    Object arg = frame.get(source);
+                    arguments[target] = arg;
                 }
             }
             return new Frame(this, arguments, 0);
@@ -275,7 +285,7 @@ public final class Scope {
             String[] pa = new String[parms - (hoistedVariables == null? 0 : 
hoistedVariables.size())];
             int p = 0;
             for (Map.Entry<String, Integer> entry : namedVariables.entrySet()) 
{
-                int symnum = entry.getValue().intValue();
+                int symnum = entry.getValue();
                 if (symnum >= parms && (hoistedVariables == null || 
!hoistedVariables.containsKey(symnum))) {
                     pa[p++] = entry.getKey();
                 }
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTAmbiguous.java 
b/src/main/java/org/apache/commons/jexl3/parser/ASTAmbiguous.java
new file mode 100644
index 0000000..7bbbb54
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTAmbiguous.java
@@ -0,0 +1,34 @@
+/*
+ * 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.parser;
+
+public final class ASTAmbiguous extends JexlNode {
+
+    ASTAmbiguous(int id) {
+        super(id);
+    }
+
+    ASTAmbiguous(Parser p, int id) {
+        super(p, id);
+    }
+
+    @Override
+    public Object jjtAccept(ParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+}
+
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java 
b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
index 188626e..5c02a70 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -33,7 +33,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.Stack;
 import java.util.TreeMap;
-
+import static org.apache.commons.jexl3.parser.ParserConstants.EOF;
+import static org.apache.commons.jexl3.parser.ParserConstants.SEMICOL;
 
 /**
  * The base class for parsing, manages the parameter/local variable frame.
@@ -44,6 +45,10 @@ public abstract class JexlParser extends StringParser {
      */
     protected final FeatureController featureController = new 
FeatureController(JexlEngine.DEFAULT_FEATURES);
     /**
+     * The basic source info.
+     */
+    protected JexlInfo info = null;
+    /**
      * The source being processed.
      */
     protected String source = null;
@@ -82,8 +87,8 @@ public abstract class JexlParser extends StringParser {
     }
 
     /**
-     * Reading a given source line
-     * @parma src the source
+     * Read a given source line.
+     * @param src the source
      * @param lineno the line number
      * @return the line
      */
@@ -101,9 +106,10 @@ public abstract class JexlParser extends StringParser {
         }
         return msg;
     }
-
+    
     /**
      * Internal, for debug purpose only.
+     * @param registers whether register syntax is recognized by this parser
      */
     public void allowRegisters(boolean registers) {
         featureController.setFeatures(new 
JexlFeatures(featureController.getFeatures()).register(registers));
@@ -260,9 +266,37 @@ public abstract class JexlParser extends StringParser {
      */
     protected abstract Token getToken(int index);
 
+    /**
+     * Overridden in actual parser to access tokens stack.
+     * @return the next token on the stack
+     */
+    protected abstract Token getNextToken();
+    
+    /**
+     * Throws Ambiguous exception.
+     * <p>It seeks a ';' (or EOF) to recover.
+     * @param begin the first token in ambiguous expression
+     */
+    protected void throwAmbiguousException(Token begin) {
+        Token pt = null, end = null;
+        while((end = getNextToken()) != null) {
+            if (end.kind == EOF || end.kind == SEMICOL) {
+                if (pt != null) {
+                    end = pt;
+                }
+                break;
+            }
+            pt = end;
+        }
+        JexlInfo infob = info.at(begin.beginLine, begin.beginColumn);
+        JexlInfo infoe = end != null? info.at(end.endLine, end.endColumn) : 
null;
+        String msg = readSourceLine(source, begin.beginLine);
+        throw new JexlException.Ambiguous(infob, infoe, msg);
+    }
+    
     protected void jjtreeOpenNodeScope(JexlNode node) {
         if (node instanceof ASTAmbiguous) {
-            throwParsingException(JexlException.Ambiguous.class, node);
+            throwAmbiguousException(getToken(1));
         }
     }
 
@@ -304,7 +338,7 @@ public abstract class JexlParser extends StringParser {
         } else if (ASSIGN_NODES.contains(node.getClass())) {
             JexlNode lv = node.jjtGetChild(0);
             if (!lv.isLeftValue()) {
-                throwParsingException(JexlException.Assignment.class, lv);
+                throwParsingException(JexlException.Assignment.class, null);
             }
         }
         // heavy check
@@ -334,8 +368,8 @@ public abstract class JexlParser extends StringParser {
                 throw new JexlException.Parsing(null, 
JexlFeatures.stringify(feature));
             }
         }
-        JexlInfo info = new JexlInfo(token.image, token.beginLine, 
token.beginColumn);
-        throwFeatureException(feature, info);
+        JexlInfo xinfo = info.at(token.beginLine, token.beginColumn);
+        throwFeatureException(feature, xinfo);
     }
 
     /**
@@ -343,30 +377,35 @@ public abstract class JexlParser extends StringParser {
      * @param node the node that caused it
      */
     protected void throwParsingException(JexlNode node) {
-        throwParsingException(null, node);
+        throwParsingException(null, null);
     }
 
     /**
-     * Throws a parsing exception.
+     * Creates a parsing exception.
      * @param xclazz the class of exception
-     * @param node the node that caused it
+     * @param tok the token to report
+     * @param <T> the parsing exception subclass
      */
-    protected void throwParsingException(Class<? extends JexlException> 
xclazz, JexlNode node) {
-        Token tok = this.getToken(0);
+    protected <T extends JexlException.Parsing> void 
throwParsingException(Class<T> xclazz, Token tok) {
+        JexlInfo xinfo  = null;
+        String msg = "unrecoverable state";
+        JexlException.Parsing xparse = null;
         if (tok == null) {
-            throw new JexlException.Parsing(null, "unrecoverable state");
+            tok = this.getToken(0);
         }
-        JexlInfo dbgInfo = new JexlInfo(tok.image, tok.beginLine, 
tok.beginColumn);
-        String msg = readSourceLine(source, tok.beginLine);
-        JexlException xjexl = null;
-        if (xclazz != null) {
-            try {
-                Constructor<? extends JexlException> ctor = 
xclazz.getConstructor(JexlInfo.class, String.class);
-                xjexl = ctor.newInstance(dbgInfo, msg);
-            } catch (Exception xany) {
-                // ignore, very unlikely but then again..
+        if (tok != null) {
+            xinfo = info.at(tok.beginLine, tok.beginColumn);
+            msg = readSourceLine(source, tok.beginLine);
+            if (xclazz != null) {
+                try {
+                    Constructor<T> ctor = 
xclazz.getConstructor(JexlInfo.class, String.class);
+                    xparse = ctor.newInstance(xinfo, msg);
+                } catch (Exception xany) {
+                    // ignore, very unlikely but then again..
+                }
             }
         }
-        throw xjexl != null ? xjexl : new JexlException.Parsing(dbgInfo, msg);
+        // unlikely but safe
+        throw xparse != null ? xparse : new JexlException.Parsing(xinfo, msg);
     }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt 
b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
index b7bfd27..1ec783c 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -46,7 +46,7 @@ public final class Parser extends JexlParser
 {
     private int loopCount = 0;
 
-    public ASTJexlScript parse(JexlInfo info, JexlFeatures jexlFeatures, 
String jexlSrc, Scope scope) {
+    public ASTJexlScript parse(JexlInfo jexlInfo, JexlFeatures jexlFeatures, 
String jexlSrc, Scope scope) {
         JexlFeatures previous = getFeatures();
         try {
             setFeatures(jexlFeatures);
@@ -55,6 +55,7 @@ public final class Parser extends JexlParser
                 token_source.defaultLexState = REGISTERS;
             }
             // lets do the 'Unique Init' in here to be safe - it's a pain to 
remember
+            info = jexlInfo != null? jexlInfo : new JexlInfo();
             source = jexlSrc;
             pragmas = null;
             frame = scope;
@@ -71,6 +72,7 @@ public final class Parser extends JexlParser
         } catch (ParseException xparse) {
             throw new JexlException.Parsing(info, xparse).clean();
         } finally {
+            info = null;
             source = null;
             frame = null;
             token_source.defaultLexState = DEFAULT;
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index 7156f73..e2316d4 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -26,6 +26,9 @@
     </properties>
     <body>
         <release version="3.2" date="unreleased">
+            <action dev="henrib" type="fix" issue="JEXL-278">
+                Ambiguous exceptions should point to actual statement ambiguity
+            </action>
             <action dev="henrib" type="add" issue="JEXL-275">
                  Allow safe navigation as option
             </action>
diff --git a/src/test/java/org/apache/commons/jexl3/Issues200Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
index ea5a84e..63a33bf 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues200Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.jexl3;
 
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -384,7 +385,7 @@ public class Issues200Test extends JexlTestCase {
         result = script.execute(ctx);
         Assert.assertEquals(10, result);
     }
-
+      
     @Test
     public void test230() throws Exception {
         JexlEngine jexl = new JexlBuilder().cache(4).create();
@@ -595,5 +596,37 @@ public class Issues200Test extends JexlTestCase {
             String sxs = xstack.toString();
             Assert.assertTrue(sxs.contains("jvm"));
         }
+    } 
+               
+    @Test
+    public void test278() throws Exception {
+        String[] srcs = new String[]{
+            "return union x('arg',5,6) ",
+            "return union 143('arg',5,6)   ;",
+            "return union\n 143('arg',5,6)   ;",
+            "var f =()->{ return union 143; } foo[0]"
+        };
+        Object[] ctls = new Object[]{
+            "42","42","42", 42
+        };
+        JexlEngine jexl = new JexlBuilder().cache(4).create();
+        JexlContext ctxt = new MapContext();
+        int[] foo = {42};
+        ctxt.set("foo", foo);
+        ctxt.set("union", "42");
+        Object value;
+        JexlScript jc;
+        for(int i = 0; i < srcs.length; ++i) { 
+            String src = srcs[i];
+            try {
+                jc = jexl.createScript(src);
+                Assert.fail("should have failed, " + (jc != null));
+            } catch(JexlException.Ambiguous xa) {
+                src = xa.tryCleanSource(src);
+            }
+            jc = jexl.createScript(src);
+            value = jc.execute(ctxt);
+            Assert.assertEquals(src, ctls[i], value);
+        }
     }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/LambdaTest.java 
b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
index 78bb49d..930377c 100644
--- a/src/test/java/org/apache/commons/jexl3/LambdaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
@@ -146,7 +146,7 @@ public class LambdaTest extends JexlTestCase {
     }
 
     @Test
-    public void testHoistLambada() throws Exception {
+    public void testHoistLambda() throws Exception {
         JexlEngine jexl = createEngine();
         JexlContext ctx = null;
         JexlScript s42;
@@ -343,4 +343,14 @@ public class LambdaTest extends JexlTestCase {
         Object result = y.execute(null, 2, 3);
         Assert.assertEquals(8, result);
     }
+    
+    // redefining an hoisted var is not resolved correctly in left hand side;
+    // declare the var in local frame, resolved in local frame instead of 
parent
+//    @Test
+//    public void test271e() throws Exception {
+//        JexlEngine jexl = createEngine();
+//        JexlScript base = jexl.createScript("var base = 1000; var f = (x, 
y)->{ var base = x + y + (base?:-1000); base; }; f(100, 20)");
+//        Object result = base.execute(null);
+//        Assert.assertEquals(-880, result);
+//    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/parser/ParserTest.java 
b/src/test/java/org/apache/commons/jexl3/parser/ParserTest.java
index 8a038fa..0c66c16 100644
--- a/src/test/java/org/apache/commons/jexl3/parser/ParserTest.java
+++ b/src/test/java/org/apache/commons/jexl3/parser/ParserTest.java
@@ -17,9 +17,11 @@
 package org.apache.commons.jexl3.parser;
 
 import java.io.StringReader;
+import org.apache.commons.jexl3.JexlEngine;
 
 import org.apache.commons.jexl3.JexlException;
 import org.apache.commons.jexl3.JexlFeatures;
+import org.apache.commons.logging.LogFactory;
 import org.junit.Assert;
 import org.junit.Test;
 

Reply via email to