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

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


The following commit(s) were added to refs/heads/master by this push:
     new fff0f405 JEXL: JexlSandbox clean up; - added test related to a 
StackOverflow question (testSortArray);
fff0f405 is described below

commit fff0f40580987d29ff5b22dbc98deb52367472e5
Author: Henri Biestro <hbies...@cloudera.com>
AuthorDate: Wed Aug 21 12:15:43 2024 +0200

    JEXL: JexlSandbox clean up;
    - added test related to a StackOverflow question (testSortArray);
---
 .../commons/jexl3/introspection/JexlSandbox.java   | 1257 ++++++++++----------
 .../org/apache/commons/jexl3/Issues400Test.java    |   38 +
 2 files changed, 653 insertions(+), 642 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java 
b/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java
index cae92e27..cc935454 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java
@@ -67,647 +67,620 @@ import java.util.concurrent.ConcurrentHashMap;
  * @since 3.0
  */
 public final class JexlSandbox {
-    /**
-     * An allow set of names.
-     */
-    static class AllowSet extends Names {
-        /** The map of controlled names and aliases. */
-        private Map<String, String> names;
-
-        @Override
-        public boolean add(final String name) {
-            if (names == null) {
-                names = new HashMap<>();
-            }
-            return names.put(name, name) == null;
-        }
-
-        @Override
-        public boolean alias(final String name, final String alias) {
-            if (names == null) {
-                names = new HashMap<>();
-            }
-            return names.put(alias, name) == null;
-        }
-
-        @Override
-        protected Names copy() {
-            final AllowSet copy = new AllowSet();
-            copy.names = names == null ? null : new HashMap<>(names);
-            return copy;
-        }
-
-        @Override
-        public String get(final String name) {
-            if (names == null) {
-                return name;
-            }
-            final String actual = names.get(name);
-            // if null is not explicitly allowed, explicit null aka NULL
-            if (name == null && actual == null && !names.containsKey(null)) {
-                return JexlSandbox.NULL;
-            }
-            return actual;
-        }
-
-        @Override
-        public String toString() {
-            return "allow{" + (names == null? "all" : 
Objects.toString(names.entrySet())) + "}";
-        }
-    }
-
-    /**
-     * @deprecated since 3.2, use {@link BlockSet}
-     */
-    @Deprecated
-    public static final class BlackSet extends BlockSet {}
-
-    /**
-     * A block set of names.
-     */
-    static class BlockSet extends Names {
-        /** The set of controlled names. */
-        private Set<String> names;
-
-        @Override
-        public boolean add(final String name) {
-            if (names == null) {
-                names = new HashSet<>();
-            }
-            return names.add(name);
-        }
-
-        @Override
-        protected Names copy() {
-            final BlockSet copy = new BlockSet();
-            copy.names = names == null ? null : new HashSet<>(names);
-            return copy;
-        }
-
-        @Override
-        public String get(final String name) {
-            // if name is null and contained in set, explicit null aka NULL
-            return names != null && !names.contains(name) ? name : name != 
null ? null : NULL;
-        }
-
-        @Override
-        public String toString() {
-            return "block{" + (names == null? "all" :Objects.toString(names)) 
+ "}";
-        }
-    }
-
-    /**
-     * A base set of names.
-     */
-    public abstract static class Names {
-        /**
-         * Adds a name to this set.
-         *
-         * @param name the name to add
-         * @return  true if the name was really added, false if not
-         */
-        public abstract boolean add(String name);
-
-        /**
-         * Adds an alias to a name to this set.
-         * <p>This only has an effect on allow lists.</p>
-         *
-         * @param name the name to alias
-         * @param alias the alias
-         * @return  true if the alias was added, false if it was already 
present
-         */
-        public boolean alias(final String name, final String alias) {
-            return false;
-        }
-
-        /**
-         * @return a copy of these Names
-         */
-        protected Names copy() {
-            return this;
-        }
-
-        /**
-         * Whether a given name is allowed or not.
-         *
-         * @param name the method/property name to check
-         * @return null (or NULL if name is null) if not allowed, the actual 
name to use otherwise
-         */
-        public String get(final String name) {
-            return name;
-        }
-    }
-
-    /**
-     * Contains the allow or block lists for properties and methods for a 
given class.
-     */
-    public static final class Permissions {
-        /** Whether these permissions are inheritable, ie can be used by 
derived classes. */
-        private final boolean inheritable;
-        /** The controlled readable properties. */
-        private final Names read;
-        /** The controlled  writable properties. */
-        private final Names write;
-        /** The controlled methods. */
-        private final Names execute;
-
-        /**
-         * Creates a new permissions instance.
-         *
-         * @param inherit whether these permissions are inheritable
-         * @param readFlag whether the read property list is allow or block
-         * @param writeFlag whether the write property list is allow or block
-         * @param executeFlag whether the method list is allow of block
-         */
-        Permissions(final boolean inherit, final boolean readFlag, final 
boolean writeFlag, final boolean executeFlag) {
-            this(inherit,
-                    readFlag ? new AllowSet() : new BlockSet(),
-                    writeFlag ? new AllowSet() : new BlockSet(),
-                    executeFlag ? new AllowSet() : new BlockSet());
-        }
-
-        /**
-         * Creates a new permissions instance.
-         *
-         * @param inherit whether these permissions are inheritable
-         * @param nread the read set
-         * @param nwrite the write set
-         * @param nexecute the method set
-         */
-        Permissions(final boolean inherit, final Names nread, final Names 
nwrite, final Names nexecute) {
-            this.read = nread != null ? nread : ALLOW_NAMES;
-            this.write = nwrite != null ? nwrite : ALLOW_NAMES;
-            this.execute = nexecute != null ? nexecute : ALLOW_NAMES;
-            this.inheritable = inherit;
-        }
-
-        /**
-         * @return a copy of these permissions
-         */
-        Permissions copy() {
-            return new Permissions(inheritable, read.copy(), write.copy(), 
execute.copy());
-        }
-
-        /**
-         * Gets the set of method names in these permissions.
-         *
-         * @return the set of method names
-         */
-        public Names execute() {
-            return execute;
-        }
-
-        /**
-         * Adds a list of executable methods names to these permissions.
-         * <p>The constructor is denoted as the empty-string, all other 
methods by their names.</p>
-         *
-         * @param methodNames the method names
-         * @return this instance of permissions
-         */
-        public Permissions execute(final String... methodNames) {
-            for (final String methodName : methodNames) {
-                execute.add(methodName);
-            }
-            return this;
-        }
-
-        /**
-         * @return whether these permissions apply to derived classes.
-         */
-        public boolean isInheritable() {
-            return inheritable;
-        }
-
-        /**
-         * Gets the set of readable property names in these permissions.
-         *
-         * @return the set of property names
-         */
-        public Names read() {
-            return read;
-        }
-
-        /**
-         * Adds a list of readable property names to these permissions.
-         *
-         * @param propertyNames the property names
-         * @return this instance of permissions
-         */
-        public Permissions read(final String... propertyNames) {
-            for (final String propertyName : propertyNames) {
-                read.add(propertyName);
-            }
-            return this;
-        }
-
-        /**
-         * Gets the set of writable property names in these permissions.
-         *
-         * @return the set of property names
-         */
-        public Names write() {
-            return write;
-        }
-
-        /**
-         * Adds a list of writable property names to these permissions.
-         *
-         * @param propertyNames the property names
-         * @return this instance of permissions
-         */
-        public Permissions write(final String... propertyNames) {
-            for (final String propertyName : propertyNames) {
-                write.add(propertyName);
-            }
-            return this;
-        }
-    }
-
-    /**
-     * @deprecated since 3.2, use {@link AllowSet}
-     */
-    @Deprecated
-    public static final class WhiteSet extends AllowSet {}
-
-    /**
-     * The marker string for explicitly disallowed null properties.
-     */
-    public static final String NULL = "?";
-
-    /**
-     * The pass-thru name set.
-     */
-    static final Names ALLOW_NAMES = new Names() {
-        @Override
-        public boolean add(final String name) {
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            return "allowAll";
-        }
-    };
-
-    /**
-     * The block-all name set.
-     */
-    private static final Names BLOCK_NAMES = new Names() {
-        @Override
-        public boolean add(final String name) {
-            return false;
-        }
-
-        @Override
-        public String get(final String name) {
-            return name == null ? NULL : null;
-        }
-
-        @Override
-        public String toString() {
-            return "blockAll";
-        }
-    };
-
-    /**
-     * The pass-thru permissions.
-     */
-    private static final Permissions ALLOW_ALL = new Permissions(false, 
ALLOW_NAMES, ALLOW_NAMES, ALLOW_NAMES);
-
-    /**
-     * The block-all permissions.
-     */
-    private static final Permissions BLOCK_ALL = new Permissions(false, 
BLOCK_NAMES, BLOCK_NAMES, BLOCK_NAMES);
-
-    /**
-     * Gets a class by name, crude mechanism for backwards (&lt;3.2 ) 
compatibility.
-     * @param cname the class name
-     * @return the class
-     */
-    static Class<?> forName(final String cname) {
-        try {
-            return Class.forName(cname);
-        } catch (final Exception xany) {
-            return null;
-        }
-    }
-
-    /**
-     * The map from class names to permissions.
-     */
-    private final Map<String, Permissions> sandbox;
-
-    /**
-     * Whether permissions can be inherited (through implementation or 
extension).
-     */
-    private final boolean inherit;
-
-    /**
-     * Default behavior, block or allow.
-     */
-    private final boolean allow;
-
-    /**
-     * Creates a new default sandbox.
-     * <p>In the absence of explicit permissions on a class, the
-     * sandbox is a allow-box, allow-listing that class for all permissions 
(read, write and execute).
-     */
-    public JexlSandbox() {
-        this(true, false, null);
-    }
-
-    /**
-     * Creates a new default sandbox.
-     * <p>A allow-box considers no permissions as &quot;everything is 
allowed&quot; when
-     * a block-box considers no permissions as &quot;nothing is allowed&quot;.
-     * @param ab whether this sandbox is allow (true) or block (false)
-     * if no permission is explicitly defined for a class.
-     * @since 3.1
-     */
-    public JexlSandbox(final boolean ab) {
-        this(ab, false, null);
-    }
-
-    /**
-     * Creates a sandbox.
-     * @param ab whether this sandbox is allow (true) or block (false)
-     * @param inh whether permissions on interfaces and classes are inherited 
(true) or not (false)
-     * @since 3.2
-     */
-    public JexlSandbox(final boolean ab, final boolean inh) {
-        this(ab, inh, null);
-    }
-
-    /**
-     * Creates a sandbox based on an existing permissions map.
-     * @param ab whether this sandbox is allow (true) or block (false)
-     * @param inh whether permissions are inherited, default false
-     * @param map the permissions map
-     * @since 3.2
-     */
-    protected JexlSandbox(final boolean ab, final boolean inh, final 
Map<String, Permissions> map) {
-        allow = ab;
-        inherit = inh;
-        sandbox = map != null ? map : new HashMap<>();
-    }
-
-    /**
-     * Creates a sandbox based on an existing permissions map.
-     * @param ab whether this sandbox is allow (true) or block (false)
-     * @param map the permissions map
-     * @since 3.1
-     */
-    @Deprecated
-    protected JexlSandbox(final boolean ab, final Map<String, Permissions> 
map) {
-        this(ab, false, map);
-    }
-
-    /**
-     * Creates a sandbox based on an existing permissions map.
-     * @param map the permissions map
-     */
-    @Deprecated
-    protected JexlSandbox(final Map<String, Permissions> map) {
-        this(true, false, map);
-    }
-
-    /**
-     * Creates a new set of permissions based on allow lists for methods and 
properties for a given class.
-     * <p>The sandbox inheritance property will apply to the permissions 
created by this method
-     *
-     * @param clazz the allowed class name
-     * @return the permissions instance
-     */
-    public Permissions allow(final String clazz) {
-        return permissions(clazz, true, true, true);
-    }
-
-    /**
-     * Use block() instead.
-     * @param clazz the allowed class name
-     * @return the permissions instance
-     */
-    @Deprecated
-    public Permissions black(final String clazz) {
-        return block(clazz);
-    }
-
-    /**
-     * Creates a new set of permissions based on block lists for methods and 
properties for a given class.
-     * <p>The sandbox inheritance property will apply to the permissions 
created by this method
-     *
-     * @param clazz the blocked class name
-     * @return the permissions instance
-     */
-    public Permissions block(final String clazz) {
-        return permissions(clazz, false, false, false);
-    }
-
-    /**
-     * @return a copy of this sandbox
-     */
-    public JexlSandbox copy() {
-        // modified concurrently at runtime so...
-        final Map<String, Permissions> map = new ConcurrentHashMap<>();
-        for (final Map.Entry<String, Permissions> entry : sandbox.entrySet()) {
-            map.put(entry.getKey(), entry.getValue().copy());
-        }
-        return new JexlSandbox(allow, inherit, map);
-    }
-
-    /**
-     * Gets the execute permission value for a given method of a class.
-     *
-     * @param clazz the class
-     * @param name the method name
-     * @return null if not allowed, the name of the method to use otherwise
-     */
-    public String execute(final Class<?> clazz, final String name) {
-        final String m = get(clazz).execute().get(name);
-        return "".equals(name) && m != null ? clazz.getName() : m;
-    }
-
-    /**
-     * Gets the execute permission value for a given method of a class.
-     *
-     * @param clazz the class name
-     * @param name the method name
-     * @return null if not allowed, the name of the method to use otherwise
-     */
-    @Deprecated
-    public String execute(final String clazz, final String name) {
-        final String m = get(clazz).execute().get(name);
-        return "".equals(name) && m != null ? clazz : m;
-    }
-    /**
-     * Gets the permissions associated to a class.
-     * @param clazz the class
-     * @return the permissions
-     */
-    @SuppressWarnings("null") // clazz can not be null since permissions would 
be not null and block;
-    public Permissions get(final Class<?> clazz) {
-        // we only store the result for classes we actively seek permissions 
for
-        return clazz == null ? BLOCK_ALL : compute(clazz, true);
-    }
-
-    /**
-     * Computes and optionally the permissions associated to a class.
-     * @param clazz the class
-     * @param store whether the resulting permissions should be stored in the 
sandbox
-     * @return the permissions
-     */
-    private Permissions compute(final Class<?> clazz, boolean store) {
-        Permissions permissions = sandbox.get(clazz.getName());
-        if (permissions == null) {
-            if (inherit) {
-                // find first inherited interface that defines permissions
-                for (final Class<?> inter : clazz.getInterfaces()) {
-                    permissions = compute(inter, false);
-                    if (permissions != null) {
-                        if (permissions.isInheritable()) {
-                            break;
-                        }
-                        permissions = null;
-                    }
-                }
-                // nothing defined yet, find first superclass that defines 
permissions
-                if (permissions == null) {
-                    // let's walk all super classes
-                    Class<?> zuper = clazz.getSuperclass();
-                    // walk all superclasses
-                    while (zuper != null) {
-                        permissions = compute(zuper, false);
-                        if (permissions != null) {
-                            if (permissions.isInheritable()) {
-                                break;
-                            }
-                            permissions = null;
-                        }
-                        zuper = zuper.getSuperclass();
-                    }
-                }
-                // nothing was inheritable
-                if (permissions == null) {
-                    permissions = allow ? ALLOW_ALL : BLOCK_ALL;
-                }
-                // store the info to avoid doing this costly look-up
-                if (store) {
-                    sandbox.put(clazz.getName(), permissions);
-                }
-            } else {
-                permissions = allow ? ALLOW_ALL : BLOCK_ALL;
-            }
-        }
-        return permissions;
-    }
-
-    /**
-     * Gets the set of permissions associated to a class.
-     *
-     * @param clazz the class name
-     * @return the defined permissions or an all-allow permission instance if 
none were defined
-     */
-    public Permissions get(final String clazz) {
-        if (inherit) {
-            return get(forName(clazz));
-        }
-        final Permissions permissions = sandbox.get(clazz);
-        if (permissions == null) {
-            return allow ? ALLOW_ALL : BLOCK_ALL;
-        }
-        return permissions;
-    }
-
-    /**
-     * Creates the set of permissions for a given class.
-     * <p>The sandbox inheritance property will apply to the permissions 
created by this method
-     *
-     * @param clazz the class for which these permissions apply
-     * @param readFlag whether the readable property list is allow - true - or 
block - false -
-     * @param writeFlag whether the writable property list is allow - true - 
or block - false -
-     * @param executeFlag whether the executable method list is allow - true - 
or block - false -
-     * @return the set of permissions
-     */
-    public Permissions permissions(final String clazz,
-                                   final boolean readFlag,
-                                   final boolean writeFlag,
-                                   final boolean executeFlag) {
-        return permissions(clazz, inherit, readFlag, writeFlag, executeFlag);
-    }
-
-    /**
-     * Creates the set of permissions for a given class.
-     *
-     * @param clazz the class for which these permissions apply
-     * @param inhf whether these permissions are inheritable
-     * @param readf whether the readable property list is allow - true - or 
block - false -
-     * @param writef whether the writable property list is allow - true - or 
block - false -
-     * @param execf whether the executable method list is allow - true - or 
block - false -
-     * @return the set of permissions
-     */
-    public Permissions permissions(final String clazz,
-                                   final boolean inhf,
-                                   final boolean readf,
-                                   final boolean writef,
-                                   final boolean execf) {
-        final Permissions box = new Permissions(inhf, readf, writef, execf);
-        sandbox.put(clazz, box);
-        return box;
-    }
-    /**
-     * Gets the read permission value for a given property of a class.
-     *
-     * @param clazz the class
-     * @param name the property name
-     * @return null (or NULL if name is null) if not allowed, the name of the 
property to use otherwise
-     */
-    public String read(final Class<?> clazz, final String name) {
-        return get(clazz).read().get(name);
-    }
-
-    /**
-     * Gets the read permission value for a given property of a class.
-     *
-     * @param clazz the class name
-     * @param name the property name
-     * @return null if not allowed, the name of the property to use otherwise
-     */
-    @Deprecated
-    public String read(final String clazz, final String name) {
-        return get(clazz).read().get(name);
-    }
-
-    /**
-     * Use allow() instead.
-     * @param clazz the allowed class name
-     * @return the permissions instance
-     */
-    @Deprecated
-    public Permissions white(final String clazz) {
-        return allow(clazz);
-    }
-
-    /**
-     * Gets the write permission value for a given property of a class.
-     *
-     * @param clazz the class
-     * @param name the property name
-     * @return null (or NULL if name is null) if not allowed, the name of the 
property to use otherwise
-     */
-    public String write(final Class<?> clazz, final String name) {
-        return get(clazz).write().get(name);
-    }
-
-    /**
-     * Gets the write permission value for a given property of a class.
-     *
-     * @param clazz the class name
-     * @param name the property name
-     * @return null if not allowed, the name of the property to use otherwise
-     */
-    @Deprecated
-    public String write(final String clazz, final String name) {
-        return get(clazz).write().get(name);
-    }
+       /**
+        * The marker string for explicitly disallowed null properties.
+        */
+       public static final String NULL = "?";
+
+       /**
+        * The pass-thru name set.
+        */
+       static final Names ALLOW_NAMES = new Names() {
+               @Override
+               public boolean add(final String name) {
+                       return false;
+               }
+
+               @Override
+               public String toString() {
+                       return "allowAll";
+               }
+       };
+
+       /**
+        * The block-all name set.
+        */
+       private static final Names BLOCK_NAMES = new Names() {
+               @Override
+               public boolean add(final String name) {
+                       return false;
+               }
+
+               @Override
+               public String get(final String name) {
+                       return name == null ? NULL : null;
+               }
+
+               @Override
+               public String toString() {
+                       return "blockAll";
+               }
+       };
+
+       /**
+        * The block-all permissions.
+        */
+       private static final Permissions BLOCK_ALL = new Permissions(false, 
BLOCK_NAMES, BLOCK_NAMES, BLOCK_NAMES);
+       /**
+        * The pass-thru permissions.
+        */
+       private static final Permissions ALLOW_ALL = new Permissions(false, 
ALLOW_NAMES, ALLOW_NAMES, ALLOW_NAMES);
+       /**
+        * The map from class names to permissions.
+        */
+       private final Map<String, Permissions> sandbox;
+       /**
+        * Whether permissions can be inherited (through implementation or 
extension).
+        */
+       private final boolean inherit;
+       /**
+        * Default behavior, block or allow.
+        */
+       private final boolean allow;
+
+       /**
+        * Creates a new default sandbox.
+        * <p>In the absence of explicit permissions on a class, the
+        * sandbox is a allow-box, allow-listing that class for all permissions 
(read, write and execute).
+        */
+       public JexlSandbox() {
+               this(true, false, null);
+       }
+
+       /**
+        * Creates a new default sandbox.
+        * <p>A allow-box considers no permissions as &quot;everything is 
allowed&quot; when
+        * a block-box considers no permissions as &quot;nothing is 
allowed&quot;.
+        *
+        * @param ab whether this sandbox is allow (true) or block (false)
+        *           if no permission is explicitly defined for a class.
+        * @since 3.1
+        */
+       public JexlSandbox(final boolean ab) {
+               this(ab, false, null);
+       }
+
+       /**
+        * Creates a sandbox.
+        *
+        * @param ab  whether this sandbox is allow (true) or block (false)
+        * @param inh whether permissions on interfaces and classes are 
inherited (true) or not (false)
+        * @since 3.2
+        */
+       public JexlSandbox(final boolean ab, final boolean inh) {
+               this(ab, inh, null);
+       }
+
+       /**
+        * Creates a sandbox based on an existing permissions map.
+        *
+        * @param ab  whether this sandbox is allow (true) or block (false)
+        * @param inh whether permissions are inherited, default false
+        * @param map the permissions map
+        * @since 3.2
+        */
+       private JexlSandbox(final boolean ab, final boolean inh, final 
Map<String, Permissions> map) {
+               allow = ab;
+               inherit = inh;
+               sandbox = map != null ? map : new HashMap<>();
+       }
+
+       /**
+        * Gets a class by name, crude mechanism for backwards (&lt;3.2 ) 
compatibility.
+        *
+        * @param cname the class name
+        * @return the class
+        */
+       static Class<?> forName(final String cname) {
+               try {
+                       return Class.forName(cname);
+               } catch (final Exception xany) {
+                       return null;
+               }
+       }
+
+       /**
+        * Creates a new set of permissions based on allow lists for methods 
and properties for a given class.
+        * <p>The sandbox inheritance property will apply to the permissions 
created by this method
+        *
+        * @param clazz the allowed class name
+        * @return the permissions instance
+        */
+       public Permissions allow(final String clazz) {
+               return permissions(clazz, true, true, true);
+       }
+
+       /**
+        * Creates a new set of permissions based on block lists for methods 
and properties for a given class.
+        * <p>The sandbox inheritance property will apply to the permissions 
created by this method
+        *
+        * @param clazz the blocked class name
+        * @return the permissions instance
+        */
+       public Permissions block(final String clazz) {
+               return permissions(clazz, false, false, false);
+       }
+
+       /**
+        * @return a copy of this sandbox
+        */
+       public JexlSandbox copy() {
+               // modified concurrently at runtime so...
+               final Map<String, Permissions> map = new ConcurrentHashMap<>();
+               for (final Map.Entry<String, Permissions> entry : 
sandbox.entrySet()) {
+                       map.put(entry.getKey(), entry.getValue().copy());
+               }
+               return new JexlSandbox(allow, inherit, map);
+       }
+
+       /**
+        * Gets the execute permission value for a given method of a class.
+        *
+        * @param clazz the class
+        * @param name  the method name
+        * @return null if not allowed, the name of the method to use otherwise
+        */
+       public String execute(final Class<?> clazz, final String name) {
+               final String m = get(clazz).execute().get(name);
+               return "".equals(name) && m != null ? clazz.getName() : m;
+       }
+
+       /**
+        * Gets the execute permission value for a given method of a class.
+        *
+        * @param clazz the class name
+        * @param name  the method name
+        * @return null if not allowed, the name of the method to use otherwise
+        * @deprecated 3.3
+        */
+       @Deprecated
+       public String execute(final String clazz, final String name) {
+               final String m = get(clazz).execute().get(name);
+               return "".equals(name) && m != null ? clazz : m;
+       }
+
+       /**
+        * Gets the permissions associated to a class.
+        *
+        * @param clazz the class
+        * @return the permissions
+        */
+       @SuppressWarnings("null")
+       public Permissions get(final Class<?> clazz) {
+               // clazz can not be null since permissions would be not null 
and block;
+               // we only store the result for classes we actively seek 
permissions for.
+               return clazz == null ? BLOCK_ALL : compute(clazz);
+       }
+
+       /**
+        * Computes and optionally stores the permissions associated to a class.
+        *
+        * @param clazz the class
+        * @return the permissions
+        */
+       private Permissions compute(final Class<?> clazz) {
+               Permissions permissions = sandbox.get(clazz.getName());
+               if (permissions == null) {
+                       if (inherit) {
+                               // find first inherited interface that defines 
permissions
+                               Class<?>[] interfaces = clazz.getInterfaces();
+                               for (int i = 0; permissions == null && i < 
interfaces.length; ++i) {
+                                       permissions = 
inheritable(sandbox.get(interfaces[i].getName()));
+                               }
+                               // nothing defined yet, find first superclass 
that defines permissions
+                               if (permissions == null) {
+                                       // let's walk all super classes
+                                       for (Class<?> zuper = 
clazz.getSuperclass();
+                                            permissions == null && zuper != 
null;
+                                            zuper = zuper.getSuperclass()) {
+                                               permissions = 
inheritable(sandbox.get(zuper.getName()));
+                                       }
+                               }
+                       }
+                       // nothing was determined through inheritance
+                       if (permissions == null) {
+                               permissions = allow ? ALLOW_ALL : BLOCK_ALL;
+                       }
+                       // store the info to avoid doing this costly look-up
+                       sandbox.put(clazz.getName(), permissions);
+               }
+               return permissions;
+       }
+
+       private Permissions inheritable(Permissions p) {
+               return p != null && p.isInheritable() ? p : null;
+       }
+
+       /**
+        * Gets the set of permissions associated to a class.
+        *
+        * @param clazz the class name
+        * @return the defined permissions or an all-allow permission instance 
if none were defined
+        */
+       public Permissions get(final String clazz) {
+               if (inherit) {
+                       return get(forName(clazz));
+               }
+               final Permissions permissions = sandbox.get(clazz);
+               if (permissions == null) {
+                       return allow ? ALLOW_ALL : BLOCK_ALL;
+               }
+               return permissions;
+       }
+
+       /**
+        * Creates the set of permissions for a given class.
+        * <p>The sandbox inheritance property will apply to the permissions 
created by this method
+        *
+        * @param clazz       the class for which these permissions apply
+        * @param readFlag    whether the readable property list is allow - 
true - or block - false -
+        * @param writeFlag   whether the writable property list is allow - 
true - or block - false -
+        * @param executeFlag whether the executable method list is allow - 
true - or block - false -
+        * @return the set of permissions
+        */
+       public Permissions permissions(final String clazz,
+                                      final boolean readFlag,
+                                      final boolean writeFlag,
+                                      final boolean executeFlag) {
+               return permissions(clazz, inherit, readFlag, writeFlag, 
executeFlag);
+       }
+
+       /**
+        * Creates the set of permissions for a given class.
+        *
+        * @param clazz  the class for which these permissions apply
+        * @param inhf   whether these permissions are inheritable
+        * @param readf  whether the readable property list is allow - true - 
or block - false -
+        * @param writef whether the writable property list is allow - true - 
or block - false -
+        * @param execf  whether the executable method list is allow - true - 
or block - false -
+        * @return the set of permissions
+        */
+       public Permissions permissions(final String clazz,
+                                      final boolean inhf,
+                                      final boolean readf,
+                                      final boolean writef,
+                                      final boolean execf) {
+               final Permissions box = new Permissions(inhf, readf, writef, 
execf);
+               sandbox.put(clazz, box);
+               return box;
+       }
+
+       /**
+        * Gets the read permission value for a given property of a class.
+        *
+        * @param clazz the class
+        * @param name  the property name
+        * @return null (or NULL if name is null) if not allowed, the name of 
the property to use otherwise
+        */
+       public String read(final Class<?> clazz, final String name) {
+               return get(clazz).read().get(name);
+       }
+
+       /**
+        * Gets the read permission value for a given property of a class.
+        *
+        * @param clazz the class name
+        * @param name  the property name
+        * @return null if not allowed, the name of the property to use 
otherwise
+        * @deprecated 3.3
+        */
+       @Deprecated
+       public String read(final String clazz, final String name) {
+               return get(clazz).read().get(name);
+       }
+
+       /**
+        * Use allow() instead.
+        *
+        * @param clazz the allowed class name
+        * @return the permissions instance
+        * @deprecated 3.3
+        */
+       @Deprecated
+       public Permissions white(final String clazz) {
+               return allow(clazz);
+       }
+
+       /**
+        * Gets the write permission value for a given property of a class.
+        *
+        * @param clazz the class
+        * @param name  the property name
+        * @return null (or NULL if name is null) if not allowed, the name of 
the property to use otherwise
+        */
+       public String write(final Class<?> clazz, final String name) {
+               return get(clazz).write().get(name);
+       }
+
+       /**
+        * Gets the write permission value for a given property of a class.
+        *
+        * @param clazz the class name
+        * @param name  the property name
+        * @return null if not allowed, the name of the property to use 
otherwise
+        * @deprecated 3.3
+        */
+       @Deprecated
+       public String write(final String clazz, final String name) {
+               return get(clazz).write().get(name);
+       }
+
+       /**
+        * An allow set of names.
+        */
+       static class AllowSet extends Names {
+               /**
+                * The map of controlled names and aliases.
+                */
+               private Map<String, String> names;
+
+               @Override
+               public boolean add(final String name) {
+                       if (names == null) {
+                               names = new HashMap<>();
+                       }
+                       return names.put(name, name) == null;
+               }
+
+               @Override
+               public boolean alias(final String name, final String alias) {
+                       if (names == null) {
+                               names = new HashMap<>();
+                       }
+                       return names.put(alias, name) == null;
+               }
+
+               @Override
+               protected Names copy() {
+                       final AllowSet copy = new AllowSet();
+                       copy.names = names == null ? null : new 
HashMap<>(names);
+                       return copy;
+               }
+
+               @Override
+               public String get(final String name) {
+                       if (names == null) {
+                               return name;
+                       }
+                       final String actual = names.get(name);
+                       // if null is not explicitly allowed, explicit null aka 
NULL
+                       if (name == null && actual == null && 
!names.containsKey(null)) {
+                               return JexlSandbox.NULL;
+                       }
+                       return actual;
+               }
+
+               @Override
+               public String toString() {
+                       return "allow{" + (names == null ? "all" : 
Objects.toString(names.entrySet())) + "}";
+               }
+       }
+
+       /**
+        * A block set of names.
+        */
+       static class BlockSet extends Names {
+               /**
+                * The set of controlled names.
+                */
+               private Set<String> names;
+
+               @Override
+               public boolean add(final String name) {
+                       if (names == null) {
+                               names = new HashSet<>();
+                       }
+                       return names.add(name);
+               }
+
+               @Override
+               protected Names copy() {
+                       final BlockSet copy = new BlockSet();
+                       copy.names = names == null ? null : new 
HashSet<>(names);
+                       return copy;
+               }
+
+               @Override
+               public String get(final String name) {
+                       // if name is null and contained in set, explicit null 
aka NULL
+                       if (names != null && !names.contains(name)) {
+                               return name;
+                       } else if (name != null) {
+                               return null;
+                       } else {
+                               return NULL;
+                       }
+               }
+
+               @Override
+               public String toString() {
+                       return "block{" + (names == null ? "all" : 
Objects.toString(names)) + "}";
+               }
+       }
+
+       /**
+        * A base set of names.
+        */
+       public abstract static class Names {
+               /**
+                * Adds a name to this set.
+                *
+                * @param name the name to add
+                * @return true if the name was really added, false if not
+                */
+               public abstract boolean add(String name);
+
+               /**
+                * Adds an alias to a name to this set.
+                * <p>This only has an effect on allow lists.</p>
+                *
+                * @param name  the name to alias
+                * @param alias the alias
+                * @return true if the alias was added, false if it was already 
present
+                */
+               public boolean alias(final String name, final String alias) {
+                       return false;
+               }
+
+               /**
+                * @return a copy of these Names
+                */
+               protected Names copy() {
+                       return this;
+               }
+
+               /**
+                * Whether a given name is allowed or not.
+                *
+                * @param name the method/property name to check
+                * @return null (or NULL if name is null) if not allowed, the 
actual name to use otherwise
+                */
+               public String get(final String name) {
+                       return name;
+               }
+       }
+
+       /**
+        * Contains the allow or block lists for properties and methods for a 
given class.
+        */
+       public static final class Permissions {
+               /**
+                * Whether these permissions are inheritable, ie can be used by 
derived classes.
+                */
+               private final boolean inheritable;
+               /**
+                * The controlled readable properties.
+                */
+               private final Names read;
+               /**
+                * The controlled  writable properties.
+                */
+               private final Names write;
+               /**
+                * The controlled methods.
+                */
+               private final Names execute;
+
+               /**
+                * Creates a new permissions instance.
+                *
+                * @param inherit     whether these permissions are inheritable
+                * @param readFlag    whether the read property list is allow 
or block
+                * @param writeFlag   whether the write property list is allow 
or block
+                * @param executeFlag whether the method list is allow of block
+                */
+               Permissions(final boolean inherit, final boolean readFlag, 
final boolean writeFlag, final boolean executeFlag) {
+                       this(inherit,
+                               readFlag ? new AllowSet() : new BlockSet(),
+                               writeFlag ? new AllowSet() : new BlockSet(),
+                               executeFlag ? new AllowSet() : new BlockSet());
+               }
+
+               /**
+                * Creates a new permissions instance.
+                *
+                * @param inherit  whether these permissions are inheritable
+                * @param nread    the read set
+                * @param nwrite   the write set
+                * @param nexecute the method set
+                */
+               Permissions(final boolean inherit, final Names nread, final 
Names nwrite, final Names nexecute) {
+                       this.read = nread != null ? nread : ALLOW_NAMES;
+                       this.write = nwrite != null ? nwrite : ALLOW_NAMES;
+                       this.execute = nexecute != null ? nexecute : 
ALLOW_NAMES;
+                       this.inheritable = inherit;
+               }
+
+               /**
+                * @return a copy of these permissions
+                */
+               Permissions copy() {
+                       return new Permissions(inheritable, read.copy(), 
write.copy(), execute.copy());
+               }
+
+               /**
+                * Gets the set of method names in these permissions.
+                *
+                * @return the set of method names
+                */
+               public Names execute() {
+                       return execute;
+               }
+
+               /**
+                * Adds a list of executable methods names to these permissions.
+                * <p>The constructor is denoted as the empty-string, all other 
methods by their names.</p>
+                *
+                * @param methodNames the method names
+                * @return this instance of permissions
+                */
+               public Permissions execute(final String... methodNames) {
+                       for (final String methodName : methodNames) {
+                               execute.add(methodName);
+                       }
+                       return this;
+               }
+
+               /**
+                * @return whether these permissions apply to derived classes.
+                */
+               public boolean isInheritable() {
+                       return inheritable;
+               }
+
+               /**
+                * Gets the set of readable property names in these permissions.
+                *
+                * @return the set of property names
+                */
+               public Names read() {
+                       return read;
+               }
+
+               /**
+                * Adds a list of readable property names to these permissions.
+                *
+                * @param propertyNames the property names
+                * @return this instance of permissions
+                */
+               public Permissions read(final String... propertyNames) {
+                       for (final String propertyName : propertyNames) {
+                               read.add(propertyName);
+                       }
+                       return this;
+               }
+
+               /**
+                * Gets the set of writable property names in these permissions.
+                *
+                * @return the set of property names
+                */
+               public Names write() {
+                       return write;
+               }
+
+               /**
+                * Adds a list of writable property names to these permissions.
+                *
+                * @param propertyNames the property names
+                * @return this instance of permissions
+                */
+               public Permissions write(final String... propertyNames) {
+                       for (final String propertyName : propertyNames) {
+                               write.add(propertyName);
+                       }
+                       return this;
+               }
+       }
 
 }
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index 812c1ca3..98ca2d1a 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -29,6 +29,7 @@ import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -443,4 +444,41 @@ public class Issues400Test {
         assertNotNull(r);
         assertEquals(42, r);
     }
+
+    public static class SortingContext extends MapContext {
+        /**
+         * Sorts an array using a script to evaluate the property used to 
compare elements.
+         * @param array the elements array
+         * @param expr the property evaluation lambda
+         */
+        public void sort(Object[] array, JexlScript expr) {
+            final Comparator<Object> cmp = new Comparator<Object>() {
+                @Override
+                public int compare(Object o1, Object o2) {
+                    Comparable left = (Comparable<?>) 
expr.execute(SortingContext.this, o1);
+                    Comparable right = (Comparable<?>) 
expr.execute(SortingContext.this, o2);
+                    return left.compareTo(right);
+                }
+            };
+            Arrays.sort(array, cmp);
+        }
+    }
+
+    @Test
+    public void testSortArray() {
+        final JexlEngine jexl = new 
JexlBuilder().safe(false).strict(true).silent(false).create();
+        // test data, json like
+        String src = 
"[{'id':1,'name':'John','type':9},{'id':2,'name':'Doe','type':7},{'id':3,'name':'Doe','type':10}]";
+        Object a =  jexl.createExpression(src).evaluate(null);
+        assertNotNull(a);
+        // row 0 and 1 are not ordered
+        Map[] m = (Map[]) a;
+        assertEquals(9, m[0].get("type"));
+        assertEquals(7, m[1].get("type"));
+        // sort the elements on the type
+        jexl.createScript("array.sort( e -> e.type )", "array").execute(new 
SortingContext(), a);
+        // row 0 and 1 are now ordered
+        assertEquals(7, m[0].get("type"));
+        assertEquals(9, m[1].get("type"));
+    }
 }


Reply via email to