You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2025/07/03 16:43:33 UTC

(commons-jexl) branch JEXL-440 updated: JEXL-440 : added constants/enum handling defined through imports usable in case expression/statement; - enum constant and (public) static final fields are usable as values in case expression/statement; - import is now stricter, will verify package or class is visible (apply permission) and throw if not; - light refactoring around ClassNameResolve/ClassConstantResolver/ConstantResolverFactory (moved to JexlUberspect); - also fixed autoSemicolon issue (JEXL-437);

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

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


The following commit(s) were added to refs/heads/JEXL-440 by this push:
     new b85369ac JEXL-440 : added constants/enum handling defined through imports usable in case expression/statement; - enum constant and (public) static final fields are usable as values in case expression/statement; - import is now stricter, will verify package or class is visible (apply permission) and throw if not; - light refactoring around ClassNameResolve/ClassConstantResolver/ConstantResolverFactory (moved to JexlUberspect); - also fixed autoSemicolon issue (JEXL-437);
b85369ac is described below

commit b85369ac1b368cf6d9187f93343cf7511d947f41
Author: Henrib <hb...@gmail.com>
AuthorDate: Thu Jul 3 18:43:21 2025 +0200

    JEXL-440 : added constants/enum handling defined through imports usable in case expression/statement;
    - enum constant and (public) static final fields are usable as values in case expression/statement;
    - import is now stricter, will verify package or class is visible (apply permission) and throw if not;
    - light refactoring around ClassNameResolve/ClassConstantResolver/ConstantResolverFactory (moved to JexlUberspect);
    - also fixed autoSemicolon issue (JEXL-437);
---
 RELEASE-NOTES.txt                                  |  23 +++
 src/changes/changes.xml                            |  10 +-
 .../java/org/apache/commons/jexl3/JexlContext.java |  11 +-
 .../org/apache/commons/jexl3/JexlFeatures.java     |   7 +-
 .../java/org/apache/commons/jexl3/JexlOptions.java |   4 +-
 .../org/apache/commons/jexl3/internal/Engine.java  |  53 ++++---
 .../commons/jexl3/internal/FqcnResolver.java       | 165 ++++++++++++---------
 .../apache/commons/jexl3/internal/Interpreter.java |   2 +-
 .../commons/jexl3/internal/InterpreterBase.java    |   8 +-
 .../internal/introspection/FieldGetExecutor.java   |  13 ++
 .../jexl3/introspection/JexlPropertyGet.java       |   8 +
 .../commons/jexl3/introspection/JexlUberspect.java |  50 ++++++-
 .../apache/commons/jexl3/parser/JexlParser.java    |  73 +++++++--
 .../commons/jexl3/parser/JexlScriptParser.java     |   1 +
 .../org/apache/commons/jexl3/parser/Parser.jjt     |  64 +++++---
 src/site/xdoc/relnotes35.xml                       |  32 +++-
 .../org/apache/commons/jexl3/ArithmeticTest.java   | 130 ++++++++--------
 .../org/apache/commons/jexl3/FeaturesTest.java     |  38 ++---
 .../org/apache/commons/jexl3/Issues200Test.java    |  82 +++++-----
 .../org/apache/commons/jexl3/Issues400Test.java    |  70 ++++++++-
 .../java/org/apache/commons/jexl3/JexlTest.java    |  80 +++++-----
 .../apache/commons/jexl3/internal/FqcnTest.java    |  94 ++++++++++++
 .../apache/commons/jexl3/internal/RangeTest.java   |  16 +-
 23 files changed, 714 insertions(+), 320 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index eff4c7c2..1e6f0903 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -29,6 +29,29 @@ Download page: https://commons.apache.org/proper/commons-jexl/download_jexl.cgi
 Have fun!
 -Apache Commons Team
 
+========================================================================================================================
+Release 3.6.0
+========================================================================================================================
+
+Version 3.6.0 is a minor release.
+
+Compatibility with previous releases
+====================================
+Version 3.6.0 is source and binary compatible with 3.5.
+
+
+What's new in 3.6.0:
+====================
+Switch / case statements and expressions have been added to JEXL.
+
+New Features in 3.6.0:
+======================
+* JEXL-440:     Switch/case statement and expression
+Bugs Fixed in 3.6.0:
+====================
+* JEXL-437:     Semicolons not actually optional between function calls on separate lines
+
+
 ========================================================================================================================
 Release 3.5.0
 ========================================================================================================================
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d1615c09..1ee93191 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -27,11 +27,17 @@
         <author email="dev@commons.apache.org">Commons Developers</author>
     </properties>
     <body>
-        <release version="3.5.1" date="YYYY-MM-DD" description="This is a feature and maintenance release. Java 8 or later is required.">
+        <release version="3.6.0" date="YYYY-MM-DD" description="This is a feature and maintenance release. Java 8 or later is required.">
           <!-- FIX -->
-          <action type="fix" dev="ggregory" due-to="Gary Gregory">org.apache.commons.jexl3.internal.introspection.AbstractExecutor.initMarker(Class, String, Class...) throws IllegalArgumentException instead of Error.</action>
+            <action dev="henrib" type="fix" issue="JEXL-437" due-to="William Price">
+                Semicolons not actually optional between function calls on separate lines
+            </action>
+          <action type="fix" dev="ggregory" due-to="Gary Gregory">oac....AbstractExecutor.initMarker(Class, String, Class...) throws IllegalArgumentException instead of Error.</action>
           <action type="fix" dev="ggregory" due-to="Gary Gregory">Reuse BigInteger constants instead of creating new instances in org.apache.commons.jexl3.JexlArithmetic.toBigInteger(Object).</action>
           <!-- ADD -->
+            <action dev="henrib" type="add" issue="JEXL-440">
+                Switch statement &amp; expressions
+            </action>
           <!-- UPDATE -->
           <action type="update" dev="ggregory" due-to="Gary Gregory, Dependabot">Bump org.apache.commons:commons-parent from 81 to 84 #344.</action>
         </release>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlContext.java b/src/main/java/org/apache/commons/jexl3/JexlContext.java
index 1fb92768..9e1e7512 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlContext.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlContext.java
@@ -17,6 +17,8 @@
 
 package org.apache.commons.jexl3;
 
+import org.apache.commons.jexl3.introspection.JexlUberspect;
+
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -83,14 +85,7 @@ public interface JexlContext {
      * A marker interface that solves a simple class name into a fully-qualified one.
      * @since 3.3
      */
-    interface ClassNameResolver {
-        /**
-         * Resolves a class name.
-         * @param name the simple class name
-         * @return the fully qualified class name
-         */
-        String resolveClassName(String name);
-    }
+    interface ClassNameResolver extends JexlUberspect.ClassNameResolver {}
 
     /**
      * A marker interface of the JexlContext that processes module definitions.
diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index eb735915..1eb51bdd 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -725,9 +725,11 @@ public final class JexlFeatures {
      * may be missing an operator or that the intent is not clear.
      * </p>
      * @param flag true to enable, false to disable
+     * @return this features instance
      */
-    public void setAmbiguousStatement(final boolean flag) {
+    public JexlFeatures ambiguousStatement(final boolean flag) {
         setFeature(AMBIGUOUS_STATEMENT, flag);
+        return this;
     }
 
     /**
@@ -740,7 +742,8 @@ public final class JexlFeatures {
      * @return true if statements can be ambiguous, false otherwise
      */
     public boolean supportsAmbiguousStatement() {
-        return getFeature(AMBIGUOUS_STATEMENT);
+        boolean sas = getFeature(AMBIGUOUS_STATEMENT);
+        return sas;
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
index afc3f3b2..2a3a1741 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -334,9 +334,9 @@ public final class JexlOptions {
      */
     public JexlOptions set(final JexlEngine jexl) {
         if (jexl instanceof Engine) {
-            ((Engine) jexl).optionsSet(this);
+            return ((Engine) jexl).optionsSet(this);
         }
-        return this;
+        throw new UnsupportedOperationException("JexlEngine is not an Engine instance: " + jexl.getClass().getName());
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index 4d966d51..b651ac14 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -23,6 +23,7 @@ import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_OPTIONS;
 
 import java.nio.charset.Charset;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -71,7 +72,7 @@ import org.apache.commons.logging.LogFactory;
  * A JexlEngine implementation.
  * @since 2.0
  */
-public class Engine extends JexlEngine {
+public class Engine extends JexlEngine implements JexlUberspect.ConstantResolverFactory {
     /**
      * Gets the default instance of Uberspect.
      * <p>This is lazily initialized to avoid building a default instance if there
@@ -177,12 +178,15 @@ public class Engine extends JexlEngine {
     public static Uberspect getUberspect(final Log logger, final JexlUberspect.ResolverStrategy strategy) {
         return getUberspect(logger, strategy, null);
     }
+
     /**
      * Gets the default instance of Uberspect.
      * <p>This is lazily initialized to avoid building a default instance if there
-     * is no use for it. The main reason for not using the default Uberspect instance is to
-     * be able to use a (low level) introspector created with a given logger
-     * instead of the default one and even more so for with a different (restricted) set of permissions.</p>
+     * is no use for it.</p>
+     * <lu>The main reason for not using the default Uberspect instance are:
+     * <li>Using a (low level) introspector created with a given logger instead of the default one</li>
+     * <li>Using a (restricted) set of permissions</li>
+     * </lu>
      * @param logger the logger to use for the underlying Uberspect
      * @param strategy the property resolver strategy
      * @param permissions the introspection permissions
@@ -762,6 +766,13 @@ public class Engine extends JexlEngine {
         return doCreateInstance(clazz, args);
     }
 
+    @Override
+    public JexlUberspect.ClassConstantResolver createConstantResolver(Collection<String> imports) {
+        return imports == null || imports.isEmpty()
+                ? classNameSolver
+                : new FqcnResolver(classNameSolver).importPackages(imports);
+    }
+
     /**
      * Sets options from this engine options.
      * @param opts the options to set
@@ -810,21 +821,27 @@ public class Engine extends JexlEngine {
             }
         }
         final JexlInfo ninfo = info == null && debug ? createInfo() : info;
-        // if parser not in use...
-        if (parsing.compareAndSet(false, true)) {
-            try {
-                // lets parse
-                script = parser.parse(ninfo, features, src, scope);
-            } finally {
-                // no longer in use
-                parsing.set(false);
+        JexlEngine se = putThreadEngine(this);
+        try {
+            // if parser not in use...
+            if (parsing.compareAndSet(false, true)) {
+                try {
+                    // lets parse
+                    script = parser.parse(ninfo, features, src, scope);
+                } finally {
+                    // no longer in use
+                    parsing.set(false);
+                }
+            } else {
+                // ...otherwise parser was in use, create a new temporary one
+                script = parserFactory.get().parse(ninfo, features, src, scope);
             }
-        } else {
-            // ...otherwise parser was in use, create a new temporary one
-            script = parserFactory.get().parse(ninfo, features, src, scope);
-        }
-        if (source != null) {
-            cache.put(source, script);
+            if (source != null) {
+                cache.put(source, script);
+            }
+        } finally {
+            // restore thread local engine
+            putThreadEngine(se);
         }
         return script;
     }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/FqcnResolver.java b/src/main/java/org/apache/commons/jexl3/internal/FqcnResolver.java
index 65f3644f..817ddfe9 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/FqcnResolver.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/FqcnResolver.java
@@ -16,40 +16,37 @@
  */
 package org.apache.commons.jexl3.internal;
 
-import java.util.HashMap;
+import org.apache.commons.jexl3.JexlEngine;
+import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.introspection.JexlPropertyGet;
+import org.apache.commons.jexl3.introspection.JexlUberspect;
+
+import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import org.apache.commons.jexl3.JexlContext;
-import org.apache.commons.jexl3.introspection.JexlUberspect;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
- * Helper resolving a simple class name into a fully-qualified class name (hence FqcnResolver) using
- * package names as roots of import.
- * <p>This only keeps names of classes to avoid any class loading/reloading/permissions issue.</p>
+ * Helper resolving a simple class name into a Fully Qualified Class Name (hence FqcnResolver) using
+ * package names and classes as roots of import.
+ * <p>This only keeps the names of the classes to avoid any class loading/reloading/permissions issue.</p>
  */
- final class FqcnResolver implements JexlContext.ClassNameResolver {
+public class FqcnResolver implements JexlUberspect.ClassConstantResolver {
     /**
-     * The class loader.
+     * The uberspect.
      */
     private final JexlUberspect uberspect;
-    /**
-     * A lock for RW concurrent ops.
-     */
-    private final ReadWriteLock lock = new ReentrantReadWriteLock();
     /**
      * The set of packages to be used as import roots.
      */
-    private final Set<String> imports = new LinkedHashSet<>();
+    private final Set<String> imports = Collections.synchronizedSet(new LinkedHashSet<>());
     /**
      * The map of solved fqcns based on imports keyed on (simple) name,
-     * valued as fully-qualified class name.
+     * valued as fully qualified class name.
      */
-    private final Map<String, String> fqcns = new HashMap<>();
+    private final Map<String, String> fqcns = new ConcurrentHashMap<>();
     /**
      * Optional parent solver.
      */
@@ -62,19 +59,18 @@ import org.apache.commons.jexl3.introspection.JexlUberspect;
      * @throws NullPointerException if parent solver is null
      */
     FqcnResolver(final FqcnResolver solver) {
-        Objects.requireNonNull(solver, "solver");
-        this.parent = solver;
+        this.parent = Objects.requireNonNull(solver, "solver");
         this.uberspect = solver.uberspect;
     }
 
     /**
      * Creates a class name solver.
      *
-     * @param uber   the optional class loader
+     * @param uber     the optional class loader
      * @param packages the optional package names
      */
     FqcnResolver(final JexlUberspect uber, final Iterable<String> packages) {
-        this.uberspect = uber;
+        this.uberspect = Objects.requireNonNull(uber, "uberspect");
         this.parent = null;
         importCheck(packages);
     }
@@ -88,42 +84,35 @@ import org.apache.commons.jexl3.introspection.JexlUberspect;
     String getQualifiedName(final String name) {
         String fqcn;
         if (parent != null && (fqcn = parent.getQualifiedName(name)) != null) {
-            return  fqcn;
-        }
-        lock.readLock().lock();
-        try {
-            fqcn = fqcns.get(name);
-        } finally {
-            lock.readLock().unlock();
+            return fqcn;
         }
-        if (fqcn == null) {
-            final ClassLoader loader = uberspect.getClassLoader();
-            for (final String pkg : imports) {
-                Class<?> clazz;
-                try {
-                    clazz = loader.loadClass(pkg + "." + name);
-                } catch (final ClassNotFoundException e) {
-                    // not in this package
-                    continue;
-                }
-                // solved it, insert in map and return
+        return fqcns.computeIfAbsent(name, this::solveClassName);
+    }
+
+    /**
+     * Attempts to solve a fully qualified class name from a simple class name.
+     * <p>It tries to solve the class name as package.classname or package$classname (inner class).</p>
+     *
+     * @param name the simple class name
+     * @return the fully qualified class name or null if not found
+     */
+    private String solveClassName(String name) {
+        for (final String pkg : imports) {
+            // try package.classname or fqcn$classname (inner class)
+            for (char dot : new char[]{'.', '$'}) {
+                Class<?> clazz = uberspect.getClassByName(pkg + dot + name);
+                // solved it
                 if (clazz != null) {
-                    fqcn = clazz.getName();
-                    lock.writeLock().lock();
-                    try {
-                        fqcns.put(name, fqcn);
-                    } finally {
-                        lock.writeLock().unlock();
-                    }
-                    break;
+                    return clazz.getName();
                 }
             }
         }
-        return fqcn;
+        return null;
     }
 
     /**
-     * Adds a collection of packages as import root, checks the names are one of a package.
+     * Adds a collection of packages/classes as import root, check each name point to one or the other.
+     *
      * @param names the package names
      */
     private void importCheck(final Iterable<String> names) {
@@ -133,14 +122,25 @@ import org.apache.commons.jexl3.introspection.JexlUberspect;
     }
 
     /**
-     * Adds a package as import root, checks the name if one of a package.
+     * Adds a package as import root, checks the name points to a package or a class.
+     *
      * @param name the package name
      */
     private void importCheck(final String name) {
+        if (name == null || name.isEmpty()) {
+            return;
+        }
         // check the package name actually points to a package to avoid clutter
-        if (name != null && Package.getPackage(name) != null) {
-            imports.add(name);
+        Package pkg = Package.getPackage(name);
+        if (pkg == null) {
+            // if it is a class, solve it now
+            Class<?> clazz = uberspect.getClassByName(name);
+            if (clazz == null) {
+                throw new JexlException(null, "Cannot import '" + name + "' as it is neither a package nor a class");
+            }
+            fqcns.put(name, clazz.getName());
         }
+        imports.add(name);
     }
 
     /**
@@ -151,15 +151,14 @@ import org.apache.commons.jexl3.introspection.JexlUberspect;
      */
     FqcnResolver importPackages(final Iterable<String> packages) {
         if (packages != null) {
-            lock.writeLock().lock();
-            try {
-                if (parent == null) {
-                    importCheck(packages);
-                } else {
-                    packages.forEach(pkg ->{ if (!parent.isImporting(pkg)) { importCheck(pkg); }});
-                }
-            } finally {
-                lock.writeLock().unlock();
+            if (parent == null) {
+                importCheck(packages);
+            } else {
+                packages.forEach(pkg -> {
+                    if (!parent.isImporting(pkg)) {
+                        importCheck(pkg);
+                    }
+                });
             }
         }
         return this;
@@ -175,16 +174,46 @@ import org.apache.commons.jexl3.introspection.JexlUberspect;
         if (parent != null && parent.isImporting(pkg)) {
             return true;
         }
-        lock.readLock().lock();
-        try {
-            return imports.contains(pkg);
-        } finally {
-            lock.readLock().unlock();
-        }
+        return imports.contains(pkg);
     }
 
     @Override
     public String resolveClassName(final String name) {
         return getQualifiedName(name);
     }
+
+    @Override
+    public Object resolveConstant(final String cname) {
+        return getConstant(cname.split("\\."));
+    }
+
+    private Object getConstant(final String... ids) {
+        if (ids.length == 1) {
+            final String pname = ids[0];
+            for (String cname : fqcns.keySet()) {
+                Object constant = getConstant(cname, pname);
+                if (constant != JexlEngine.TRY_FAILED) {
+                    return constant;
+                }
+            }
+        } else if (ids.length == 2) {
+            String cname = ids[0];
+            String id = ids[1];
+            String fqcn = resolveClassName(cname);
+            if (fqcn != null) {
+                Class<?> clazz = uberspect.getClassByName(fqcn);
+                if (clazz != null) {
+                    JexlPropertyGet getter = uberspect.getPropertyGet(clazz, id);
+                    if (getter != null && getter.isConstant()) {
+                        try {
+                            return getter.invoke(clazz);
+                        } catch (Exception xany) {
+                            // ignore
+                        }
+                    }
+                }
+            }
+        }
+        return JexlEngine.TRY_FAILED;
+    }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index decbc26e..9d010494 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -932,7 +932,7 @@ public class Interpreter extends InterpreterBase {
         if (fqcn != null) {
             return fqcn;
         }
-        // context may be solving class name ?
+        // context may be solving class name?
         if (context instanceof JexlContext.ClassNameResolver) {
             final JexlContext.ClassNameResolver resolver = (JexlContext.ClassNameResolver) context;
             fqcn = resolver.resolveClassName(name);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
index d7b0aff8..747700ed 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.jexl3.internal;
 
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -309,7 +308,7 @@ public abstract class InterpreterBase extends ParserVisitor {
     /** The namespace resolver. */
     protected final JexlContext.NamespaceResolver ns;
     /** The class name resolver. */
-    protected final JexlContext.ClassNameResolver fqcnSolver;
+    protected final JexlUberspect.ClassNameResolver fqcnSolver;
     /** The operators evaluation delegate. */
     protected final JexlOperator.Uberspect operators;
     /** The map of 'prefix:function' to object resolving as namespaces. */
@@ -355,10 +354,7 @@ public abstract class InterpreterBase extends ParserVisitor {
         }
         this.operators = ops;
         // the import package facility
-        final Collection<String> imports = options.getImports();
-        this.fqcnSolver = imports.isEmpty()
-                ? engine.classNameSolver
-                : new FqcnResolver(engine.classNameSolver).importPackages(imports);
+        this.fqcnSolver = engine.createConstantResolver(options.getImports());
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/FieldGetExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/FieldGetExecutor.java
index 7f275165..b6074c0e 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/FieldGetExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/FieldGetExecutor.java
@@ -21,6 +21,9 @@ import java.lang.reflect.Field;
 
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
 
+import static java.lang.reflect.Modifier.isFinal;
+import static java.lang.reflect.Modifier.isStatic;
+
 /**
  * A JexlPropertyGet for public fields.
  */
@@ -65,6 +68,16 @@ public final class FieldGetExecutor implements JexlPropertyGet {
         return true;
     }
 
+    @Override
+    public boolean isConstant() {
+        if (field.isEnumConstant()) {
+            return true;
+        }
+        final int modifiers = field.getModifiers();
+        // public static final fields are (considered) constants
+        return isFinal(modifiers) && isStatic(modifiers);
+    }
+
     @Override
     public boolean tryFailed(final Object rval) {
         return rval == Uberspect.TRY_FAILED;
diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlPropertyGet.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlPropertyGet.java
index b6d0e9d2..9cb0b2cb 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlPropertyGet.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlPropertyGet.java
@@ -46,6 +46,14 @@ public interface JexlPropertyGet {
      */
     boolean isCacheable();
 
+    /**
+     * Checks whether this JexlPropertyGet returns a constant.
+     * @return true if invoking this getter will always return the same value, false otherwise
+     */
+    default boolean isConstant() {
+        return false;
+    }
+
     /**
      * Checks whether a tryInvoke failed or not.
      *
diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
index d28ebdf8..669ab717 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
@@ -18,6 +18,7 @@
 package org.apache.commons.jexl3.introspection;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -48,7 +49,7 @@ public interface JexlUberspect {
         /** Seeks map methods get/put. */
         MAP,
 
-        /** Seeks list methods get/set. */
+        /** Seeks list methods to get/set. */
         LIST,
 
         /** Seeks any get/{set,put} method (quacking like a list or a map). */
@@ -76,6 +77,45 @@ public interface JexlUberspect {
         }
     }
 
+    /**
+     * A marker interface that solves a simple class name into a fully qualified one.
+     * <p>The base implementation uses imports.</p>
+     * @since 3.3
+     */
+    interface ClassNameResolver {
+        /**
+         * Resolves a class name.
+         * @param name the simple class name
+         * @return the fully qualified class name
+         */
+        String resolveClassName(String name);
+    }
+
+    /**
+     * A marker interface that solves a class constant by name.
+     * <p>The base implementation uses imports to solve enums and public static final fields.</p>
+     * @since 3.6
+     */
+    interface ClassConstantResolver extends ClassNameResolver {
+        /**
+         * Resolves a constant by its name.
+         * @param name the constant name, a qualified name
+         * @return the constant value or TRY_FAILED if not found
+         */
+        Object resolveConstant(String name);
+    }
+
+    /**
+     * The factory type for creating constant resolvers.
+     */
+    interface ConstantResolverFactory {
+        /**
+         * Creates a constant resolver.
+         * @return a constant resolver
+         */
+        ClassConstantResolver createConstantResolver(Collection<String> imports);
+    }
+
     /**
      * Abstracts getting property setter and getter.
      * <p>
@@ -115,7 +155,7 @@ public interface JexlUberspect {
     /**
      * Determines property resolution strategy.
      *
-     * <p>To use a strategy instance, you have to set it at engine creation using
+     * <p>To use a strategy, you have to set it at engine creation using
      * {@link org.apache.commons.jexl3.JexlBuilder#strategy(JexlUberspect.ResolverStrategy)}
      * as in:</p>
      *
@@ -127,9 +167,9 @@ public interface JexlUberspect {
         /**
          * Applies this strategy to a list of resolver types.
          *
-         * @param operator the property access operator, may be null
+         * @param operator the property access operator, can be null
          * @param obj      the instance we seek to obtain a property setter/getter from, cannot be null
-         * @return the ordered list of resolvers types, must not be null
+         * @return the ordered list of resolver types, cannot be null
          */
         List<PropertyResolver> apply(JexlOperator operator, Object obj);
     }
@@ -226,7 +266,7 @@ public interface JexlUberspect {
     default Class<?> getClassByName(final String className) {
         try {
             return Class.forName(className, false, getClassLoader());
-        } catch (final ClassNotFoundException xignore) {
+        } catch (final ClassNotFoundException ignore) {
             return null;
         }
     }
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 6a1e823a..a073afc8 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.StringReader;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Deque;
@@ -27,6 +28,7 @@ import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
@@ -37,6 +39,7 @@ import org.apache.commons.jexl3.JexlFeatures;
 import org.apache.commons.jexl3.JexlInfo;
 import org.apache.commons.jexl3.internal.LexicalScope;
 import org.apache.commons.jexl3.internal.Scope;
+import org.apache.commons.jexl3.introspection.JexlUberspect;
 
 /**
  * The base class for parsing, manages the parameter/local variable frame.
@@ -175,54 +178,83 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
      * The source being processed.
      */
     protected String source;
-
     /**
      * The map of named registers aka script parameters.
      * <p>Each parameter is associated to a register and is materialized
      * as an offset in the registers array used during evaluation.</p>
      */
     protected Scope scope;
-
     /**
      * When parsing inner functions/lambda, need to stack the scope (sic).
      */
     protected final Deque<Scope> scopes = new ArrayDeque<>();
-
     /**
      * The list of pragma declarations.
      */
     protected Map<String, Object> pragmas;
+    /**
+     * The optional class name and constant resolver.
+     */
+    protected JexlUberspect.ClassConstantResolver fqcnResolver = null;
+    /**
+     * The list of imports.
+     * <p>Imports are used to resolve simple class names into fully qualified class names.</p>
+     */
+    protected List<String> imports = new ArrayList<>();
+
+
+    void addImport(String importName) {
+        if (importName != null && !importName.isEmpty()) {
+            if (imports == null) {
+                imports = new ArrayList<>();
+            }
+            if (!imports.contains(importName)) {
+                imports.add(importName);
+            }
+        }
+    }
+
+    Object resolveConstant(String name) {
+        JexlUberspect.ClassConstantResolver resolver = fqcnResolver;
+        if (resolver == null) {
+            JexlEngine engine = JexlEngine.getThreadEngine();
+            if (engine instanceof JexlUberspect.ConstantResolverFactory) {
+                fqcnResolver = resolver = ((JexlUberspect.ConstantResolverFactory) engine).createConstantResolver(imports);
+            }
+        }
+        return resolver != null
+            ? resolver.resolveConstant(name)
+            : JexlEngine.TRY_FAILED;
+    }
 
+    /**
+     * Whether automatic semicolon insertion is enabled.
+     */
+    protected boolean autoSemicolon = true;
     /**
      * The known namespaces.
      */
     protected Set<String> namespaces;
-
     /**
      * The number of nested loops.
      */
     protected int loopCount;
-
     /**
      * Stack of parsing loop counts.
      */
     protected final Deque<Integer> loopCounts = new ArrayDeque<>();
-
     /**
      * The current lexical block.
      */
     protected LexicalUnit block;
-
     /**
      * Stack of lexical blocks.
      */
     protected final Deque<LexicalUnit> blocks = new ArrayDeque<>();
-
     /**
      * The map of lexical to functional blocks.
      */
     protected final Map<LexicalUnit, Scope> blockScopes = new IdentityHashMap<>();
-
     /**
      * The name of the null case constant.
      */
@@ -441,6 +473,8 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
         scopes.clear();
         pragmas = null;
         namespaces = null;
+        fqcnResolver = null;
+        imports.clear();
         loopCounts.clear();
         loopCount = 0;
         blocks.clear();
@@ -795,6 +829,27 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse
         return scope != null && scope.getSymbol(name) != null;
     }
 
+    /**
+     * Checks whether a statement is ambiguous.
+     * <p>
+     * This is used to detect statements that are not terminated by a semicolon,
+     * and that may be confused with an expression.
+     * </p>
+     * @param semicolon the semicolon token kind
+     * @return true if statement is ambiguous, false otherwise
+     */
+    protected boolean isAmbiguousStatement(int semicolon) {
+        if (autoSemicolon) {
+            Token current = getToken(0);
+            Token next = getToken(1);
+            if (current != null && next != null && current.endLine != next.beginLine) {
+                // if the next token is on a different line, no ambiguity reported
+                return false;
+            }
+        }
+        return !getFeatures().supportsAmbiguousStatement();
+    }
+
     /**
      * Called by parser at end of node construction.
      * <p>
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlScriptParser.java b/src/main/java/org/apache/commons/jexl3/parser/JexlScriptParser.java
index e4ab6913..80277455 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlScriptParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlScriptParser.java
@@ -37,4 +37,5 @@ public interface JexlScriptParser {
    * @throws JexlException if any error occurred during parsing
    */
   ASTJexlScript parse(final JexlInfo info, final JexlFeatures features, final String src, final Scope scope);
+
 }
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 caed1e89..5470bc01 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -49,6 +49,17 @@ import org.apache.commons.jexl3.internal.Scope;
 
 public final class Parser extends JexlParser
 {
+
+    /**
+     * Parses a JEXL source string and returns the corresponding AST.
+     * @param jexlInfo the JEXL info to use, may be null
+     * @param jexlFeatures the JEXL features to use
+     * @param jexlSrc the JEXL source string to parse
+     * @param jexlScope the scope to use for parsing
+     * @return the AST for the JEXL source string
+     * @throws JexlException.Tokenization if there is a tokenization error
+     * @throws JexlException.Parsing if there is a parsing error
+     */
     public ASTJexlScript parse(JexlInfo jexlInfo, JexlFeatures jexlFeatures, String jexlSrc, Scope jexlScope) {
         JexlFeatures previous = getFeatures();
         try {
@@ -108,7 +119,7 @@ TOKEN_MGR_DECLS : {
     | <"//" (~["\n","\r"])* ("\n" | "\r" | "\r\n")? >
 }
 
-<NEVER> TOKEN : /* Exception handling. */
+<DEFAULT> TOKEN : /* Exception handling. */
 {
       < THROW : "throw" >
     | < TRY : "try" >
@@ -116,7 +127,7 @@ TOKEN_MGR_DECLS : {
     | < FINALLY : "finally" >
 }
 
-<NEVER> TOKEN : /* Type check. */
+<DEFAULT> TOKEN : /* Type check. */
 {
       < ISA : "instanceof" >
 }
@@ -246,6 +257,7 @@ TOKEN_MGR_DECLS : {
     | < SWITCH : "switch" >
     | < CASE : "case" >
     | < CASE_DEFAULT : "default" >
+    | < IMPORT : "import" >
 }
 
 
@@ -364,7 +376,7 @@ ASTJexlScript JexlScript(Scope frame, JexlFeatures features) : {
    {
         pushUnit(jjtThis);
    }
-        ( LOOKAHEAD(<PRAGMA>) Pragma() | { controlPragmaAnywhere(); } Statement() )* <EOF>
+        (LOOKAHEAD(<IMPORT>) Import())* ( LOOKAHEAD(<PRAGMA>) Pragma() | { controlPragmaAnywhere(); } Statement() )* <EOF>
    {
         popUnit(jjtThis);
         return jjtThis.script();
@@ -440,7 +452,7 @@ void FunctionStatement() #JexlLambda : {}
 
 void ExpressionStatement() #void : {}
 {
-    Expression() (LOOKAHEAD(Expression(), { !getFeatures().supportsAmbiguousStatement() } ) Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)?
+    Expression() (LOOKAHEAD(Expression(), { isAmbiguousStatement(SEMICOL) } ) Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)*
 }
 
 
@@ -487,14 +499,18 @@ void DoWhileStatement() : {}
     <DO> { loopCount += 1; } (LOOKAHEAD(1) Block() | StatementNoVar()) <WHILE> <LPAREN> Expression() <RPAREN> { loopCount -= 1; }
 }
 
-void ReturnStatement() : {}
+void ReturnStatement() : {
+    this.autoSemicolon = false;
+ }
 {
-    <RETURN> (LOOKAHEAD(2) ExpressionStatement() )?
+    <RETURN> (LOOKAHEAD(2) ExpressionStatement() )? { this.autoSemicolon = true; }
 }
 
-void ThrowStatement() : {}
+void ThrowStatement() : {
+    this.autoSemicolon = false;
+ }
 {
-    <THROW> (LOOKAHEAD(2) ExpressionStatement() )?
+    <THROW> (LOOKAHEAD(2) ExpressionStatement() )? { this.autoSemicolon = true; }
 }
 
 void Continue() #Continue : {
@@ -580,6 +596,7 @@ void DeclareVar(boolean lexical, boolean constant) #Var :
 {
     t=<IDENTIFIER> { declareVariable(jjtThis, t, lexical, constant); }
 }
+
 void DeclareFunction() #Var :
 {
     Token t;
@@ -588,7 +605,6 @@ void DeclareFunction() #Var :
     t=<IDENTIFIER> { declareFunction(jjtThis, t); }
 }
 
-
 void SwitchStatement() #SwitchStatement :
 {
     ASTSwitchStatement.Helper helper = new ASTSwitchStatement.Helper();
@@ -647,7 +663,7 @@ void SwitchExpressionCase(SwitchSet cases) #CaseExpression :
         |
         <CASE_DEFAULT> <LAMBDA>
     )
-    ( LOOKAHEAD(3) Block() | Expression())
+    ( LOOKAHEAD(3) Block() | Expression() (<SEMICOL>)?)
 }
 
 void ConstLiterals(List<Object> constants) #void :
@@ -661,9 +677,10 @@ void ConstLiterals(List<Object> constants) #void :
 
 Object constLiteral() #void :
 {
-Token s = null;
-Token v;
-Object result;
+    Token s = null;
+    Token v;
+    Object result;
+    LinkedList<String> lstr = new LinkedList<String>();
 }
 {
   (
@@ -674,12 +691,23 @@ Object result;
     | LOOKAHEAD(1) <FALSE> { result = false; }
     | LOOKAHEAD(1) <NULL> { result = null; }
     | LOOKAHEAD(1) <NAN_LITERAL> { result = Double.NaN; }
+    | LOOKAHEAD(1) propertyKey(lstr) { result = resolveConstant(stringify(lstr)); }
   )
   {
     return result;
   }
 }
 
+void Import() #void :
+{
+    List<String> imports = new ArrayList<String>();
+}
+{
+    <IMPORT> propertyKey(imports) (LOOKAHEAD(1) <SEMICOL>)?
+    {
+       addImport(stringify(imports));
+    }
+}
 
 void Pragma() #void :
 {
@@ -687,15 +715,15 @@ void Pragma() #void :
     Object value;
 }
 {
-<PRAGMA> pragmaKey(lstr)  value=pragmaValue() { declarePragma(stringify(lstr), value); }
+<PRAGMA> propertyKey(lstr)  value=pragmaValue() { declarePragma(stringify(lstr), value); }
 }
 
-void pragmaKey(List<String> lstr) #void :
+void propertyKey(List<String> lstr) #void :
 {
     Token t;
 }
 {
-    t=<IDENTIFIER>  { lstr.add(t.image); } ( LOOKAHEAD(<DOT>) pragmaKey(lstr) )*
+    t=<IDENTIFIER>  { lstr.add(t.image); } ( LOOKAHEAD(<DOT>) propertyKey(lstr) )*
    |
     <DOT> t=<DOT_IDENTIFIER> { lstr.add(t.image); }
 }
@@ -712,7 +740,7 @@ Object result;
       LOOKAHEAD(2) (s=<plus>|s=<minus>)? v=<INTEGER_LITERAL> { result = NumberParser.parseInteger(s, v); }
     | LOOKAHEAD(2) (s=<plus>|s=<minus>)? v=<FLOAT_LITERAL> { result = NumberParser.parseDouble(s, v); }
     | LOOKAHEAD(1) v=<STRING_LITERAL> { result = Parser.buildString(v.image, true); }
-    | LOOKAHEAD(1)  pragmaKey(lstr) { result = stringify(lstr); }
+    | LOOKAHEAD(1)  propertyKey(lstr) { result = stringify(lstr); }
     | LOOKAHEAD(1) <TRUE> { result = true; }
     | LOOKAHEAD(1) <FALSE> { result = false; }
     | LOOKAHEAD(1) <NULL> { result = null; }
@@ -1112,7 +1140,7 @@ void QualifiedIdentifier() #QualifiedIdentifier : {
     LinkedList<String> lstr = new LinkedList<String>();
 }
 {
-    pragmaKey(lstr) { jjtThis.setName(stringify(lstr));}
+    propertyKey(lstr) { jjtThis.setName(stringify(lstr));}
 }
 
 void Constructor() #ConstructorNode : {}
diff --git a/src/site/xdoc/relnotes35.xml b/src/site/xdoc/relnotes35.xml
index 5c9fbb3e..75c683a0 100644
--- a/src/site/xdoc/relnotes35.xml
+++ b/src/site/xdoc/relnotes35.xml
@@ -24,7 +24,7 @@
   <body>
     <section name="Compatibility with previous release">
       <p>
-        Version 3.5 is source and binary compatible with 3.4.
+        Version 3.6 is source and binary compatible with 3.5.
       </p>
     </section>
     <section name="Compatibility with older releases (&lt; 3.3, 2.x)">
@@ -49,7 +49,35 @@
         or whether loops or side effects are accessible are part of your choice to make.
       </p>
     </section>
-    <section name="What's new in 3.5:">
+
+    <section name="What is new in 3.6:">
+      <p>
+        JEXL 3.6 is a minor release that adds a few features and fixes some bugs.
+        It does not introduce any breaking change.
+      </p>
+    </section>
+    <section name="New Features in 3.6:">
+    <p>
+    <table>
+    <tr>
+      <td><a href="https://issues.apache.org/jira/browse/JEXL-440">JEXL-440:</a></td>
+      <td>Switch statement &amp; expressions.</td>
+    </tr>
+    </table>
+    </p>
+    </section>
+    <section name="Bugs fixed in 3.6:">
+      <p>
+        <table>
+          <tr>
+            <td><a href="https://issues.apache.org/jira/browse/JEXL-437">JEXL-437:</a></td>
+            <td>Semicolons not actually optional between function calls on separate lines</td>
+          </tr>
+        </table>
+      </p>
+    </section>
+
+    <section name="What was new in 3.5:">
       <p>
         A feature that captures variables as references allowing side effect from a lambda to its
         outer scope; this is mimicking JavaScript scoping rule (JEXL-426).
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index 7dbdb4b2..2e6102c1 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -765,7 +765,7 @@ public class ArithmeticTest extends JexlTestCase {
 
     // JEXL-24: doubles with exponent
     @Test
-    public void test2DoubleLiterals() {
+    void test2DoubleLiterals() {
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
         options.setStrictArithmetic(true);
@@ -782,7 +782,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testAddWithStringsLenient() {
+    void testAddWithStringsLenient() {
         final JexlEngine jexl = new JexlBuilder().arithmetic(new JexlArithmetic(false)).create();
         JexlScript script;
         Object result;
@@ -820,7 +820,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testAddWithStringsStrict() {
+    void testAddWithStringsStrict() {
         final JexlEngine jexl = new JexlBuilder().arithmetic(new JexlArithmetic(true)).create();
         JexlScript script;
         Object result;
@@ -859,7 +859,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testArithmeticPlus() throws Exception {
+    void testArithmeticPlus() {
         final JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new ArithmeticPlus(false)).create();
         final JexlContext jc = new EmptyTestContext();
         runOverload(jexl, jc);
@@ -867,14 +867,14 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testArithmeticPlusNoCache() {
+    void testArithmeticPlusNoCache() {
         final JexlEngine jexl = new JexlBuilder().cache(0).arithmetic(new ArithmeticPlus(false)).create();
         final JexlContext jc = new EmptyTestContext();
         runOverload(jexl, jc);
     }
 
     @Test
-    public void testAtomicBoolean() {
+    void testAtomicBoolean() {
         // in a condition
         JexlScript e = JEXL.createScript("if (x) 1 else 2;", "x");
         final JexlArithmetic jexla = JEXL.getArithmetic();
@@ -946,7 +946,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testBigDecimal() throws Exception {
+    void testBigDecimal() throws Exception {
         asserter.setVariable("left", new BigDecimal(2));
         asserter.setVariable("right", new BigDecimal(6));
         asserter.assertExpression("left + right", new BigDecimal(8));
@@ -957,7 +957,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testBigdOp() {
+    void testBigdOp() {
         final BigDecimal sevendot475 = new BigDecimal("7.475");
         final BigDecimal SO = new BigDecimal("325");
         final JexlContext jc = new MapContext();
@@ -971,7 +971,7 @@ public class ArithmeticTest extends JexlTestCase {
 
     // JEXL-24: big decimals with exponent
     @Test
-    public void testBigExponentLiterals() {
+    void testBigExponentLiterals() {
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
         options.setStrictArithmetic(true);
@@ -986,7 +986,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testBigInteger() throws Exception {
+    void testBigInteger() throws Exception {
         asserter.setVariable("left", new BigInteger("2"));
         asserter.setVariable("right", new BigInteger("6"));
         asserter.assertExpression("left + right", new BigInteger("8"));
@@ -998,7 +998,7 @@ public class ArithmeticTest extends JexlTestCase {
 
     // JEXL-24: big integers and big decimals
     @Test
-    public void testBigLiterals() {
+    void testBigLiterals() {
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
         options.setStrictArithmetic(true);
@@ -1012,7 +1012,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testBigLiteralValue() {
+    void testBigLiteralValue() {
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
         options.setStrictArithmetic(true);
@@ -1025,7 +1025,7 @@ public class ArithmeticTest extends JexlTestCase {
      * test some simple mathematical calculations
      */
     @Test
-    public void testCalculations() throws Exception {
+    void testCalculations() throws Exception {
         asserter.setStrict(true, false);
         /*
          * test new null coersion
@@ -1068,7 +1068,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoerceBigDecimal() {
+    void testCoerceBigDecimal() {
         final JexlArithmetic ja = JEXL.getArithmetic();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -1098,7 +1098,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoerceBigInteger() {
+    void testCoerceBigInteger() {
         final JexlArithmetic ja = JEXL.getArithmetic();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -1117,7 +1117,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoerceDouble() {
+    void testCoerceDouble() {
         final JexlArithmetic ja = JEXL.getArithmetic();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -1136,7 +1136,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoerceInteger() {
+    void testCoerceInteger() {
         final JexlArithmetic ja = JEXL.getArithmetic();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -1155,7 +1155,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoerceLong() {
+    void testCoerceLong() {
         final JexlArithmetic ja = JEXL.getArithmetic();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -1174,7 +1174,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoercions() throws Exception {
+    void testCoercions() throws Exception {
         asserter.assertExpression("1", Integer.valueOf(1)); // numerics default to Integer
         asserter.assertExpression("5L", Long.valueOf(5));
 
@@ -1206,7 +1206,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCompare() {
+    void testCompare() {
         // JEXL doesn't support more than one operator in the same expression, for example: 1 == 1 == 1
         final Object[] EXPRESSIONS = {
                 // Basic compare
@@ -1287,7 +1287,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testDivClass() {
+    void testDivClass() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlContext jc = new MapContext();
         final Object ra = jexl.createExpression("463.0d / 0.1").evaluate(jc);
@@ -1304,7 +1304,7 @@ public class ArithmeticTest extends JexlTestCase {
      * if not silent, all arith exception throw
      */
     @Test
-    public void testDivideByZero() throws Exception {
+    void testDivideByZero() throws Exception {
         final Map<String, Object> vars = new HashMap<>();
         final JexlEvalContext context = new JexlEvalContext(vars);
         final JexlOptions options = context.getEngineOptions();
@@ -1375,7 +1375,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testDivideEdges() {
+    void testDivideEdges() {
         assertNullOperand(() -> jexla.divide(null, null));
         assertEquals(0, jexlb.divide(null, null));
         assertNullOperand(() -> jexla.divide(null, null));
@@ -1389,7 +1389,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testEmpty() throws Exception {
+    void testEmpty() throws Exception {
         final Object[] SCRIPTS = {
             "var x = null; log('x = %s', x);", 0,
             "var x = 'abc'; log('x = %s', x);", 1,
@@ -1426,7 +1426,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testEmptyDouble()  {
+    void testEmptyDouble()  {
         Object x;
         x = JEXL.createScript("4294967296.d").execute(null);
         assertEquals(4294967296.0d, (Double) x, EPSILON);
@@ -1443,7 +1443,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testEmptyFloat()  {
+    void testEmptyFloat()  {
         Object x;
         x = JEXL.createScript("4294967296.f").execute(null);
         assertEquals(4294967296.0f, (Float) x, EPSILON);
@@ -1459,7 +1459,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testEmptyLong()  {
+    void testEmptyLong()  {
         Object x;
         x = JEXL.createScript("new('java.lang.Long', 4294967296)").execute(null);
         assertEquals(4294967296L, ((Long) x).longValue());
@@ -1478,7 +1478,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testFailAllOperators() {
+    void testFailAllOperators() {
         // @formatter:off
         final String[] scripts = {
             "(x, y)->{ x < y }",
@@ -1527,7 +1527,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testInfiniteArithmetic() throws Exception {
+    void testInfiniteArithmetic() {
         final Map<String, Object> ns = new HashMap<>();
         ns.put("math", Math.class);
         final JexlEngine jexl = new JexlBuilder().arithmetic(new Arithmetic132()).namespaces(ns).create();
@@ -1555,20 +1555,20 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testInstanceOf0() throws Exception {
+    void testInstanceOf0() {
         final JexlEngine jexl = new JexlBuilder().strict(true).safe(false).create();
         final JexlContext ctxt = new InstanceofContext();
         runInstanceof(jexl, ctxt);
     }
 
     @Test
-    public void testInstanceOf1() throws Exception {
+    void testInstanceOf1() {
         final JexlEngine jexl = new JexlBuilder().strict(true).safe(false).imports("java.lang").create();
         runInstanceof(jexl, null);
     }
 
     @Test
-    public void testIntegerCoercionEdges() {
+    void testIntegerCoercionEdges() {
         assertNullOperand(() -> jexla.toBoolean(null));
         assertTrue(jexla.toBoolean(date));
         // int coercions
@@ -1593,7 +1593,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testIsFloatingPointPattern() {
+    void testIsFloatingPointPattern() {
         final JexlArithmetic ja = new JexlArithmetic(true);
 
         assertFalse(ja.isFloatingPointNumber("floating point"));
@@ -1634,7 +1634,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testJexl173() {
+    void testJexl173() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlContext jc = new MapContext();
         final Callable173 c173 = new Callable173();
@@ -1648,7 +1648,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testLeftNullOperand() throws Exception {
+    void testLeftNullOperand() throws Exception {
         asserter.setVariable("left", null);
         asserter.setVariable("right", Integer.valueOf(8));
         asserter.setStrict(true);
@@ -1667,7 +1667,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testLeftNullOperand2() throws Exception {
+    void testLeftNullOperand2() throws Exception {
         asserter.setVariable("x.left", null);
         asserter.setVariable("right", Integer.valueOf(8));
         asserter.setStrict(true);
@@ -1687,7 +1687,7 @@ public class ArithmeticTest extends JexlTestCase {
 
     // JEXL-24: long integers (and doubles)
     @Test
-    public void testLongLiterals() {
+    void testLongLiterals() {
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
         options.setStrictArithmetic(true);
@@ -1703,12 +1703,12 @@ public class ArithmeticTest extends JexlTestCase {
         assertEquals(63.5d, ctxt.get("g"));
         assertEquals(0x10, ctxt.get("h"));
         assertEquals(010, ctxt.get("i")); // octal 010
-        assertEquals(0x10L, ctxt.get("j")); // octal 010L
-        assertEquals(010L, ctxt.get("k"));
+        assertEquals(0x10L, ctxt.get("j")); // hexa 010L
+        assertEquals(010L, ctxt.get("k")); // octal 010L
     }
 
     @Test
-    public void testMinusClass() {
+    void testMinusClass() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlContext jc = new MapContext();
         final Object ra = jexl.createExpression("463.0d - 0.1").evaluate(jc);
@@ -1720,7 +1720,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testMinusMinusPostfix() throws Exception {
+    void testMinusMinusPostfix() throws Exception {
         asserter.setVariable("aByte", Byte.valueOf((byte) 2));
         asserter.setVariable("aShort", Short.valueOf((short) 3));
         asserter.setVariable("anInteger", Integer.valueOf(4));
@@ -1753,7 +1753,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testMinusMinusPrefix() throws Exception {
+    void testMinusMinusPrefix() throws Exception {
         asserter.setVariable("aByte", Byte.valueOf((byte) 2));
         asserter.setVariable("aShort", Short.valueOf((short) 3));
         asserter.setVariable("anInteger", Integer.valueOf(4));
@@ -1777,7 +1777,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testModEdge() {
+    void testModEdge() {
         assertNullOperand(() -> jexla.mod(null, null));
         assertEquals(0, jexlb.mod(null, null));
         assertArithmeticException(() -> jexla.mod(1, 0));
@@ -1793,7 +1793,7 @@ public class ArithmeticTest extends JexlTestCase {
      * JEXL-156.
      */
     @Test
-    public void testMultClass(){
+    void testMultClass(){
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlContext jc = new MapContext();
         final Object ra = jexl.createExpression("463.0d * 0.1").evaluate(jc);
@@ -1805,7 +1805,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testNaN() {
+    void testNaN() {
         final Map<String, Object> ns = new HashMap<>();
         ns.put("double", Double.class);
         final JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
@@ -1826,7 +1826,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testNarrowBigDecimal() throws Exception {
+    void testNarrowBigDecimal() throws Exception {
         final List<String> ls = Arrays.asList("zero", "one", "two");
         asserter.setVariable("list", ls);
         asserter.assertExpression("a -> list.get(a)", "zero", BigDecimal.ZERO);
@@ -1844,7 +1844,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testNarrowBigInteger() throws Exception {
+    void testNarrowBigInteger() throws Exception {
         final List<String> ls = Arrays.asList("zero", "one", "two");
         asserter.setVariable("list",ls);
         asserter.assertExpression("a -> list.get(a)", "zero", BigInteger.ZERO);
@@ -1861,10 +1861,10 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testNullArgs() {
+    void testNullArgs() {
         final JexlEngine jexl =  new JexlBuilder().arithmetic(new JexlArithmetic(true) {
             @Override public boolean isStrict(final JexlOperator op) {
-                return JexlOperator.ADD == op? false: super.isStrict(op);
+                return JexlOperator.ADD != op && super.isStrict(op);
             }
         }).create();
         final JexlScript script = jexl.createScript("'1.2' + x ", "x");
@@ -1873,13 +1873,13 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testNullOperand() throws Exception {
+    void testNullOperand() throws Exception {
         asserter.setVariable("right", null);
         asserter.failExpression("~right", ".*null.*");
     }
 
     @Test
-    public void testNullOperands() throws Exception {
+    void testNullOperands() throws Exception {
         asserter.setVariable("left", null);
         asserter.setVariable("right", null);
         asserter.failExpression("left + right", ".*null.*");
@@ -1893,7 +1893,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testOperatorsEdges() {
+    void testOperatorsEdges() {
         assertNullOperand(() -> jexla.multiply(null, null));
         assertEquals(0, jexlb.multiply(null, null));
         assertNullOperand(() -> jexla.add(null, null));
@@ -1914,7 +1914,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testOption() {
+    void testOption() {
         final Map<String, Object> vars = new HashMap<>();
         final JexlEvalContext context = new JexlEvalContext(vars);
         final JexlOptions options = context.getEngineOptions();
@@ -1932,7 +1932,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testOverflows() throws Exception {
+    void testOverflows() throws Exception {
         asserter.assertExpression("1 + 2147483647", Long.valueOf("2147483648"));
         asserter.assertExpression("3 + " + (Long.MAX_VALUE - 2),  BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE));
         asserter.assertExpression("-2147483648 - 1", Long.valueOf("-2147483649"));
@@ -1945,7 +1945,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testPlusClass() {
+    void testPlusClass() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlContext jc = new MapContext();
         final Object ra = jexl.createExpression("463.0d + 0.1").evaluate(jc);
@@ -1957,7 +1957,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testPlusPlusPostfix() throws Exception {
+    void testPlusPlusPostfix() throws Exception {
         asserter.setVariable("aByte", Byte.valueOf((byte) 0));
         asserter.setVariable("aShort", Short.valueOf((short) 1));
         asserter.setVariable("anInteger", Integer.valueOf(2));
@@ -1990,7 +1990,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testPlusPlusPrefix() throws Exception {
+    void testPlusPlusPrefix() throws Exception {
         asserter.setVariable("aByte", Byte.valueOf((byte) 0));
         asserter.setVariable("aShort", Short.valueOf((short) 1));
         asserter.setVariable("anInteger", Integer.valueOf(2));
@@ -2014,7 +2014,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testRealCoercionEdges() throws Exception {
+    void testRealCoercionEdges() throws Exception {
         assertNullOperand(() -> jexla.toDouble(null));
         assertEquals(0.0d, jexlb.toDouble(null), EPSILON);
         assertEquals(32.0d, jexlb.toDouble((char) 32), EPSILON);
@@ -2045,7 +2045,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testRightNullOperand() throws Exception {
+    void testRightNullOperand() throws Exception {
         asserter.setVariable("left", Integer.valueOf(9));
         asserter.setVariable("right", null);
         asserter.failExpression("left + right", ".*null.*");
@@ -2063,7 +2063,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testRightNullOperand2() throws Exception {
+    void testRightNullOperand2() throws Exception {
         asserter.setVariable("left", Integer.valueOf(9));
         asserter.setVariable("y.right", null);
         asserter.failExpression("left + y.right", ".*null.*");
@@ -2084,7 +2084,7 @@ public class ArithmeticTest extends JexlTestCase {
      * test some simple mathematical calculations
      */
     @Test
-    public void testUnaryMinus() throws Exception {
+    void testUnaryMinus() throws Exception {
         asserter.setVariable("aByte", Byte.valueOf((byte) 1));
         asserter.setVariable("aShort", Short.valueOf((short) 2));
         asserter.setVariable("anInteger", Integer.valueOf(3));
@@ -2110,7 +2110,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testUnaryopsEdges() {
+    void testUnaryopsEdges() {
         assertArithmeticException(() -> jexla.positivize(date));
         assertNullOperand(() -> jexla.positivize(null));
         assertNull(jexlb.positivize(null));
@@ -2131,7 +2131,7 @@ public class ArithmeticTest extends JexlTestCase {
      * test some simple mathematical calculations
      */
     @Test
-    public void testUnaryPlus() throws Exception {
+    void testUnaryPlus() throws Exception {
         asserter.setVariable("aByte", Byte.valueOf((byte) 1));
         asserter.setVariable("aShort", Short.valueOf((short) 2));
         asserter.setVariable("anInteger", Integer.valueOf(3));
@@ -2157,7 +2157,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testUndefinedVar() throws Exception {
+    void testUndefinedVar() throws Exception {
         asserter.failExpression("objects[1].status", ".*variable 'objects' is undefined.*");
     }
 
@@ -2165,7 +2165,7 @@ public class ArithmeticTest extends JexlTestCase {
      * Inspired by JEXL-16{1,2}.
      */
     @Test
-    public void testXmlArithmetic() throws Exception {
+    void testXmlArithmetic() throws Exception {
         Document xml;
         Node x;
         Boolean empty;
diff --git a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
index 161a3660..58abf7e0 100644
--- a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
@@ -59,7 +59,7 @@ public class FeaturesTest extends JexlTestCase {
      * @param scripts the scripts
      * @throws Exception
      */
-    private void checkFeature(final JexlFeatures features, final String[] scripts) throws Exception {
+    private void checkFeature(final JexlFeatures features, final String[] scripts) {
         for (final String script : scripts) {
             final JexlScript ctl = JEXL.createScript(script);
             assertNotNull(ctl);
@@ -108,12 +108,12 @@ public class FeaturesTest extends JexlTestCase {
             "3 ge 2"
         };
         for(final String src : cmpNameScripts) {
-            assertThrows(JexlException.Ambiguous.class, () -> jexl.createScript(src));
+            assertThrows(JexlException.Ambiguous.class, () -> jexl.createScript(src), src);
         }
     }
 
     @Test
-    void testAnnotations() throws Exception {
+    void testAnnotations() { 
         final JexlFeatures f = new JexlFeatures().annotation(false);
         final String[] scripts = {
             "@synchronized(2) { return 42; }",
@@ -123,7 +123,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testArrayRefs() throws Exception {
+    void testArrayRefs() { 
         final JexlFeatures f = new JexlFeatures().arrayReferenceExpr(false);
 
         final String[] scripts = {
@@ -145,7 +145,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testConstCapture() throws Exception {
+    void testConstCapture() { 
         final JexlFeatures f = new JexlFeatures().constCapture(true);
         final String[] scripts = {
             "let x = 0; const f = y -> x += y; f(42)",
@@ -202,7 +202,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testMethodCalls() throws Exception {
+    void testMethodCalls() { 
         final JexlFeatures f = new JexlFeatures().methodCall(false);
         final String[] scripts = {
             "x.y(z)",
@@ -221,7 +221,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testMixedFeatures() throws Exception {
+    void testMixedFeatures() { 
         // no new, no local, no lambda, no loops, no-side effects
         final JexlFeatures f = new JexlFeatures()
                 .newInstance(false)
@@ -241,7 +241,7 @@ public class FeaturesTest extends JexlTestCase {
         checkFeature(f, scripts);
     }
     @Test
-    void testNoComparatorNames() throws Exception {
+    void testNoComparatorNames() { 
         final JexlFeatures f = new JexlFeatures().comparatorNames(false);
         final String[] scripts = {
             "1 eq 1",
@@ -255,7 +255,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testNoLambda() throws Exception {
+    void testNoLambda() { 
         final JexlFeatures f = new JexlFeatures().lambda(false);
         final String[] scripts = {
             "var x  = ()->{ return 0 };",
@@ -269,7 +269,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testNoLocals() throws Exception {
+    void testNoLocals() { 
         final JexlFeatures f = new JexlFeatures().localVar(false);
         final String[] scripts = {
             "var x = 0;",
@@ -279,7 +279,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testNoLoop() throws Exception {
+    void testNoLoop() { 
         final JexlFeatures f = new JexlFeatures().loops(false);
         final String[] scripts = {
             "while(true);",
@@ -288,7 +288,7 @@ public class FeaturesTest extends JexlTestCase {
         checkFeature(f, scripts);
     }
     @Test
-    void testNoNew() throws Exception {
+    void testNoNew() { 
         final JexlFeatures f = new JexlFeatures().newInstance(false);
         final String[] scripts = {
             "return new(clazz);",
@@ -298,7 +298,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testNoScript() throws Exception {
+    void testNoScript() { 
         final JexlFeatures f = new JexlFeatures().script(false);
         assertTrue(f.supportsExpression());
         final String[] scripts = {
@@ -311,7 +311,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testNoSideEffects() throws Exception {
+    void testNoSideEffects() { 
         final JexlFeatures f = new JexlFeatures().sideEffect(false);
         final String[] scripts = {
             "x = 1",
@@ -335,7 +335,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testNoSideEffectsGlobal() throws Exception {
+    void testNoSideEffectsGlobal() { 
         final JexlFeatures f = new JexlFeatures().sideEffectGlobal(false);
         final String[] scripts = {
             "x = 1",
@@ -366,7 +366,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testPragma() throws Exception {
+    void testPragma() { 
         final JexlFeatures f = new JexlFeatures().pragma(false);
         final String[] scripts = {
             "#pragma foo 42",
@@ -376,7 +376,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testPragmaAnywhere() throws Exception {
+    void testPragmaAnywhere() { 
         final JexlFeatures f = new JexlFeatures().pragmaAnywhere(false);
         final String[] scripts = {
                 "var x = 3;\n#pragma foo 42",
@@ -385,7 +385,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testReservedVars() throws Exception {
+    void testReservedVars() { 
         final JexlFeatures f = new JexlFeatures().reservedNames(Arrays.asList("foo", "bar"));
         final String[] scripts = {
             "var foo = 0;",
@@ -402,7 +402,7 @@ public class FeaturesTest extends JexlTestCase {
     }
 
     @Test
-    void testStructuredLiterals() throws Exception {
+    void testStructuredLiterals() { 
         final JexlFeatures f = new JexlFeatures().structuredLiteral(false);
         final String[] scripts = {
             "{1, 2, 3}",
diff --git a/src/test/java/org/apache/commons/jexl3/Issues200Test.java b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
index 20c84077..75ee6fc5 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues200Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
@@ -219,7 +219,7 @@ public class Issues200Test extends JexlTestCase {
 
         public Object call(final Collection<?> c, final Number n) {
             if (c instanceof List) {
-                return ((List) c).get(n.intValue());
+                return ((List<?>) c).get(n.intValue());
             }
             return nth(c, n.intValue());
         }
@@ -258,13 +258,13 @@ public class Issues200Test extends JexlTestCase {
 
     @BeforeEach
     @Override
-    public void setUp() throws Exception {
+    public void setUp() {
         // ensure jul logging is only error to avoid warning in silent mode
         java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
     }
 
     @Test
-    public void test200() throws Exception {
+    void test200() {
         final JexlContext jc = new MapContext();
         final Map<String, Object> funcs = new HashMap<>();
         final Eval eval = new Eval();
@@ -278,7 +278,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test200b() throws Exception {
+    void test200b() {
         final JexlContext jc = new MapContext();
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlScript e = jexl.createScript("var x = 0; var f = (y)->{ x = y; }; f(42); x");
@@ -287,7 +287,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test209a() throws Exception {
+    void test209a() {
         final JexlContext jc = new MapContext();
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlScript e = jexl.createScript("var x = new('java.util.HashMap'); x.a = ()->{return 1}; x['a']()");
@@ -296,7 +296,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test209b() throws Exception {
+    void test209b() {
         final JexlContext jc = new MapContext();
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlScript e = jexl.createScript("var x = new('java.util.HashMap'); x['a'] = ()->{return 1}; x.a()");
@@ -305,7 +305,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test210() throws Exception {
+    void test210() {
         final JexlContext jc = new MapContext();
         jc.set("v210", new T210());
         final JexlEngine jexl = new JexlBuilder().strict(false).silent(false).create();
@@ -316,7 +316,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test217() throws Exception {
+    void test217() {
         final JexlEvalContext jc = new JexlEvalContext();
         final JexlOptions options = jc.getEngineOptions();
         jc.set("foo", new int[]{0, 1, 2, 42});
@@ -341,7 +341,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test221() throws Exception {
+    void test221() {
         final JexlEvalContext jc = new JexlEvalContext();
         final Map<String, Integer> map = new HashMap<>();
         map.put("one", 1);
@@ -358,7 +358,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test224() throws Exception {
+    void test224() {
         final List<String> a0 = Arrays.asList("one", "two");
         final Set<String> a1 = new TreeSet<>(a0);
         final JexlContext jc = new MapContext();
@@ -382,7 +382,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test225() throws Exception {
+    void test225() {
         final Context225 df = new Context225();
         final JexlEngine jexl = new JexlBuilder().create();
 
@@ -393,7 +393,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test230() throws Exception {
+    void test230() {
         final JexlEngine jexl = new JexlBuilder().cache(4).create();
         final JexlContext ctxt = new MapContext();
         final int[] foo = {42};
@@ -412,7 +412,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test241() throws Exception {
+    void test241() {
         ExecutorService pool;
         final JexlScript script = new JexlBuilder().create().createScript("`${item}`");
 
@@ -429,7 +429,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test242() throws Exception {
+    void test242() {
         final Double a = -40.05d;
         final Double b = -8.01d;
         final Double c = a + b;
@@ -444,13 +444,13 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test243a() throws Exception {
+    void test243a() {
         final JexlEngine jexl = new JexlBuilder().cache(32).create();
         final JexlScript script = jexl.createScript("while(true);");
         assertThrows(JexlException.class, () -> jexl.createExpression("while(true);"), "expr do not allow 'while' statement");
     }
     @Test
-    public void test245() throws Exception {
+    void test245() {
         final MapContext ctx = new MapContext();
         final Foo245 foo245 = new Foo245();
         ctx.set("foo", foo245);
@@ -473,7 +473,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test256() throws Exception {
+    void test256() {
         final MapContext ctx = new MapContext() {
             @Override
             public Object get(final String name) {
@@ -507,7 +507,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test265() throws Exception {
+    void test265() {
         final JexlEngine jexl = new JexlBuilder().cache(4).create();
         final JexlContext ctxt = new MapContext();
         ctxt.set("x", 42);
@@ -526,7 +526,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test266() throws Exception {
+    void test266() {
         Object result;
         JexlScript script;
         final JexlEngine jexl = new JexlBuilder().arithmetic(new Arithmetic266(true)).create();
@@ -554,7 +554,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test267() throws Exception {
+    void test267() {
         Object result;
         JexlScript script;
         final JexlEngine jexl = new JexlBuilder().create();
@@ -574,7 +574,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test274() throws Exception {
+    void test274() {
         JexlEngine jexl = new JexlBuilder().strict(true).safe(true).stackOverflow(5).create();
         final JexlContext ctxt = new MapContext();
         final JexlScript script = jexl.createScript("var f = (x)->{ x > 1? x * f(x - 1) : x }; f(a)", "a");
@@ -589,7 +589,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test275a() throws Exception {
+    void test275a() {
         final JexlContext ctxt = new MapContext();
         ctxt.set("out", System.out);
         final JexlEngine jexl = new JexlBuilder().strict(true).safe(true).create();
@@ -599,7 +599,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test275b() throws Exception {
+    void test275b() {
         final JexlContext ctxt = new MapContext();
         // ctxt.set("out", System.out);
         final JexlEngine jexl = new JexlBuilder().strict(true).safe(true).create();
@@ -609,7 +609,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test275c() throws Exception {
+    void test275c() {
         final JexlContext ctxt = new MapContext();
         // ctxt.set("out", System.out);
         final JexlEngine jexl = new JexlBuilder().strict(true).safe(true).silent(true).create();
@@ -622,7 +622,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test275d() throws Exception {
+    void test275d() {
         final JexlContext ctxt = new MapContext();
         ctxt.set("out", System.out);
         final JexlEngine jexl = new JexlBuilder().strict(true).safe(true).create();
@@ -632,7 +632,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test278() throws Exception {
+    void test278() {
         // @formatter:off
         final String[] srcs = {
             "return union x143('arg',5,6) ",
@@ -653,7 +653,7 @@ public class Issues200Test extends JexlTestCase {
         JexlScript jc;
         for (int i = 0; i < srcs.length; ++i) {
             final String src = srcs[i];
-            final JexlException.Ambiguous xa = assertThrows(JexlException.Ambiguous.class, () -> jexl.createScript(src));
+            final JexlException.Ambiguous xa = assertThrows(JexlException.Ambiguous.class, () -> jexl.createScript(src), src);
             final String str = xa.toString();
             assertTrue(str.contains("143"));
             final String clean = xa.tryCleanSource(src);
@@ -665,7 +665,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test279() throws Exception {
+    void test279() {
         final Log logger = null; // LogFactory.getLog(Issues200Test.class);
         Object result;
         JexlScript script;
@@ -734,7 +734,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test279b() throws Exception {
+    void test279b() {
         Object result;
         JexlScript script;
         final JexlContext ctxt = new Context279();
@@ -749,7 +749,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test285() throws Exception {
+    void test285() {
         final List<String> out = new ArrayList<>(6);
         final JexlContext ctxt = new MapContext();
         ctxt.set("$out", out);
@@ -778,7 +778,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test285a() throws Exception {
+    void test285a() {
         final List<String> out = new ArrayList<>(6);
         final JexlContext ctxt = new MapContext();
         ctxt.set("$out", out);
@@ -796,7 +796,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test285b() throws Exception {
+    void test285b() {
         final List<String> out = new ArrayList<>(6);
         final JexlContext ctxt = new MapContext();
         ctxt.set("$out", out);
@@ -814,14 +814,14 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test286() {
+    void test286() {
         final String s286 = "var x = 0; for(x : 1..2){}; return x";
         final JexlEngine jexl = new JexlBuilder().strict(true).create();
         assertEquals(2, jexl.createScript(s286).execute(null));
     }
 
     @Test
-    public void test287() {
+    void test287() {
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
         final JexlEngine jexl = new JexlBuilder().strict(true).create();
@@ -860,7 +860,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test289() {
+    void test289() {
         final JexlContext ctxt = new MapContext();
         final JexlEngine jexl = new JexlBuilder().strict(true).create();
         String src;
@@ -873,7 +873,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test290a() throws Exception {
+    void test290a() {
         Object result;
         JexlScript script;
         final String[] srcs = {
@@ -904,7 +904,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test290b() throws Exception {
+    void test290b() {
         Object result;
         JexlScript script;
         final String[] srcs = {
@@ -920,7 +920,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test291() throws Exception {
+    void test291() {
         final String str = "{1:'one'}[1]";
         final JexlContext ctxt = new MapContext();
         final JexlEngine jexl = new JexlBuilder().create();
@@ -937,7 +937,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void test298() throws Exception {
+    void test298() {
         final Cls298 c298 = new Cls298();
         final JexlContext ctxt = new MapContext();
         final JexlEngine jexl = new JexlBuilder().create();
@@ -959,7 +959,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void testTemplate6565a() throws Exception {
+    void testTemplate6565a() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JxltEngine jexlt = jexl.createJxltEngine();
         final String source =
@@ -982,7 +982,7 @@ public class Issues200Test extends JexlTestCase {
     }
 
     @Test
-    public void testTemplate6565b() throws Exception {
+    void testTemplate6565b() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JxltEngine jexlt = jexl.createJxltEngine();
         final String source =
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index 8a969b2d..f81bda42 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -614,6 +614,45 @@ public class Issues400Test {
         }
     }
 
+    @Test
+    void test437a() {
+        JexlEngine jexl = new JexlBuilder().create();
+        final String src = "let values = [...]\n"
+                + "function append(const value) {\n"
+                + "  values.add(value)\n"
+                + "}\n"
+                + "\n"
+                + "append(1)\n"
+                + "append(2)\n"
+                + "return values ";
+        final JexlScript script = jexl.createScript(src);
+        assertNotNull(script);
+        final Object result = script.execute(null);
+        assertInstanceOf(List.class, result);
+        List<?> values = (List<?>) result;
+        assertEquals(2, values.size());
+    }
+
+    @Test
+    void test437b() {
+        JexlFeatures features = JexlFeatures.createDefault().ambiguousStatement(true);
+        assertTrue(features.supportsAmbiguousStatement());
+        JexlEngine jexl = new JexlBuilder().features(features).create();
+        final String src = "let values = [...]"
+                + "function append(const value) {"
+                + "  values.add(value)"
+                + "}"
+                + "append(1)"
+                + "append(2)"
+                + "return values ";
+        final JexlScript script = jexl.createScript(src);
+        assertNotNull(script);
+        final Object result = script.execute(null);
+        assertInstanceOf(List.class, result);
+        List<?> values = (List<?>) result;
+        assertEquals(2, values.size());
+    }
+
     /** The set of characters that may be followed by a '='.*/
     static final char[] EQ_FRIEND;
     static {
@@ -729,11 +768,10 @@ public class Issues400Test {
 
     @Test
     void test440a() {
-        JexlFeatures f = JexlFeatures.createDefault();
-        f.setAmbiguousStatement(true);
+        JexlFeatures f = JexlFeatures.createDefault().ambiguousStatement(true);
         JexlEngine jexl = new JexlBuilder().features(f).safe(false).strict(true).create();
-        String src =
-"let y = switch (x) { case 10,11 -> 3 case 20, 21 -> 4\ndefault -> { let z = 4; z + x } } y";
+        String src = "let y = switch (x) { case 10,11 -> 3 case 20, 21 -> 4\n" +
+                "default -> { let z = 4; z + x } } y";
         JexlScript script = jexl.createScript(src, "x");
         assertNotNull(script);
         String dbgStr = script.getParsedText();
@@ -749,8 +787,7 @@ public class Issues400Test {
         Assertions.assertEquals(4, result);
         result = script.execute(null, 38);
         Assertions.assertEquals(42, result);
-        src =
-                "let y = switch (x) { case 10,11 -> break; case 20, 21 -> 4 } y";
+        src = "let y = switch (x) { case 10,11 -> break; case 20, 21 -> 4; } y";
         try {
             script = jexl.createScript(src, "x");
             fail("should not be able to create script with break in switch");
@@ -780,4 +817,25 @@ public class Issues400Test {
         result = script.execute(null, 38);
         Assertions.assertEquals(42, result);
     }
+
+    public enum Scope440 {
+        UNDEFINED, UNDECLARED, GLOBAL, LOCAL, THIS, SUPER;
+    }
+
+    @Test
+    void test440c() {
+        JexlEngine jexl = new JexlBuilder().loader(getClass().getClassLoader()).imports(this.getClass().getName()).create();
+        final String src = "let s = switch (x) { case Scope440.UNDEFINED -> 'undefined'; case Scope440.THIS -> 'this'; default -> 'OTHER'; } s";
+        final JexlScript script = jexl.createScript(src, "x");
+        assertNotNull(script);
+        String dbgStr = script.getParsedText();
+        assertNotNull(dbgStr);
+
+        Object result = script.execute(null, Scope440.UNDEFINED);
+        Assertions.assertEquals("undefined", result);
+        result = script.execute(null, Scope440.THIS);
+        Assertions.assertEquals("this", result);
+        result = script.execute(null, 21);
+        Assertions.assertEquals("OTHER", result);
+    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTest.java b/src/test/java/org/apache/commons/jexl3/JexlTest.java
index f3f16d43..baa66380 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTest.java
@@ -92,14 +92,14 @@ public final class JexlTest extends JexlTestCase {
      * Asserts that the given expression returns the given value when applied to the
      * given context
      */
-    protected void assertExpression(final JexlContext jc, final String expression, final Object expected) throws Exception {
+    protected void assertExpression(final JexlContext jc, final String expression, final Object expected) {
         final JexlExpression e = JEXL.createExpression(expression);
         final Object actual = e.evaluate(jc);
         assertEquals(expected, actual, expression);
     }
 
     @Test
-    public void testAntPropertiesWithMethods() throws Exception {
+    public void testAntPropertiesWithMethods() {
         final JexlContext jc = new MapContext();
         final String value = "Stinky Cheese";
         jc.set("maven.bob.food", value);
@@ -116,7 +116,7 @@ public final class JexlTest extends JexlTestCase {
 
     @SuppressWarnings("boxing")
     @Test
-    public void testArray() throws Exception {
+    public void testArray() {
         final int[] array = {100, 101, 102};
         final JexlEngine jexl = JEXL;
         final JexlContext jc = new MapContext();
@@ -139,7 +139,7 @@ public final class JexlTest extends JexlTestCase {
      * @throws Exception
      */
     @Test
-    public void testAssignment() throws Exception {
+    public void testAssignment() {
         final JexlContext jc = new MapContext();
         jc.set("aString", "Hello");
         final Foo foo = new Foo();
@@ -161,7 +161,7 @@ public final class JexlTest extends JexlTestCase {
      * @throws Exception on errors
      */
     @Test
-    public void testBadParse() throws Exception {
+    public void testBadParse() {
         assertThrows(JexlException.class, () -> assertExpression(new MapContext(), "empty()", null));
     }
 
@@ -169,7 +169,7 @@ public final class JexlTest extends JexlTestCase {
      * test some blank strings
      */
     @Test
-    public void testBlankStrings() throws Exception {
+    public void testBlankStrings() {
         final JexlContext jc = new MapContext();
         jc.set("bar", "");
 
@@ -180,7 +180,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    public void testBoolean() throws Exception {
+    public void testBoolean() {
         final JexlContext jc = new MapContext();
         jc.set("foo", new Foo());
         jc.set("a", Boolean.TRUE);
@@ -199,7 +199,7 @@ public final class JexlTest extends JexlTestCase {
      * @throws Exception if there are errors
      */
     @Test
-    public void testBooleanShortCircuitAnd() throws Exception {
+    public void testBooleanShortCircuitAnd() {
         // handle false for the left arg of 'and'
         Foo tester = new Foo();
         final JexlContext jc = new MapContext();
@@ -221,7 +221,7 @@ public final class JexlTest extends JexlTestCase {
      * @throws Exception if there are errors
      */
     @Test
-    public void testBooleanShortCircuitOr() throws Exception {
+    public void testBooleanShortCircuitOr() {
         // handle false for the left arg of 'or'
         Foo tester = new Foo();
         final JexlContext jc = new MapContext();
@@ -242,7 +242,7 @@ public final class JexlTest extends JexlTestCase {
      * test some simple mathematical calculations
      */
     @Test
-    public void testCalculations() throws Exception {
+    public void testCalculations() {
         final JexlEvalContext jc = new JexlEvalContext();
         final JexlOptions options = jc.getEngineOptions();
         options.setStrict(false);
@@ -270,7 +270,7 @@ public final class JexlTest extends JexlTestCase {
      * Attempts to recreate bug https://jira.werken.com/ViewIssue.jspa?key=JELLY-8
      */
     @Test
-    public void testCharAtBug() throws Exception {
+    public void testCharAtBug() {
         final JexlEvalContext jc = new JexlEvalContext();
         final JexlOptions options = jc.getEngineOptions();
         options.setSilent(true);
@@ -284,7 +284,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoercionWithComparisionOperators() throws Exception {
+    public void testCoercionWithComparisionOperators() {
         final JexlContext jc = new MapContext();
 
         assertExpression(jc, "'2' > 1", Boolean.TRUE);
@@ -307,7 +307,7 @@ public final class JexlTest extends JexlTestCase {
      * @throws Exception
      */
     @Test
-    public void testComment() throws Exception {
+    public void testComment() {
         assertExpression(new MapContext(), "## double or nothing\n 1 + 1", Integer.valueOf("2"));
     }
 
@@ -315,7 +315,7 @@ public final class JexlTest extends JexlTestCase {
      * test some simple conditions
      */
     @Test
-    public void testComparisons() throws Exception {
+    public void testComparisons() {
         final JexlContext jc = new MapContext();
         jc.set("foo", "the quick and lazy fox");
 
@@ -328,7 +328,7 @@ public final class JexlTest extends JexlTestCase {
      * test some simple conditions
      */
     @Test
-    public void testConditions() throws Exception {
+    public void testConditions() {
         final JexlEvalContext jc = new JexlEvalContext();
         final JexlOptions options = jc.getEngineOptions();
         jc.set("foo", Integer.valueOf(2));
@@ -375,7 +375,7 @@ public final class JexlTest extends JexlTestCase {
 
     @SuppressWarnings("boxing")
     @Test
-    public void testDuck() throws Exception {
+    public void testDuck() {
         final JexlEngine jexl = JEXL;
         final JexlContext jc = new MapContext();
         jc.set("duck", new Duck());
@@ -402,7 +402,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    public void testEmpty() throws Exception {
+    public void testEmpty() {
         final JexlEvalContext jc = new JexlEvalContext();
         final JexlOptions options = jc.getEngineOptions();
         options.setStrict(false);
@@ -427,7 +427,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    public void testEmptyDottedVariableName() throws Exception {
+    public void testEmptyDottedVariableName() {
         final JexlContext jc = new MapContext();
 
         jc.set("this.is.a.test", "");
@@ -436,7 +436,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    public void testEmptySubListOfMap() throws Exception {
+    public void testEmptySubListOfMap() {
         final JexlContext jc = new MapContext();
         final Map<String, ArrayList<?>> m = new HashMap<>();
         m.put("aList", new ArrayList<>());
@@ -447,7 +447,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    public void testExpression() throws Exception {
+    public void testExpression() {
         final JexlContext jc = new MapContext();
         jc.set("foo", new Foo());
         jc.set("a", Boolean.TRUE);
@@ -510,7 +510,7 @@ public final class JexlTest extends JexlTestCase {
      * test the use of an int based property
      */
     @Test
-    public void testIntProperty() throws Exception {
+    public void testIntProperty() {
         final Foo foo = new Foo();
 
         // lets check the square function first.
@@ -529,7 +529,7 @@ public final class JexlTest extends JexlTestCase {
      * test some blank strings
      */
     @Test
-    public void testLogicExpressions() throws Exception {
+    public void testLogicExpressions() {
         final JexlContext jc = new MapContext();
         jc.set("foo", "abc");
         jc.set("bar", "def");
@@ -549,7 +549,7 @@ public final class JexlTest extends JexlTestCase {
      * test the use of dot notation to lookup map entries
      */
     @Test
-    public void testMapDot() throws Exception {
+    public void testMapDot() {
         final Map<String, String> foo = new HashMap<>();
         foo.put("bar", "123");
 
@@ -563,7 +563,7 @@ public final class JexlTest extends JexlTestCase {
      * test the -1 comparison bug
      */
     @Test
-    public void testNegativeIntComparison() throws Exception {
+    public void testNegativeIntComparison() {
         final JexlContext jc = new MapContext();
         final Foo foo = new Foo();
         jc.set("foo", foo);
@@ -577,7 +577,7 @@ public final class JexlTest extends JexlTestCase {
      * test the new function e.g constructor invocation
      */
     @Test
-    public void testNew() throws Exception {
+    public void testNew() {
         final JexlContext jc = new MapContext();
         jc.set("double", Double.class);
         jc.set("foo", "org.apache.commons.jexl3.Foo");
@@ -595,8 +595,8 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    public void testNewImports() throws Exception {
-        final JexlEngine jexl = new JexlBuilder().imports("not a package", "java.lang", "java.util").create();
+    public void testNewImports() {
+        final JexlEngine jexl = new JexlBuilder().imports("java.lang", "java.util").create();
         JexlExpression expr;
         Object result;
         expr = jexl.createExpression("new LinkedList([1,2,3,...])");
@@ -608,7 +608,7 @@ public final class JexlTest extends JexlTestCase {
      * test some simple conditions
      */
     @Test
-    public void testNotConditions() throws Exception {
+    public void testNotConditions() {
         final JexlContext jc = new MapContext();
 
         final Foo foo = new Foo();
@@ -640,7 +640,7 @@ public final class JexlTest extends JexlTestCase {
      * test some simple conditions
      */
     @Test
-    public void testNotConditionsWithDots() throws Exception {
+    public void testNotConditionsWithDots() {
         final JexlContext jc = new MapContext();
 
         jc.set("x.a", Boolean.TRUE);
@@ -655,7 +655,7 @@ public final class JexlTest extends JexlTestCase {
      * test some null conditions
      */
     @Test
-    public void testNull() throws Exception {
+    public void testNull() {
         final JexlEvalContext jc = new JexlEvalContext();
         final JexlOptions options = jc.getEngineOptions();
         options.setStrict(false);
@@ -674,7 +674,7 @@ public final class JexlTest extends JexlTestCase {
      * test a simple property expression
      */
     @Test
-    public void testProperty() throws Exception {
+    public void testProperty() {
         /*
          *  tests a simple property expression
          */
@@ -690,7 +690,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    public void testSize() throws Exception {
+    public void testSize() {
         final JexlEvalContext jc = new JexlEvalContext();
         final JexlOptions options = jc.getEngineOptions();
         options.setStrict(false);
@@ -744,7 +744,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    public void testSizeAsProperty() throws Exception {
+    public void testSizeAsProperty() {
         final JexlContext jc = new MapContext();
         final Map<String, Object> map = new HashMap<>();
         map.put("size", "cheese");
@@ -766,7 +766,7 @@ public final class JexlTest extends JexlTestCase {
      * @throws Exception
      */
     @Test
-    public void testStringConcatenation() throws Exception {
+    public void testStringConcatenation() {
         final JexlContext jc = new MapContext();
         jc.set("first", "Hello");
         jc.set("second", "World");
@@ -774,7 +774,7 @@ public final class JexlTest extends JexlTestCase {
     }
 
     @Test
-    public void testStringLit() throws Exception {
+    public void testStringLit() {
         /*
          *  tests a simple property expression
          */
@@ -787,7 +787,7 @@ public final class JexlTest extends JexlTestCase {
      * Tests string literals
      */
     @Test
-    public void testStringLiterals() throws Exception {
+    public void testStringLiterals() {
         final JexlContext jc = new MapContext();
         jc.set("foo", "bar");
 
@@ -799,21 +799,21 @@ public final class JexlTest extends JexlTestCase {
      * test quoting in strings
      */
     @Test
-    public void testStringQuoting() throws Exception {
+    public void testStringQuoting() {
         final JexlContext jc = new MapContext();
         assertExpression(jc, "'\"Hello\"'", "\"Hello\"");
         assertExpression(jc, "\"I'm testing\"", "I'm testing");
     }
 
     @Test
-    public void testToString() throws Exception {
+    public void testToString() {
         final String code = "abcd";
         final JexlExpression expr = JEXL.createExpression(code);
         assertEquals(code, expr.toString(), "Bad expression value");
     }
 
     @Test
-    public void testUnicodeSupport() throws Exception {
+    public void testUnicodeSupport() {
         final JexlContext jc = new MapContext();
         assertExpression(jc, "'x' == '\\u0032?ytkownik'", Boolean.FALSE);
         assertExpression(jc, "'c:\\some\\windows\\path'", "c:\\some\\windows\\path");
@@ -826,7 +826,7 @@ public final class JexlTest extends JexlTestCase {
      * test variables with underscore names
      */
     @Test
-    public void testVariableNames() throws Exception {
+    public void testVariableNames() {
         final JexlContext jc = new MapContext();
         jc.set("foo_bar", "123");
 
diff --git a/src/test/java/org/apache/commons/jexl3/internal/FqcnTest.java b/src/test/java/org/apache/commons/jexl3/internal/FqcnTest.java
new file mode 100644
index 00000000..52fe7e85
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/internal/FqcnTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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
+ *
+ *      https://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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.jexl3.JexlBuilder;
+import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlTestCase;
+import org.apache.commons.jexl3.internal.introspection.Uberspect;
+import org.apache.commons.jexl3.introspection.JexlPermissions;
+import org.apache.commons.jexl3.introspection.JexlUberspect;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+
+public class FqcnTest extends JexlTestCase {
+  public static final int FORTYTWO = 42;
+  public FqcnTest() {
+    super("FqcnTest");
+  }
+
+  @BeforeEach
+  @Override
+  public void setUp() throws Exception {
+    // ensure jul logging is only error
+    java.util.logging.Logger.getLogger(org.apache.commons.jexl3.JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
+  }
+
+  @AfterEach
+  @Override
+  public void tearDown() throws Exception {
+    super.tearDown();
+  }
+
+  public enum FqcnScope {
+    UNDEFINED, UNDECLARED, GLOBAL, LOCAL, THIS, SUPER;
+  }
+
+  Object getConstant(List<String> imports, String name) throws Exception {
+    JexlUberspect uber = new Uberspect(null, null, JexlPermissions.UNRESTRICTED);
+    final FqcnResolver resolver = new FqcnResolver(uber, imports);
+    return resolver.resolveConstant(name);
+  }
+
+  @Test
+  void testBadImport0() {
+    List<String> imports = Collections.singletonList("org.apache.commons.jexl4242");
+    assertThrows(JexlException.class, () -> new JexlBuilder().imports(imports).create());
+  }
+
+  @Test
+  void testBadImport1() {
+    // permissions will not allow this import
+    List<String> imports = Collections.singletonList("org.apache.commons.jexl.JexlEngine");
+    assertThrows(JexlException.class, () -> new JexlBuilder().imports(imports).create());
+  }
+
+  @Test
+  public void testFqcn() throws Exception {
+    List<String> imports = Arrays.asList("org.apache.commons.jexl3.internal.FqcnTest", "org.apache.commons.jexl3.internal", "java.lang");
+    Object c = getConstant(imports, "FqcnScope.UNDEFINED");
+    assertNotNull(c);
+    assertEquals(FqcnScope.UNDEFINED, c);
+    c = getConstant(imports, "FqcnScope.SUPER");
+    assertEquals(FqcnScope.SUPER, c);
+    c = getConstant(imports, "FqcnScope.SUPER");
+    assertEquals(FqcnScope.SUPER, c);
+    c = getConstant(imports, "FqcnTest.FORTYTWO");
+    assertEquals(42, c);
+  }
+}
diff --git a/src/test/java/org/apache/commons/jexl3/internal/RangeTest.java b/src/test/java/org/apache/commons/jexl3/internal/RangeTest.java
index 627e2a43..0f3b1fa0 100644
--- a/src/test/java/org/apache/commons/jexl3/internal/RangeTest.java
+++ b/src/test/java/org/apache/commons/jexl3/internal/RangeTest.java
@@ -40,10 +40,10 @@ import org.junit.jupiter.api.Test;
 public class RangeTest extends JexlTestCase {
 
     public RangeTest() {
-        super("InternalTest");
+        super("RangeTest");
     }
 
-    private void checkIteration(final IntegerRange ir, final int first, final int last) throws Exception {
+    private void checkIteration(final IntegerRange ir, final int first, final int last) {
         final Iterator<Integer> ii = ir.iterator();
         if (ii.hasNext()) {
             int l = ii.next();
@@ -57,7 +57,7 @@ public class RangeTest extends JexlTestCase {
         }
     }
 
-    private void checkIteration(final LongRange lr, final long first, final long last) throws Exception {
+    private void checkIteration(final LongRange lr, final long first, final long last) {
         final Iterator<Long> ii = lr.iterator();
         if (ii.hasNext()) {
             long l = ii.next();
@@ -85,7 +85,7 @@ public class RangeTest extends JexlTestCase {
     }
 
     @Test
-    public void testAscIterator() {
+    void testAscIterator() {
         final Iterator<Integer> ii = new AscIntegerIterator(3, 5);
         Integer i = 3;
         while (ii.hasNext()) {
@@ -97,7 +97,7 @@ public class RangeTest extends JexlTestCase {
     }
 
     @Test
-    public void testAscLongIterator() {
+    void testAscLongIterator() {
         final Iterator<Long> ii = new AscLongIterator(3L, 5L);
         Long i = 3L;
         while (ii.hasNext()) {
@@ -108,13 +108,13 @@ public class RangeTest extends JexlTestCase {
         assertThrows(UnsupportedOperationException.class, ii::remove, "remove not implemented");
     }
 
-    @Test public void testMisc() {
+    @Test void testMisc() {
         assertEquals("?", Scope.UNDEFINED.toString());
         assertEquals("??", Scope.UNDECLARED.toString());
     }
 
     @Test
-    public void testRanges() throws Exception {
+    void testRanges() throws Exception {
         final LongRange lr0 = LongRange.create(20,10);
         assertEquals(10L, lr0.getMin());
         assertEquals(20L, lr0.getMax());
@@ -198,7 +198,7 @@ public class RangeTest extends JexlTestCase {
     }
 
     @Test
-    public void testSource() {
+    void testSource() {
         final JexlFeatures features = JexlFeatures.createDefault();
         final Source src0 = new Source(features, "x -> -x");
         final Source src0b = new Source(features, "x -> -x");