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

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


The following commit(s) were added to refs/heads/JEXL-458 by this push:
     new e01612be JEXL-458: added +/- syntax for permission package parsing; - 
refined ability to specify and correctly apply class permissions; - added new 
JexlPermissions methods (allow(class, method), allow(class, field); - use 
specific test permissions in tests (sic);
e01612be is described below

commit e01612be0d4c441ad13b2f41e1c66393c8e0f4ad
Author: Henrib <[email protected]>
AuthorDate: Tue Apr 21 22:01:43 2026 +0200

    JEXL-458: added +/- syntax for permission package parsing;
    - refined ability to specify and correctly apply class permissions;
    - added new JexlPermissions methods (allow(class, method), allow(class, 
field);
    - use specific test permissions in tests (sic);
---
 RELEASE-NOTES.txt                                  |   3 +-
 pom.xml                                            |   2 +-
 src/changes/changes.xml                            |   1 +
 .../java/org/apache/commons/jexl3/JexlBuilder.java |   6 +-
 .../jexl3/introspection/JexlPermissions.java       | 196 +++++++-----
 .../commons/jexl3/scripting/JexlScriptEngine.java  |  20 +-
 .../jexl3/{JexlTest.java => ExpressionsTest.java}  |   6 +-
 src/test/java/org/apache/commons/jexl3/Foo.java    |   5 +-
 .../org/apache/commons/jexl3/Issues300Test.java    |  15 +-
 .../org/apache/commons/jexl3/Issues400Test.java    |  35 ++-
 .../java/org/apache/commons/jexl3/JXLTTest.java    |   4 +-
 .../org/apache/commons/jexl3/JexlTestCase.java     | 343 ++++++++-------------
 .../java/org/apache/commons/jexl3/SwitchTest.java  |   3 +-
 .../apache/commons/jexl3/examples/StreamTest.java  |  30 +-
 .../internal/introspection/DiscoveryTest.java      |  16 +-
 .../internal/introspection/PermissionsTest.java    |  15 +-
 .../scripting/JexlScriptEngineOptionalTest.java    |   8 +
 .../jexl3/scripting/JexlScriptEngineTest.java      |  29 +-
 18 files changed, 385 insertions(+), 352 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index bd0acd5b..f760c168 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -14,8 +14,9 @@ This is a feature and maintenance release. Java 8 or later is 
required.
 Bugs Fixed in 3.6.3:
 ====================
 
+o JEXL-458:  Improve permissions expressivity.
+o JEXL-457:  Reduce default exposure for RESTRICTED JexlPermissions.
 o JEXL-456:  Change in template parser behavior.
-o JEXL-457:  Reduce default exposure for RESTRICTED JexlPermissions
 
 
 Changes in 3.6.3:
diff --git a/pom.xml b/pom.xml
index 87a95b80..f0eb9261 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-parent</artifactId>
-        <version>97</version>
+        <version>98</version>
     </parent>
     <artifactId>commons-jexl3</artifactId>
     <version>3.6.3-SNAPSHOT</version>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ed6bf379..bbdb1200 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,6 +30,7 @@
         <release version="3.6.3" date="YYYY-MM-DD"
                  description="This is a feature and maintenance release. Java 
8 or later is required.">
             <!-- FIX -->
+            <action dev="henrib" type="fix" issue="JEXL-458" due-to="Daniil 
Averin">Improve permissions expressivity</action>
             <action dev="henrib" type="fix" issue="JEXL-457" due-to="Daniil 
Averin">Reduce default exposure for RESTRICTED JexlPermissions</action>
             <action dev="henrib" type="fix" issue="JEXL-456" due-to="Vincent 
Bussol">Change in template parser behavior.</action>
             <!-- ADD -->
diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java 
b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index fc826318..66b1d8df 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -87,7 +87,7 @@ public class JexlBuilder {
 
     /**
      * The set of default permissions used when creating a new builder.
-     * <p>Static but modifiable so these default permissions can be changed to 
a purposeful set.</p>
+     * <p>Static but modifiable, so these default permissions can be changed 
to a purposeful set.</p>
      * <p>In JEXL 3.3, these are {@link JexlPermissions#RESTRICTED}.</p>
      * <p>In JEXL 3.2, these were equivalent to {@link 
JexlPermissions#UNRESTRICTED}.</p>
      */
@@ -167,7 +167,7 @@ public class JexlBuilder {
      * and methods are visible to JEXL and usable in scripts using default 
implicit behaviors.
      * </p><p>
      * However, without mitigation, this change will likely break some scripts 
at runtime, especially those exposing
-     * your own class instances through arguments, contexts or namespaces.
+     * your own class instances through arguments, contexts, or namespaces.
      * The new default set of allowed packages and denied classes is described 
by {@link JexlPermissions#RESTRICTED}.
      * </p><p>
      * The recommended mitigation if your usage of JEXL is impacted is to 
first thoroughly review what should be
@@ -550,7 +550,7 @@ public class JexlBuilder {
     /**
      * Sets whether the engine is in lexical shading mode.
      *
-     * @param flag true means lexical shading is in effect, false implies no 
lexical shading
+     * @param flag true means lexical shading is in effect; false implies no 
lexical shading
      * @return this builder
      * @since 3.2
      */
diff --git 
a/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java 
b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
index 8a944cee..e5748476 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
@@ -44,7 +44,27 @@ import 
org.apache.commons.jexl3.internal.introspection.PermissionsParser;
  * implementations shall delegate calls to its methods for {@link 
org.apache.commons.jexl3.annotations.NoJexl} to be
  * processed.</p>
  * <p>A simple textual configuration can be used to create user-defined 
permissions using
- * {@link JexlPermissions#parse(String...)}.</p>
+ * {@link JexlPermissions#parse(String...)}. The permission syntax supports 
both positive (+) and negative (-)
+ * declarations:</p>
+ * <ul>
+ * <li><b>Negative restrictions ({@code -})</b>: By default or when prefixed 
with {@code -}, class restrictions
+ * explicitly <b>deny</b> access to the specified members (or the entire class 
if the block is empty).
+ * This is the default mode and works like {@link 
org.apache.commons.jexl3.annotations.NoJexl}.</li>
+ * <li><b>Positive restrictions ({@code +})</b>: When prefixed with {@code +}, 
class restrictions
+ * explicitly <b>allow only</b> the specified members (or the entire class if 
the block is empty), denying
+ * all others. This provides a whitelist approach where you must explicitly 
list what is permitted.</li>
+ * </ul>
+ * <p>For example:</p>
+ * <pre>
+ * // Deny specific methods in a class (negative restriction - default)
+ * java.lang { System { exit(); } }  // or -System { exit(); }
+ *
+ * // Allow only specific methods in a class (positive restriction)
+ * java.lang { +System { currentTimeMillis(); nanoTime(); } }
+ *
+ * // Allow entire class (positive restriction with empty block)
+ * java.io -{ +PrintWriter{} +Writer{} }
+ * </pre>
  *
  *<p>To instantiate a JEXL engine using permissions, one should use a {@link 
org.apache.commons.jexl3.JexlBuilder}
  * and call {@link 
org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another 
approach would
@@ -74,63 +94,79 @@ public interface JexlPermissions {
      * <p>Note that the newer positive restriction syntax is preferable as in:
      * <code>RESTRICTED.compose("java.lang { +Class {} }")</code>.</p>
      */
-     final class ClassPermissions extends JexlPermissions.Delegate {
+    final class ClassPermissions extends JexlPermissions.Delegate {
+      /**
+       * The set of explicitly allowed classes, overriding the delegate 
permissions.
+       */
+      private final Set<String> allowedClasses;
+
+      /**
+       * Creates permissions based on the RESTRICTED set but allowing an 
explicit set.
+       *
+       * @param allow the set of allowed classes
+       */
+      public ClassPermissions(final Class<?>... allow) {
+        this(JexlPermissions.RESTRICTED, allow);
+      }
 
-        /** The set of explicitly allowed classes, overriding the delegate 
permissions. */
-        private final Set<String> allowedClasses;
+      public ClassPermissions(final JexlPermissions permissions, final 
Class<?>... allow) {
+        this(permissions, 
Arrays.stream(Objects.requireNonNull(allow)).map(Class::getCanonicalName).collect(Collectors.toList()));
+      }
 
-        /**
-         * Creates permissions based on the RESTRICTED set but allowing an 
explicit set.
-         *
-         * @param allow the set of allowed classes
-         */
-        public ClassPermissions(final Class<?>... allow) {
-            this(JexlPermissions.RESTRICTED,
-                    Arrays.stream(Objects.requireNonNull(allow))
-                        .map(Class::getCanonicalName)
-                        .collect(Collectors.toList()));
-        }
+      /**
+       * Required for compose().
+       *
+       * @param delegate the base to delegate to
+       * @param allow    the list of class canonical names
+       */
+      public ClassPermissions(final JexlPermissions delegate, final 
Collection<String> allow) {
+        super(Objects.requireNonNull(delegate));
+        allowedClasses = new HashSet<>(Objects.requireNonNull(allow));
+      }
 
-        /**
-         * Required for compose().
-         *
-         * @param delegate the base to delegate to
-         * @param allow the list of class canonical names
-         */
-        public ClassPermissions(final JexlPermissions delegate, final 
Collection<String> allow) {
-            super(Objects.requireNonNull(delegate));
-            allowedClasses = new HashSet<>(Objects.requireNonNull(allow));
-        }
+      @Override
+      public boolean allow(final Constructor<?> constructor) {
+        return validate(constructor) &&
+            
(allowedClasses.contains(constructor.getDeclaringClass().getCanonicalName()) || 
super.allow(constructor));
+      }
 
-        @Override
-        public boolean allow(final Class<?> clazz) {
-            return validate(clazz) && (isClassAllowed(clazz) || 
super.allow(clazz));
-        }
+      @Override
+      public boolean allow(final Class<?> clazz) {
+        return validate(clazz) && 
allowedClasses.contains(clazz.getCanonicalName()) || super.allow(clazz);
+      }
 
-        @Override
-        public boolean allow(final Constructor<?> constructor) {
-            return validate(constructor) &&
-                
(allowedClasses.contains(constructor.getDeclaringClass().getCanonicalName()) || 
super.allow(constructor));
+      @Override
+      public boolean allow(final Class<?> clazz, final Field field) {
+        if (!validate(field)) {
+          return false;
         }
-
-        @Override
-        public boolean allow(final Class<?> clazz, final Method method) {
-            if (!validate(method)) {
-              return false;
-            }
-            if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
-              return false;
-            }
-            if (super.allow(clazz, method)) {
-              return true;
-            }
-            return isClassAllowed(clazz);
+        if (!clazz.isAssignableFrom(field.getDeclaringClass())) {
+          return false;
         }
+        if (super.allow(clazz, field)) {
+          return true;
+        }
+        return isClassAllowed(clazz);
+      }
 
-        @Override
-        public JexlPermissions compose(final String... src) {
-            return new ClassPermissions(base.compose(src), allowedClasses);
+      @Override
+      public boolean allow(final Class<?> clazz, final Method method) {
+        if (!validate(method)) {
+          return false;
+        }
+        if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
+          return false;
+        }
+        if (super.allow(clazz, method)) {
+          return true;
         }
+        return isClassAllowed(clazz);
+      }
+
+      @Override
+      public JexlPermissions compose(final String... src) {
+        return new ClassPermissions(base.compose(src), allowedClasses);
+      }
 
       private boolean isClassAllowed(final Class<?> aClass) {
         Class<?> clazz = aClass;
@@ -155,9 +191,10 @@ public interface JexlPermissions {
      * A base for permission delegation allowing functional refinement.
      * Overloads should call the appropriate validate() method early in their 
body.
      */
-     class Delegate implements JexlPermissions {
-
-         /** The permissions we delegate to. */
+    class Delegate implements JexlPermissions {
+        /**
+         * The permissions we delegate to.
+         */
         protected final JexlPermissions base;
 
         /**
@@ -181,12 +218,22 @@ public interface JexlPermissions {
 
         @Override
         public boolean allow(final Field field) {
-            return base.allow(field);
+            return validate(field) && allow(field.getDeclaringClass(), field);
+        }
+
+        @Override
+        public boolean allow(final Class<?> clazz, final Field field) {
+            return base.allow(clazz, field);
         }
 
         @Override
         public boolean allow(final Method method) {
-            return base.allow(method);
+            return validate(method) && allow(method.getDeclaringClass(), 
method);
+        }
+
+        @Override
+        public boolean allow(final Class<?> clazz, final Method method) {
+            return base.allow(clazz, method);
         }
 
         @Override
@@ -240,7 +287,7 @@ public interface JexlPermissions {
      * </ul>
      */
 
-    JexlPermissions DEFAULT = JexlPermissions.parse(
+    JexlPermissions RESTRICTED = JexlPermissions.parse(
         "# Default Uberspect Permissions",
         "java.math.*",
         "java.text.*",
@@ -254,15 +301,8 @@ public interface JexlPermissions {
             "}",
         "java.io -{ +PrintWriter{} +Writer{} +StringWriter{} +Reader{} 
+InputStream{} +OutputStream{} }",
         "java.nio +{}",
-        "java.nio.charset +{}"
-    );
-    JexlPermissions RESTRICTED = DEFAULT.compose(
-        // the following ones are mostly for tests, these should be reassessed 
(jxlt)
-        "org.apache.commons.jexl3.*",
-        "org.apache.commons.jexl3 +{ -JexlBuilder{} }",
-        "org.apache.commons.jexl3.introspection -{ JexlPermissions{} 
JexlPermissions$ClassPermissions{} }",
-        "org.apache.commons.jexl3.internal -{ Engine{} Engine32{} 
TemplateEngine{} }",
-        "org.apache.commons.jexl3.internal.introspection -{ Uberspect{} 
Introspector{} }"
+        "java.nio.charset +{}",
+        "org.apache.commons.jexl3 +{ -JexlBuilder{} }"
     );
 
     /**
@@ -290,6 +330,7 @@ public interface JexlPermissions {
      *  java.lang { Runtime {} System {} ProcessBuilder {} Class {} }
      *  org.apache.commons.jexl3 { JexlBuilder {} }
      *  </pre>
+     *  <p><b>Syntax Overview:</b></p>
      *  <ul>
      *  <li>Syntax for wildcards is the name of the package suffixed by {@code 
.*}.</li>
      *  <li>Syntax for restrictions is a list of package restrictions.</li>
@@ -301,26 +342,36 @@ public interface JexlPermissions {
      *  nested classes -, a field which is the Java field name suffixed with 
{@code ;}, a method composed of
      *  its Java name suffixed with {@code ();}. Constructor restrictions are 
specified like methods using the
      *  class name as method name.</li>
-     *  <li>By default or when prefixed with a {@code -}, a class restriction 
is explicitly denying the members
-     *  declared in its block (or the whole class)</li>
-     *  <li>When prefixed with a {@code +}, a class restriction is explicitly 
allowing the members
-     *  declared in its block (or the whole class)</li>
+     *  </ul>
+     *  <p><b>Negative ({@code -}) vs Positive ({@code +}) 
Restrictions:</b></p>
+     *  <ul>
+     *  <li><b>Negative restriction (default or {@code -} prefix)</b>: 
Explicitly <b>denies</b> access to the members
+     *  declared in its block. If the block is empty, the entire class is 
denied.
+     *  <br>Example: {@code java.lang { -System { exit(); } }} denies 
System.exit() but allows other System methods.
+     *  <br>Example: {@code java.lang { Runtime {} }} denies the entire 
Runtime class (empty block means deny all).</li>
+     *  <li><b>Positive restriction ({@code +} prefix)</b>: Explicitly 
<b>allows only</b> the members declared
+     *  in its block, denying all others not listed. If the block is empty, 
the entire class is allowed.
+     *  <br>Example: {@code java.lang { +System { currentTimeMillis(); } }} 
allows only System.currentTimeMillis(),
+     *  denying all other System methods.
+     *  <br>Example: {@code java.io -{ +PrintWriter{} +Writer{} }} in the 
context of a denied java.io package,
+     *  allows only PrintWriter and Writer classes entirely (empty blocks mean 
allow all members).</li>
      *  </ul>
      *  <p>
-     *  All overrides and overloads of a constructors or method are allowed or 
restricted at the same time,
+     *  All overrides and overloads of constructors or methods are allowed or 
restricted at the same time,
      *  the restriction being based on their names, not their whole signature. 
This differs from the @NoJexl annotation.
      *  </p>
+     *  <p><b>Complete Example:</b></p>
      *  <pre>
      *  # some wildcards
-     *  java.lang.*; # java.lang is pretty much a must have
+     *  java.util.* # java.util is pretty much a must-have
      *  my.allowed.package0.*
      *  another.allowed.package1.*
      *  # nojexl like restrictions
      *  my.package.internal {} # the whole package is hidden
      *  my.package {
-     *   +class4 { theMethod(); } # only theMethod can be called in class4
+     *   +class4 { theMethod(); } # POSITIVE: only theMethod can be called in 
class4, all others denied
      *   class0 {
-     *     class1 {} # the whole class1 is hidden
+     *     class1 {} # NEGATIVE (default): the whole class1 is hidden
      *     class2 {
      *         class2(); # class2 constructors cannot be invoked
      *         class3 {
@@ -407,6 +458,7 @@ public interface JexlPermissions {
      * <p>Since methods can be overridden and overloaded, this checks that 
this class explicitly allows
      * this method - superseding any superclass or interface specified 
permissions.</p>
      *
+     * @param clazz the class from which the method is accessed, used to check 
that the method is allowed for this class
      * @param method the method to check
      * @return true if JEXL is allowed to introspect, false otherwise
      * @since 3.7
diff --git 
a/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java 
b/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java
index 52c9db7e..f9bc2791 100644
--- a/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java
@@ -184,8 +184,10 @@ public class JexlScriptEngine extends AbstractScriptEngine 
implements Compilable
      */
     public class JexlScriptObject {
 
-        /** Default constructor */
-        public JexlScriptObject() {} // Keep Javadoc happy
+        /** Default constructor. */
+        public JexlScriptObject() {
+            // Keep Javadoc happy
+        }
 
         /**
          * Gives access to the underlying JEXL engine shared between all 
ScriptEngine instances.
@@ -296,9 +298,11 @@ public class JexlScriptEngine extends AbstractScriptEngine 
implements Compilable
                             .safe(false)
                             .logger(JexlScriptEngine.LOG)
                             .cache(JexlScriptEngine.CACHE_SIZE);
-                    if (PERMISSIONS != null) {
-                        builder.permissions(PERMISSIONS);
-                    }
+                    // ensure the script object class is always allowed for 
all permissions set
+                    JexlPermissions permissions = new 
JexlPermissions.ClassPermissions(
+                        PERMISSIONS == null ? JexlPermissions.RESTRICTED : 
PERMISSIONS,
+                        JexlScriptObject.class);
+                    builder.permissions(permissions);
                     engine = builder.create();
                     ENGINE = new SoftReference<>(engine);
                 }
@@ -442,15 +446,15 @@ public class JexlScriptEngine extends 
AbstractScriptEngine implements Compilable
     public Object eval(final Reader reader, final ScriptContext context) 
throws ScriptException {
         // This is mandated by JSR-223 (see SCR.5.5.2   Methods)
         Objects.requireNonNull(reader, "reader");
-        Objects.requireNonNull(context, "context");
+        Objects.requireNonNull(context, CONTEXT_KEY);
         return eval(readerToString(reader), context);
     }
 
     @Override
     public Object eval(final String script, final ScriptContext context) 
throws ScriptException {
         // This is mandated by JSR-223 (see SCR.5.5.2   Methods)
-        Objects.requireNonNull(script, "context");
-        Objects.requireNonNull(context, "context");
+        Objects.requireNonNull(script, CONTEXT_KEY);
+        Objects.requireNonNull(context, CONTEXT_KEY);
         // This is mandated by JSR-223 (end of section SCR.4.3.4.1.2 - 
JexlScript Execution)
         context.setAttribute(CONTEXT_KEY, context, ScriptContext.ENGINE_SCOPE);
         try {
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTest.java 
b/src/test/java/org/apache/commons/jexl3/ExpressionsTest.java
similarity index 99%
rename from src/test/java/org/apache/commons/jexl3/JexlTest.java
rename to src/test/java/org/apache/commons/jexl3/ExpressionsTest.java
index 35de2b5b..b90e784b 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ExpressionsTest.java
@@ -43,7 +43,7 @@ import org.junit.jupiter.api.Test;
  * Simple test cases.
  */
 @SuppressWarnings({"UnnecessaryBoxing", 
"AssertEqualsBetweenInconvertibleTypes"})
-public final class JexlTest extends JexlTestCase {
+public final class ExpressionsTest extends JexlTestCase {
     public static final class Duck {
         int user = 10;
 
@@ -79,8 +79,8 @@ public final class JexlTest extends JexlTestCase {
 
     static final String GET_METHOD_STRING = "GetMethod string";
 
-    public JexlTest() {
-        super("JexlTest",
+    public ExpressionsTest() {
+        super("ExpressionsTest",
                 new JexlBuilder()
                         .strict(false)
                         .imports(Arrays.asList("java.lang","java.math"))
diff --git a/src/test/java/org/apache/commons/jexl3/Foo.java 
b/src/test/java/org/apache/commons/jexl3/Foo.java
index f1b3ad24..a3d96cf6 100644
--- a/src/test/java/org/apache/commons/jexl3/Foo.java
+++ b/src/test/java/org/apache/commons/jexl3/Foo.java
@@ -34,11 +34,10 @@ public class Foo {
     }
     private boolean beenModified;
     private String property1 = "some value";
-    public Foo() {}
 
     public String bar()
     {
-        return JexlTest.METHOD_STRING;
+        return ExpressionsTest.METHOD_STRING;
     }
 
     public String convertBoolean(final boolean b)
@@ -58,7 +57,7 @@ public class Foo {
 
     public String getBar()
     {
-        return JexlTest.GET_METHOD_STRING;
+        return ExpressionsTest.GET_METHOD_STRING;
     }
 
     public List<String> getCheeseList()
diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
index f9879567..a56efdf6 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
@@ -17,7 +17,7 @@
 package org.apache.commons.jexl3;
 
 import static org.apache.commons.jexl3.internal.Util.debuggerCheck;
-import static 
org.apache.commons.jexl3.introspection.JexlPermissions.RESTRICTED;
+import static org.apache.commons.jexl3.JexlTestCase.TEST_PERMS;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@@ -48,6 +48,7 @@ import java.util.stream.Stream;
 import org.apache.commons.jexl3.internal.Engine32;
 import org.apache.commons.jexl3.internal.OptionsContext;
 import org.apache.commons.jexl3.introspection.JexlSandbox;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -62,11 +63,11 @@ class Issues300Test {
         @Override
         public boolean isStrict(final JexlOperator op) {
             switch (op) {
-            case NOT:
-            case CONDITION:
-                return false;
+                case NOT:
+                case CONDITION:
+                    return false;
+                default: return super.isStrict(op);
             }
-            return super.isStrict(op);
         }
     }
 
@@ -777,7 +778,9 @@ class Issues300Test {
         final String text = "(A ? C.D : E)";
         final JexlEngine jexl = new JexlBuilder().safe(true).create();
         final JexlExpression expr = jexl.createExpression(text);
+        Assertions.assertNotNull(expr);
         final JexlScript script = jexl.createScript(text);
+        Assertions.assertNotNull(script);
     }
 
     @Test
@@ -1143,7 +1146,7 @@ class Issues300Test {
     void testIssue397() {
         String result;
         final String control = Class397.class.getName();
-        final JexlEngine jexl = new 
JexlBuilder().permissions(RESTRICTED).create();
+        final JexlEngine jexl = new 
JexlBuilder().permissions(TEST_PERMS).create();
 
         final Interface397i instance = new Class397();
         result = (String) jexl.invokeMethod(instance, "summary");
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index 119385d4..cf366c81 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -54,6 +54,7 @@ import org.apache.commons.jexl3.parser.JexlScriptParser;
 import org.apache.commons.jexl3.parser.Parser;
 import org.apache.commons.jexl3.parser.StringProvider;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -115,6 +116,11 @@ public class Issues400Test {
         assertEquals(42, result);
     }
 
+    @BeforeAll
+    static void setUpClass() {
+        JexlBuilder.setDefaultPermissions(JexlTestCase.TEST_PERMS);
+    }
+
     @Test
     void test402() {
         final JexlContext jc = new MapContext();
@@ -876,7 +882,7 @@ public class Issues400Test {
         // need to explicitly allow Uberspect and the current class loader to 
load System
         Class<? extends ClassLoader> cloader = 
getClass().getClassLoader().getClass();
         JexlPermissions perm = new JexlPermissions.ClassPermissions(
-            ClassLoader.class,
+            getClass().getClassLoader().getClass(),
             org.apache.commons.jexl3.internal.introspection.Uberspect.class);
         //assertTrue(perm.allow(cloader), "should be able to access 
ClassLoader");
         assertTrue(perm.allow(cloader, cloader.getMethod("loadClass", 
String.class)), "should be able to access ClassLoader::loadClass");
@@ -898,8 +904,11 @@ public class Issues400Test {
         // need explicit permissions to ClassPermissions and Uberspect to 
reach and invoke
         // System::currentTimeMillis
         JexlPermissions perm = new JexlPermissions.ClassPermissions(
+            getClass().getClassLoader().getClass(),
             JexlPermissions.ClassPermissions.class,
-            org.apache.commons.jexl3.internal.introspection.Uberspect.class);
+            org.apache.commons.jexl3.internal.introspection.Uberspect.class,
+            
org.apache.commons.jexl3.internal.introspection.MethodExecutor.class,
+            BrkContext.class);
         assertNotNull(run450c(perm));
         // cannot reach and invoke System::currentTimeMillis with RESTRICTED
         assertThrows(JxltEngine.Exception.class, () -> 
run450c(JexlPermissions.RESTRICTED),
@@ -915,17 +924,21 @@ public class Issues400Test {
                 + "sys = x?.getClassLoader()?.loadClass('java.lang.System') ?: 
SYSTEM;"
                 + "p = 
new('org.apache.commons.jexl3.introspection.JexlPermissions$ClassPermissions', 
[sys]);"
                 + "c = 
new('org.apache.commons.jexl3.internal.introspection.Uberspect', null, null, 
p);"
-                + "z = c.getMethod(sys,'currentTimeMillis').invoke(x,null);}")
+                + "z = 
brk(c.getMethod(sys,'currentTimeMillis')).invoke(x,null);}")
             .evaluate(new BrkContext());
         return result;
     }
 
     @Test
-    void test450() {
+    void test450() throws Exception {
+        JexlPermissions perms =  Engine33.createPermissions();
+        Class<? extends ClassLoader> cl = 
Issues400Test.class.getClassLoader().getClass();
+        assertTrue(perms.allow(cl, cl.getMethod("loadClass", String.class)),
+            "should be able to access ClassLoader::loadClass with specific 
permissions");
         assertNotNull(run450(JexlPermissions.UNRESTRICTED),
             "should be able to reach and invoke System::currentTimeMillis with 
UNRESTRICTED");
         assertNotNull(
-            run450(new JexlPermissions.ClassPermissions(
+            run450(new JexlPermissions.ClassPermissions(perms,
                 org.apache.commons.jexl3.internal.TemplateEngine.class)),
             "should be able to reach and invoke System::currentTimeMillis with 
TemplateEngine permission");
         assertThrows(JexlException.Method.class, () -> run450(RESTRICTED),
@@ -941,12 +954,18 @@ public class Issues400Test {
             super(builder);
         }
 
-        static JexlBuilder createBuilder() {
-            JexlPermissions perm = new JexlPermissions.ClassPermissions(
-                Issues400Test.class.getClassLoader().getClass(),
+        static JexlPermissions createPermissions() {
+            // Need a lot of things on top of JEXL37 to allow execution
+            return new 
JexlPermissions.ClassPermissions(JexlTestCase.TEST_PERMS,
+                Engine33.class.getClassLoader().getClass(),
+                Engine33.class,
                 JexlPermissions.ClassPermissions.class,
                 org.apache.commons.jexl3.internal.TemplateEngine.class,
                 
org.apache.commons.jexl3.internal.introspection.Uberspect.class);
+        }
+
+        static JexlBuilder createBuilder() {
+            JexlPermissions perm = createPermissions();
             return new 
JexlBuilder().safe(false).silent(false).permissions(perm);
         }
     }
diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java 
b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
index fe9515f1..803d1207 100644
--- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
@@ -58,8 +58,6 @@ import org.junit.jupiter.params.provider.MethodSource;
  */
 @SuppressWarnings({"UnnecessaryBoxing", 
"AssertEqualsBetweenInconvertibleTypes"})
 class JXLTTest extends JexlTestCase {
-    static JexlPermissions P0 = JexlPermissions.DEFAULT;
-    static JexlPermissions P1 = JexlPermissions.RESTRICTED;
 
     public static class Context311 extends MapContext
       implements JexlContext.OptionsHandle, JexlContext.ThreadLocal {
@@ -150,7 +148,7 @@ class JXLTTest extends JexlTestCase {
    public static List<JexlBuilder> engines() {
        final JexlFeatures f = new JexlFeatures();
        f.lexical(true).lexicalShade(true);
-       JexlPermissions permissions = 
JexlPermissions.DEFAULT.compose("org.apache.commons.jexl3 +{ JXLTTest{} }");
+       JexlPermissions permissions = 
JexlPermissions.RESTRICTED.compose("org.apache.commons.jexl3 +{ JXLTTest{} }");
       return Arrays.asList(
               new 
JexlBuilder().permissions(permissions).silent(false).lexical(true).lexicalShade(true).cache(128).strict(true),
               new 
JexlBuilder().permissions(permissions).features(f).silent(false).cache(128).strict(true),
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java 
b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
index 246be186..e9b3b9a3 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
@@ -17,19 +17,15 @@
 
 package org.apache.commons.jexl3;
 
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
 import org.apache.commons.jexl3.internal.Debugger;
 import org.apache.commons.jexl3.internal.OptionsContext;
 import org.apache.commons.jexl3.internal.Util;
 import org.apache.commons.jexl3.internal.introspection.Uberspect;
 import org.apache.commons.jexl3.introspection.JexlPermissions;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+
+import java.util.Arrays;
 
 /**
  * Implements runTest methods to dynamically instantiate and invoke a test,
@@ -37,206 +33,135 @@ import org.junit.jupiter.api.AfterEach;
  * Eases the implementation of main methods to debug.
  */
 public class JexlTestCase {
-    public static class PragmaticContext extends OptionsContext implements 
JexlContext.PragmaProcessor, JexlContext.OptionsHandle {
-        private final JexlOptions options;
-
-        public PragmaticContext() {
-            this(new JexlOptions());
-        }
-
-        public PragmaticContext(final JexlOptions o) {
-            this.options = o;
-        }
-
-        @Override
-        public JexlOptions getEngineOptions() {
-            return options;
-        }
-
-        @Override
-        public void processPragma(final JexlOptions opts, final String key, 
final Object value) {
-            if ("script.mode".equals(key) && "pro50".equals(value)) {
-                opts.set(MODE_PRO50);
-            }
-        }
-
-        @Override
-        public void processPragma(final String key, final Object value) {
-            processPragma(null, key, value);
-        }
-    }
-    // The default options: all tests where engine lexicality is
-    // important can be identified by the builder  calling lexical(...).
-    static {
-        JexlOptions.setDefaultFlags("-safe", "+lexical");
-    }
-
-    /** No parameters signature for test run. */
-    private static final Class<?>[] NO_PARMS = {};
-
-    /** String parameter signature for test run. */
-    private static final Class<?>[] STRING_PARM = {String.class};
-
-    // define mode pro50
-    static final JexlOptions MODE_PRO50 = new JexlOptions();
-    static {
-        MODE_PRO50.setFlags("+strict +cancellable +lexical +lexicalShade 
-safe".split(" "));
-    }
-
-    /**
-     * A very secure singleton.
-     */
-    public static final JexlPermissions SECURE = JexlPermissions.RESTRICTED;
-
-    public static final JexlPermissions TESTPERMS = 
JexlPermissions.RESTRICTED.compose(
-        "org.apache.commons.jexl3.*",
-        "org.apache.commons.jexl3 +{ -JexlBuilder{} }",
-        "org.apache.commons.jexl3.introspection -{ JexlPermissions{} 
JexlPermissions$ClassPermissions{} }",
-        "org.apache.commons.jexl3.internal -{ Engine{} Engine32{} 
TemplateEngine{} }",
-        "org.apache.commons.jexl3.internal.introspection -{ Uberspect{} 
Introspector{} }");
-
-    static JexlEngine createEngine() {
-        return new JexlBuilder().create();
-    }
-
-    public static JexlEngine createEngine(final boolean lenient) {
-        return createEngine(lenient, SECURE);
-    }
-    public static JexlEngine createEngine(final boolean lenient, final 
JexlPermissions permissions) {
-        return new JexlBuilder()
-                .uberspect(new Uberspect(null, null, permissions))
-                .arithmetic(new JexlArithmetic(!lenient)).cache(128).create();
-    }
-
-    static JexlEngine createEngine(final JexlFeatures features) {
-        return new JexlBuilder().features(features).create();
-    }
-
-    /**
-     * Will force testing the debugger for each derived test class by
-     * recreating each expression from the JexlNode in the JexlEngine cache &
-     * testing them for equality with the origin.
-     * @throws Exception
-     */
-    public static void debuggerCheck(final JexlEngine ijexl) throws Exception {
-         Util.debuggerCheck(ijexl);
-    }
-
-    /**
-     * Compare strings ignoring white space differences.
-     * <p>This replaces any sequence of whitespaces (ie \\s) by one space (ie 
ASCII 32) in both
-     * arguments then compares them.</p>
-     * @param lhs left hand side
-     * @param rhs right hand side
-     * @return true if strings are equal besides whitespace
-     */
-    public static boolean equalsIgnoreWhiteSpace(final String lhs, final 
String rhs) {
-        final String lhsw = lhs.trim().replaceAll("\\s+", "");
-        final String rhsw = rhs.trim().replaceAll("\\s+", "");
-        return lhsw.equals(rhsw);
-    }
-
-    /**
-     * Runs a test.
-     * @param args where args[0] is the test class name and args[1] the test 
class method
-     * @throws Exception
-     */
-    public static void main(final String[] args) throws Exception {
-        runTest(args[0], args[1]);
-    }
-
-    /**
-     * Instantiate and runs a test method; useful for debugging purpose.
-     * For instance:
-     * <code>
-     * public static void main(String[] args) throws Exception {
-     *   runTest("BitwiseOperatorTest","testAndVariableNumberCoercion");
-     * }
-     * </code>
-     * @param tname the test class name
-     * @param mname the test class method
-     * @throws Exception
-     */
-    public static void runTest(final String tname, final String mname) throws 
Exception {
-        final String testClassName = "org.apache.commons.jexl3." + tname;
-        final Class<JexlTestCase> clazz = null;
-        JexlTestCase test = null;
-        // find the class
-        assertThrows(ClassNotFoundException.class, () -> 
Class.forName(testClassName), () -> "no such class: " + testClassName);
-        // find ctor & instantiate
-        Constructor<JexlTestCase> ctor = null;
-        try {
-            ctor = clazz.getConstructor(STRING_PARM);
-            test = ctor.newInstance("debug");
-        } catch (final NoSuchMethodException xctor) {
-            // instantiate default class ctor
-            try {
-                test = clazz.getConstructor().newInstance();
-            } catch (final Exception xany) {
-                fail("cant instantiate test: " + xany);
-                return;
-            }
-        } catch (final Exception xany) {
-            fail("cant instantiate test: " + xany);
-            return;
-        }
-        // Run the test
-        test.runTest(mname);
-    }
-
-    public static String toString(final JexlScript script) {
-        final Debugger d = new Debugger().lineFeed("").indentation(0);
-        d.debug(script);
-        return  d.toString();
-    }
-
-    /** A default JEXL engine instance. */
-    protected final JexlEngine JEXL;
-
-    public JexlTestCase(final String name) {
-        this(name, new 
JexlBuilder().imports(Arrays.asList("java.lang","java.math")).permissions(null).cache(128).create());
-    }
-
-    protected JexlTestCase(final String name, final JexlEngine jexl) {
-        //super(name);
-        JEXL = jexl;
-    }
-
-    /**
-     * Dynamically runs a test method.
-     * @param name the test method to run
-     * @throws Exception if anything goes wrong
-     */
-    public void runTest(final String name) throws Exception {
-        if ("runTest".equals(name)) {
-            return;
-        }
-        Method method = null;
-        try {
-            method = this.getClass().getDeclaredMethod(name, NO_PARMS);
-        }
-        catch (final Exception xany) {
-            fail("no such test: " + name);
-            return;
-        }
-        try {
-            setUp();
-            method.invoke(this);
-        } finally {
-            tearDown();
-        }
-    }
-
-    public void setUp() throws Exception {
-        // nothing to do
-    }
-
-    public String simpleWhitespace(final String arg) {
-        return arg.trim().replaceAll("\\s+", " ");
-    }
-
-    @AfterEach
-    public void tearDown() throws Exception {
-        debuggerCheck(JEXL);
-    }
+  /**
+   * The restricted permissions that are used by default in JEXL tests.
+   * <p>Need to add jexl3 package but removes most internals</p>
+   */
+  public static final JexlPermissions TEST_PERMS = 
JexlPermissions.RESTRICTED.compose(
+"org.apache.commons.jexl3.*",
+      "org.apache.commons.jexl3 +{ -JexlBuilder{} }",
+      "org.apache.commons.jexl3.introspection -{ JexlPermissions{} 
JexlPermissions$ClassPermissions{} }",
+      "org.apache.commons.jexl3.internal -{ Engine{} Engine32{} 
TemplateEngine{} }",
+      "org.apache.commons.jexl3.internal.introspection -{ Uberspect{} 
Introspector{} }",
+      "java.net -{ +URI { -toURL() } }");
+
+  // define mode pro50
+  static final JexlOptions MODE_PRO50 = new JexlOptions();
+
+  // The default options: all tests where engine lexicality is
+  // important can be identified by the builder  calling lexical(...).
+  static {
+    JexlBuilder.setDefaultPermissions(TEST_PERMS);
+    JexlOptions.setDefaultFlags("-safe", "+lexical");
+    MODE_PRO50.setFlags("+strict +cancellable +lexical +lexicalShade 
-safe".split(" "));
+  }
+
+  /**
+   * A default JEXL engine instance.
+   */
+  protected final JexlEngine JEXL;
+
+  public JexlTestCase(final String name) {
+    this(name, new JexlBuilder().imports(Arrays.asList("java.lang", 
"java.math")).permissions(null).cache(128).create());
+  }
+
+  protected JexlTestCase(final String name, final JexlEngine jexl) {
+    //super(name);
+    JEXL = jexl;
+  }
+
+  static JexlEngine createEngine() {
+    return new JexlBuilder().create();
+  }
+
+  public static JexlEngine createEngine(final boolean lenient) {
+    return createEngine(lenient, TEST_PERMS);
+  }
+
+  public static JexlEngine createEngine(final boolean lenient, final 
JexlPermissions permissions) {
+    return new JexlBuilder().uberspect(new Uberspect(null, null, 
permissions)).arithmetic(new JexlArithmetic(!lenient)).cache(128).create();
+  }
+
+  static JexlEngine createEngine(final JexlFeatures features) {
+    return new JexlBuilder().features(features).create();
+  }
+
+  /**
+   * Will force testing the debugger for each derived test class by
+   * recreating each expression from the JexlNode in the JexlEngine cache &
+   * testing them for equality with the origin.
+   *
+   * @throws Exception
+   */
+  public static void debuggerCheck(final JexlEngine ijexl) throws Exception {
+    Util.debuggerCheck(ijexl);
+  }
+
+  /**
+   * Compare strings ignoring white space differences.
+   * <p>This replaces any sequence of whitespaces (ie \\s) by one space (ie 
ASCII 32) in both
+   * arguments then compares them.</p>
+   *
+   * @param lhs left hand side
+   * @param rhs right hand side
+   * @return true if strings are equal besides whitespace
+   */
+  public static boolean equalsIgnoreWhiteSpace(final String lhs, final String 
rhs) {
+    final String lhsw = lhs.trim().replaceAll("\\s+", "");
+    final String rhsw = rhs.trim().replaceAll("\\s+", "");
+    return lhsw.equals(rhsw);
+  }
+
+  public static String toString(final JexlScript script) {
+    final Debugger d = new Debugger().lineFeed("").indentation(0);
+    d.debug(script);
+    return d.toString();
+  }
+
+  @BeforeAll
+  static void setUpClass() {
+    JexlBuilder.setDefaultPermissions(TEST_PERMS);
+  }
+
+  public String simpleWhitespace(final String arg) {
+    return arg.trim().replaceAll("\\s+", " ");
+  }
+
+  public void setUp() throws Exception {
+    // nothing to do
+  }
+
+  @AfterEach
+  public void tearDown() throws Exception {
+    debuggerCheck(JEXL);
+  }
+
+  public static class PragmaticContext extends OptionsContext implements 
JexlContext.PragmaProcessor, JexlContext.OptionsHandle {
+    private final JexlOptions options;
+
+    public PragmaticContext() {
+      this(new JexlOptions());
+    }
+
+    public PragmaticContext(final JexlOptions o) {
+      this.options = o;
+    }
+
+    @Override
+    public JexlOptions getEngineOptions() {
+      return options;
+    }
+
+    @Override
+    public void processPragma(final JexlOptions opts, final String key, final 
Object value) {
+      if ("script.mode".equals(key) && "pro50".equals(value)) {
+        opts.set(MODE_PRO50);
+      }
+    }
+
+    @Override
+    public void processPragma(final String key, final Object value) {
+      processPragma(null, key, value);
+    }
+  }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/SwitchTest.java 
b/src/test/java/org/apache/commons/jexl3/SwitchTest.java
index 7d2eb332..83c8eb97 100644
--- a/src/test/java/org/apache/commons/jexl3/SwitchTest.java
+++ b/src/test/java/org/apache/commons/jexl3/SwitchTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.jexl3;
 
-import static 
org.apache.commons.jexl3.introspection.JexlPermissions.RESTRICTED;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -140,7 +139,7 @@ public class SwitchTest extends JexlTestCase {
 
     @Test
     void test440p() {
-        Assertions.assertTrue(RESTRICTED.allow(this.getClass()));
+        Assertions.assertTrue(TEST_PERMS.allow(this.getClass()));
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/jexl3/examples/StreamTest.java 
b/src/test/java/org/apache/commons/jexl3/examples/StreamTest.java
index 41f65db5..810c941e 100644
--- a/src/test/java/org/apache/commons/jexl3/examples/StreamTest.java
+++ b/src/test/java/org/apache/commons/jexl3/examples/StreamTest.java
@@ -17,10 +17,12 @@
 package org.apache.commons.jexl3.examples;
 
 import static java.lang.Boolean.TRUE;
+import static org.apache.commons.jexl3.JexlTestCase.TEST_PERMS;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.lang.reflect.Method;
 import java.net.URI;
 import java.util.Arrays;
 import java.util.Collection;
@@ -38,12 +40,18 @@ import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.MapContext;
 import org.apache.commons.jexl3.introspection.JexlPermissions;
 import org.apache.commons.jexl3.introspection.JexlPermissions.ClassPermissions;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 /**
  * A test around scripting streams.
  */
-class StreamTest {
+public class StreamTest {
+    @BeforeAll
+    static void setUpClass() {
+        JexlBuilder.setDefaultPermissions(TEST_PERMS);
+    }
 
     /**
      * A MapContext that can operate on streams and collections.
@@ -108,25 +116,31 @@ class StreamTest {
 
     /** Our engine instance. */
     private final JexlEngine jexl;
+    private final JexlPermissions jexlPermissions;
 
-    public StreamTest() {
+    public StreamTest() throws Exception {
         // Restricting features; no loops, no side effects
         final JexlFeatures features = new JexlFeatures()
             .loops(false)
             .sideEffectGlobal(false)
             .sideEffect(false);
         // Restricted permissions to a safe set but with URI allowed
-        final JexlPermissions permissions = new 
ClassPermissions(java.net.URI.class);
-        // Create the engine
+        jexlPermissions = new ClassPermissions(java.net.URI.class, 
StreamContext.class, CollectionContext.class);
+       // Create the engine
         jexl = new JexlBuilder()
             .features(features)
-            .permissions(permissions)
+            .permissions(jexlPermissions)
             .namespaces(Collections.singletonMap("URI", java.net.URI.class))
             .create();
     }
 
     @Test
-    void testURICollection() {
+    void testURICollection() throws Exception {
+       Assertions.assertTrue(jexlPermissions.allow(java.net.URI.class));
+       
Assertions.assertTrue(jexlPermissions.allow(java.util.stream.Stream.class));
+       for (Method m : Arrays.stream(Stream.class.getMethods()).filter(m -> 
m.getName().equals("filter")).collect(Collectors.toSet())) {
+            Assertions.assertTrue(jexlPermissions.allow(m));
+        }
         // A collection map/filter aware context
         final JexlContext sctxt = new CollectionContext();
         // Some uris
@@ -162,7 +176,9 @@ class StreamTest {
     }
 
     @Test
-    void testURIStream() {
+    void testURIStream() throws Exception {
+        
Assertions.assertTrue(jexlPermissions.allow(StreamContext.class.getMethod("filter",
 new Class[]{Stream.class, JexlScript.class})));
+
         // Assume a collection of uris need to be processed and transformed to 
be simplified ;
         // we want only http/https ones, only the host part and using a https 
scheme
         final List<URI> uris = Arrays.asList(
diff --git 
a/src/test/java/org/apache/commons/jexl3/internal/introspection/DiscoveryTest.java
 
b/src/test/java/org/apache/commons/jexl3/internal/introspection/DiscoveryTest.java
index 512c9db7..42ecf1ae 100644
--- 
a/src/test/java/org/apache/commons/jexl3/internal/introspection/DiscoveryTest.java
+++ 
b/src/test/java/org/apache/commons/jexl3/internal/introspection/DiscoveryTest.java
@@ -32,11 +32,13 @@ import org.apache.commons.jexl3.internal.Engine;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
 import org.apache.commons.jexl3.introspection.JexlPropertySet;
+import org.apache.commons.jexl3.introspection.JexlUberspect;
 import org.junit.jupiter.api.Test;
 
 /**
  * Tests for checking introspection discovery.
  */
+@SuppressWarnings("unused")
 class DiscoveryTest extends JexlTestCase {
     public static class Bean {
         private String value;
@@ -144,7 +146,7 @@ class DiscoveryTest extends JexlTestCase {
 
     @Test
     void testBeanIntrospection() throws Exception {
-        final Uberspect uber = Engine.getUberspect(null, null);
+        final JexlUberspect uber = Engine.getUberspect(null, null, 
JexlTestCase.TEST_PERMS);
         final Bean bean = new Bean("JEXL", "LXEJ");
 
         final JexlPropertyGet get = uber.getPropertyGet(bean, "value");
@@ -173,7 +175,7 @@ class DiscoveryTest extends JexlTestCase {
 
     @Test
     void testDuckIntrospection() throws Exception {
-        final Uberspect uber = Engine.getUberspect(null, null);
+        final JexlUberspect uber = Engine.getUberspect(null, null, 
JexlTestCase.TEST_PERMS);
         final Duck duck = new Duck("JEXL", "LXEJ");
 
         final JexlPropertyGet get = uber.getPropertyGet(duck, "value");
@@ -201,7 +203,7 @@ class DiscoveryTest extends JexlTestCase {
 
     @Test
     void testListIntrospection() throws Exception {
-        final Uberspect uber = Engine.getUberspect(null, null);
+        final JexlUberspect uber = new Uberspect(null, null);
         final List<Object> list = new ArrayList<>();
         list.add("LIST");
         list.add("TSIL");
@@ -215,7 +217,7 @@ class DiscoveryTest extends JexlTestCase {
         assertEquals(set, uber.getPropertySet(list, 1, "foo"));
         // different property should return different setter/getter
         assertNotEquals(get, uber.getPropertyGet(list, 0));
-        assertNotEquals(get, uber.getPropertySet(list, 0, "foo"));
+        assertNotEquals(set, uber.getPropertySet(list, 0, "foo"));
         // setter returns argument
         final Object bar = set.invoke(list, "bar");
         assertEquals("bar", bar);
@@ -232,7 +234,7 @@ class DiscoveryTest extends JexlTestCase {
 
     @Test
     void testMapIntrospection() throws Exception {
-        final Uberspect uber = Engine.getUberspect(null, null);
+        final JexlUberspect uber = new Uberspect(null, null);
         final Map<String, Object> map = new HashMap<>();
         map.put("value", "MAP");
         map.put("eulav", "PAM");
@@ -246,7 +248,7 @@ class DiscoveryTest extends JexlTestCase {
         assertEquals(set, uber.getPropertySet(map, "value", "foo"));
         // different property should return different setter/getter
         assertNotEquals(get, uber.getPropertyGet(map, "eulav"));
-        assertNotEquals(get, uber.getPropertySet(map, "eulav", "foo"));
+        assertNotEquals(set, uber.getPropertySet(map, "eulav", "foo"));
         // setter returns argument
         final Object bar = set.invoke(map, "bar");
         assertEquals("bar", bar);
@@ -263,7 +265,7 @@ class DiscoveryTest extends JexlTestCase {
 
     @Test
     void testMethodIntrospection() throws Exception {
-        final Uberspect uber = new Uberspect(null, null);
+        final JexlUberspect uber = new Uberspect(null, null, 
JexlTestCase.TEST_PERMS);
         final Bulgroz bulgroz = new Bulgroz();
         JexlMethod jmethod;
         Object result;
diff --git 
a/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
 
b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
index 9d970470..23c22188 100644
--- 
a/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
+++ 
b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
@@ -54,7 +54,7 @@ import org.junit.jupiter.api.Test;
 /**
  * Checks the CacheMap.MethodKey implementation
  */
-
+@SuppressWarnings({"unused","empty"})
 class PermissionsTest {
 
     public static class A {
@@ -464,7 +464,7 @@ class PermissionsTest {
 
     @Test
     void testSecurePermissions() {
-        assertNotNull(JexlTestCase.SECURE);
+        assertNotNull(JexlTestCase.TEST_PERMS);
         final List<Class<?>> acs = Arrays.asList(
             java.lang.Runtime.class,
             java.math.BigDecimal.class,
@@ -473,7 +473,7 @@ class PermissionsTest {
         for(final Class<?> ac: acs) {
             final Package p = ac.getPackage();
             assertNotNull(p, ac::getName);
-            assertTrue(JexlTestCase.SECURE.allow(p), ac::getName);
+            assertTrue(JexlTestCase.TEST_PERMS.allow(p), ac::getName);
         }
         final List<Class<?>> nacs = Arrays.asList(
                 java.lang.annotation.ElementType.class,
@@ -483,10 +483,10 @@ class PermissionsTest {
                 java.lang.ref.SoftReference.class,
                 java.lang.reflect.Method.class);
         for(final Class<?> nac : nacs) {
-            assertFalse(JexlTestCase.SECURE.allow(nac), nac::getName);
+            assertFalse(JexlTestCase.TEST_PERMS.allow(nac), nac::getName);
             final Package p = nac.getPackage();
             assertNotNull(p, nac::getName);
-            assertFalse(JexlTestCase.SECURE.allow(p), nac::getName);
+            assertFalse(JexlTestCase.TEST_PERMS.allow(p), nac::getName);
         }
     }
 
@@ -508,10 +508,11 @@ class PermissionsTest {
     }
 
     @Test
-    void testPair0() {
+    void testPair0() throws Exception {
         final Map<String, Object> funcs = new HashMap<>();
         funcs.put("lisp", new Scheme());
-        final JexlPermissions permissions = 
JexlPermissions.RESTRICTED.compose("org.example.*");
+        final JexlPermissions permissions = 
JexlPermissions.RESTRICTED.compose("org.example.*", 
"org.apache.commons.jexl3.internal.introspection { +PermissionsTest$Scheme {}");
+        Assertions.assertTrue(permissions.allow(Scheme.class.getMethod("cons", 
new Class[]{ Object.class, Object.class})));
         final JexlEngine jexl = new 
JexlBuilder().cache(8).permissions(permissions).namespaces(funcs).create();
         String src = "let p = lisp:cons(17, 25); p.car + p.cdr;";
         JexlScript script = jexl.createScript(src);
diff --git 
a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineOptionalTest.java
 
b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineOptionalTest.java
index 1206dfa6..92d1c5ad 100644
--- 
a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineOptionalTest.java
+++ 
b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineOptionalTest.java
@@ -27,6 +27,9 @@ import javax.script.CompiledScript;
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineManager;
 
+import org.apache.commons.jexl3.JexlBuilder;
+import org.apache.commons.jexl3.JexlTestCase;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 class JexlScriptEngineOptionalTest {
@@ -34,6 +37,11 @@ class JexlScriptEngineOptionalTest {
     private final ScriptEngineManager manager = new ScriptEngineManager();
     private final ScriptEngine engine = manager.getEngineByName("jexl");
 
+    @BeforeAll
+    static void setUpClass() {
+        JexlBuilder.setDefaultPermissions(JexlTestCase.TEST_PERMS);
+    }
+
     @Test
     void testCompilable() throws Exception {
         assertInstanceOf(Compilable.class, engine, "Engine should implement 
Compilable");
diff --git 
a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java 
b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
index bd1b622e..626bb5e1 100644
--- a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
+++ b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
@@ -140,6 +140,8 @@ class JexlScriptEngineTest {
 
     @Test
     void testErrors() throws Exception {
+        JexlPermissions permissions = new 
JexlPermissions.ClassPermissions(Errors.class);
+        JexlScriptEngine.setPermissions(permissions);
         final ScriptEngineManager manager = new ScriptEngineManager();
         final JexlScriptEngine engine = (JexlScriptEngine) 
manager.getEngineByName("JEXL");
         engine.put("errors", new Errors());
@@ -259,8 +261,7 @@ class JexlScriptEngineTest {
 
     @Test
     void testScriptingPermissions1() throws Exception {
-        JexlBuilder.setDefaultPermissions(JexlPermissions.UNRESTRICTED);
-        JexlScriptEngine.setPermissions(null);
+        JexlScriptEngine.setPermissions(JexlPermissions.UNRESTRICTED);
         final ScriptEngineManager manager = new ScriptEngineManager();
         final ScriptEngine engine = manager.getEngineByName("jexl3");
         final Long time2 = (Long) engine.eval(
@@ -292,24 +293,28 @@ class JexlScriptEngineTest {
 
     @Test
     void testMain2() throws Exception {
-        final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
         final PrintStream originalOut = System.out;
         Path file = null;
         try {
-            System.setOut(new PrintStream(outContent));
             file = Files.createTempFile("test-jsr233", ".jexl");
-            final BufferedWriter writer = new BufferedWriter(new 
FileWriter(file.toFile()));
-            writer.write("a=20;\nb=22;\na+b\n");
-            writer.close();
-            final String ctl = ">>: 42" + LF;
-            Main.main(new String[]{file.toString()});
-            Assertions.assertEquals(ctl, outContent.toString());
+            try (BufferedWriter writer = new BufferedWriter(new 
FileWriter(file.toFile()));
+                 ByteArrayOutputStream outContent = new 
ByteArrayOutputStream();
+                 PrintStream printStream = new PrintStream(outContent)) {
+
+                writer.write("a=20;\nb=22;\na+b\n");
+                writer.close(); // Explicit close before using the file
+
+                System.setOut(printStream);
+                final String ctl = ">>: 42" + LF;
+                Main.main(new String[]{file.toString()});
+                Assertions.assertEquals(ctl, outContent.toString());
+            } finally {
+                System.setOut(originalOut);
+            }
         } finally {
-            System.setOut(originalOut);
             if (file != null) {
                 Files.delete(file);
             }
         }
     }
-
 }

Reply via email to