diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlArithmetic.java
new file mode 100644
index 0000000..243997f
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlArithmetic.java
@@ -0,0 +1,1810 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.internal.IntegerRange;
+import aiyh.utils.tool.org.apache.commons.jexl3.internal.LongRange;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlMethod;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Perform arithmetic, implements JexlOperator methods.
+ *
+ *
This is the class to derive to implement new operator behaviors.
+ *
+ * The 5 base arithmetic operators (+, - , *, /, %) follow the same evaluation rules regarding their arguments.
+ *
+ * If both are null, result is 0
+ * If either is a BigDecimal, coerce both to BigDecimal and perform operation
+ * If either is a floating point number, coerce both to Double and perform operation
+ * Else treat as BigInteger, perform operation and attempt to narrow result:
+ *
+ * if both arguments can be narrowed to Integer, narrow result to Integer
+ * if both arguments can be narrowed to Long, narrow result to Long
+ * Else return result as BigInteger
+ *
+ *
+ *
+ *
+ * Note that the only exception thrown by JexlArithmetic is and must be ArithmeticException.
+ *
+ * @see JexlOperator
+ * @since 2.0
+ */
+public class JexlArithmetic {
+
+ /** Marker class for null operand exceptions. */
+ public static class NullOperand extends ArithmeticException {
+ }
+
+ /** Double.MAX_VALUE as BigDecimal. */
+ protected static final BigDecimal BIGD_DOUBLE_MAX_VALUE = BigDecimal.valueOf(Double.MAX_VALUE);
+
+ /** Double.MIN_VALUE as BigDecimal. */
+ protected static final BigDecimal BIGD_DOUBLE_MIN_VALUE = BigDecimal.valueOf(Double.MIN_VALUE);
+
+ /** Long.MAX_VALUE as BigInteger. */
+ protected static final BigInteger BIGI_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE);
+
+ /** Long.MIN_VALUE as BigInteger. */
+ protected static final BigInteger BIGI_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE);
+
+ /** Default BigDecimal scale. */
+ protected static final int BIGD_SCALE = -1;
+
+ /** Whether this JexlArithmetic instance behaves in strict or lenient mode. */
+ private final boolean strict;
+
+ /** The big decimal math context. */
+ private final MathContext mathContext;
+
+ /** The big decimal scale. */
+ private final int mathScale;
+
+ /** The dynamic constructor. */
+ private final Constructor extends JexlArithmetic> ctor;
+
+ /**
+ * Creates a JexlArithmetic.
+ *
If you derive your own arithmetic, implement the
+ * other constructor that may be needed when dealing with options.
+ *
+ * @param astrict whether this arithmetic is strict or lenient
+ */
+ public JexlArithmetic(final boolean astrict) {
+ this(astrict, null, Integer.MIN_VALUE);
+ }
+
+ /**
+ * Creates a JexlArithmetic.
+ *
The constructor to define in derived classes.
+ *
+ * @param astrict whether this arithmetic is lenient or strict
+ * @param bigdContext the math context instance to use for +,-,/,*,% operations on big decimals.
+ * @param bigdScale the scale used for big decimals.
+ */
+ public JexlArithmetic(final boolean astrict, final MathContext bigdContext, final int bigdScale) {
+ this.strict = astrict;
+ this.mathContext = bigdContext == null ? MathContext.DECIMAL128 : bigdContext;
+ this.mathScale = bigdScale == Integer.MIN_VALUE ? BIGD_SCALE : bigdScale;
+ Constructor extends JexlArithmetic> actor = null;
+ try {
+ actor = getClass().getConstructor(boolean.class, MathContext.class, int.class);
+ } catch (final Exception xany) {
+ // ignore
+ }
+ this.ctor = actor;
+ }
+
+ /**
+ * Apply options to this arithmetic which eventually may create another instance.
+ *
+ * @param options the {@link JexlEngine.Options} to use
+ * @return an arithmetic with those options set
+ * @see #createWithOptions(boolean, MathContext, int)
+ */
+ public JexlArithmetic options(final JexlOptions options) {
+ if (options != null) {
+ final boolean ostrict = options.isStrictArithmetic();
+ MathContext bigdContext = options.getMathContext();
+ if (bigdContext == null) {
+ bigdContext = getMathContext();
+ }
+ int bigdScale = options.getMathScale();
+ if (bigdScale == Integer.MIN_VALUE) {
+ bigdScale = getMathScale();
+ }
+ if (ostrict != isStrict()
+ || bigdScale != getMathScale()
+ || bigdContext != getMathContext()) {
+ return createWithOptions(ostrict, bigdContext, bigdScale);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Apply options to this arithmetic which eventually may create another instance.
+ *
+ * @param options the {@link JexlEngine.Options} to use
+ * @return an arithmetic with those options set
+ * @see #createWithOptions(boolean, MathContext, int)
+ * @deprecated 3.2
+ */
+ @Deprecated
+ public JexlArithmetic options(final JexlEngine.Options options) {
+ if (options != null) {
+ Boolean ostrict = options.isStrictArithmetic();
+ if (ostrict == null) {
+ ostrict = isStrict();
+ }
+ MathContext bigdContext = options.getArithmeticMathContext();
+ if (bigdContext == null) {
+ bigdContext = getMathContext();
+ }
+ int bigdScale = options.getArithmeticMathScale();
+ if (bigdScale == Integer.MIN_VALUE) {
+ bigdScale = getMathScale();
+ }
+ if (ostrict != isStrict()
+ || bigdScale != getMathScale()
+ || bigdContext != getMathContext()) {
+ return createWithOptions(ostrict, bigdContext, bigdScale);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Apply options to this arithmetic which eventually may create another instance.
+ *
+ * @param context the context that may extend {@link JexlContext.OptionsHandle} to use
+ * @return a new arithmetic instance or this
+ * @see #createWithOptions(boolean, MathContext, int)
+ * @since 3.1
+ */
+ public JexlArithmetic options(final JexlContext context) {
+ if (context instanceof JexlContext.OptionsHandle) {
+ return options(((JexlContext.OptionsHandle) context).getEngineOptions());
+ }
+ if (context instanceof JexlEngine.Options) {
+ return options((JexlEngine.Options) context);
+ }
+ return this;
+ }
+
+ /**
+ * Creates a JexlArithmetic instance.
+ * Called by options(...) method when another instance of the same class of arithmetic is required.
+ *
+ * @param astrict whether this arithmetic is lenient or strict
+ * @param bigdContext the math context instance to use for +,-,/,*,% operations on big decimals.
+ * @param bigdScale the scale used for big decimals.
+ * @return default is a new JexlArithmetic instance
+ * @see #options(JexlEngine.Options)
+ * @since 3.1
+ */
+ protected JexlArithmetic createWithOptions(final boolean astrict, final MathContext bigdContext, final int bigdScale) {
+ if (ctor != null) {
+ try {
+ return ctor.newInstance(astrict, bigdContext, bigdScale);
+ } catch (IllegalAccessException | IllegalArgumentException
+ | InstantiationException | InvocationTargetException xany) {
+ // it was worth the try
+ }
+ }
+ return new JexlArithmetic(astrict, bigdContext, bigdScale);
+ }
+
+ /**
+ * The interface that uberspects JexlArithmetic classes.
+ *
This allows overloaded operator methods discovery.
+ */
+ public interface Uberspect {
+ /**
+ * Checks whether this uberspect has overloads for a given operator.
+ *
+ * @param operator the operator to check
+ * @return true if an overload exists, false otherwise
+ */
+ boolean overloads(JexlOperator operator);
+
+ /**
+ * Gets the most specific method for an operator.
+ *
+ * @param operator the operator
+ * @param arg the arguments
+ * @return the most specific method or null if no specific override could be found
+ */
+ JexlMethod getOperator(JexlOperator operator, Object... arg);
+ }
+
+ /**
+ * Helper interface used when creating an array literal.
+ *
+ * The default implementation creates an array and attempts to type it strictly.
+ *
+ *
+ * If all objects are of the same type, the array returned will be an array of that same type
+ * If all objects are Numbers, the array returned will be an array of Numbers
+ * If all objects are convertible to a primitive type, the array returned will be an array
+ * of the primitive type
+ *
+ */
+ public interface ArrayBuilder {
+
+ /**
+ * Adds a literal to the array.
+ *
+ * @param value the item to add
+ */
+ void add(Object value);
+
+ /**
+ * Creates the actual "array" instance.
+ *
+ * @param extended true when the last argument is ', ...'
+ * @return the array
+ */
+ Object create(boolean extended);
+ }
+
+ /**
+ * Called by the interpreter when evaluating a literal array.
+ *
+ * @param size the number of elements in the array
+ * @return the array builder
+ */
+ public ArrayBuilder arrayBuilder(final int size) {
+ return new aiyh.utils.tool.org.apache.commons.jexl3.internal.ArrayBuilder(size);
+ }
+
+ /**
+ * Helper interface used when creating a set literal.
+ * The default implementation creates a java.util.HashSet.
+ */
+ public interface SetBuilder {
+ /**
+ * Adds a literal to the set.
+ *
+ * @param value the item to add
+ */
+ void add(Object value);
+
+ /**
+ * Creates the actual "set" instance.
+ *
+ * @return the set
+ */
+ Object create();
+ }
+
+ /**
+ * Called by the interpreter when evaluating a literal set.
+ *
+ * @param size the number of elements in the set
+ * @return the array builder
+ */
+ public SetBuilder setBuilder(final int size) {
+ return new aiyh.utils.tool.org.apache.commons.jexl3.internal.SetBuilder(size);
+ }
+
+ /**
+ * Helper interface used when creating a map literal.
+ * The default implementation creates a java.util.HashMap.
+ */
+ public interface MapBuilder {
+ /**
+ * Adds a new entry to the map.
+ *
+ * @param key the map entry key
+ * @param value the map entry value
+ */
+ void put(Object key, Object value);
+
+ /**
+ * Creates the actual "map" instance.
+ *
+ * @return the map
+ */
+ Object create();
+ }
+
+ /**
+ * Called by the interpreter when evaluating a literal map.
+ *
+ * @param size the number of elements in the map
+ * @return the map builder
+ */
+ public MapBuilder mapBuilder(final int size) {
+ return new aiyh.utils.tool.org.apache.commons.jexl3.internal.MapBuilder(size);
+ }
+
+ /**
+ * Creates a literal range.
+ * The default implementation only accepts integers and longs.
+ *
+ * @param from the included lower bound value (null if none)
+ * @param to the included upper bound value (null if none)
+ * @return the range as an iterable
+ * @throws ArithmeticException as an option if creation fails
+ */
+ public Iterable> createRange(final Object from, final Object to) throws ArithmeticException {
+ final long lfrom = toLong(from);
+ final long lto = toLong(to);
+ if ((lfrom >= Integer.MIN_VALUE && lfrom <= Integer.MAX_VALUE)
+ && (lto >= Integer.MIN_VALUE && lto <= Integer.MAX_VALUE)) {
+ return IntegerRange.create((int) lfrom, (int) lto);
+ }
+ return LongRange.create(lfrom, lto);
+ }
+
+ /**
+ * Checks whether this JexlArithmetic instance
+ * strictly considers null as an error when used as operand unexpectedly.
+ *
+ * @return true if strict, false if lenient
+ */
+ public boolean isStrict() {
+ return this.strict;
+ }
+
+ /**
+ * The MathContext instance used for +,-,/,*,% operations on big decimals.
+ *
+ * @return the math context
+ */
+ public MathContext getMathContext() {
+ return mathContext;
+ }
+
+ /**
+ * The BigDecimal scale used for comparison and coericion operations.
+ *
+ * @return the scale
+ */
+ public int getMathScale() {
+ return mathScale;
+ }
+
+ /**
+ * Ensure a big decimal is rounded by this arithmetic scale and rounding mode.
+ *
+ * @param number the big decimal to round
+ * @return the rounded big decimal
+ */
+ protected BigDecimal roundBigDecimal(final BigDecimal number) {
+ final int mscale = getMathScale();
+ if (mscale >= 0) {
+ return number.setScale(mscale, getMathContext().getRoundingMode());
+ }
+ return number;
+ }
+
+ /**
+ * The result of +,/,-,*,% when both operands are null.
+ *
+ * @return Integer(0) if lenient
+ * @throws ArithmeticException if strict
+ */
+ protected Object controlNullNullOperands() {
+ if (isStrict()) {
+ throw new NullOperand();
+ }
+ return 0;
+ }
+
+ /**
+ * Throw a NPE if arithmetic is strict.
+ *
+ * @throws ArithmeticException if strict
+ */
+ protected void controlNullOperand() {
+ if (isStrict()) {
+ throw new NullOperand();
+ }
+ }
+
+ /**
+ * The float regular expression pattern.
+ *
+ * The decimal and exponent parts are optional and captured allowing to determine if the number is a real
+ * by checking whether one of these 2 capturing groups is not empty.
+ */
+ public static final Pattern FLOAT_PATTERN = Pattern.compile("^[+-]?\\d*(\\.\\d*)?([eE][+-]?\\d+)?$");
+
+ /**
+ * Test if the passed value is a floating point number, i.e. a float, double
+ * or string with ( "." | "E" | "e").
+ *
+ * @param val the object to be tested
+ * @return true if it is, false otherwise.
+ */
+ protected boolean isFloatingPointNumber(final Object val) {
+ if (val instanceof Float || val instanceof Double) {
+ return true;
+ }
+ if (val instanceof CharSequence) {
+ final Matcher m = FLOAT_PATTERN.matcher((CharSequence) val);
+ // first group is decimal, second is exponent;
+ // one of them must exist hence start({1,2}) >= 0
+ return m.matches() && (m.start(1) >= 0 || m.start(2) >= 0);
+ }
+ return false;
+ }
+
+ /**
+ * Is Object a floating point number.
+ *
+ * @param o Object to be analyzed.
+ * @return true if it is a Float or a Double.
+ */
+ protected boolean isFloatingPoint(final Object o) {
+ return o instanceof Float || o instanceof Double;
+ }
+
+ /**
+ * Is Object a whole number.
+ *
+ * @param o Object to be analyzed.
+ * @return true if Integer, Long, Byte, Short or Character.
+ */
+ protected boolean isNumberable(final Object o) {
+ return o instanceof Integer
+ || o instanceof Long
+ || o instanceof Byte
+ || o instanceof Short
+ || o instanceof Character;
+ }
+
+ /**
+ * Given a Number, return back the value using the smallest type the result
+ * will fit into.
+ *
This works hand in hand with parameter 'widening' in java
+ * method calls, e.g. a call to substring(int,int) with an int and a long
+ * will fail, but a call to substring(int,int) with an int and a short will
+ * succeed.
+ *
+ * @param original the original number.
+ * @return a value of the smallest type the original number will fit into.
+ */
+ public Number narrow(final Number original) {
+ return narrowNumber(original, null);
+ }
+
+ /**
+ * Whether we consider the narrow class as a potential candidate for narrowing the source.
+ *
+ * @param narrow the target narrow class
+ * @param source the original source class
+ * @return true if attempt to narrow source to target is accepted
+ */
+ protected boolean narrowAccept(final Class> narrow, final Class> source) {
+ return narrow == null || narrow.equals(source);
+ }
+
+ /**
+ * Given a Number, return back the value attempting to narrow it to a target class.
+ *
+ * @param original the original number
+ * @param narrow the attempted target class
+ * @return the narrowed number or the source if no narrowing was possible
+ */
+ public Number narrowNumber(final Number original, final Class> narrow) {
+ if (original == null) {
+ return null;
+ }
+ Number result = original;
+ if (original instanceof BigDecimal) {
+ final BigDecimal bigd = (BigDecimal) original;
+ // if it's bigger than a double it can't be narrowed
+ if (bigd.compareTo(BIGD_DOUBLE_MAX_VALUE) > 0
+ || bigd.compareTo(BIGD_DOUBLE_MIN_VALUE) < 0) {
+ return original;
+ }
+ try {
+ final long l = bigd.longValueExact();
+ // coerce to int when possible (int being so often used in method parms)
+ if (narrowAccept(narrow, Integer.class)
+ && l <= Integer.MAX_VALUE
+ && l >= Integer.MIN_VALUE) {
+ return (int) l;
+ }
+ if (narrowAccept(narrow, Long.class)) {
+ return l;
+ }
+ } catch (final ArithmeticException xa) {
+ // ignore, no exact value possible
+ }
+ }
+ if (original instanceof Double || original instanceof Float) {
+ final double value = original.doubleValue();
+ if (narrowAccept(narrow, Float.class)
+ && value <= Float.MAX_VALUE
+ && value >= Float.MIN_VALUE) {
+ result = result.floatValue();
+ }
+ // else it fits in a double only
+ } else {
+ if (original instanceof BigInteger) {
+ final BigInteger bigi = (BigInteger) original;
+ // if it's bigger than a Long it can't be narrowed
+ if (bigi.compareTo(BIGI_LONG_MAX_VALUE) > 0
+ || bigi.compareTo(BIGI_LONG_MIN_VALUE) < 0) {
+ return original;
+ }
+ }
+ final long value = original.longValue();
+ if (narrowAccept(narrow, Byte.class)
+ && value <= Byte.MAX_VALUE
+ && value >= Byte.MIN_VALUE) {
+ // it will fit in a byte
+ result = (byte) value;
+ } else if (narrowAccept(narrow, Short.class)
+ && value <= Short.MAX_VALUE
+ && value >= Short.MIN_VALUE) {
+ result = (short) value;
+ } else if (narrowAccept(narrow, Integer.class)
+ && value <= Integer.MAX_VALUE
+ && value >= Integer.MIN_VALUE) {
+ result = (int) value;
+ }
+ // else it fits in a long
+ }
+ return result;
+ }
+
+ /**
+ * Given a BigInteger, narrow it to an Integer or Long if it fits and the arguments
+ * class allow it.
+ *
+ * The rules are:
+ * if either arguments is a BigInteger, no narrowing will occur
+ * if either arguments is a Long, no narrowing to Integer will occur
+ *
+ *
+ * @param lhs the left hand side operand that lead to the bigi result
+ * @param rhs the right hand side operand that lead to the bigi result
+ * @param bigi the BigInteger to narrow
+ * @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
+ */
+ protected Number narrowBigInteger(final Object lhs, final Object rhs, final BigInteger bigi) {
+ // coerce to long if possible
+ if (!(lhs instanceof BigInteger || rhs instanceof BigInteger)
+ && bigi.compareTo(BIGI_LONG_MAX_VALUE) <= 0
+ && bigi.compareTo(BIGI_LONG_MIN_VALUE) >= 0) {
+ // coerce to int if possible
+ final long l = bigi.longValue();
+ // coerce to int when possible (int being so often used in method parms)
+ if (!(lhs instanceof Long || rhs instanceof Long)
+ && l <= Integer.MAX_VALUE
+ && l >= Integer.MIN_VALUE) {
+ return (int) l;
+ }
+ return l;
+ }
+ return bigi;
+ }
+
+ /**
+ * Given a BigDecimal, attempt to narrow it to an Integer or Long if it fits if
+ * one of the arguments is a numberable.
+ *
+ * @param lhs the left hand side operand that lead to the bigd result
+ * @param rhs the right hand side operand that lead to the bigd result
+ * @param bigd the BigDecimal to narrow
+ * @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
+ */
+ protected Number narrowBigDecimal(final Object lhs, final Object rhs, final BigDecimal bigd) {
+ if (isNumberable(lhs) || isNumberable(rhs)) {
+ try {
+ final long l = bigd.longValueExact();
+ // coerce to int when possible (int being so often used in method parms)
+ if (l <= Integer.MAX_VALUE && l >= Integer.MIN_VALUE) {
+ return (int) l;
+ }
+ return l;
+ } catch (final ArithmeticException xa) {
+ // ignore, no exact value possible
+ }
+ }
+ return bigd;
+ }
+
+ /**
+ * Replace all numbers in an arguments array with the smallest type that will fit.
+ *
+ * @param args the argument array
+ * @return true if some arguments were narrowed and args array is modified,
+ * false if no narrowing occurred and args array has not been modified
+ */
+ public boolean narrowArguments(final Object[] args) {
+ boolean narrowed = false;
+ if (args != null) {
+ for (int a = 0; a < args.length; ++a) {
+ final Object arg = args[a];
+ if (arg instanceof Number) {
+ final Number narg = (Number) arg;
+ final Number narrow = narrow(narg);
+ if (!narg.equals(narrow)) {
+ args[a] = narrow;
+ narrowed = true;
+ }
+ }
+ }
+ }
+ return narrowed;
+ }
+
+ /**
+ * Given a long, attempt to narrow it to an int.
+ * Narrowing will only occur if no operand is a Long.
+ *
+ * @param lhs the left hand side operand that lead to the long result
+ * @param rhs the right hand side operand that lead to the long result
+ * @param r the long to narrow
+ * @return an Integer if narrowing is possible, the original Long otherwise
+ */
+ protected Number narrowLong(final Object lhs, final Object rhs, final long r) {
+ if (!(lhs instanceof Long || rhs instanceof Long) && (int) r == r) {
+ return (int) r;
+ }
+ return r;
+ }
+
+ /**
+ * Checks if value class is a number that can be represented exactly in a long.
+ *
+ * @param value argument
+ * @return true if argument can be represented by a long
+ */
+ protected Number asLongNumber(final Object value) {
+ return value instanceof Long
+ || value instanceof Integer
+ || value instanceof Short
+ || value instanceof Byte
+ ? (Number) value
+ : null;
+ }
+
+ /**
+ * Add two values together.
+ *
+ * If any numeric add fails on coercion to the appropriate type,
+ * treat as Strings and do concatenation.
+ *
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return left + right.
+ */
+ public Object add(final Object left, final Object right) {
+ if (left == null && right == null) {
+ return controlNullNullOperands();
+ }
+ final boolean strconcat = strict
+ ? left instanceof String || right instanceof String
+ : left instanceof String && right instanceof String;
+ if (!strconcat) {
+ try {
+ // if both (non null) args fit as long
+ final Number ln = asLongNumber(left);
+ final Number rn = asLongNumber(right);
+ if (ln != null && rn != null) {
+ final long x = ln.longValue();
+ final long y = rn.longValue();
+ final long result = x + y;
+ // detect overflow, see java8 Math.addExact
+ if (((x ^ result) & (y ^ result)) < 0) {
+ return BigInteger.valueOf(x).add(BigInteger.valueOf(y));
+ }
+ return narrowLong(left, right, result);
+ }
+ // if either are bigdecimal use that type
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ final BigDecimal l = toBigDecimal(left);
+ final BigDecimal r = toBigDecimal(right);
+ final BigDecimal result = l.add(r, getMathContext());
+ return narrowBigDecimal(left, right, result);
+ }
+ // if either are floating point (double or float) use double
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+ final double l = toDouble(left);
+ final double r = toDouble(right);
+ return l + r;
+ }
+ // otherwise treat as (big) integers
+ final BigInteger l = toBigInteger(left);
+ final BigInteger r = toBigInteger(right);
+ final BigInteger result = l.add(r);
+ return narrowBigInteger(left, right, result);
+ } catch (final NumberFormatException nfe) {
+ if (left == null || right == null) {
+ controlNullOperand();
+ }
+ }
+ }
+ return toString(left).concat(toString(right));
+ }
+
+ /**
+ * Divide the left value by the right.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return left / right
+ * @throws ArithmeticException if right == 0
+ */
+ public Object divide(final Object left, final Object right) {
+ if (left == null && right == null) {
+ return controlNullNullOperands();
+ }
+ // if both (non null) args fit as long
+ final Number ln = asLongNumber(left);
+ final Number rn = asLongNumber(right);
+ if (ln != null && rn != null) {
+ final long x = ln.longValue();
+ final long y = rn.longValue();
+ if (y == 0L) {
+ throw new ArithmeticException("/");
+ }
+ final long result = x / y;
+ return narrowLong(left, right, result);
+ }
+ // if either are bigdecimal use that type
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ final BigDecimal l = toBigDecimal(left);
+ final BigDecimal r = toBigDecimal(right);
+ if (BigDecimal.ZERO.equals(r)) {
+ throw new ArithmeticException("/");
+ }
+ final BigDecimal result = l.divide(r, getMathContext());
+ return narrowBigDecimal(left, right, result);
+ }
+ // if either are floating point (double or float) use double
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+ final double l = toDouble(left);
+ final double r = toDouble(right);
+ if (r == 0.0) {
+ throw new ArithmeticException("/");
+ }
+ return l / r;
+ }
+ // otherwise treat as integers
+ final BigInteger l = toBigInteger(left);
+ final BigInteger r = toBigInteger(right);
+ if (BigInteger.ZERO.equals(r)) {
+ throw new ArithmeticException("/");
+ }
+ final BigInteger result = l.divide(r);
+ return narrowBigInteger(left, right, result);
+ }
+
+ /**
+ * left value modulo right.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return left % right
+ * @throws ArithmeticException if right == 0.0
+ */
+ public Object mod(final Object left, final Object right) {
+ if (left == null && right == null) {
+ return controlNullNullOperands();
+ }
+ // if both (non null) args fit as long
+ final Number ln = asLongNumber(left);
+ final Number rn = asLongNumber(right);
+ if (ln != null && rn != null) {
+ final long x = ln.longValue();
+ final long y = rn.longValue();
+ if (y == 0L) {
+ throw new ArithmeticException("%");
+ }
+ final long result = x % y;
+ return narrowLong(left, right, result);
+ }
+ // if either are bigdecimal use that type
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ final BigDecimal l = toBigDecimal(left);
+ final BigDecimal r = toBigDecimal(right);
+ if (BigDecimal.ZERO.equals(r)) {
+ throw new ArithmeticException("%");
+ }
+ final BigDecimal remainder = l.remainder(r, getMathContext());
+ return narrowBigDecimal(left, right, remainder);
+ }
+ // if either are floating point (double or float) use double
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+ final double l = toDouble(left);
+ final double r = toDouble(right);
+ if (r == 0.0) {
+ throw new ArithmeticException("%");
+ }
+ return l % r;
+ }
+ // otherwise treat as integers
+ final BigInteger l = toBigInteger(left);
+ final BigInteger r = toBigInteger(right);
+ if (BigInteger.ZERO.equals(r)) {
+ throw new ArithmeticException("%");
+ }
+ final BigInteger result = l.mod(r);
+ return narrowBigInteger(left, right, result);
+ }
+
+ /**
+ * Checks if the product of the arguments overflows a {@code long}.
+ * see java8 Math.multiplyExact
+ *
+ * @param x the first value
+ * @param y the second value
+ * @param r the product
+ * @return true if product fits a long, false if it overflows
+ */
+ @SuppressWarnings("MagicNumber")
+ protected static boolean isMultiplyExact(final long x, final long y, final long r) {
+ final long ax = Math.abs(x);
+ final long ay = Math.abs(y);
+ return !(((ax | ay) >>> (Integer.SIZE - 1) != 0)
+ && (((y != 0) && (r / y != x))
+ || (x == Long.MIN_VALUE && y == -1)));
+ }
+
+ /**
+ * Multiply the left value by the right.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return left * right.
+ */
+ public Object multiply(final Object left, final Object right) {
+ if (left == null && right == null) {
+ return controlNullNullOperands();
+ }
+ // if both (non null) args fit as int
+ final Number ln = asLongNumber(left);
+ final Number rn = asLongNumber(right);
+ if (ln != null && rn != null) {
+ final long x = ln.longValue();
+ final long y = rn.longValue();
+ final long result = x * y;
+ // detect overflow
+ if (!isMultiplyExact(x, y, result)) {
+ return BigInteger.valueOf(x).multiply(BigInteger.valueOf(y));
+ }
+ return narrowLong(left, right, result);
+ }
+ // if either are bigdecimal use that type
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ final BigDecimal l = toBigDecimal(left);
+ final BigDecimal r = toBigDecimal(right);
+ final BigDecimal result = l.multiply(r, getMathContext());
+ return narrowBigDecimal(left, right, result);
+ }
+ // if either are floating point (double or float) use double
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+ final double l = toDouble(left);
+ final double r = toDouble(right);
+ return l * r;
+ }
+ // otherwise treat as integers
+ final BigInteger l = toBigInteger(left);
+ final BigInteger r = toBigInteger(right);
+ final BigInteger result = l.multiply(r);
+ return narrowBigInteger(left, right, result);
+ }
+
+ /**
+ * Subtract the right value from the left.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return left - right.
+ */
+ public Object subtract(final Object left, final Object right) {
+ if (left == null && right == null) {
+ return controlNullNullOperands();
+ }
+ // if both (non null) args fit as long
+ final Number ln = asLongNumber(left);
+ final Number rn = asLongNumber(right);
+ if (ln != null && rn != null) {
+ final long x = ln.longValue();
+ final long y = rn.longValue();
+ final long result = x - y;
+ // detect overflow, see java8 Math.subtractExact
+ if (((x ^ y) & (x ^ result)) < 0) {
+ return BigInteger.valueOf(x).subtract(BigInteger.valueOf(y));
+ }
+ return narrowLong(left, right, result);
+ }
+ // if either are bigdecimal use that type
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ final BigDecimal l = toBigDecimal(left);
+ final BigDecimal r = toBigDecimal(right);
+ final BigDecimal result = l.subtract(r, getMathContext());
+ return narrowBigDecimal(left, right, result);
+ }
+ // if either are floating point (double or float) use double
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+ final double l = toDouble(left);
+ final double r = toDouble(right);
+ return l - r;
+ }
+ // otherwise treat as integers
+ final BigInteger l = toBigInteger(left);
+ final BigInteger r = toBigInteger(right);
+ final BigInteger result = l.subtract(r);
+ return narrowBigInteger(left, right, result);
+ }
+
+ /**
+ * Negates a value (unary minus for numbers).
+ *
+ * @param val the value to negate
+ * @return the negated value
+ */
+ public Object negate(final Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return null;
+ }
+ if (val instanceof Integer) {
+ return -((Integer) val);
+ }
+ if (val instanceof Double) {
+ return -((Double) val);
+ }
+ if (val instanceof Long) {
+ return -((Long) val);
+ }
+ if (val instanceof BigDecimal) {
+ return ((BigDecimal) val).negate();
+ }
+ if (val instanceof BigInteger) {
+ return ((BigInteger) val).negate();
+ }
+ if (val instanceof Float) {
+ return -((Float) val);
+ }
+ if (val instanceof Short) {
+ return (short) -((Short) val);
+ }
+ if (val instanceof Byte) {
+ return (byte) -((Byte) val);
+ }
+ if (val instanceof Boolean) {
+ return !(Boolean) val;
+ }
+ if (val instanceof AtomicBoolean) {
+ return !((AtomicBoolean) val).get();
+ }
+ throw new ArithmeticException("Object negate:(" + val + ")");
+ }
+
+ /**
+ * Whether negate called with a given argument will always return the same result.
+ *
This is used to determine whether negate results on number literals can be cached.
+ * If the result on calling negate with the same constant argument may change between calls,
+ * which means the function is not deterministic, this method must return false.
+ *
+ * @return true if negate is idempotent, false otherwise
+ * @see #isNegateStable()
+ */
+ public boolean isNegateStable() {
+ return true;
+ }
+
+ /**
+ * Positivize value (unary plus for numbers).
+ *
C/C++/C#/Java perform integral promotion of the operand, ie
+ * cast to int if type can represented as int without loss of precision.
+ *
+ * @param val the value to positivize
+ * @return the positive value
+ * @see #isPositivizeStable()
+ */
+ public Object positivize(final Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return null;
+ }
+ if (val instanceof Short) {
+ return ((Short) val).intValue();
+ }
+ if (val instanceof Byte) {
+ return ((Byte) val).intValue();
+ }
+ if (val instanceof Number) {
+ return val;
+ }
+ if (val instanceof Character) {
+ return (int) (Character) val;
+ }
+ if (val instanceof Boolean) {
+ return val;
+ }
+ if (val instanceof AtomicBoolean) {
+ return ((AtomicBoolean) val).get();
+ }
+ throw new ArithmeticException("Object positivize:(" + val + ")");
+ }
+
+ /**
+ * Whether positivize called with a given argument will always return the same result.
+ *
This is used to determine whether positivize results on number literals can be cached.
+ * If the result on calling positivize with the same constant argument may change between calls,
+ * which means the function is not deterministic, this method must return false.
+ *
+ * @return true if positivize is idempotent, false otherwise
+ */
+ public boolean isPositivizeStable() {
+ return true;
+ }
+
+ /**
+ * Test if left contains right (right matches/in left).
+ *
Beware that this method arguments are the opposite of the operator arguments.
+ * 'x in y' means 'y contains x'.
+ *
+ * @param container the container
+ * @param value the value
+ * @return test result or null if there is no arithmetic solution
+ */
+ public Boolean contains(final Object container, final Object value) {
+ if (value == null && container == null) {
+ // if both are null L == R
+ return true;
+ }
+ if (value == null || container == null) {
+ // we know both aren't null, therefore L != R
+ return false;
+ }
+ // use arithmetic / pattern matching ?
+ if (container instanceof Pattern) {
+ return ((Pattern) container).matcher(value.toString()).matches();
+ }
+ if (container instanceof CharSequence) {
+ return value.toString().matches(container.toString());
+ }
+ // try contains on map key
+ if (container instanceof Map, ?>) {
+ if (value instanceof Map, ?>) {
+ return ((Map, ?>) container).keySet().containsAll(((Map, ?>) value).keySet());
+ }
+ return ((Map, ?>) container).containsKey(value);
+ }
+ // try contains on collection
+ if (container instanceof Collection>) {
+ if (value instanceof Collection>) {
+ return ((Collection>) container).containsAll((Collection>) value);
+ }
+ // left in right ? <=> right.contains(left) ?
+ return ((Collection>) container).contains(value);
+ }
+ return null;
+ }
+
+ /**
+ * Test if left ends with right.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return left $= right if there is no arithmetic solution
+ */
+ public Boolean endsWith(final Object left, final Object right) {
+ if (left == null && right == null) {
+ // if both are null L == R
+ return true;
+ }
+ if (left == null || right == null) {
+ // we know both aren't null, therefore L != R
+ return false;
+ }
+ if (left instanceof CharSequence) {
+ return (toString(left)).endsWith(toString(right));
+ }
+ return null;
+ }
+
+ /**
+ * Test if left starts with right.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return left ^= right or null if there is no arithmetic solution
+ */
+ public Boolean startsWith(final Object left, final Object right) {
+ if (left == null && right == null) {
+ // if both are null L == R
+ return true;
+ }
+ if (left == null || right == null) {
+ // we know both aren't null, therefore L != R
+ return false;
+ }
+ if (left instanceof CharSequence) {
+ return (toString(left)).startsWith(toString(right));
+ }
+ return null;
+ }
+
+ /**
+ * Check for emptiness of various types: Number, Collection, Array, Map, String.
+ * Override or overload this method to add new signatures to the size operators.
+ *
+ * @param object the object to check the emptiness of
+ * @return the boolean or false if object is not null
+ * @since 3.2
+ */
+ public Boolean empty(final Object object) {
+ return object == null || isEmpty(object, false);
+ }
+
+ /**
+ * Check for emptiness of various types: Number, Collection, Array, Map, String.
+ *
+ * @param object the object to check the emptiness of
+ * @return the boolean or null if there is no arithmetic solution
+ */
+ public Boolean isEmpty(final Object object) {
+ return isEmpty(object, object == null);
+ }
+
+ /**
+ * Check for emptiness of various types: Number, Collection, Array, Map, String.
+ *
+ * @param object the object to check the emptiness of
+ * @param def the default value if object emptyness can not be determined
+ * @return the boolean or null if there is no arithmetic solution
+ */
+ public Boolean isEmpty(final Object object, final Boolean def) {
+ if (object instanceof Number) {
+ final double d = ((Number) object).doubleValue();
+ return Double.isNaN(d) || d == 0.d;
+ }
+ if (object instanceof CharSequence) {
+ return ((CharSequence) object).length() == 0;
+ }
+ if (object.getClass().isArray()) {
+ return Array.getLength(object) == 0;
+ }
+ if (object instanceof Collection>) {
+ return ((Collection>) object).isEmpty();
+ }
+ // Map isn't a collection
+ if (object instanceof Map, ?>) {
+ return ((Map, ?>) object).isEmpty();
+ }
+ return def;
+ }
+
+ /**
+ * Calculate the size
of various types: Collection, Array, Map, String.
+ *
+ * @param object the object to get the size of
+ * @return the size of object, 0 if null, 1 if there is no better solution
+ */
+ public Integer size(final Object object) {
+ return size(object, object == null ? 0 : 1);
+ }
+
+ /**
+ * Calculate the size
of various types: Collection, Array, Map, String.
+ *
+ * @param object the object to get the size of
+ * @param def the default value if object size can not be determined
+ * @return the size of object or null if there is no arithmetic solution
+ */
+ public Integer size(final Object object, final Integer def) {
+ if (object instanceof CharSequence) {
+ return ((CharSequence) object).length();
+ }
+ if (object.getClass().isArray()) {
+ return Array.getLength(object);
+ }
+ if (object instanceof Collection>) {
+ return ((Collection>) object).size();
+ }
+ if (object instanceof Map, ?>) {
+ return ((Map, ?>) object).size();
+ }
+ return def;
+ }
+
+ /**
+ * Performs a bitwise and.
+ *
+ * @param left the left operand
+ * @param right the right operator
+ * @return left & right
+ */
+ public Object and(final Object left, final Object right) {
+ final long l = toLong(left);
+ final long r = toLong(right);
+ return l & r;
+ }
+
+ /**
+ * Performs a bitwise or.
+ *
+ * @param left the left operand
+ * @param right the right operator
+ * @return left | right
+ */
+ public Object or(final Object left, final Object right) {
+ final long l = toLong(left);
+ final long r = toLong(right);
+ return l | r;
+ }
+
+ /**
+ * Performs a bitwise xor.
+ *
+ * @param left the left operand
+ * @param right the right operator
+ * @return left ^ right
+ */
+ public Object xor(final Object left, final Object right) {
+ final long l = toLong(left);
+ final long r = toLong(right);
+ return l ^ r;
+ }
+
+ /**
+ * Performs a bitwise complement.
+ *
+ * @param val the operand
+ * @return ~val
+ */
+ public Object complement(final Object val) {
+ final long l = toLong(val);
+ return ~l;
+ }
+
+ /**
+ * Performs a logical not.
+ *
+ * @param val the operand
+ * @return !val
+ */
+ public Object not(final Object val) {
+ return !toBoolean(val);
+ }
+
+ /**
+ * Performs a comparison.
+ *
+ * @param left the left operand
+ * @param right the right operator
+ * @param operator the operator
+ * @return -1 if left < right; +1 if left > right; 0 if left == right
+ * @throws ArithmeticException if either left or right is null
+ */
+ protected int compare(final Object left, final Object right, final String operator) {
+ if (left != null && right != null) {
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ final BigDecimal l = toBigDecimal(left);
+ final BigDecimal r = toBigDecimal(right);
+ return l.compareTo(r);
+ }
+ if (left instanceof BigInteger || right instanceof BigInteger) {
+ final BigInteger l = toBigInteger(left);
+ final BigInteger r = toBigInteger(right);
+ return l.compareTo(r);
+ }
+ if (isFloatingPoint(left) || isFloatingPoint(right)) {
+ final double lhs = toDouble(left);
+ final double rhs = toDouble(right);
+ if (Double.isNaN(lhs)) {
+ if (Double.isNaN(rhs)) {
+ return 0;
+ }
+ return -1;
+ }
+ if (Double.isNaN(rhs)) {
+ // lhs is not NaN
+ return +1;
+ }
+ if (lhs < rhs) {
+ return -1;
+ }
+ if (lhs > rhs) {
+ return +1;
+ }
+ return 0;
+ }
+ if (isNumberable(left) || isNumberable(right)) {
+ final long lhs = toLong(left);
+ final long rhs = toLong(right);
+ if (lhs < rhs) {
+ return -1;
+ }
+ if (lhs > rhs) {
+ return +1;
+ }
+ return 0;
+ }
+ if (left instanceof String || right instanceof String) {
+ return toString(left).compareTo(toString(right));
+ }
+ if ("==".equals(operator)) {
+ return left.equals(right) ? 0 : -1;
+ }
+ if (left instanceof Comparable>) {
+ @SuppressWarnings("unchecked") // OK because of instanceof check above
+ final Comparable comparable = (Comparable) left;
+ return comparable.compareTo(right);
+ }
+ if (right instanceof Comparable>) {
+ @SuppressWarnings("unchecked") // OK because of instanceof check above
+ final Comparable comparable = (Comparable) right;
+ return comparable.compareTo(left);
+ }
+ }
+ throw new ArithmeticException("Object comparison:(" + left + " " + operator + " " + right + ")");
+ }
+
+ /**
+ * Test if left and right are equal.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return the test result
+ */
+ public boolean equals(final Object left, final Object right) {
+ if (left == right) {
+ return true;
+ }
+ if (left == null || right == null) {
+ return false;
+ }
+ if (left instanceof Boolean || right instanceof Boolean) {
+ return toBoolean(left) == toBoolean(right);
+ }
+ return compare(left, right, "==") == 0;
+ }
+
+ /**
+ * Test if left < right.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return the test result
+ */
+ public boolean lessThan(final Object left, final Object right) {
+ if ((left == right) || (left == null) || (right == null)) {
+ return false;
+ }
+ return compare(left, right, "<") < 0;
+
+ }
+
+ /**
+ * Test if left > right.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return the test result
+ */
+ public boolean greaterThan(final Object left, final Object right) {
+ if ((left == right) || left == null || right == null) {
+ return false;
+ }
+ return compare(left, right, ">") > 0;
+ }
+
+ /**
+ * Test if left <= right.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return the test result
+ */
+ public boolean lessThanOrEqual(final Object left, final Object right) {
+ if (left == right) {
+ return true;
+ }
+ if (left == null || right == null) {
+ return false;
+ }
+ return compare(left, right, "<=") <= 0;
+ }
+
+ /**
+ * Test if left >= right.
+ *
+ * @param left left argument
+ * @param right right argument
+ * @return the test result
+ */
+ public boolean greaterThanOrEqual(final Object left, final Object right) {
+ if (left == right) {
+ return true;
+ }
+ if (left == null || right == null) {
+ return false;
+ }
+ return compare(left, right, ">=") >= 0;
+ }
+
+ /**
+ * Coerce to a primitive boolean.
+ * Double.NaN, null, "false" and empty string coerce to false.
+ *
+ * @param val value to coerce
+ * @return the boolean value if coercion is possible, true if value was not null.
+ */
+ public boolean toBoolean(final Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return false;
+ }
+ if (val instanceof Boolean) {
+ return ((Boolean) val);
+ }
+ if (val instanceof Number) {
+ final double number = toDouble(val);
+ return !Double.isNaN(number) && number != 0.d;
+ }
+ if (val instanceof AtomicBoolean) {
+ return ((AtomicBoolean) val).get();
+ }
+ if (val instanceof String) {
+ final String strval = val.toString();
+ return !strval.isEmpty() && !"false".equals(strval);
+ }
+ // non null value is true
+ return true;
+ }
+
+ /**
+ * Coerce to a primitive int.
+ * Double.NaN, null and empty string coerce to zero.
+ * Boolean false is 0, true is 1.
+ *
+ * @param val value to coerce
+ * @return the value coerced to int
+ * @throws ArithmeticException if val is null and mode is strict or if coercion is not possible
+ */
+ public int toInteger(final Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return 0;
+ }
+ if (val instanceof Double) {
+ final Double dval = (Double) val;
+ if (Double.isNaN(dval)) {
+ return 0;
+ }
+ return dval.intValue();
+ }
+ if (val instanceof Number) {
+ return ((Number) val).intValue();
+ }
+ if (val instanceof String) {
+ if ("".equals(val)) {
+ return 0;
+ }
+ return Integer.parseInt((String) val);
+ }
+ if (val instanceof Boolean) {
+ return ((Boolean) val) ? 1 : 0;
+ }
+ if (val instanceof AtomicBoolean) {
+ return ((AtomicBoolean) val).get() ? 1 : 0;
+ }
+ if (val instanceof Character) {
+ return ((Character) val);
+ }
+
+ throw new ArithmeticException("Integer coercion: "
+ + val.getClass().getName() + ":(" + val + ")");
+ }
+
+ /**
+ * Coerce to a primitive long.
+ * Double.NaN, null and empty string coerce to zero.
+ * Boolean false is 0, true is 1.
+ *
+ * @param val value to coerce
+ * @return the value coerced to long
+ * @throws ArithmeticException if value is null and mode is strict or if coercion is not possible
+ */
+ public long toLong(final Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return 0L;
+ }
+ if (val instanceof Double) {
+ final Double dval = (Double) val;
+ if (Double.isNaN(dval)) {
+ return 0L;
+ }
+ return dval.longValue();
+ }
+ if (val instanceof Number) {
+ return ((Number) val).longValue();
+ }
+ if (val instanceof String) {
+ if ("".equals(val)) {
+ return 0L;
+ }
+ return Long.parseLong((String) val);
+ }
+ if (val instanceof Boolean) {
+ return ((Boolean) val) ? 1L : 0L;
+ }
+ if (val instanceof AtomicBoolean) {
+ return ((AtomicBoolean) val).get() ? 1L : 0L;
+ }
+ if (val instanceof Character) {
+ return ((Character) val);
+ }
+
+ throw new ArithmeticException("Long coercion: "
+ + val.getClass().getName() + ":(" + val + ")");
+ }
+
+ /**
+ * Coerce to a BigInteger.
+ * Double.NaN, null and empty string coerce to zero.
+ * Boolean false is 0, true is 1.
+ *
+ * @param val the object to be coerced.
+ * @return a BigDecimal
+ * @throws ArithmeticException if val is null and mode is strict or if coercion is not possible
+ */
+ public BigInteger toBigInteger(final Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return BigInteger.ZERO;
+ }
+ if (val instanceof BigInteger) {
+ return (BigInteger) val;
+ }
+ if (val instanceof Double) {
+ final Double dval = (Double) val;
+ if (Double.isNaN(dval)) {
+ return BigInteger.ZERO;
+ }
+ return BigInteger.valueOf(dval.longValue());
+ }
+ if (val instanceof BigDecimal) {
+ return ((BigDecimal) val).toBigInteger();
+ }
+ if (val instanceof Number) {
+ return BigInteger.valueOf(((Number) val).longValue());
+ }
+ if (val instanceof Boolean) {
+ return BigInteger.valueOf(((Boolean) val) ? 1L : 0L);
+ }
+ if (val instanceof AtomicBoolean) {
+ return BigInteger.valueOf(((AtomicBoolean) val).get() ? 1L : 0L);
+ }
+ if (val instanceof String) {
+ final String string = (String) val;
+ if ("".equals(string)) {
+ return BigInteger.ZERO;
+ }
+ return new BigInteger(string);
+ }
+ if (val instanceof Character) {
+ final int i = ((Character) val);
+ return BigInteger.valueOf(i);
+ }
+
+ throw new ArithmeticException("BigInteger coercion: "
+ + val.getClass().getName() + ":(" + val + ")");
+ }
+
+ /**
+ * Coerce to a BigDecimal.
+ * Double.NaN, null and empty string coerce to zero.
+ * Boolean false is 0, true is 1.
+ *
+ * @param val the object to be coerced.
+ * @return a BigDecimal.
+ * @throws ArithmeticException if val is null and mode is strict or if coercion is not possible
+ */
+ public BigDecimal toBigDecimal(final Object val) {
+ if (val instanceof BigDecimal) {
+ return roundBigDecimal((BigDecimal) val);
+ }
+ if (val == null) {
+ controlNullOperand();
+ return BigDecimal.ZERO;
+ }
+ if (val instanceof Double) {
+ if (Double.isNaN(((Double) val))) {
+ return BigDecimal.ZERO;
+ }
+ return roundBigDecimal(new BigDecimal(val.toString(), getMathContext()));
+ }
+ if (val instanceof Number) {
+ return roundBigDecimal(new BigDecimal(val.toString(), getMathContext()));
+ }
+ if (val instanceof Boolean) {
+ return BigDecimal.valueOf(((Boolean) val) ? 1. : 0.);
+ }
+ if (val instanceof AtomicBoolean) {
+ return BigDecimal.valueOf(((AtomicBoolean) val).get() ? 1L : 0L);
+ }
+ if (val instanceof String) {
+ final String string = (String) val;
+ if ("".equals(string)) {
+ return BigDecimal.ZERO;
+ }
+ return roundBigDecimal(new BigDecimal(string, getMathContext()));
+ }
+ if (val instanceof Character) {
+ final int i = ((Character) val);
+ return new BigDecimal(i);
+ }
+ throw new ArithmeticException("BigDecimal coercion: "
+ + val.getClass().getName() + ":(" + val + ")");
+ }
+
+ /**
+ * Coerce to a primitive double.
+ * Double.NaN, null and empty string coerce to zero.
+ * Boolean false is 0, true is 1.
+ *
+ * @param val value to coerce.
+ * @return The double coerced value.
+ * @throws ArithmeticException if val is null and mode is strict or if coercion is not possible
+ */
+ public double toDouble(final Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return 0;
+ }
+ if (val instanceof Double) {
+ return ((Double) val);
+ }
+ if (val instanceof Number) {
+ // The below construct is used rather than ((Number)val).doubleValue() to ensure
+ // equality between comparing new Double( 6.4 / 3 ) and the jexl expression of 6.4 / 3
+ return Double.parseDouble(String.valueOf(val));
+ }
+ if (val instanceof Boolean) {
+ return ((Boolean) val) ? 1. : 0.;
+ }
+ if (val instanceof AtomicBoolean) {
+ return ((AtomicBoolean) val).get() ? 1. : 0.;
+ }
+ if (val instanceof String) {
+ final String string = (String) val;
+ if ("".equals(string)) {
+ return Double.NaN;
+ }
+ // the spec seems to be iffy about this. Going to give it a wack anyway
+ return Double.parseDouble(string);
+ }
+ if (val instanceof Character) {
+ final int i = ((Character) val);
+ return i;
+ }
+ throw new ArithmeticException("Double coercion: "
+ + val.getClass().getName() + ":(" + val + ")");
+ }
+
+ /**
+ * Coerce to a string.
+ * Double.NaN coerce to the empty string.
+ *
+ * @param val value to coerce.
+ * @return The String coerced value.
+ * @throws ArithmeticException if val is null and mode is strict or if coercion is not possible
+ */
+ public String toString(final Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return "";
+ }
+ if (!(val instanceof Double)) {
+ return val.toString();
+ }
+ final Double dval = (Double) val;
+ if (Double.isNaN(dval)) {
+ return "";
+ }
+ return dval.toString();
+ }
+
+ /**
+ * Use or overload and() instead.
+ *
+ * @param lhs left hand side
+ * @param rhs right hand side
+ * @return lhs & rhs
+ * @see JexlArithmetic#and
+ * @deprecated
+ */
+ @Deprecated
+ public final Object bitwiseAnd(final Object lhs, final Object rhs) {
+ return and(lhs, rhs);
+ }
+
+ /**
+ * Use or overload or() instead.
+ *
+ * @param lhs left hand side
+ * @param rhs right hand side
+ * @return lhs | rhs
+ * @see JexlArithmetic#or
+ * @deprecated
+ */
+ @Deprecated
+ public final Object bitwiseOr(final Object lhs, final Object rhs) {
+ return or(lhs, rhs);
+ }
+
+ /**
+ * Use or overload xor() instead.
+ *
+ * @param lhs left hand side
+ * @param rhs right hand side
+ * @return lhs ^ rhs
+ * @see JexlArithmetic#xor
+ * @deprecated
+ */
+ @Deprecated
+ public final Object bitwiseXor(final Object lhs, final Object rhs) {
+ return xor(lhs, rhs);
+ }
+
+ /**
+ * Use or overload not() instead.
+ *
+ * @param arg argument
+ * @return !arg
+ * @see JexlArithmetic#not
+ * @deprecated
+ */
+ @Deprecated
+ public final Object logicalNot(final Object arg) {
+ return not(arg);
+ }
+
+ /**
+ * Use or overload contains() instead.
+ *
+ * @param lhs left hand side
+ * @param rhs right hand side
+ * @return contains(rhs, lhs)
+ * @see JexlArithmetic#contains
+ * @deprecated
+ */
+ @Deprecated
+ public final Object matches(final Object lhs, final Object rhs) {
+ return contains(rhs, lhs);
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlBuilder.java
new file mode 100644
index 0000000..3ebc1ea
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlBuilder.java
@@ -0,0 +1,551 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.internal.Engine;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlSandbox;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlUberspect;
+import org.apache.commons.logging.Log;
+
+import java.util.Map;
+import java.nio.charset.Charset;
+
+/**
+ * Configure and builds a JexlEngine.
+ *
+ * The setSilent
and setStrict
methods allow to fine-tune an engine instance behavior
+ * according to various error control needs. The strict flag tells the engine when and if null as operand is
+ * considered an error, the silent flag tells the engine what to do with the error
+ * (log as warning or throw exception).
+ *
+ *
+ * When "silent" & "not-strict":
+ * 0 & null should be indicators of "default" values so that even in an case of error,
+ * something meaningful can still be inferred; may be convenient for configurations.
+ *
+ *
+ * When "silent" & "strict":
+ * One should probably consider using null as an error case - ie, every object
+ * manipulated by JEXL should be valued; the ternary operator, especially the '?:' form
+ * can be used to workaround exceptional cases.
+ * Use case could be configuration with no implicit values or defaults.
+ *
+ *
+ * When "not-silent" & "not-strict":
+ * The error control grain is roughly on par with JEXL 1.0
+ *
+ * When "not-silent" & "strict":
+ * The finest error control grain is obtained; it is the closest to Java code -
+ * still augmented by "script" capabilities regarding automated conversions and type matching.
+ *
+ *
+ *
+ */
+public class JexlBuilder {
+
+ /** The default maximum expression length to hit the expression cache. */
+ protected static final int CACHE_THRESHOLD = 64;
+
+ /** The JexlUberspect instance. */
+ private JexlUberspect uberspect = null;
+
+ /** The strategy strategy. */
+ private JexlUberspect.ResolverStrategy strategy = null;
+
+ /** The sandbox. */
+ private JexlSandbox sandbox = null;
+
+ /** The Log to which all JexlEngine messages will be logged. */
+ private Log logger = null;
+
+ /** Whether error messages will carry debugging information. */
+ private Boolean debug = null;
+
+ /** Whether interrupt throws JexlException.Cancel. */
+ private Boolean cancellable = null;
+
+ /** The options. */
+ private final JexlOptions options = new JexlOptions();
+
+ /** Whether getVariables considers all potential equivalent syntactic forms. */
+ private int collectMode = 1;
+
+ /** The {@link JexlArithmetic} instance. */
+ private JexlArithmetic arithmetic = null;
+
+ /** The cache size. */
+ private int cache = -1;
+
+ /** The stack overflow limit. */
+ private int stackOverflow = Integer.MAX_VALUE;
+
+ /** The maximum expression length to hit the expression cache. */
+ private int cacheThreshold = CACHE_THRESHOLD;
+
+ /** The charset. */
+ private Charset charset = Charset.defaultCharset();
+
+ /** The class loader. */
+ private ClassLoader loader = null;
+
+ /** The features. */
+ private JexlFeatures features = null;
+
+ /**
+ * Sets the JexlUberspect instance the engine will use.
+ *
+ * @param u the uberspect
+ * @return this builder
+ */
+ public JexlBuilder uberspect(final JexlUberspect u) {
+ this.uberspect = u;
+ return this;
+ }
+
+ /** @return the uberspect */
+ public JexlUberspect uberspect() {
+ return this.uberspect;
+ }
+
+ /**
+ * Sets the JexlUberspect strategy strategy the engine will use.
+ * This is ignored if the uberspect has been set.
+ *
+ * @param rs the strategy
+ * @return this builder
+ */
+ public JexlBuilder strategy(final JexlUberspect.ResolverStrategy rs) {
+ this.strategy = rs;
+ return this;
+ }
+
+ /** @return the strategy strategy */
+ public JexlUberspect.ResolverStrategy strategy() {
+ return this.strategy;
+ }
+
+ /** @return the current set of options */
+ public JexlOptions options() {
+ return options;
+ }
+
+ /**
+ * Sets the JexlArithmetic instance the engine will use.
+ *
+ * @param a the arithmetic
+ * @return this builder
+ */
+ public JexlBuilder arithmetic(final JexlArithmetic a) {
+ this.arithmetic = a;
+ options.setStrictArithmetic(a.isStrict());
+ options.setMathContext(a.getMathContext());
+ options.setMathScale(a.getMathScale());
+ return this;
+ }
+
+ /** @return the arithmetic */
+ public JexlArithmetic arithmetic() {
+ return this.arithmetic;
+ }
+
+ /**
+ * Sets the sandbox the engine will use.
+ *
+ * @param box the sandbox
+ * @return this builder
+ */
+ public JexlBuilder sandbox(final JexlSandbox box) {
+ this.sandbox = box;
+ return this;
+ }
+
+ /** @return the sandbox */
+ public JexlSandbox sandbox() {
+ return this.sandbox;
+ }
+
+ /**
+ * Sets the features the engine will use as a base by default.
+ *
Note that the script flag will be ignored; the engine will be able to parse expressions and scripts.
+ *
Note also that these will apply to template expressions and scripts.
+ *
As a last remark, if lexical or lexicalShade are set as features, this
+ * method will also set the corresponding options.
+ * @param f the features
+ * @return this builder
+ */
+ public JexlBuilder features(final JexlFeatures f) {
+ this.features = f;
+ if (features != null) {
+ if (features.isLexical()) {
+ options.setLexical(true);
+ }
+ if (features.isLexicalShade()) {
+ options.setLexicalShade(true);
+ }
+ }
+ return this;
+ }
+
+ /** @return the features */
+ public JexlFeatures features() {
+ return this.features;
+ }
+
+ /**
+ * Sets the o.a.c.Log instance to use.
+ *
+ * @param log the logger
+ * @return this builder
+ */
+ public JexlBuilder logger(final Log log) {
+ this.logger = log;
+ return this;
+ }
+
+ /** @return the logger */
+ public Log logger() {
+ return this.logger;
+ }
+
+ /**
+ * Sets the class loader to use.
+ *
+ * @param l the class loader
+ * @return this builder
+ */
+ public JexlBuilder loader(final ClassLoader l) {
+ this.loader = l;
+ return this;
+ }
+
+ /** @return the class loader */
+ public ClassLoader loader() {
+ return loader;
+ }
+
+ /**
+ * Sets the charset to use.
+ *
+ * @param arg the charset
+ * @return this builder
+ * @deprecated since 3.1 use {@link #charset(Charset)} instead
+ */
+ @Deprecated
+ public JexlBuilder loader(final Charset arg) {
+ return charset(arg);
+ }
+
+ /**
+ * Sets the charset to use.
+ *
+ * @param arg the charset
+ * @return this builder
+ * @since 3.1
+ */
+ public JexlBuilder charset(final Charset arg) {
+ this.charset = arg;
+ return this;
+ }
+
+ /** @return the charset */
+ public Charset charset() {
+ return charset;
+ }
+
+ /**
+ * Sets whether the engine will resolve antish variable names.
+ *
+ * @param flag true means antish resolution is enabled, false disables it
+ * @return this builder
+ */
+ public JexlBuilder antish(final boolean flag) {
+ options.setAntish(flag);
+ return this;
+ }
+
+ /** @return whether antish resolution is enabled */
+ public boolean antish() {
+ return options.isAntish();
+ }
+
+ /**
+ * Sets whether the engine is in lexical mode.
+ *
+ * @param flag true means lexical function scope is in effect, false implies non-lexical scoping
+ * @return this builder
+ * @since 3.2
+ */
+ public JexlBuilder lexical(final boolean flag) {
+ options.setLexical(flag);
+ return this;
+ }
+
+ /** @return whether lexical scope is enabled */
+ public boolean lexical() {
+ return options.isLexical();
+ }
+
+ /**
+ * Sets whether the engine is in lexical shading mode.
+ *
+ * @param flag true means lexical shading is in effect, false implies no lexical shading
+ * @return this builder
+ * @since 3.2
+ */
+ public JexlBuilder lexicalShade(final boolean flag) {
+ options.setLexicalShade(flag);
+ return this;
+ }
+
+ /** @return whether lexical shading is enabled */
+ public boolean lexicalShade() {
+ return options.isLexicalShade();
+ }
+
+ /**
+ * Sets whether the engine will throw JexlException during evaluation when an error is triggered.
+ *
+ * @param flag true means no JexlException will occur, false allows them
+ * @return this builder
+ */
+ public JexlBuilder silent(final boolean flag) {
+ options.setSilent(flag);
+ return this;
+ }
+
+ /** @return the silent error handling flag */
+ public Boolean silent() {
+ return options.isSilent();
+ }
+
+ /**
+ * Sets whether the engine considers unknown variables, methods, functions and constructors as errors or
+ * evaluates them as null.
+ *
+ * @param flag true means strict error reporting, false allows them to be evaluated as null
+ * @return this builder
+ */
+ public JexlBuilder strict(final boolean flag) {
+ options.setStrict(flag);
+ return this;
+ }
+
+ /** @return true if strict, false otherwise */
+ public Boolean strict() {
+ return options.isStrict();
+ }
+
+ /**
+ * Sets whether the engine considers dereferencing null in navigation expressions
+ * as errors or evaluates them as null.
+ *
x.y()
if x is null throws an exception when not safe,
+ * return null and warns if it is.
+ *
+ * @param flag true means safe navigation, false throws exception when dereferencing null
+ * @return this builder
+ */
+ public JexlBuilder safe(final boolean flag) {
+ options.setSafe(flag);
+ return this;
+ }
+
+ /** @return true if safe, false otherwise */
+ public Boolean safe() {
+ return options.isSafe();
+ }
+
+ /**
+ * Sets whether the engine will report debugging information when error occurs.
+ *
+ * @param flag true implies debug is on, false implies debug is off.
+ * @return this builder
+ */
+ public JexlBuilder debug(final boolean flag) {
+ this.debug = flag;
+ return this;
+ }
+
+ /** @return the debugging information flag */
+ public Boolean debug() {
+ return this.debug;
+ }
+
+ /**
+ * Sets the engine behavior upon interruption: throw an JexlException.Cancel or terminates the current evaluation
+ * and return null.
+ *
+ * @param flag true implies the engine throws the exception, false makes the engine return null.
+ * @return this builder
+ * @since 3.1
+ */
+ public JexlBuilder cancellable(final boolean flag) {
+ this.cancellable = flag;
+ options.setCancellable(flag);
+ return this;
+ }
+
+ /**
+ * @return the cancellable information flag
+ * @since 3.1
+ */
+ public Boolean cancellable() {
+ return this.cancellable;
+ }
+
+ /**
+ * Sets whether the engine variable collectors considers all potential forms of variable syntaxes.
+ *
+ * @param flag true means var collections considers constant array accesses equivalent to dotted references
+ * @return this builder
+ * @since 3.2
+ */
+ public JexlBuilder collectAll(final boolean flag) {
+ return collectMode(flag? 1 : 0);
+ }
+
+ /**
+ * Experimental collector mode setter.
+ *
+ * @param mode 0 or 1 as equivalents to false and true, other values are experimental
+ * @return this builder
+ * @since 3.2
+ */
+ public JexlBuilder collectMode(final int mode) {
+ this.collectMode = mode;
+ return this;
+ }
+
+ /**
+ * @return true if variable collection follows strict syntactic rule
+ * @since 3.2
+ */
+ public boolean collectAll() {
+ return this.collectMode != 0;
+ }
+
+ /**
+ * @return 0 if variable collection follows strict syntactic rule
+ * @since 3.2
+ */
+ public int collectMode() {
+ return this.collectMode;
+ }
+
+ /**
+ * Sets the default namespaces map the engine will use.
+ *
+ * Each entry key is used as a prefix, each entry value used as a bean implementing
+ * methods; an expression like 'nsx:method(123)' will thus be solved by looking at
+ * a registered bean named 'nsx' that implements method 'method' in that map.
+ * If all methods are static, you may use the bean class instead of an instance as value.
+ *
+ *
+ * If the entry value is a class that has one constructor taking a JexlContext as argument, an instance
+ * of the namespace will be created at evaluation time. It might be a good idea to derive a JexlContext
+ * to carry the information used by the namespace to avoid variable space pollution and strongly type
+ * the constructor with this specialized JexlContext.
+ *
+ *
+ * The key or prefix allows to retrieve the bean that plays the role of the namespace.
+ * If the prefix is null, the namespace is the top-level namespace allowing to define
+ * top-level user defined namespaces ( ie: myfunc(...) )
+ *
+ * Note that the JexlContext is also used to try to solve top-level namespaces. This allows ObjectContext
+ * derived instances to call methods on the wrapped object.
+ *
+ * @param ns the map of namespaces
+ * @return this builder
+ */
+ public JexlBuilder namespaces(final Map ns) {
+ options.setNamespaces(ns);
+ return this;
+ }
+
+ /**
+ * @return the map of namespaces.
+ */
+ public Map namespaces() {
+ return options.getNamespaces();
+ }
+
+ /**
+ * Sets the expression cache size the engine will use.
+ * The cache will contain at most size
expressions of at most cacheThreshold
length.
+ * Note that all JEXL caches are held through SoftReferences and may be garbage-collected.
+ *
+ * @param size if not strictly positive, no cache is used.
+ * @return this builder
+ */
+ public JexlBuilder cache(final int size) {
+ this.cache = size;
+ return this;
+ }
+
+ /**
+ * @return the cache size
+ */
+ public int cache() {
+ return cache;
+ }
+
+ /**
+ * Sets the maximum length for an expression to be cached.
+ * Expression whose length is greater than this expression cache length threshold will
+ * bypass the cache.
+ * It is expected that a "long" script will be parsed once and its reference kept
+ * around in user-space structures; the jexl expression cache has no added-value in this case.
+ *
+ * @param length if not strictly positive, the value is silently replaced by the default value (64).
+ * @return this builder
+ */
+ public JexlBuilder cacheThreshold(final int length) {
+ this.cacheThreshold = length > 0? length : CACHE_THRESHOLD;
+ return this;
+ }
+
+ /**
+ * @return the cache threshold
+ */
+ public int cacheThreshold() {
+ return cacheThreshold;
+ }
+
+ /**
+ * Sets the number of script/expression evaluations that can be stacked.
+ * @param size if not strictly positive, limit is reached when java StackOverflow is thrown.
+ * @return this builder
+ */
+ public JexlBuilder stackOverflow(final int size) {
+ this.stackOverflow = size;
+ return this;
+ }
+
+ /**
+ * @return the cache size
+ */
+ public int stackOverflow() {
+ return stackOverflow;
+ }
+
+ /**
+ * @return a {@link JexlEngine} instance
+ */
+ public JexlEngine create() {
+ return new Engine(this);
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlContext.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlContext.java
new file mode 100644
index 0000000..e4fb0a7
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlContext.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Manages variables which can be referenced in a JEXL expression.
+ *
+ * JEXL variable names in their simplest form are 'java-like' identifiers.
+ * JEXL also considers 'ant' inspired variables expressions as valid.
+ * For instance, the expression 'x.y.z' is an 'antish' variable and will be resolved as a whole by the context,
+ * i.e. using the key "x.y.z". This proves to be useful to solve "fully qualified class names".
+ *
+ * The interpreter variable resolution algorithm will try the different sequences of identifiers till it finds
+ * one that exists in the context; if "x" is an object known in the context (JexlContext.has("x") returns true),
+ * "x.y" will not be looked up in the context but will most likely refer to "x.getY()".
+ *
+ * Note that JEXL may use '$jexl' and '$ujexl' variables for internal purpose; setting or getting those
+ * variables may lead to unexpected results unless specified otherwise.
+ *
+ * @since 1.0
+ */
+public interface JexlContext {
+
+ /**
+ * Gets the value of a variable.
+ *
+ * @param name the variable's name
+ * @return the value
+ */
+ Object get(String name);
+
+ /**
+ * Sets the value of a variable.
+ *
+ * @param name the variable's name
+ * @param value the variable's value
+ */
+ void set(String name, Object value);
+
+ /**
+ * Checks whether a variable is defined in this context.
+ *
+ * A variable may be defined with a null value; this method checks whether the
+ * value is null or if the variable is undefined.
+ *
+ * @param name the variable's name
+ * @return true if it exists, false otherwise
+ */
+ boolean has(String name);
+
+ /**
+ * A marker interface of the JexlContext that declares how to resolve a namespace from its name;
+ * it is used by the interpreter during evaluation.
+ *
+ * In JEXL, a namespace is an object that serves the purpose of encapsulating functions; for instance,
+ * the "math" namespace would be the proper object to expose functions like "log(...)", "sinus(...)", etc.
+ *
+ * In expressions like "ns:function(...)", the resolver is called with resolveNamespace("ns").
+ *
+ *
JEXL itself reserves 'jexl' and 'ujexl' as namespaces for internal purpose; resolving those may lead to
+ * unexpected results.
+ *
+ * @since 3.0
+ */
+ interface NamespaceResolver {
+
+ /**
+ * Resolves a namespace by its name.
+ *
+ * @param name the name
+ * @return the namespace object
+ */
+ Object resolveNamespace(String name);
+ }
+
+ /**
+ * A marker interface of the JexlContext, NamespaceFunctor allows creating an instance
+ * to delegate namespace methods calls to.
+ *
+ * The functor is created once during the lifetime of a script evaluation.
+ */
+ interface NamespaceFunctor {
+ /**
+ * Creates the functor object that will be used instead of the namespace.
+ *
+ * @param context the context
+ * @return the namespace functor instance
+ */
+ Object createFunctor(JexlContext context);
+ }
+
+ /**
+ * A marker interface of the JexlContext that indicates the interpreter to put this context
+ * in the JexlEngine thread local context instance during evaluation.
+ * This allows user functions or methods to access the context during a call.
+ * Note that the usual caveats wrt using thread local apply (caching/leaking references, etc.); in particular,
+ * keeping a reference to such a context is to be considered with great care and caution.
+ * It should also be noted that sharing such a context between threads should implicate synchronizing variable
+ * accessing the implementation class.
+ *
+ * @see JexlEngine#setThreadContext(ThreadLocal)
+ * @see JexlEngine#getThreadContext()
+ */
+ interface ThreadLocal extends JexlContext {
+ // no specific method
+ }
+
+ /**
+ * A marker interface of the JexlContext that processes annotations.
+ * It is used by the interpreter during evaluation to execute annotation evaluations.
+ * If the JexlContext is not an instance of an AnnotationProcessor, encountering an annotation will generate
+ * an error or a warning depending on the engine strictness.
+ *
+ * @since 3.1
+ */
+ interface AnnotationProcessor {
+ /**
+ * Processes an annotation.
+ *
All annotations are processed through this method; the statement 'call' is to be performed within
+ * the processAnnotation method. The implementation must perform the call explicitly.
+ *
The arguments and the statement must not be referenced or cached for longer than the duration
+ * of the processAnnotation call.
+ *
+ * @param name the annotation name
+ * @param args the arguments of the annotation, evaluated as arguments of this call
+ * @param statement the statement that was annotated; the processor should invoke this statement 'call' method
+ * @return the result of statement.call()
+ * @throws Exception if annotation processing fails
+ */
+ Object processAnnotation(String name, Object[] args, Callable statement) throws Exception;
+ }
+
+ /**
+ * A marker interface of the JexlContext that exposes runtime evaluation options.
+ *
+ * @since 3.2
+ */
+ interface OptionsHandle {
+ /**
+ * Retrieves the current set of options though the context.
+ *
+ * This method will be called once at beginning of evaluation and an interpreter private copy
+ * of the context handled JexlOptions instance used for the duration of the execution;
+ * the context handled JexlOptions instance being only used as the source of that copy,
+ * it can safely alter its boolean flags during execution with no effect, avoiding any behavior ambiguity.
+ *
+ * @return the engine options
+ */
+ JexlOptions getEngineOptions();
+ }
+
+ /**
+ * A marker interface of the JexlContext that processes pragmas.
+ * It is called by the engine before interpreter creation; as a marker of
+ * JexlContext, it is expected to have access and interact with the context
+ * instance.
+ *
+ * @since 3.2
+ */
+ interface PragmaProcessor {
+ /**
+ * Process one pragma.
+ *
+ * @param key the key
+ * @param value the value
+ */
+ void processPragma(String key, Object value);
+ }
+
+ /**
+ * A marker interface of the JexlContext sharing a cancelling flag.
+ *
A script running in a thread can thus be notified through this reference
+ * of its cancellation through the context. It uses the same interpreter logic
+ * that reacts to cancellation and is an alternative to using callable() and/or
+ * interrupting script interpreter threads.
+ *
+ * @since 3.2
+ */
+ interface CancellationHandle {
+ /**
+ * @return a cancelable boolean used by the interpreter
+ */
+ AtomicBoolean getCancellation();
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlEngine.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlEngine.java
new file mode 100644
index 0000000..468aba6
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlEngine.java
@@ -0,0 +1,641 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlUberspect;
+
+import java.io.*;
+import java.math.MathContext;
+import java.net.URL;
+import java.nio.charset.Charset;
+
+/**
+ * Creates and evaluates JexlExpression and JexlScript objects.
+ * Determines the behavior of expressions and scripts during their evaluation with respect to:
+ *
+ * Introspection, see {@link JexlUberspect}
+ * Arithmetic and comparison, see {@link JexlArithmetic}
+ * Error reporting
+ * Logging
+ *
+ *
+ * Note that methods that evaluate expressions may throw unchecked exceptions;
+ * The {@link JexlException} are thrown in "non-silent" mode but since these are
+ * RuntimeException, user-code should catch them wherever most appropriate.
+ *
+ * @since 2.0
+ */
+public abstract class JexlEngine {
+
+ /** A marker singleton for invocation failures in tryInvoke. */
+ public static final Object TRY_FAILED = new FailObject();
+
+ /** The failure marker class. */
+ private static final class FailObject {
+ /**
+ * Default ctor.
+ */
+ private FailObject() {
+ }
+
+ @Override
+ public String toString() {
+ return "tryExecute failed";
+ }
+ }
+
+ /**
+ * The thread local context.
+ */
+ protected static final ThreadLocal CONTEXT =
+ new ThreadLocal<>();
+
+ /**
+ * Accesses the current thread local context.
+ *
+ * @return the context or null
+ */
+ public static JexlContext.ThreadLocal getThreadContext() {
+ return CONTEXT.get();
+ }
+
+ /**
+ * The thread local engine.
+ */
+ protected static final ThreadLocal ENGINE =
+ new ThreadLocal<>();
+
+ /**
+ * Accesses the current thread local engine.
+ * Advanced: you should only use this to retrieve the engine within a method/ctor called through the evaluation
+ * of a script/expression.
+ *
+ * @return the engine or null
+ */
+ public static JexlEngine getThreadEngine() {
+ return ENGINE.get();
+ }
+
+ /**
+ * Sets the current thread local context.
+ * This should only be used carefully, for instance when re-evaluating a "stored" script that requires a
+ * given Namespace resolver. Remember to synchronize access if context is shared between threads.
+ *
+ * @param tls the thread local context to set
+ */
+ public static void setThreadContext(final JexlContext.ThreadLocal tls) {
+ CONTEXT.set(tls);
+ }
+
+ /**
+ * Script evaluation options.
+ *
The JexlContext used for evaluation can implement this interface to alter behavior.
+ *
+ * @deprecated 3.2
+ */
+ @Deprecated
+ public interface Options {
+
+ /**
+ * The charset used for parsing.
+ *
+ * @return the charset
+ */
+ Charset getCharset();
+
+ /**
+ * Sets whether the engine will throw a {@link JexlException} when an error is encountered during evaluation.
+ *
+ * @return true if silent, false otherwise
+ */
+ Boolean isSilent();
+
+ /**
+ * Checks whether the engine considers unknown variables, methods, functions and constructors as errors or
+ * evaluates them as null.
+ *
+ * @return true if strict, false otherwise
+ */
+ Boolean isStrict();
+
+ /**
+ * Checks whether the arithmetic triggers errors during evaluation when null is used as an operand.
+ *
+ * @return true if strict, false otherwise
+ */
+ Boolean isStrictArithmetic();
+
+ /**
+ * Whether evaluation will throw JexlException.Cancel (true) or return null (false) when interrupted.
+ *
+ * @return true when cancellable, false otherwise
+ * @since 3.1
+ */
+ Boolean isCancellable();
+
+ /**
+ * The MathContext instance used for +,-,/,*,% operations on big decimals.
+ *
+ * @return the math context
+ */
+ MathContext getArithmeticMathContext();
+
+ /**
+ * The BigDecimal scale used for comparison and coercion operations.
+ *
+ * @return the scale
+ */
+ int getArithmeticMathScale();
+ }
+
+ /** Default features. */
+ public static final JexlFeatures DEFAULT_FEATURES = new JexlFeatures();
+
+ /**
+ * An empty/static/non-mutable JexlContext singleton used instead of null context.
+ */
+ public static final JexlContext EMPTY_CONTEXT = new EmptyContext();
+
+ /**
+ * The empty context class, public for instrospection.
+ */
+ public static final class EmptyContext implements JexlContext {
+ /**
+ * Default ctor.
+ */
+ private EmptyContext() {
+ }
+
+ @Override
+ public Object get(final String name) {
+ return null;
+ }
+
+ @Override
+ public boolean has(final String name) {
+ return false;
+ }
+
+ @Override
+ public void set(final String name, final Object value) {
+ throw new UnsupportedOperationException("Not supported in void context.");
+ }
+ }
+
+ /**
+ * An empty/static/non-mutable JexlNamespace singleton used instead of null namespace.
+ */
+ public static final JexlContext.NamespaceResolver EMPTY_NS = new EmptyNamespaceResolver();
+
+ /**
+ * The empty/static/non-mutable JexlNamespace class, public for instrospection.
+ */
+ public static final class EmptyNamespaceResolver implements JexlContext.NamespaceResolver {
+ /**
+ * Default ctor.
+ */
+ private EmptyNamespaceResolver() {
+ }
+
+ @Override
+ public Object resolveNamespace(final String name) {
+ return null;
+ }
+ }
+
+ /** The default Jxlt cache size. */
+ private static final int JXLT_CACHE_SIZE = 256;
+
+ /**
+ * Gets the charset used for parsing.
+ *
+ * @return the charset
+ */
+ public abstract Charset getCharset();
+
+ /**
+ * Gets this engine underlying {@link JexlUberspect}.
+ *
+ * @return the uberspect
+ */
+ public abstract JexlUberspect getUberspect();
+
+ /**
+ * Gets this engine underlying {@link JexlArithmetic}.
+ *
+ * @return the arithmetic
+ */
+ public abstract JexlArithmetic getArithmetic();
+
+ /**
+ * Checks whether this engine is in debug mode.
+ *
+ * @return true if debug is on, false otherwise
+ */
+ public abstract boolean isDebug();
+
+ /**
+ * Checks whether this engine throws JexlException during evaluation.
+ *
+ * @return true if silent, false (default) otherwise
+ */
+ public abstract boolean isSilent();
+
+ /**
+ * Checks whether this engine considers unknown variables, methods, functions and constructors as errors.
+ *
+ * @return true if strict, false otherwise
+ */
+ public abstract boolean isStrict();
+
+ /**
+ * Checks whether this engine will throw JexlException.Cancel (true) or return null (false) when interrupted
+ * during an execution.
+ *
+ * @return true if cancellable, false otherwise
+ */
+ public abstract boolean isCancellable();
+
+ /**
+ * Sets the class loader used to discover classes in 'new' expressions.
+ * This method is not thread safe; it may be called after JexlEngine
+ * initialization and allow scripts to use new classes definitions.
+ *
+ * @param loader the class loader to use
+ */
+ public abstract void setClassLoader(ClassLoader loader);
+
+ /**
+ * Creates a new {@link JxltEngine} instance using this engine.
+ *
+ * @return a JEXL Template engine
+ */
+ public JxltEngine createJxltEngine() {
+ return createJxltEngine(true);
+ }
+
+ /**
+ * Creates a new {@link JxltEngine} instance using this engine.
+ *
+ * @param noScript whether the JxltEngine only allows Jexl expressions or scripts
+ * @return a JEXL Template engine
+ */
+ public JxltEngine createJxltEngine(final boolean noScript) {
+ return createJxltEngine(noScript, JXLT_CACHE_SIZE, '$', '#');
+ }
+
+ /**
+ * Creates a new instance of {@link JxltEngine} using this engine.
+ *
+ * @param noScript whether the JxltEngine only allows JEXL expressions or scripts
+ * @param cacheSize the number of expressions in this cache, default is 256
+ * @param immediate the immediate template expression character, default is '$'
+ * @param deferred the deferred template expression character, default is '#'
+ * @return a JEXL Template engine
+ */
+ public abstract JxltEngine createJxltEngine(boolean noScript, int cacheSize, char immediate, char deferred);
+
+ /**
+ * Clears the expression cache.
+ */
+ public abstract void clearCache();
+
+ /**
+ * Creates an JexlExpression from a String containing valid JEXL syntax.
+ * This method parses the expression which must contain either a reference or an expression.
+ *
+ * @param info An info structure to carry debugging information if needed
+ * @param expression A String containing valid JEXL syntax
+ * @return An {@link JexlExpression} which can be evaluated using a {@link JexlContext}
+ * @throws JexlException if there is a problem parsing the script
+ */
+ public abstract JexlExpression createExpression(JexlInfo info, String expression);
+
+ /**
+ * Creates a JexlExpression from a String containing valid JEXL syntax.
+ * This method parses the expression which must contain either a reference or an expression.
+ *
+ * @param expression A String containing valid JEXL syntax
+ * @return An {@link JexlExpression} which can be evaluated using a {@link JexlContext}
+ * @throws JexlException if there is a problem parsing the script
+ */
+ public final JexlExpression createExpression(final String expression) {
+ return createExpression(null, expression);
+ }
+
+ /**
+ * Creates a JexlScript from a String containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param features A set of features that will be enforced during parsing
+ * @param info An info structure to carry debugging information if needed
+ * @param source A string containing valid JEXL syntax
+ * @param names The script parameter names used during parsing; a corresponding array of arguments containing
+ * values should be used during evaluation
+ * @return A {@link JexlScript} which can be executed using a {@link JexlContext}
+ * @throws JexlException if there is a problem parsing the script
+ */
+ public abstract JexlScript createScript(JexlFeatures features, JexlInfo info, String source, String... names);
+
+ /**
+ * Creates a JexlScript from a String containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param info An info structure to carry debugging information if needed
+ * @param source A string containing valid JEXL syntax
+ * @param names The script parameter names used during parsing; a corresponding array of arguments containing
+ * values should be used during evaluation
+ * @return A {@link JexlScript} which can be executed using a {@link JexlContext}
+ * @throws JexlException if there is a problem parsing the script
+ */
+ public final JexlScript createScript(final JexlInfo info, final String source, final String... names) {
+ return createScript(null, info, source, names);
+ }
+
+ /**
+ * Creates a Script from a String containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param scriptText A String containing valid JEXL syntax
+ * @return A {@link JexlScript} which can be executed using a {@link JexlContext}
+ * @throws JexlException if there is a problem parsing the script.
+ */
+ public final JexlScript createScript(final String scriptText) {
+ return createScript(null, null, scriptText, (String[]) null);
+ }
+
+ /**
+ * Creates a Script from a String containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param source A String containing valid JEXL syntax
+ * @param names The script parameter names used during parsing; a corresponding array of arguments containing
+ * values should be used during evaluation
+ * @return A {@link JexlScript} which can be executed using a {@link JexlContext}
+ * @throws JexlException if there is a problem parsing the script
+ */
+ public final JexlScript createScript(final String source, final String... names) {
+ return createScript(null, null, source, names);
+ }
+
+ /**
+ * Creates a Script from a {@link File} containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param scriptFile A {@link File} containing valid JEXL syntax. Must not be null. Must be a readable file.
+ * @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
+ * @throws JexlException if there is a problem reading or parsing the script.
+ */
+ public final JexlScript createScript(final File scriptFile) {
+ return createScript(null, null, readSource(scriptFile), (String[]) null);
+ }
+
+ /**
+ * Creates a Script from a {@link File} containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param scriptFile A {@link File} containing valid JEXL syntax. Must not be null. Must be a readable file.
+ * @param names The script parameter names used during parsing; a corresponding array of arguments containing
+ * values should be used during evaluation.
+ * @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
+ * @throws JexlException if there is a problem reading or parsing the script.
+ */
+ public final JexlScript createScript(final File scriptFile, final String... names) {
+ return createScript(null, null, readSource(scriptFile), names);
+ }
+
+ /**
+ * Creates a Script from a {@link File} containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param info An info structure to carry debugging information if needed
+ * @param scriptFile A {@link File} containing valid JEXL syntax. Must not be null. Must be a readable file.
+ * @param names The script parameter names used during parsing; a corresponding array of arguments containing
+ * values should be used during evaluation.
+ * @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
+ * @throws JexlException if there is a problem reading or parsing the script.
+ */
+ public final JexlScript createScript(final JexlInfo info, final File scriptFile, final String... names) {
+ return createScript(null, info, readSource(scriptFile), names);
+ }
+
+ /**
+ * Creates a Script from a {@link URL} containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param scriptUrl A {@link URL} containing valid JEXL syntax. Must not be null.
+ * @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
+ * @throws JexlException if there is a problem reading or parsing the script.
+ */
+ public final JexlScript createScript(final URL scriptUrl) {
+ return createScript(null, readSource(scriptUrl), (String[]) null);
+ }
+
+ /**
+ * Creates a Script from a {@link URL} containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param scriptUrl A {@link URL} containing valid JEXL syntax. Must not be null.
+ * @param names The script parameter names used during parsing; a corresponding array of arguments containing
+ * values should be used during evaluation.
+ * @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
+ * @throws JexlException if there is a problem reading or parsing the script.
+ */
+ public final JexlScript createScript(final URL scriptUrl, final String... names) {
+ return createScript(null, null, readSource(scriptUrl), names);
+ }
+
+ /**
+ * Creates a Script from a {@link URL} containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param info An info structure to carry debugging information if needed
+ * @param scriptUrl A {@link URL} containing valid JEXL syntax. Must not be null.
+ * @param names The script parameter names used during parsing; a corresponding array of arguments containing
+ * values should be used during evaluation.
+ * @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
+ * @throws JexlException if there is a problem reading or parsing the script.
+ */
+ public final JexlScript createScript(final JexlInfo info, final URL scriptUrl, final String... names) {
+ return createScript(null, info, readSource(scriptUrl), names);
+ }
+
+ /**
+ * Accesses properties of a bean using an expression.
+ *
+ * jexl.get(myobject, "foo.bar"); should equate to
+ * myobject.getFoo().getBar(); (or myobject.getFoo().get("bar"))
+ *
+ *
+ * If the JEXL engine is silent, errors will be logged through its logger as warning.
+ *
+ *
+ * @param bean the bean to get properties from
+ * @param expr the property expression
+ * @return the value of the property
+ * @throws JexlException if there is an error parsing the expression or during evaluation
+ */
+ public abstract Object getProperty(Object bean, String expr);
+
+ /**
+ * Accesses properties of a bean using an expression.
+ *
+ * If the JEXL engine is silent, errors will be logged through its logger as warning.
+ *
+ *
+ * @param context the evaluation context
+ * @param bean the bean to get properties from
+ * @param expr the property expression
+ * @return the value of the property
+ * @throws JexlException if there is an error parsing the expression or during evaluation
+ */
+ public abstract Object getProperty(JexlContext context, Object bean, String expr);
+
+ /**
+ * Assign properties of a bean using an expression.
+ *
+ * jexl.set(myobject, "foo.bar", 10); should equate to
+ * myobject.getFoo().setBar(10); (or myobject.getFoo().put("bar", 10) )
+ *
+ *
+ * If the JEXL engine is silent, errors will be logged through its logger as warning.
+ *
+ *
+ * @param bean the bean to set properties in
+ * @param expr the property expression
+ * @param value the value of the property
+ * @throws JexlException if there is an error parsing the expression or during evaluation
+ */
+ public abstract void setProperty(Object bean, String expr, Object value);
+
+ /**
+ * Assign properties of a bean using an expression. If the JEXL engine is silent, errors will be logged through
+ * its logger as warning.
+ *
+ * @param context the evaluation context
+ * @param bean the bean to set properties in
+ * @param expr the property expression
+ * @param value the value of the property
+ * @throws JexlException if there is an error parsing the expression or during evaluation
+ */
+ public abstract void setProperty(JexlContext context, Object bean, String expr, Object value);
+
+ /**
+ * Invokes an object's method by name and arguments.
+ *
+ * @param obj the method's invoker object
+ * @param meth the method's name
+ * @param args the method's arguments
+ * @return the method returned value or null if it failed and engine is silent
+ * @throws JexlException if method could not be found or failed and engine is not silent
+ */
+ public abstract Object invokeMethod(Object obj, String meth, Object... args);
+
+ /**
+ * Creates a new instance of an object using the most appropriate constructor based on the arguments.
+ *
+ * @param the type of object
+ * @param clazz the class to instantiate
+ * @param args the constructor arguments
+ * @return the created object instance or null on failure when silent
+ */
+ public abstract T newInstance(Class extends T> clazz, Object... args);
+
+ /**
+ * Creates a new instance of an object using the most appropriate constructor based on the arguments.
+ *
+ * @param clazz the name of the class to instantiate resolved through this engine's class loader
+ * @param args the constructor arguments
+ * @return the created object instance or null on failure when silent
+ */
+ public abstract Object newInstance(String clazz, Object... args);
+
+ /**
+ * Creates a JexlInfo instance.
+ *
+ * @param fn url/file/template/script user given name
+ * @param l line number
+ * @param c column number
+ * @return a JexlInfo instance
+ */
+ public JexlInfo createInfo(final String fn, final int l, final int c) {
+ return new JexlInfo(fn, l, c);
+ }
+
+ /**
+ * Create an information structure for dynamic set/get/invoke/new.
+ * This gathers the class, method and line number of the first calling method
+ * outside of o.a.c.jexl3.
+ *
+ * @return a JexlInfo instance
+ */
+ public JexlInfo createInfo() {
+ return new JexlInfo();
+ }
+
+ /**
+ * Creates a string from a reader.
+ *
+ * @param reader to be read.
+ * @return the contents of the reader as a String.
+ * @throws IOException on any error reading the reader.
+ */
+ protected static String toString(final BufferedReader reader) throws IOException {
+ final StringBuilder buffer = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ buffer.append(line).append('\n');
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Reads a JEXL source from a File.
+ *
+ * @param file the script file
+ * @return the source
+ */
+ protected String readSource(final File file) {
+ if (file == null) {
+ throw new NullPointerException("source file is null");
+ }
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file),
+ getCharset()))) {
+ return toString(reader);
+ } catch (final IOException xio) {
+ throw new JexlException(createInfo(file.toString(), 1, 1), "could not read source File", xio);
+ }
+ }
+
+ /**
+ * Reads a JEXL source from an URL.
+ *
+ * @param url the script url
+ * @return the source
+ */
+ protected String readSource(final URL url) {
+ if (url == null) {
+ throw new NullPointerException("source URL is null");
+ }
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), getCharset()))) {
+ return toString(reader);
+ } catch (final IOException xio) {
+ throw new JexlException(createInfo(url.toString(), 1, 1), "could not read source URL", xio);
+ }
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlException.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlException.java
new file mode 100644
index 0000000..c1d43ca
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlException.java
@@ -0,0 +1,1119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.JexlNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.internal.Debugger;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.JavaccError;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ParseException;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.TokenMgrException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Wraps any error that might occur during interpretation of a script or expression.
+ *
+ * @since 2.0
+ */
+public class JexlException extends RuntimeException {
+ private static final long serialVersionUID = 20210606123900L;
+
+ /** The point of origin for this exception. */
+ private final transient JexlNode mark;
+
+ /** The debug info. */
+ private final transient JexlInfo info;
+
+ /** Maximum number of characters around exception location. */
+ private static final int MAX_EXCHARLOC = 42;
+
+
+ /**
+ * Creates a new JexlException.
+ *
+ * @param node the node causing the error
+ * @param msg the error message
+ */
+ public JexlException(final JexlNode node, final String msg) {
+ this(node, msg, null);
+ }
+
+ /**
+ * Creates a new JexlException.
+ *
+ * @param node the node causing the error
+ * @param msg the error message
+ * @param cause the exception causing the error
+ */
+ public JexlException(final JexlNode node, final String msg, final Throwable cause) {
+ this(node, msg != null ? msg : "", unwrap(cause), true);
+ }
+
+ /**
+ * Creates a new JexlException.
+ *
+ * @param node the node causing the error
+ * @param msg the error message
+ * @param cause the exception causing the error
+ * @param trace whether this exception has a stacktrace and can not be suppressed
+ */
+ protected JexlException(final JexlNode node, final String msg, final Throwable cause, boolean trace) {
+ super(msg != null ? msg : "", unwrap(cause), !trace, trace);
+ if (node != null) {
+ mark = node;
+ info = node.jexlInfo();
+ } else {
+ mark = null;
+ info = null;
+ }
+ }
+
+ /**
+ * Creates a new JexlException.
+ *
+ * @param jinfo the debugging information associated
+ * @param msg the error message
+ * @param cause the exception causing the error
+ */
+ public JexlException(final JexlInfo jinfo, final String msg, final Throwable cause) {
+ super(msg != null ? msg : "", unwrap(cause));
+ mark = null;
+ info = jinfo;
+ }
+
+ /**
+ * Gets the specific information for this exception.
+ *
+ * @return the information
+ */
+ public JexlInfo getInfo() {
+ return detailedInfo(mark, info);
+ }
+
+ /**
+ * Creates a string builder pre-filled with common error information (if possible).
+ *
+ * @param node the node
+ * @return a string builder
+ */
+ private static StringBuilder errorAt(final JexlNode node) {
+ final JexlInfo info = node != null ? detailedInfo(node, node.jexlInfo()) : null;
+ final StringBuilder msg = new StringBuilder();
+ if (info != null) {
+ msg.append(info);
+ } else {
+ msg.append("?:");
+ }
+ msg.append(' ');
+ return msg;
+ }
+
+ /**
+ * Gets the most specific information attached to a node.
+ *
+ * @param node the node
+ * @param info the information
+ * @return the information or null
+ * @deprecated 3.2
+ */
+ @Deprecated
+ public static JexlInfo getInfo(final JexlNode node, final JexlInfo info) {
+ return detailedInfo(node, info);
+ }
+
+ /**
+ * Gets the most specific information attached to a node.
+ *
+ * @param node the node
+ * @param info the information
+ * @return the information or null
+ */
+ private static JexlInfo detailedInfo(final JexlNode node, final JexlInfo info) {
+ if (info != null && node != null) {
+ final Debugger dbg = new Debugger();
+ if (dbg.debug(node)) {
+ return new JexlInfo(info) {
+ @Override
+ public Detail getDetail() {
+ return dbg;
+ }
+ };
+ }
+ }
+ return info;
+ }
+
+ /**
+ * Cleans a JexlException from any org.apache.commons.jexl3.internal stack trace element.
+ *
+ * @return this exception
+ */
+ public JexlException clean() {
+ return clean(this);
+ }
+
+ /**
+ * Cleans a Throwable from any org.apache.commons.jexl3.internal stack trace element.
+ *
+ * @param the throwable type
+ * @param xthrow the thowable
+ * @return the throwable
+ */
+ private static X clean(final X xthrow) {
+ if (xthrow != null) {
+ final List stackJexl = new ArrayList<>();
+ for (final StackTraceElement se : xthrow.getStackTrace()) {
+ final String className = se.getClassName();
+ if (!className.startsWith("org.apache.commons.jexl3.internal")
+ && !className.startsWith("org.apache.commons.jexl3.parser")) {
+ stackJexl.add(se);
+ }
+ }
+ xthrow.setStackTrace(stackJexl.toArray(new StackTraceElement[stackJexl.size()]));
+ }
+ return xthrow;
+ }
+
+ /**
+ * Unwraps the cause of a throwable due to reflection.
+ *
+ * @param xthrow the throwable
+ * @return the cause
+ */
+ private static Throwable unwrap(final Throwable xthrow) {
+ if (xthrow instanceof TryFailed
+ || xthrow instanceof InvocationTargetException
+ || xthrow instanceof UndeclaredThrowableException) {
+ return xthrow.getCause();
+ }
+ return xthrow;
+ }
+
+ /**
+ * Merge the node info and the cause info to obtain best possible location.
+ *
+ * @param info the node
+ * @param cause the cause
+ * @return the info to use
+ */
+ private static JexlInfo merge(final JexlInfo info, final JavaccError cause) {
+ if (cause == null || cause.getLine() < 0) {
+ return info;
+ }
+ if (info == null) {
+ return new JexlInfo("", cause.getLine(), cause.getColumn());
+ }
+ return new JexlInfo(info.getName(), cause.getLine(), cause.getColumn());
+ }
+
+ /**
+ * Accesses detailed message.
+ *
+ * @return the message
+ */
+ protected String detailedMessage() {
+ Class extends JexlException> clazz = getClass();
+ String name = clazz == JexlException.class ? "JEXL" : clazz.getSimpleName().toLowerCase();
+ return name + " error : " + getDetail();
+ }
+
+ /**
+ * @return this exception specific detail
+ * @since 3.2
+ */
+ public final String getDetail() {
+ return super.getMessage();
+ }
+
+ /**
+ * Formats an error message from the parser.
+ *
+ * @param prefix the prefix to the message
+ * @param expr the expression in error
+ * @return the formatted message
+ */
+ protected String parserError(final String prefix, final String expr) {
+ final int length = expr.length();
+ if (length < MAX_EXCHARLOC) {
+ return prefix + " error in '" + expr + "'";
+ }
+ final int me = MAX_EXCHARLOC / 2;
+ int begin = info.getColumn() - me;
+ if (begin < 0 || length < me) {
+ begin = 0;
+ } else if (begin > length) {
+ begin = me;
+ }
+ int end = begin + MAX_EXCHARLOC;
+ if (end > length) {
+ end = length;
+ }
+ return prefix + " error near '... "
+ + expr.substring(begin, end) + " ...'";
+ }
+
+ /**
+ * Pleasing checkstyle.
+ *
+ * @return the info
+ */
+ protected JexlInfo info() {
+ return info;
+ }
+
+ /**
+ * Thrown when tokenization fails.
+ *
+ * @since 3.0
+ */
+ public static class Tokenization extends JexlException {
+ private static final long serialVersionUID = 20210606123901L;
+
+ /**
+ * Creates a new Tokenization exception instance.
+ *
+ * @param info the location info
+ * @param cause the javacc cause
+ */
+ public Tokenization(final JexlInfo info, final TokenMgrException cause) {
+ super(merge(info, cause), Objects.requireNonNull(cause).getAfter(), null);
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return parserError("tokenization", getDetail());
+ }
+ }
+
+ /**
+ * Thrown when parsing fails.
+ *
+ * @since 3.0
+ */
+ public static class Parsing extends JexlException {
+ private static final long serialVersionUID = 20210606123902L;
+
+ /**
+ * Creates a new Parsing exception instance.
+ *
+ * @param info the location information
+ * @param cause the javacc cause
+ */
+ public Parsing(final JexlInfo info, final ParseException cause) {
+ super(merge(info, cause), Objects.requireNonNull(cause).getAfter(), null);
+ }
+
+ /**
+ * Creates a new Parsing exception instance.
+ *
+ * @param info the location information
+ * @param msg the message
+ */
+ public Parsing(final JexlInfo info, final String msg) {
+ super(info, msg, null);
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return parserError("parsing", getDetail());
+ }
+ }
+
+ /**
+ * Thrown when parsing fails due to an ambiguous statement.
+ *
+ * @since 3.0
+ */
+ public static class Ambiguous extends Parsing {
+ private static final long serialVersionUID = 20210606123903L;
+ /** The mark at which ambiguity might stop and recover. */
+ private final transient JexlInfo recover;
+
+ /**
+ * Creates a new Ambiguous statement exception instance.
+ *
+ * @param info the location information
+ * @param expr the source expression line
+ */
+ public Ambiguous(final JexlInfo info, final String expr) {
+ this(info, null, expr);
+ }
+
+ /**
+ * Creates a new Ambiguous statement exception instance.
+ *
+ * @param begin the start location information
+ * @param end the end location information
+ * @param expr the source expression line
+ */
+ public Ambiguous(final JexlInfo begin, final JexlInfo end, final String expr) {
+ super(begin, expr);
+ recover = end;
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return parserError("ambiguous statement", getDetail());
+ }
+
+ /**
+ * Tries to remove this ambiguity in the source.
+ *
+ * @param src the source that triggered this exception
+ * @return the source with the ambiguous statement removed
+ * or null if no recovery was possible
+ */
+ public String tryCleanSource(final String src) {
+ final JexlInfo ji = info();
+ return ji == null || recover == null
+ ? src
+ : sliceSource(src, ji.getLine(), ji.getColumn(), recover.getLine(), recover.getColumn());
+ }
+ }
+
+ /**
+ * Removes a slice from a source.
+ *
+ * @param src the source
+ * @param froml the begin line
+ * @param fromc the begin column
+ * @param tol the to line
+ * @param toc the to column
+ * @return the source with the (begin) to (to) zone removed
+ */
+ public static String sliceSource(final String src, final int froml, final int fromc, final int tol, final int toc) {
+ final BufferedReader reader = new BufferedReader(new StringReader(src));
+ final StringBuilder buffer = new StringBuilder();
+ String line;
+ int cl = 1;
+ try {
+ while ((line = reader.readLine()) != null) {
+ if (cl < froml || cl > tol) {
+ buffer.append(line).append('\n');
+ } else {
+ if (cl == froml) {
+ buffer.append(line, 0, fromc - 1);
+ }
+ if (cl == tol) {
+ buffer.append(line.substring(toc + 1));
+ }
+ } // else ignore line
+ cl += 1;
+ }
+ } catch (final IOException xignore) {
+ // damn the checked exceptions :-)
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Thrown when reaching stack-overflow.
+ *
+ * @since 3.2
+ */
+ public static class StackOverflow extends JexlException {
+ private static final long serialVersionUID = 20210606123904L;
+
+ /**
+ * Creates a new stack overflow exception instance.
+ *
+ * @param info the location information
+ * @param name the unknown method
+ * @param cause the exception causing the error
+ */
+ public StackOverflow(final JexlInfo info, final String name, final Throwable cause) {
+ super(info, name, cause);
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return "stack overflow " + getDetail();
+ }
+ }
+
+ /**
+ * Thrown when parsing fails due to an invalid assigment.
+ *
+ * @since 3.0
+ */
+ public static class Assignment extends Parsing {
+ private static final long serialVersionUID = 20210606123905L;
+
+ /**
+ * Creates a new Assignment statement exception instance.
+ *
+ * @param info the location information
+ * @param expr the source expression line
+ */
+ public Assignment(final JexlInfo info, final String expr) {
+ super(info, expr);
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return parserError("assignment", getDetail());
+ }
+ }
+
+ /**
+ * Thrown when parsing fails due to a disallowed feature.
+ *
+ * @since 3.2
+ */
+ public static class Feature extends Parsing {
+ private static final long serialVersionUID = 20210606123906L;
+ /** The feature code. */
+ private final int code;
+
+ /**
+ * Creates a new Ambiguous statement exception instance.
+ *
+ * @param info the location information
+ * @param feature the feature code
+ * @param expr the source expression line
+ */
+ public Feature(final JexlInfo info, final int feature, final String expr) {
+ super(info, expr);
+ this.code = feature;
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return parserError(JexlFeatures.stringify(code), getDetail());
+ }
+ }
+
+ /** Used 3 times. */
+ private static final String VARQUOTE = "variable '";
+
+ /**
+ * The various type of variable issues.
+ */
+ public enum VariableIssue {
+ /** The variable is undefined. */
+ UNDEFINED,
+ /** The variable is already declared. */
+ REDEFINED,
+ /** The variable has a null value. */
+ NULLVALUE;
+
+ /**
+ * Stringifies the variable issue.
+ *
+ * @param var the variable name
+ * @return the issue message
+ */
+ public String message(final String var) {
+ switch (this) {
+ case NULLVALUE:
+ return VARQUOTE + var + "' is null";
+ case REDEFINED:
+ return VARQUOTE + var + "' is already defined";
+ case UNDEFINED:
+ default:
+ return VARQUOTE + var + "' is undefined";
+ }
+ }
+ }
+
+ /**
+ * Thrown when a variable is unknown.
+ *
+ * @since 3.0
+ */
+ public static class Variable extends JexlException {
+ private static final long serialVersionUID = 20210606123907L;
+ /**
+ * Undefined variable flag.
+ */
+ private final VariableIssue issue;
+
+ /**
+ * Creates a new Variable exception instance.
+ *
+ * @param node the offending ASTnode
+ * @param var the unknown variable
+ * @param vi the variable issue
+ */
+ public Variable(final JexlNode node, final String var, final VariableIssue vi) {
+ super(node, var, null);
+ issue = vi;
+ }
+
+ /**
+ * Creates a new Variable exception instance.
+ *
+ * @param node the offending ASTnode
+ * @param var the unknown variable
+ * @param undef whether the variable is undefined or evaluated as null
+ */
+ public Variable(final JexlNode node, final String var, final boolean undef) {
+ this(node, var, undef ? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
+ }
+
+ /**
+ * Whether the variable causing an error is undefined or evaluated as null.
+ *
+ * @return true if undefined, false otherwise
+ */
+ public boolean isUndefined() {
+ return issue == VariableIssue.UNDEFINED;
+ }
+
+ /**
+ * @return the variable name
+ */
+ public String getVariable() {
+ return getDetail();
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return issue.message(getVariable());
+ }
+ }
+
+ /**
+ * Generates a message for a variable error.
+ *
+ * @param node the node where the error occurred
+ * @param variable the variable
+ * @param undef whether the variable is null or undefined
+ * @return the error message
+ * @deprecated 3.2
+ */
+ @Deprecated
+ public static String variableError(final JexlNode node, final String variable, final boolean undef) {
+ return variableError(node, variable, undef ? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
+ }
+
+ /**
+ * Generates a message for a variable error.
+ *
+ * @param node the node where the error occurred
+ * @param variable the variable
+ * @param issue the variable kind of issue
+ * @return the error message
+ */
+ public static String variableError(final JexlNode node, final String variable, final VariableIssue issue) {
+ final StringBuilder msg = errorAt(node);
+ msg.append(issue.message(variable));
+ return msg.toString();
+ }
+
+ /**
+ * Thrown when a property is unknown.
+ *
+ * @since 3.0
+ */
+ public static class Property extends JexlException {
+ private static final long serialVersionUID = 20210606123908L;
+ /**
+ * Undefined variable flag.
+ */
+ private final boolean undefined;
+
+ /**
+ * Creates a new Property exception instance.
+ *
+ * @param node the offending ASTnode
+ * @param pty the unknown property
+ * @deprecated 3.2
+ */
+ @Deprecated
+ public Property(final JexlNode node, final String pty) {
+ this(node, pty, true, null);
+ }
+
+ /**
+ * Creates a new Property exception instance.
+ *
+ * @param node the offending ASTnode
+ * @param pty the unknown property
+ * @param cause the exception causing the error
+ * @deprecated 3.2
+ */
+ @Deprecated
+ public Property(final JexlNode node, final String pty, final Throwable cause) {
+ this(node, pty, true, cause);
+ }
+
+ /**
+ * Creates a new Property exception instance.
+ *
+ * @param node the offending ASTnode
+ * @param pty the unknown property
+ * @param undef whether the variable is null or undefined
+ * @param cause the exception causing the error
+ */
+ public Property(final JexlNode node, final String pty, final boolean undef, final Throwable cause) {
+ super(node, pty, cause);
+ undefined = undef;
+ }
+
+ /**
+ * Whether the variable causing an error is undefined or evaluated as null.
+ *
+ * @return true if undefined, false otherwise
+ */
+ public boolean isUndefined() {
+ return undefined;
+ }
+
+ /**
+ * @return the property name
+ */
+ public String getProperty() {
+ return getDetail();
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return (undefined ? "undefined" : "null value") + " property '" + getProperty() + "'";
+ }
+ }
+
+ /**
+ * Generates a message for an unsolvable property error.
+ *
+ * @param node the node where the error occurred
+ * @param pty the property
+ * @param undef whether the property is null or undefined
+ * @return the error message
+ */
+ public static String propertyError(final JexlNode node, final String pty, final boolean undef) {
+ final StringBuilder msg = errorAt(node);
+ if (undef) {
+ msg.append("unsolvable");
+ } else {
+ msg.append("null value");
+ }
+ msg.append(" property '");
+ msg.append(pty);
+ msg.append('\'');
+ return msg.toString();
+ }
+
+ /**
+ * Generates a message for an unsolvable property error.
+ *
+ * @param node the node where the error occurred
+ * @param var the variable
+ * @return the error message
+ * @deprecated 3.2
+ */
+ @Deprecated
+ public static String propertyError(final JexlNode node, final String var) {
+ return propertyError(node, var, true);
+ }
+
+ /**
+ * Thrown when a method or ctor is unknown, ambiguous or inaccessible.
+ *
+ * @since 3.0
+ */
+ public static class Method extends JexlException {
+ private static final long serialVersionUID = 20210606123909L;
+
+ /**
+ * Creates a new Method exception instance.
+ *
+ * @param node the offending ASTnode
+ * @param name the method name
+ * @deprecated as of 3.2, use call with method arguments
+ */
+ @Deprecated
+ public Method(final JexlNode node, final String name) {
+ this(node, name, null);
+ }
+
+ /**
+ * Creates a new Method exception instance.
+ *
+ * @param info the location information
+ * @param name the unknown method
+ * @param cause the exception causing the error
+ * @deprecated as of 3.2, use call with method arguments
+ */
+ @Deprecated
+ public Method(final JexlInfo info, final String name, final Throwable cause) {
+ this(info, name, null, cause);
+ }
+
+ /**
+ * Creates a new Method exception instance.
+ *
+ * @param node the offending ASTnode
+ * @param name the method name
+ * @param args the method arguments
+ * @since 3.2
+ */
+ public Method(final JexlNode node, final String name, final Object[] args) {
+ super(node, methodSignature(name, args));
+ }
+
+ /**
+ * Creates a new Method exception instance.
+ *
+ * @param info the location information
+ * @param name the method name
+ * @param args the method arguments
+ * @since 3.2
+ */
+ public Method(final JexlInfo info, final String name, final Object[] args) {
+ this(info, name, args, null);
+ }
+
+
+ /**
+ * Creates a new Method exception instance.
+ *
+ * @param info the location information
+ * @param name the method name
+ * @param cause the exception causing the error
+ * @param args the method arguments
+ * @since 3.2
+ */
+ public Method(final JexlInfo info, final String name, final Object[] args, final Throwable cause) {
+ super(info, methodSignature(name, args), cause);
+ }
+
+ /**
+ * @return the method name
+ */
+ public String getMethod() {
+ final String signature = getMethodSignature();
+ final int lparen = signature.indexOf('(');
+ return lparen > 0 ? signature.substring(0, lparen) : signature;
+ }
+
+ /**
+ * @return the method signature
+ * @since 3.2
+ */
+ public String getMethodSignature() {
+ return getDetail();
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return "unsolvable function/method '" + getMethodSignature() + "'";
+ }
+ }
+
+ /**
+ * Creates a signed-name for a given method name and arguments.
+ *
+ * @param name the method name
+ * @param args the method arguments
+ * @return a suitable signed name
+ */
+ private static String methodSignature(final String name, final Object[] args) {
+ if (args != null && args.length > 0) {
+ final StringBuilder strb = new StringBuilder(name);
+ strb.append('(');
+ for (int a = 0; a < args.length; ++a) {
+ if (a > 0) {
+ strb.append(", ");
+ }
+ final Class> clazz = args[a] == null ? Object.class : args[a].getClass();
+ strb.append(clazz.getSimpleName());
+ }
+ strb.append(')');
+ return strb.toString();
+ }
+ return name;
+ }
+
+ /**
+ * Generates a message for a unsolvable method error.
+ *
+ * @param node the node where the error occurred
+ * @param method the method name
+ * @return the error message
+ * @deprecated 3.2
+ */
+ @Deprecated
+ public static String methodError(final JexlNode node, final String method) {
+ return methodError(node, method, null);
+ }
+
+ /**
+ * Generates a message for a unsolvable method error.
+ *
+ * @param node the node where the error occurred
+ * @param method the method name
+ * @param args the method arguments
+ * @return the error message
+ */
+ public static String methodError(final JexlNode node, final String method, final Object[] args) {
+ final StringBuilder msg = errorAt(node);
+ msg.append("unsolvable function/method '");
+ msg.append(methodSignature(method, args));
+ msg.append('\'');
+ return msg.toString();
+ }
+
+ /**
+ * Thrown when an operator fails.
+ *
+ * @since 3.0
+ */
+ public static class Operator extends JexlException {
+ private static final long serialVersionUID = 20210606124100L;
+
+ /**
+ * Creates a new Operator exception instance.
+ *
+ * @param node the location information
+ * @param symbol the operator name
+ * @param cause the exception causing the error
+ */
+ public Operator(final JexlNode node, final String symbol, final Throwable cause) {
+ super(node, symbol, cause);
+ }
+
+ /**
+ * @return the method name
+ */
+ public String getSymbol() {
+ return getDetail();
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return "error calling operator '" + getSymbol() + "'";
+ }
+ }
+
+ /**
+ * Generates a message for an operator error.
+ *
+ * @param node the node where the error occurred
+ * @param symbol the operator name
+ * @return the error message
+ */
+ public static String operatorError(final JexlNode node, final String symbol) {
+ final StringBuilder msg = errorAt(node);
+ msg.append("error calling operator '");
+ msg.append(symbol);
+ msg.append('\'');
+ return msg.toString();
+ }
+
+ /**
+ * Thrown when an annotation handler throws an exception.
+ *
+ * @since 3.1
+ */
+ public static class Annotation extends JexlException {
+ private static final long serialVersionUID = 20210606124101L;
+
+ /**
+ * Creates a new Annotation exception instance.
+ *
+ * @param node the annotated statement node
+ * @param name the annotation name
+ * @param cause the exception causing the error
+ */
+ public Annotation(final JexlNode node, final String name, final Throwable cause) {
+ super(node, name, cause);
+ }
+
+ /**
+ * @return the annotation name
+ */
+ public String getAnnotation() {
+ return getDetail();
+ }
+
+ @Override
+ protected String detailedMessage() {
+ return "error processing annotation '" + getAnnotation() + "'";
+ }
+ }
+
+ /**
+ * Generates a message for an annotation error.
+ *
+ * @param node the node where the error occurred
+ * @param annotation the annotation name
+ * @return the error message
+ * @since 3.1
+ */
+ public static String annotationError(final JexlNode node, final String annotation) {
+ final StringBuilder msg = errorAt(node);
+ msg.append("error processing annotation '");
+ msg.append(annotation);
+ msg.append('\'');
+ return msg.toString();
+ }
+
+ /**
+ * Thrown to return a value.
+ *
+ * @since 3.0
+ */
+ public static class Return extends JexlException {
+ private static final long serialVersionUID = 20210606124102L;
+
+ /** The returned value. */
+ private final transient Object result;
+
+ /**
+ * Creates a new instance of Return.
+ *
+ * @param node the return node
+ * @param msg the message
+ * @param value the returned value
+ */
+ public Return(final JexlNode node, final String msg, final Object value) {
+ super(node, msg, null, false);
+ this.result = value;
+ }
+
+ /**
+ * @return the returned value
+ */
+ public Object getValue() {
+ return result;
+ }
+ }
+
+ /**
+ * Thrown to cancel a script execution.
+ *
+ * @since 3.0
+ */
+ public static class Cancel extends JexlException {
+ /**
+ * Creates a new instance of Cancel.
+ *
+ * @param node the node where the interruption was detected
+ */
+ public Cancel(final JexlNode node) {
+ super(node, "execution cancelled", null);
+ }
+ }
+
+ /**
+ * Thrown to break a loop.
+ *
+ * @since 3.0
+ */
+ public static class Break extends JexlException {
+ private static final long serialVersionUID = 20210606124103L;
+
+ /**
+ * Creates a new instance of Break.
+ *
+ * @param node the break
+ */
+ public Break(final JexlNode node) {
+ super(node, "break loop", null, false);
+ }
+ }
+
+ /**
+ * Thrown to continue a loop.
+ *
+ * @since 3.0
+ */
+ public static class Continue extends JexlException {
+ private static final long serialVersionUID = 20210606124104L;
+
+ /**
+ * Creates a new instance of Continue.
+ *
+ * @param node the continue
+ */
+ public Continue(final JexlNode node) {
+ super(node, "continue loop", null, false);
+ }
+ }
+
+ /**
+ * Thrown when method/ctor invocation fails.
+ * These wrap InvocationTargetException as runtime exception
+ * allowing to go through without signature modifications.
+ *
+ * @since 3.2
+ */
+ public static class TryFailed extends JexlException {
+ private static final long serialVersionUID = 20210606124105L;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param xany the original invocation target exception
+ */
+ private TryFailed(final InvocationTargetException xany) {
+ super((JexlInfo) null, "tryFailed", xany.getCause());
+ }
+ }
+
+ /**
+ * Wrap an invocation exception.
+ *
Return the cause if it is already a JexlException.
+ *
+ * @param xinvoke the invocation exception
+ * @return a JexlException
+ */
+ public static JexlException tryFailed(final InvocationTargetException xinvoke) {
+ final Throwable cause = xinvoke.getCause();
+ return cause instanceof JexlException
+ ? (JexlException) cause
+ : new TryFailed(xinvoke); // fail
+ }
+
+
+ /**
+ * Detailed info message about this error.
+ * Format is "debug![begin,end]: string \n msg" where:
+ *
+ * - debug is the debugging information if it exists (@link JexlEngine.setDebug)
+ * - begin, end are character offsets in the string for the precise location of the error
+ * - string is the string representation of the offending expression
+ * - msg is the actual explanation message for this error
+ *
+ * @return this error as a string
+ */
+ @Override
+ public String getMessage() {
+ final StringBuilder msg = new StringBuilder();
+ if (info != null) {
+ msg.append(info);
+ } else {
+ msg.append("?:");
+ }
+ msg.append(' ');
+ msg.append(detailedMessage());
+ final Throwable cause = getCause();
+ if (cause instanceof JexlArithmetic.NullOperand) {
+ msg.append(" caused by null operand");
+ }
+ return msg.toString();
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlExpression.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlExpression.java
new file mode 100644
index 0000000..1839d7b
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlExpression.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Represents a single JEXL expression.
+ *
+ * This simple interface provides access to the underlying textual expression through
+ * {@link JexlExpression#getSourceText()}.
+ *
+ *
+ *
+ * An expression is different than a script - it is simply a reference to
+ * a single expression, not to multiple statements.
+ * This implies 'if','for','while','var' and blocks '{'... '}'are not allowed in expressions.
+ *
+ * Do not create classes that implement this interface; delegate or compose instead.
+ *
+ * @since 1.0
+ */
+public interface JexlExpression {
+ /**
+ * Evaluates the expression with the variables contained in the
+ * supplied {@link JexlContext}.
+ *
+ * @param context A JexlContext containing variables.
+ * @return The result of this evaluation
+ * @throws JexlException on any error
+ */
+ Object evaluate(JexlContext context);
+
+ /**
+ * Returns the source text of this expression.
+ *
+ * @return the source text
+ */
+ String getSourceText();
+
+ /**
+ * Recreates the source text of this expression from the internal syntactic tree.
+ *
+ * @return the source text
+ */
+ String getParsedText();
+
+ /**
+ * Creates a Callable from this expression.
+ *
+ * This allows to submit it to an executor pool and provides support for asynchronous calls.
+ * The interpreter will handle interruption/cancellation gracefully if needed.
+ *
+ * @param context the context
+ * @return the callable
+ * @since 3.1
+ */
+ Callable callable(JexlContext context);
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlFeatures.java
new file mode 100644
index 0000000..ba7ec49
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlFeatures.java
@@ -0,0 +1,540 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * A set of language feature options.
+ * These control syntactical constructs that will throw JexlException.Feature exceptions (a
+ * subclass of JexlException.Parsing) when disabled.
+ *
+ * Registers: register syntax (#number), used internally for {g,s}etProperty
+ * Reserved Names: a set of reserved variable names that can not be used as local variable (or parameter) names
+ * Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)
+ * Lexical: lexical scope, prevents redefining local variables
+ * Lexical Shade: local variables shade globals, prevents confusing a global variable with a local one
+ * Side Effect : assigning/modifying values on any variables or left-value
+ * Constant Array Reference: ensures array references only use constants;they should be statically solvable.
+ * New Instance: creating an instance using new(...)
+ * Loops: loop constructs (while(true), for(...))
+ * Lambda: function definitions (()->{...}, function(...) ).
+ * Method calls: calling methods (obj.method(...) or obj['method'](...)); when disabled, leaves function calls
+ * - including namespace prefixes - available
+ * Structured literals: arrays, lists, maps, sets, ranges
+ * Pragmas: #pragma x y
+ * Annotation: @annotation statement;
+ *
+ * @since 3.2
+ */
+public final class JexlFeatures {
+ /** The feature flags. */
+ private long flags;
+ /** The set of reserved names, aka global variables that can not be masked by local variables or parameters. */
+ private Set reservedNames;
+ /** The namespace names. */
+ private Predicate nameSpaces;
+ /** The false predicate. */
+ public static final Predicate TEST_STR_FALSE = (s)->false;
+ /** Te feature names (for toString()). */
+ private static final String[] F_NAMES = {
+ "register", "reserved variable", "local variable", "assign/modify",
+ "global assign/modify", "array reference", "create instance", "loop", "function",
+ "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade"
+ };
+ /** Registers feature ordinal. */
+ private static final int REGISTER = 0;
+ /** Reserved name feature ordinal. */
+ public static final int RESERVED = 1;
+ /** Locals feature ordinal. */
+ public static final int LOCAL_VAR = 2;
+ /** Side-effects feature ordinal. */
+ public static final int SIDE_EFFECT = 3;
+ /** Global side-effects feature ordinal. */
+ public static final int SIDE_EFFECT_GLOBAL = 4;
+ /** Array get is allowed on expr. */
+ public static final int ARRAY_REF_EXPR = 5;
+ /** New-instance feature ordinal. */
+ public static final int NEW_INSTANCE = 6;
+ /** Loops feature ordinal. */
+ public static final int LOOP = 7;
+ /** Lambda feature ordinal. */
+ public static final int LAMBDA = 8;
+ /** Lambda feature ordinal. */
+ public static final int METHOD_CALL = 9;
+ /** Structured literal feature ordinal. */
+ public static final int STRUCTURED_LITERAL = 10;
+ /** Pragma feature ordinal. */
+ public static final int PRAGMA = 11;
+ /** Annotation feature ordinal. */
+ public static final int ANNOTATION = 12;
+ /** Script feature ordinal. */
+ public static final int SCRIPT = 13;
+ /** Lexical feature ordinal. */
+ public static final int LEXICAL = 14;
+ /** Lexical shade feature ordinal. */
+ public static final int LEXICAL_SHADE = 15;
+
+ /**
+ * Creates an all-features-enabled instance.
+ */
+ public JexlFeatures() {
+ flags = (1L << LOCAL_VAR)
+ | (1L << SIDE_EFFECT)
+ | (1L << SIDE_EFFECT_GLOBAL)
+ | (1L << ARRAY_REF_EXPR)
+ | (1L << NEW_INSTANCE)
+ | (1L << LOOP)
+ | (1L << LAMBDA)
+ | (1L << METHOD_CALL)
+ | (1L << STRUCTURED_LITERAL)
+ | (1L << PRAGMA)
+ | (1L << ANNOTATION)
+ | (1L << SCRIPT);
+ reservedNames = Collections.emptySet();
+ nameSpaces = TEST_STR_FALSE;
+ }
+
+ /**
+ * Copy constructor.
+ * @param features the feature to copy from
+ */
+ public JexlFeatures(final JexlFeatures features) {
+ this.flags = features.flags;
+ this.reservedNames = features.reservedNames;
+ this.nameSpaces = features.nameSpaces;
+ }
+
+ @Override
+ public int hashCode() { //CSOFF: MagicNumber
+ int hash = 3;
+ hash = 53 * hash + (int) (this.flags ^ (this.flags >>> 32));
+ hash = 53 * hash + (this.reservedNames != null ? this.reservedNames.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final JexlFeatures other = (JexlFeatures) obj;
+ if (this.flags != other.flags) {
+ return false;
+ }
+ if (!Objects.equals(this.reservedNames, other.reservedNames)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * The text corresponding to a feature code.
+ * @param feature the feature number
+ * @return the feature name
+ */
+ public static String stringify(final int feature) {
+ return feature >= 0 && feature < F_NAMES.length ? F_NAMES[feature] : "unsupported feature";
+ }
+
+ /**
+ * Sets a collection of reserved names precluding those to be used as local variables or parameter names.
+ * @param names the names to reserve
+ * @return this features instance
+ */
+ public JexlFeatures reservedNames(final Collection names) {
+ if (names == null || names.isEmpty()) {
+ reservedNames = Collections.emptySet();
+ } else {
+ reservedNames = Collections.unmodifiableSet(new TreeSet(names));
+ }
+ setFeature(RESERVED, !reservedNames.isEmpty());
+ return this;
+ }
+
+ /**
+ * @return the (unmodifiable) set of reserved names.
+ */
+ public Set getReservedNames() {
+ return reservedNames;
+ }
+
+ /**
+ * Checks whether a name is reserved.
+ * @param name the name to check
+ * @return true if reserved, false otherwise
+ */
+ public boolean isReservedName(final String name) {
+ return name != null && reservedNames.contains(name);
+ }
+
+ /**
+ * Sets a test to determine namespace declaration.
+ * @param names the name predicate
+ * @return this features instance
+ */
+ public JexlFeatures namespaceTest(final Predicate names) {
+ nameSpaces = names == null? TEST_STR_FALSE : names;
+ return this;
+ }
+
+ /**
+ * @return the declared namespaces test.
+ */
+ public Predicate namespaceTest() {
+ return nameSpaces;
+ }
+
+ /**
+ * Sets a feature flag.
+ * @param feature the feature ordinal
+ * @param flag turn-on, turn off
+ */
+ private void setFeature(final int feature, final boolean flag) {
+ if (flag) {
+ flags |= (1 << feature);
+ } else {
+ flags &= ~(1L << feature);
+ }
+ }
+
+ /**
+ * Gets a feature flag value.
+ * @param feature feature ordinal
+ * @return true if on, false if off
+ */
+ private boolean getFeature(final int feature) {
+ return (flags & (1L << feature)) != 0L;
+ }
+
+ /**
+ * Sets whether register are enabled.
+ *
+ * This is mostly used internally during execution of JexlEngine.{g,s}etProperty.
+ *
+ * When disabled, parsing a script/expression using the register syntax will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures register(final boolean flag) {
+ setFeature(REGISTER, flag);
+ return this;
+ }
+
+ /**
+ * @return true if register syntax is enabled
+ */
+ public boolean supportsRegister() {
+ return getFeature(REGISTER);
+ }
+
+ /**
+ * Sets whether local variables are enabled.
+ *
+ * When disabled, parsing a script/expression using a local variable or parameter syntax
+ * will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures localVar(final boolean flag) {
+ setFeature(LOCAL_VAR, flag);
+ return this;
+ }
+
+ /**
+ * @return true if local variables syntax is enabled
+ */
+ public boolean supportsLocalVar() {
+ return getFeature(LOCAL_VAR);
+ }
+
+ /**
+ * Sets whether side effect expressions on global variables (aka non local) are enabled.
+ *
+ * When disabled, parsing a script/expression using syntactical constructs modifying variables
+ * including all potentially ant-ish variables will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures sideEffectGlobal(final boolean flag) {
+ setFeature(SIDE_EFFECT_GLOBAL, flag);
+ return this;
+ }
+
+ /**
+ * @return true if global variables can be assigned
+ */
+ public boolean supportsSideEffectGlobal() {
+ return getFeature(SIDE_EFFECT_GLOBAL);
+ }
+
+ /**
+ * Sets whether side effect expressions are enabled.
+ *
+ * When disabled, parsing a script/expression using syntactical constructs modifying variables
+ * or members will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures sideEffect(final boolean flag) {
+ setFeature(SIDE_EFFECT, flag);
+ return this;
+ }
+
+ /**
+ * @return true if side effects are enabled, false otherwise
+ */
+ public boolean supportsSideEffect() {
+ return getFeature(SIDE_EFFECT);
+ }
+
+ /**
+ * Sets whether array references expressions are enabled.
+ *
+ * When disabled, parsing a script/expression using 'obj[ ref ]' where ref is not a string or integer literal
+ * will throw a parsing exception;
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures arrayReferenceExpr(final boolean flag) {
+ setFeature(ARRAY_REF_EXPR, flag);
+ return this;
+ }
+
+ /**
+ * @return true if array references can contain method call expressions, false otherwise
+ */
+ public boolean supportsArrayReferenceExpr() {
+ return getFeature(ARRAY_REF_EXPR);
+ }
+
+ /**
+ * Sets whether method calls expressions are enabled.
+ *
+ * When disabled, parsing a script/expression using 'obj.method()'
+ * will throw a parsing exception;
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures methodCall(final boolean flag) {
+ setFeature(METHOD_CALL, flag);
+ return this;
+ }
+
+ /**
+ * @return true if array references can contain expressions, false otherwise
+ */
+ public boolean supportsMethodCall() {
+ return getFeature(METHOD_CALL);
+ }
+
+ /**
+ * Sets whether array/map/set literal expressions are enabled.
+ *
+ * When disabled, parsing a script/expression creating one of these literals
+ * will throw a parsing exception;
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures structuredLiteral(final boolean flag) {
+ setFeature(STRUCTURED_LITERAL, flag);
+ return this;
+ }
+
+ /**
+ * @return true if array/map/set literal expressions are supported, false otherwise
+ */
+ public boolean supportsStructuredLiteral() {
+ return getFeature(STRUCTURED_LITERAL);
+ }
+
+ /**
+ * Sets whether creating new instances is enabled.
+ *
+ * When disabled, parsing a script/expression using 'new(...)' will throw a parsing exception;
+ * using a class as functor will fail at runtime.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures newInstance(final boolean flag) {
+ setFeature(NEW_INSTANCE, flag);
+ return this;
+ }
+
+ /**
+ * @return true if creating new instances is enabled, false otherwise
+ */
+ public boolean supportsNewInstance() {
+ return getFeature(NEW_INSTANCE);
+ }
+
+ /**
+ * Sets whether looping constructs are enabled.
+ *
+ * When disabled, parsing a script/expression using syntactic looping constructs (for,while)
+ * will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures loops(final boolean flag) {
+ setFeature(LOOP, flag);
+ return this;
+ }
+
+ /**
+ * @return true if loops are enabled, false otherwise
+ */
+ public boolean supportsLoops() {
+ return getFeature(LOOP);
+ }
+
+ /**
+ * Sets whether lambda/function constructs are enabled.
+ *
+ * When disabled, parsing a script/expression using syntactic lambda constructs (->,function)
+ * will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures lambda(final boolean flag) {
+ setFeature(LAMBDA, flag);
+ return this;
+ }
+
+ /**
+ * @return true if lambda are enabled, false otherwise
+ */
+ public boolean supportsLambda() {
+ return getFeature(LAMBDA);
+ }
+
+ /**
+ * Sets whether pragma constructs are enabled.
+ *
+ * When disabled, parsing a script/expression using syntactic pragma constructs (#pragma)
+ * will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures pragma(final boolean flag) {
+ setFeature(PRAGMA, flag);
+ return this;
+ }
+
+ /**
+ * @return true if pragma are enabled, false otherwise
+ */
+ public boolean supportsPragma() {
+ return getFeature(PRAGMA);
+ }
+
+ /**
+ * Sets whether annotation constructs are enabled.
+ *
+ * When disabled, parsing a script/expression using syntactic annotation constructs (@annotation)
+ * will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures annotation(final boolean flag) {
+ setFeature(ANNOTATION, flag);
+ return this;
+ }
+
+ /**
+ * @return true if annotation are enabled, false otherwise
+ */
+ public boolean supportsAnnotation() {
+ return getFeature(ANNOTATION);
+ }
+
+ /**
+ * Sets whether scripts constructs are enabled.
+ *
+ * When disabled, parsing a script using syntactic script constructs (statements, ...)
+ * will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures script(final boolean flag) {
+ setFeature(SCRIPT, flag);
+ return this;
+ }
+
+ /**
+ * @return true if scripts are enabled, false otherwise
+ */
+ public boolean supportsScript() {
+ return getFeature(SCRIPT);
+ }
+
+ /**
+ *
+ * @return true if expressions (aka not scripts) are enabled, false otherwise
+ */
+ public boolean supportsExpression() {
+ return !getFeature(SCRIPT);
+ }
+
+ /**
+ * Sets whether syntactic lexical mode is enabled.
+ *
+ * @param flag true means syntactic lexical function scope is in effect, false implies non-lexical scoping
+ * @return this features instance
+ */
+ public JexlFeatures lexical(final boolean flag) {
+ setFeature(LEXICAL, flag);
+ return this;
+ }
+
+
+ /** @return whether lexical scope feature is enabled */
+ public boolean isLexical() {
+ return getFeature(LEXICAL);
+ }
+
+ /**
+ * Sets whether syntactic lexical shade is enabled.
+ *
+ * @param flag true means syntactic lexical shade is in effect and implies lexical scope
+ * @return this features instance
+ */
+ public JexlFeatures lexicalShade(final boolean flag) {
+ setFeature(LEXICAL_SHADE, flag);
+ if (flag) {
+ setFeature(LEXICAL, true);
+ }
+ return this;
+ }
+
+
+ /** @return whether lexical shade feature is enabled */
+ public boolean isLexicalShade() {
+ return getFeature(LEXICAL_SHADE);
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlInfo.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlInfo.java
new file mode 100644
index 0000000..f2671d6
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlInfo.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.internal.Script;
+
+/**
+ * Helper class to carry information such as a url/file name, line and column for
+ * debugging information reporting.
+ */
+public class JexlInfo {
+
+ /** line number. */
+ private final int line;
+
+ /** column number. */
+ private final int column;
+
+ /** name. */
+ private final String name;
+
+ /**
+ * @return the detailed information in case of an error
+ */
+ public Detail getDetail() {
+ return null;
+ }
+
+ /**
+ * Describes errors more precisely.
+ */
+ public interface Detail {
+ /**
+ * @return the start column on the line that triggered the error
+ */
+ int start();
+
+ /**
+ * @return the end column on the line that triggered the error
+ */
+ int end();
+
+ /**
+ * @return the actual part of code that triggered the error
+ */
+
+ @Override
+ String toString();
+ }
+
+ /**
+ * Create info.
+ *
+ * @param source source name
+ * @param l line number
+ * @param c column number
+ */
+ public JexlInfo(final String source, final int l, final int c) {
+ name = source;
+ line = l;
+ column = c;
+ }
+
+ /**
+ * Create an information structure for dynamic set/get/invoke/new.
+ *
This gathers the class, method and line number of the first calling method
+ * outside of o.a.c.jexl3.
+ */
+ public JexlInfo() {
+ final StackTraceElement[] stack = new Throwable().getStackTrace();
+ String cname = getClass().getName();
+ final String pkgname = getClass().getPackage().getName();
+ StackTraceElement se = null;
+ for (int s = 1; s < stack.length; ++s) {
+ se = stack[s];
+ final String className = se.getClassName();
+ if (!className.equals(cname)) {
+ // go deeper if called from jexl implementation classes
+ if (!className.startsWith(pkgname + ".internal.") && !className.startsWith(pkgname + ".Jexl")
+ && !className.startsWith(pkgname + ".parser")) {
+ break;
+ }
+ cname = className;
+ }
+ }
+ this.name = se != null ? se.getClassName() + "." + se.getMethodName() + ":" + se.getLineNumber() : "?";
+ this.line = 0;
+ this.column = 0;
+ }
+
+ /**
+ * Creates info reusing the name.
+ *
+ * @param l the line
+ * @param c the column
+ * @return a new info instance
+ */
+ public JexlInfo at(final int l, final int c) {
+ return new JexlInfo(name, l, c);
+ }
+
+ /**
+ * The copy constructor.
+ *
+ * @param copy the instance to copy
+ */
+ protected JexlInfo(final JexlInfo copy) {
+ name = copy.getName();
+ line = copy.getLine();
+ column = copy.getColumn();
+ }
+
+ /**
+ * Formats this info in the form 'name@line:column'.
+ *
+ * @return the formatted info
+ */
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(name != null ? name : "");
+ if (line > 0) {
+ sb.append("@");
+ sb.append(line);
+ if (column > 0) {
+ sb.append(":");
+ sb.append(column);
+ }
+ }
+ final Detail dbg = getDetail();
+ if (dbg != null) {
+ sb.append("![");
+ sb.append(dbg.start());
+ sb.append(",");
+ sb.append(dbg.end());
+ sb.append("]: '");
+ sb.append(dbg);
+ sb.append("'");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Gets the file/script/url name.
+ *
+ * @return template name
+ */
+ public final String getName() {
+ return name;
+ }
+
+ /**
+ * Gets the line number.
+ *
+ * @return line number.
+ */
+ public final int getLine() {
+ return line;
+ }
+
+ /**
+ * Gets the column number.
+ *
+ * @return the column.
+ */
+ public final int getColumn() {
+ return column;
+ }
+
+ /**
+ * @return this instance or a copy without any decorations
+ */
+ public JexlInfo detach() {
+ return this;
+ }
+
+ /**
+ * Gets the info from a script.
+ *
+ * @param script the script
+ * @return the info
+ */
+ public static JexlInfo from(final JexlScript script) {
+ return script instanceof Script ? ((Script) script).getInfo() : null;
+ }
+}
+
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlOperator.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlOperator.java
new file mode 100644
index 0000000..93682a8
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlOperator.java
@@ -0,0 +1,403 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+/**
+ * The JEXL operators.
+ *
+ * These are the operators that are executed by JexlArithmetic methods.
+ *
+ * Each of them associates a symbol to a method signature.
+ * For instance, '+' is associated to 'T add(L x, R y)'.
+ *
+ * The default JexlArithmetic implements generic versions of these methods using Object as arguments.
+ * You can use your own derived JexlArithmetic that override and/or overload those operator methods.
+ * Note that these are overloads by convention, not actual Java overloads.
+ * The following rules apply to operator methods:
+ *
+ * Operator methods should be public
+ * Operators return type should be respected when primitive (int, boolean,...)
+ * Operators may be overloaded multiple times with different signatures
+ * Operators may return JexlEngine.TRY_AGAIN to fallback on default JEXL implementation
+ *
+ *
+ * @since 3.0
+ */
+public enum JexlOperator {
+
+ /**
+ * Add operator.
+ * Syntax: x + y
+ * Method: T add(L x, R y);
.
+ * @see JexlArithmetic#add
+ */
+ ADD("+", "add", 2),
+
+ /**
+ * Subtract operator.
+ * Syntax: x - y
+ * Method: T subtract(L x, R y);
.
+ * @see JexlArithmetic#subtract
+ */
+ SUBTRACT("-", "subtract", 2),
+
+ /**
+ * Multiply operator.
+ * Syntax: x * y
+ * Method: T multiply(L x, R y);
.
+ * @see JexlArithmetic#multiply
+ */
+ MULTIPLY("*", "multiply", 2),
+
+ /**
+ * Divide operator.
+ * Syntax: x / y
+ * Method: T divide(L x, R y);
.
+ * @see JexlArithmetic#divide
+ */
+ DIVIDE("/", "divide", 2),
+
+ /**
+ * Modulo operator.
+ * Syntax: x % y
+ * Method: T mod(L x, R y);
.
+ * @see JexlArithmetic#mod
+ */
+ MOD("%", "mod", 2),
+
+ /**
+ * Bitwise-and operator.
+ * Syntax: x & y
+ * Method: T and(L x, R y);
.
+ * @see JexlArithmetic#and
+ */
+ AND("&", "and", 2),
+
+ /**
+ * Bitwise-or operator.
+ * Syntax: x | y
+ * Method: T or(L x, R y);
.
+ * @see JexlArithmetic#or
+ */
+ OR("|", "or", 2),
+
+ /**
+ * Bitwise-xor operator.
+ * Syntax: x ^ y
+ * Method: T xor(L x, R y);
.
+ * @see JexlArithmetic#xor
+ */
+ XOR("^", "xor", 2),
+
+ /**
+ * Equals operator.
+ * Syntax: x == y
+ * Method: boolean equals(L x, R y);
.
+ * @see JexlArithmetic#equals
+ */
+ EQ("==", "equals", 2),
+
+ /**
+ * Less-than operator.
+ * Syntax: x < y
+ * Method: boolean lessThan(L x, R y);
.
+ * @see JexlArithmetic#lessThan
+ */
+ LT("<", "lessThan", 2),
+
+ /**
+ * Less-than-or-equal operator.
+ * Syntax: x <= y
+ * Method: boolean lessThanOrEqual(L x, R y);
.
+ * @see JexlArithmetic#lessThanOrEqual
+ */
+ LTE("<=", "lessThanOrEqual", 2),
+
+ /**
+ * Greater-than operator.
+ * Syntax: x > y
+ * Method: boolean greaterThan(L x, R y);
.
+ * @see JexlArithmetic#greaterThan
+ */
+ GT(">", "greaterThan", 2),
+
+ /**
+ * Greater-than-or-equal operator.
+ * Syntax: x >= y
+ * Method: boolean greaterThanOrEqual(L x, R y);
.
+ * @see JexlArithmetic#greaterThanOrEqual
+ */
+ GTE(">=", "greaterThanOrEqual", 2),
+
+ /**
+ * Contains operator.
+ * Syntax: x =~ y
+ * Method: boolean contains(L x, R y);
.
+ * @see JexlArithmetic#contains
+ */
+ CONTAINS("=~", "contains", 2),
+
+ /**
+ * Starts-with operator.
+ * Syntax: x =^ y
+ * Method: boolean startsWith(L x, R y);
.
+ * @see JexlArithmetic#startsWith
+ */
+ STARTSWITH("=^", "startsWith", 2),
+
+ /**
+ * Ends-with operator.
+ * Syntax: x =$ y
+ * Method: boolean endsWith(L x, R y);
.
+ * @see JexlArithmetic#endsWith
+ */
+ ENDSWITH("=$", "endsWith", 2),
+
+ /**
+ * Not operator.
+ * Syntax: !x
+ * Method: T not(L x);
.
+ * @see JexlArithmetic#not
+ */
+ NOT("!", "not", 1),
+
+ /**
+ * Complement operator.
+ * Syntax: ~x
+ * Method: T complement(L x);
.
+ * @see JexlArithmetic#complement
+ */
+ COMPLEMENT("~", "complement", 1),
+
+ /**
+ * Negate operator.
+ * Syntax: -x
+ * Method: T negate(L x);
.
+ * @see JexlArithmetic#negate
+ */
+ NEGATE("-", "negate", 1),
+
+ /**
+ * Positivize operator.
+ * Syntax: +x
+ * Method: T positivize(L x);
.
+ * @see JexlArithmetic#positivize
+ */
+ POSITIVIZE("+", "positivize", 1),
+
+ /**
+ * Empty operator.
+ * Syntax: empty x
or empty(x)
+ * Method: boolean empty(L x);
.
+ * @see JexlArithmetic#empty
+ */
+ EMPTY("empty", "empty", 1),
+
+ /**
+ * Size operator.
+ * Syntax: size x
or size(x)
+ * Method: int size(L x);
.
+ * @see JexlArithmetic#size
+ */
+ SIZE("size", "size", 1),
+
+ /**
+ * Self-add operator.
+ * Syntax: x += y
+ * Method: T selfAdd(L x, R y);
.
+ */
+ SELF_ADD("+=", "selfAdd", ADD),
+
+ /**
+ * Self-subtract operator.
+ * Syntax: x -= y
+ * Method: T selfSubtract(L x, R y);
.
+ */
+ SELF_SUBTRACT("-=", "selfSubtract", SUBTRACT),
+
+ /**
+ * Self-multiply operator.
+ * Syntax: x *= y
+ * Method: T selfMultiply(L x, R y);
.
+ */
+ SELF_MULTIPLY("*=", "selfMultiply", MULTIPLY),
+
+ /**
+ * Self-divide operator.
+ * Syntax: x /= y
+ * Method: T selfDivide(L x, R y);
.
+ */
+ SELF_DIVIDE("/=", "selfDivide", DIVIDE),
+
+ /**
+ * Self-modulo operator.
+ * Syntax: x %= y
+ * Method: T selfMod(L x, R y);
.
+ */
+ SELF_MOD("%=", "selfMod", MOD),
+
+ /**
+ * Self-and operator.
+ * Syntax: x &= y
+ * Method: T selfAnd(L x, R y);
.
+ */
+ SELF_AND("&=", "selfAnd", AND),
+
+ /**
+ * Self-or operator.
+ * Syntax: x |= y
+ * Method: T selfOr(L x, R y);
.
+ */
+ SELF_OR("|=", "selfOr", OR),
+
+ /**
+ * Self-xor operator.
+ * Syntax: x ^= y
+ * Method: T selfXor(L x, R y);
.
+ */
+ SELF_XOR("^=", "selfXor", XOR),
+
+ /**
+ * Marker for side effect.
+ * Returns this from 'self*' overload method to let the engine know the side effect has been performed and
+ * there is no need to assign the result.
+ */
+ ASSIGN("=", null, null),
+
+ /**
+ * Property get operator as in: x.y.
+ * Syntax: x.y
+ * Method: Object propertyGet(L x, R y);
.
+ */
+ PROPERTY_GET(".", "propertyGet", 2),
+
+ /**
+ * Property set operator as in: x.y = z.
+ * Syntax: x.y = z
+ * Method: void propertySet(L x, R y, V z);
.
+ */
+ PROPERTY_SET(".=", "propertySet", 3),
+
+ /**
+ * Array get operator as in: x[y].
+ * Syntax: x.y
+ * Method: Object arrayGet(L x, R y);
.
+ */
+ ARRAY_GET("[]", "arrayGet", 2),
+
+ /**
+ * Array set operator as in: x[y] = z.
+ * Syntax: x[y] = z
+ * Method: void arraySet(L x, R y, V z);
.
+ */
+ ARRAY_SET("[]=", "arraySet", 3),
+
+ /**
+ * Iterator generator as in for(var x : y).
+ * If the returned Iterator is AutoCloseable, close will be called after the last execution of the loop block.
+ * Syntax: for(var x : y){...}
+ * Method: Iterator<Object> forEach(R y);
.
+ * @since 3.1
+ */
+ FOR_EACH("for(...)", "forEach", 1);
+
+ /**
+ * The operator symbol.
+ */
+ private final String operator;
+
+ /**
+ * The associated operator method name.
+ */
+ private final String methodName;
+
+ /**
+ * The method arity (ie number of arguments).
+ */
+ private final int arity;
+
+ /**
+ * The base operator.
+ */
+ private final JexlOperator base;
+
+ /**
+ * Creates a base operator.
+ *
+ * @param o the operator name
+ * @param m the method name associated to this operator in a JexlArithmetic
+ * @param argc the number of parameters for the method
+ */
+ JexlOperator(final String o, final String m, final int argc) {
+ this.operator = o;
+ this.methodName = m;
+ this.arity = argc;
+ this.base = null;
+ }
+
+ /**
+ * Creates a side-effect operator.
+ *
+ * @param o the operator name
+ * @param m the method name associated to this operator in a JexlArithmetic
+ * @param b the base operator, ie + for +=
+ */
+ JexlOperator(final String o, final String m, final JexlOperator b) {
+ this.operator = o;
+ this.methodName = m;
+ this.arity = 2;
+ this.base = b;
+ }
+
+ /**
+ * Gets this operator symbol.
+ *
+ * @return the symbol
+ */
+ public final String getOperatorSymbol() {
+ return operator;
+ }
+
+ /**
+ * Gets this operator method name in a JexlArithmetic.
+ *
+ * @return the method name
+ */
+ public final String getMethodName() {
+ return methodName;
+ }
+
+ /**
+ * Gets this operator number of parameters.
+ *
+ * @return the method arity
+ */
+ public int getArity() {
+ return arity;
+ }
+
+ /**
+ * Gets the base operator.
+ *
+ * @return the base operator
+ */
+ public final JexlOperator getBaseOperator() {
+ return base;
+ }
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlOptions.java
new file mode 100644
index 0000000..29bb2a1
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlOptions.java
@@ -0,0 +1,411 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import java.math.MathContext;
+import java.util.Collections;
+import java.util.Map;
+import aiyh.utils.tool.org.apache.commons.jexl3.internal.Engine;
+
+/**
+ * Flags and properties that can alter the evaluation behavior.
+ * The flags, briefly explained, are the following:
+ *
+ * silent: whether errors throw exception
+ * safe: whether navigation through null is an error
+ * cancellable: whether thread interruption is an error
+ * lexical: whether redefining local variables is an error
+ * lexicalShade: whether local variables shade global ones even outside their scope
+ * strict: whether unknown or unsolvable identifiers are errors
+ * strictArithmetic: whether null as operand is an error
+ * sharedInstance: whether these options can be modified at runtime during execution (expert)
+ *
+ * The sensible default is cancellable, strict and strictArithmetic.
+ * This interface replaces the now deprecated JexlEngine.Options.
+ * @since 3.2
+ */
+public final class JexlOptions {
+ /** The shared instance bit. */
+ private static final int SHARED = 7;
+ /** The local shade bit. */
+ private static final int SHADE = 6;
+ /** The antish var bit. */
+ private static final int ANTISH = 5;
+ /** The lexical scope bit. */
+ private static final int LEXICAL = 4;
+ /** The safe bit. */
+ private static final int SAFE = 3;
+ /** The silent bit. */
+ private static final int SILENT = 2;
+ /** The strict bit. */
+ private static final int STRICT = 1;
+ /** The cancellable bit. */
+ private static final int CANCELLABLE = 0;
+ /** The flags names ordered. */
+ private static final String[] NAMES = {
+ "cancellable", "strict", "silent", "safe", "lexical", "antish", "lexicalShade", "sharedInstance"
+ };
+ /** Default mask .*/
+ private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE;
+ /** The arithmetic math context. */
+ private MathContext mathContext = null;
+ /** The arithmetic math scale. */
+ private int mathScale = Integer.MIN_VALUE;
+ /** The arithmetic strict math flag. */
+ private boolean strictArithmetic = true;
+ /** The default flags, all but safe. */
+ private int flags = DEFAULT;
+ /** The namespaces .*/
+ private Map namespaces = Collections.emptyMap();
+
+ /**
+ * Sets the value of a flag in a mask.
+ * @param ordinal the flag ordinal
+ * @param mask the flags mask
+ * @param value true or false
+ * @return the new flags mask value
+ */
+ private static int set(final int ordinal, final int mask, final boolean value) {
+ return value? mask | (1 << ordinal) : mask & ~(1 << ordinal);
+ }
+
+ /**
+ * Checks the value of a flag in the mask.
+ * @param ordinal the flag ordinal
+ * @param mask the flags mask
+ * @return the mask value with this flag or-ed in
+ */
+ private static boolean isSet(final int ordinal, final int mask) {
+ return (mask & 1 << ordinal) != 0;
+ }
+
+ /**
+ * Default ctor.
+ */
+ public JexlOptions() {}
+
+ /**
+ * Sets the default (static, shared) option flags.
+ *
+ * Whenever possible, we recommend using JexlBuilder methods to unambiguously instantiate a JEXL
+ * engine; this method should only be used for testing / validation.
+ *
A '+flag' or 'flag' will set the option named 'flag' as true, '-flag' set as false.
+ * The possible flag names are:
+ * cancellable, strict, silent, safe, lexical, antish, lexicalShade
+ *
Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL engine creation
+ * may ease validating JEXL3.2 in your environment.
+ * @param flags the flags to set
+ */
+ public static void setDefaultFlags(final String...flags) {
+ DEFAULT = parseFlags(DEFAULT, flags);
+ }
+
+ /**
+ * Parses flags by name.
+ *
A '+flag' or 'flag' will set flag as true, '-flag' set as false.
+ * The possible flag names are:
+ * cancellable, strict, silent, safe, lexical, antish, lexicalShade
+ * @param mask the initial mask state
+ * @param flags the flags to set
+ * @return the flag mask updated
+ */
+ public static int parseFlags(int mask, final String...flags) {
+ for(String name : flags) {
+ boolean b = true;
+ if (name.charAt(0) == '+') {
+ name = name.substring(1);
+ } else if (name.charAt(0) == '-') {
+ name = name.substring(1);
+ b = false;
+ }
+ for(int flag = 0; flag < NAMES.length; ++flag) {
+ if (NAMES[flag].equals(name)) {
+ if (b) {
+ mask |= (1 << flag);
+ } else {
+ mask &= ~(1 << flag);
+ }
+ break;
+ }
+ }
+ }
+ return mask;
+ }
+
+ /**
+ * Sets this option flags using the +/- syntax.
+ * @param opts the option flags
+ */
+ public void setFlags(final String[] opts) {
+ flags = parseFlags(flags, opts);
+ }
+
+ /**
+ * The MathContext instance used for +,-,/,*,% operations on big decimals.
+ * @return the math context
+ */
+ public MathContext getMathContext() {
+ return mathContext;
+ }
+
+ /**
+ * The BigDecimal scale used for comparison and coercion operations.
+ * @return the scale
+ */
+ public int getMathScale() {
+ return mathScale;
+ }
+
+ /**
+ * Checks whether evaluation will attempt resolving antish variable names.
+ * @return true if antish variables are solved, false otherwise
+ */
+ public boolean isAntish() {
+ return isSet(ANTISH, flags);
+ }
+
+ /**
+ * Checks whether evaluation will throw JexlException.Cancel (true) or
+ * return null (false) if interrupted.
+ * @return true when cancellable, false otherwise
+ */
+ public boolean isCancellable() {
+ return isSet(CANCELLABLE, flags);
+ }
+
+ /**
+ * Checks whether runtime variable scope is lexical.
+ *
If true, lexical scope applies to local variables and parameters.
+ * Redefining a variable in the same lexical unit will generate errors.
+ * @return true if scope is lexical, false otherwise
+ */
+ public boolean isLexical() {
+ return isSet(LEXICAL, flags);
+ }
+
+ /**
+ * Checks whether local variables shade global ones.
+ *
After a symbol is defined as local, dereferencing it outside its
+ * scope will trigger an error instead of seeking a global variable of the
+ * same name. To further reduce potential naming ambiguity errors,
+ * global variables (ie non local) must be declared to be assigned (@link JexlContext#has(String) )
+ * when this flag is on; attempting to set an undeclared global variables will
+ * raise an error.
+ * @return true if lexical shading is applied, false otherwise
+ */
+ public boolean isLexicalShade() {
+ return isSet(SHADE, flags);
+ }
+
+ /**
+ * Checks whether the engine considers null in navigation expression as
+ * errors during evaluation..
+ * @return true if safe, false otherwise
+ */
+ public boolean isSafe() {
+ return isSet(SAFE, flags);
+ }
+
+ /**
+ * Checks whether the engine will throw a {@link JexlException} when an
+ * error is encountered during evaluation.
+ * @return true if silent, false otherwise
+ */
+ public boolean isSilent() {
+ return isSet(SILENT, flags);
+ }
+
+ /**
+ * Checks whether the engine considers unknown variables, methods and
+ * constructors as errors during evaluation.
+ * @return true if strict, false otherwise
+ */
+ public boolean isStrict() {
+ return isSet(STRICT, flags);
+ }
+
+ /**
+ * Checks whether the arithmetic triggers errors during evaluation when null
+ * is used as an operand.
+ * @return true if strict, false otherwise
+ */
+ public boolean isStrictArithmetic() {
+ return strictArithmetic;
+ }
+
+ /**
+ * Sets whether the engine will attempt solving antish variable names from
+ * context.
+ * @param flag true if antish variables are solved, false otherwise
+ */
+ public void setAntish(final boolean flag) {
+ flags = set(ANTISH, flags, flag);
+ }
+
+ /**
+ * Sets whether the engine will throw JexlException.Cancel (true) or return
+ * null (false) when interrupted during evaluation.
+ * @param flag true when cancellable, false otherwise
+ */
+ public void setCancellable(final boolean flag) {
+ flags = set(CANCELLABLE, flags, flag);
+ }
+
+ /**
+ * Sets whether the engine uses a strict block lexical scope during
+ * evaluation.
+ * @param flag true if lexical scope is used, false otherwise
+ */
+ public void setLexical(final boolean flag) {
+ flags = set(LEXICAL, flags, flag);
+ }
+
+ /**
+ * Sets whether the engine strictly shades global variables.
+ * Local symbols shade globals after definition and creating global
+ * variables is prohibited during evaluation.
+ * If setting to lexical shade, lexical scope is also set.
+ * @param flag true if creation is allowed, false otherwise
+ */
+ public void setLexicalShade(final boolean flag) {
+ flags = set(SHADE, flags, flag);
+ if (flag) {
+ flags = set(LEXICAL, flags, true);
+ }
+ }
+
+ /**
+ * Sets the arithmetic math context.
+ * @param mcontext the context
+ */
+ public void setMathContext(final MathContext mcontext) {
+ this.mathContext = mcontext;
+ }
+
+ /**
+ * Sets the arithmetic math scale.
+ * @param mscale the scale
+ */
+ public void setMathScale(final int mscale) {
+ this.mathScale = mscale;
+ }
+
+ /**
+ * Sets whether the engine considers null in navigation expression as errors
+ * during evaluation.
+ * @param flag true if safe, false otherwise
+ */
+ public void setSafe(final boolean flag) {
+ flags = set(SAFE, flags, flag);
+ }
+
+ /**
+ * Sets whether the engine will throw a {@link JexlException} when an error
+ * is encountered during evaluation.
+ * @param flag true if silent, false otherwise
+ */
+ public void setSilent(final boolean flag) {
+ flags = set(SILENT, flags, flag);
+ }
+
+ /**
+ * Sets whether the engine considers unknown variables, methods and
+ * constructors as errors during evaluation.
+ * @param flag true if strict, false otherwise
+ */
+ public void setStrict(final boolean flag) {
+ flags = set(STRICT, flags, flag);
+ }
+
+ /**
+ * Sets the strict arithmetic flag.
+ * @param stricta true or false
+ */
+ public void setStrictArithmetic(final boolean stricta) {
+ this.strictArithmetic = stricta;
+ }
+
+ /**
+ * Whether these options are immutable at runtime.
+ *
Expert mode; allows instance handled through context to be shared
+ * instead of copied.
+ * @param flag true if shared, false if not
+ */
+ public void setSharedInstance(final boolean flag) {
+ flags = set(SHARED, flags, flag);
+ }
+
+ /**
+ * @return false if a copy of these options is used during execution,
+ * true if those can potentially be modified
+ */
+ public boolean isSharedInstance() {
+ return isSet(SHARED, flags);
+ }
+
+ /**
+ * Set options from engine.
+ * @param jexl the engine
+ * @return this instance
+ */
+ public JexlOptions set(final JexlEngine jexl) {
+ if (jexl instanceof Engine) {
+ ((Engine) jexl).optionsSet(this);
+ }
+ return this;
+ }
+
+ /**
+ * Set options from options.
+ * @param src the options
+ * @return this instance
+ */
+ public JexlOptions set(final JexlOptions src) {
+ mathContext = src.mathContext;
+ mathScale = src.mathScale;
+ strictArithmetic = src.strictArithmetic;
+ flags = src.flags;
+ namespaces = src.namespaces;
+ return this;
+ }
+
+ /**
+ * Gets the optional map of namespaces.
+ * @return the map of namespaces, may be empty, not null
+ */
+ public Map getNamespaces() {
+ return namespaces;
+ }
+
+ /**
+ * Sets the optional map of namespaces.
+ * @param ns a namespaces map
+ */
+ public void setNamespaces(final Map ns) {
+ this.namespaces = ns == null? Collections.emptyMap() : ns;
+ }
+
+ /**
+ * Creates a copy of this instance.
+ * @return a copy
+ */
+ public JexlOptions copy() {
+ return new JexlOptions().set(this);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlScript.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlScript.java
new file mode 100644
index 0000000..61e649b
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JexlScript.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * A JEXL Script.
+ *
+ * A script is some valid JEXL syntax to be executed with a given set of {@link JexlContext} variables.
+ *
+ * A script is a group of statements, separated by semicolons.
+ *
+ * The statements can be blocks
(curly braces containing code),
+ * Control statements such as if
and while
+ * as well as expressions and assignment statements.
+ *
+ * Do not create classes that implement this interface; delegate or compose instead.
+ *
+ * @since 1.1
+ */
+public interface JexlScript {
+
+ /**
+ * Returns the source text of this expression.
+ *
+ * @return the source text
+ */
+ String getSourceText();
+
+ /**
+ * Recreates the source text of this expression from the internal syntactic tree.
+ *
+ * @return the source text
+ */
+ String getParsedText();
+
+ /**
+ * Recreates the source text of this expression from the internal syntactic tree.
+ *
+ * @param indent the number of spaces for indentation, 0 meaning no indentation
+ * @return the source text
+ */
+ String getParsedText(int indent);
+
+ /**
+ * Executes the script with the variables contained in the
+ * supplied {@link JexlContext}.
+ *
+ * @param context A JexlContext containing variables.
+ * @return The result of this script, usually the result of
+ * the last statement.
+ */
+ Object execute(JexlContext context);
+
+ /**
+ * Executes the script with the variables contained in the
+ * supplied {@link JexlContext} and a set of arguments corresponding to the
+ * parameters used during parsing.
+ *
+ * @param context A JexlContext containing variables.
+ * @param args the arguments
+ * @return The result of this script, usually the result of
+ * the last statement.
+ * @since 2.1
+ */
+ Object execute(JexlContext context, Object... args);
+
+ /**
+ * Gets this script parameters.
+ *
+ * @return the parameters or null
+ * @since 2.1
+ */
+ String[] getParameters();
+
+ /**
+ * Gets this script unbound parameters.
+ * Parameters that haven't been bound by a previous call to curry().
+ * @return the parameters or null
+ * @since 3.2
+ */
+ String[] getUnboundParameters();
+
+ /**
+ * Gets this script local variables.
+ *
+ * @return the local variables or null
+ * @since 2.1
+ */
+ String[] getLocalVariables();
+
+ /**
+ * Gets this script variables.
+ * Note that since variables can be in an ant-ish form (ie foo.bar.quux), each variable is returned as
+ * a list of strings where each entry is a fragment of the variable ({"foo", "bar", "quux"} in the example.
+ *
+ * @return the variables or null
+ * @since 2.1
+ */
+ Set> getVariables();
+
+ /**
+ * Gets this script pragmas.
+ *
+ * @return the (non null, may be empty) pragmas map
+ */
+ Map getPragmas();
+
+ /**
+ * Creates a Callable from this script.
+ *
+ * This allows to submit it to an executor pool and provides support for asynchronous calls.
+ * The interpreter will handle interruption/cancellation gracefully if needed.
+ *
+ * @param context the context
+ * @return the callable
+ * @since 2.1
+ */
+ Callable callable(JexlContext context);
+
+ /**
+ * Creates a Callable from this script.
+ *
+ * This allows to submit it to an executor pool and provides support for asynchronous calls.
+ * The interpreter will handle interruption/cancellation gracefully if needed.
+ *
+ * @param context the context
+ * @param args the script arguments
+ * @return the callable
+ * @since 2.1
+ */
+ Callable callable(JexlContext context, Object... args);
+
+ /**
+ * Curries this script, returning a script with bound arguments.
+ *
+ * If this script does not declare parameters or if all of them are already bound,
+ * no error is generated and this script is returned.
+ *
+ * @param args the arguments to bind
+ * @return the curried script or this script if no binding can occur
+ */
+ JexlScript curry(Object... args);
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JxltEngine.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JxltEngine.java
new file mode 100644
index 0000000..0c2201e
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/JxltEngine.java
@@ -0,0 +1,417 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A simple "JeXL Template" engine.
+ *
+ * At the base is an evaluator similar to the Unified EL evaluator used in JSP/JSF based on JEXL.
+ * At the top is a template engine inspired by Velocity that uses JEXL (instead of OGNL/VTL) as the scripting
+ * language.
+ *
+ * The evaluator is intended to be used in configuration modules, XML based frameworks or JSP taglibs
+ * and facilitate the implementation of expression evaluation.
+ *
+ * The template engine is intended to output any form of text; html, XML, CSV...
+ *
+ * @since 3.0
+ */
+public abstract class JxltEngine {
+
+ /**
+ * The sole type of (runtime) exception the JxltEngine can throw.
+ */
+ public static class Exception extends JexlException {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = 201112030113L;
+
+ /**
+ * Creates an Exception.
+ *
+ * @param info the contextual information
+ * @param msg the exception message
+ * @param cause the exception cause
+ */
+ public Exception(final JexlInfo info, final String msg, final Throwable cause) {
+ super(info, msg, cause);
+ }
+ }
+
+ /**
+ * A unified expression that can mix immediate, deferred and nested sub-expressions as well as string constants;
+ *
+ * The "immediate" syntax is of the form "...${jexl-expr}..."
+ * The "deferred" syntax is of the form "...#{jexl-expr}..."
+ * The "nested" syntax is of the form "...#{...${jexl-expr0}...}..."
+ * The "composite" syntax is of the form "...${jexl-expr0}... #{jexl-expr1}..."
+ *
+ *
+ * Deferred and immediate expression carry different intentions:
+ *
+ *
+ * An immediate expression indicate that evaluation is intended to be performed close to
+ * the definition/parsing point.
+ * A deferred expression indicate that evaluation is intended to occur at a later stage.
+ *
+ *
+ * For instance: "Hello ${name}, now is #{time}"
is a composite "deferred" expression since one
+ * of its subexpressions is deferred. Furthermore, this (composite) expression intent is
+ * to perform two evaluations; one close to its definition and another one in a later
+ * phase.
+ *
+ * The API reflects this feature in 2 methods, prepare and evaluate. The prepare method
+ * will evaluate the immediate subexpression and return an expression that contains only
+ * the deferred subexpressions (and constants), a prepared expression. Such a prepared expression
+ * is suitable for a later phase evaluation that may occur with a different JexlContext.
+ * Note that it is valid to call evaluate without prepare in which case the same JexlContext
+ * is used for the 2 evaluation phases.
+ *
+ * In the most common use-case where deferred expressions are to be kept around as properties of objects,
+ * one should createExpression and prepare an expression before storing it and evaluate it each time
+ * the property storing it is accessed.
+ *
+ * Note that nested expression use the JEXL syntax as in:
+ *
+ * "#{${bar}+'.charAt(2)'}"
+ *
+ * The most common mistake leading to an invalid expression being the following:
+ *
+ * "#{${bar}charAt(2)}"
+ *
+ * Also note that methods that createExpression evaluate expressions may throw unchecked exceptions;
+ * The {@link Exception} are thrown when the engine instance is in "non-silent" mode
+ * but since these are RuntimeException, user-code should catch them where appropriate.
+ *
+ * @since 2.0
+ */
+ public interface Expression {
+
+ /**
+ * Generates this expression's string representation.
+ *
+ * @return the string representation
+ */
+ String asString();
+
+ /**
+ * Adds this expression's string representation to a StringBuilder.
+ *
+ * @param strb the builder to fill
+ * @return the builder argument
+ */
+ StringBuilder asString(StringBuilder strb);
+
+ /**
+ * Evaluates this expression.
+ *
+ * If the underlying JEXL engine is silent, errors will be logged through its logger as warning.
+ *
+ * @param context the variable context
+ * @return the result of this expression evaluation or null if an error occurs and the {@link JexlEngine} is
+ * running in silent mode
+ * @throws Exception if an error occurs and the {@link JexlEngine}
+ * is not silent
+ */
+ Object evaluate(JexlContext context);
+
+ /**
+ * Retrieves this expression's source expression.
+ *
+ * If this expression was prepared, this allows to retrieve the
+ * original expression that lead to it.
+ * Other expressions return themselves.
+ *
+ * @return the source expression
+ */
+ Expression getSource();
+
+ /**
+ * Gets the list of variables accessed by this expression.
+ * This method will visit all nodes of the sub-expressions and extract all variables whether they
+ * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to a['b']).
+ *
+ * @return the set of variables, each as a list of strings (ant-ish variables use more than 1 string)
+ * or the empty set if no variables are used
+ */
+ Set> getVariables();
+
+ /**
+ * Checks whether this expression is deferred.
+ *
+ * @return true if deferred, false otherwise
+ */
+ boolean isDeferred();
+
+ /**
+ * Checks whether this expression is immediate.
+ *
+ * @return true if immediate, false otherwise
+ */
+ boolean isImmediate();
+
+ /**
+ * Evaluates the immediate sub-expressions.
+ *
+ * When the expression is dependant upon immediate and deferred sub-expressions,
+ * evaluates the immediate sub-expressions with the context passed as parameter
+ * and returns this expression deferred form.
+ *
+ * In effect, this binds the result of the immediate sub-expressions evaluation in the
+ * context, allowing to differ evaluation of the remaining (deferred) expression within another context.
+ * This only has an effect to nested and composite expressions that contain differed and
+ * immediate sub-expressions.
+ *
+ * If the underlying JEXL engine is silent, errors will be logged through its logger as warning.*
+ *
+ * @param context the context to use for immediate expression evaluations
+ * @return an {@link Expression} or null if an error occurs and the {@link JexlEngine} is running
+ * in silent mode
+ * @throws Exception if an error occurs and the {@link JexlEngine} is not in silent mode
+ */
+ Expression prepare(JexlContext context);
+
+ /**
+ * Formats this expression, adding its source string representation in
+ * comments if available: 'expression /*= source *\/'' .
+ *
+ * @return the formatted expression string
+ */
+ @Override
+ String toString();
+ }
+
+ /**
+ * Creates a a {@link Expression} from an expression string.
+ * Uses and fills up the expression cache if any.
+ *
+ * If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.
+ *
+ * @param expression the {@link Template} string expression
+ * @return the {@link Expression}, null if silent and an error occurred
+ * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
+ */
+ public Expression createExpression(final String expression) {
+ return createExpression(null, expression);
+ }
+
+ /**
+ * Creates a a {@link Expression} from an expression string.
+ * Uses and fills up the expression cache if any.
+ *
+ * If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.
+ *
+ * @param info the {@link JexlInfo} source information
+ * @param expression the {@link Template} string expression
+ * @return the {@link Expression}, null if silent and an error occurred
+ * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
+ */
+ public abstract Expression createExpression(JexlInfo info, String expression);
+
+ /**
+ * A template is a JEXL script that evaluates by writing its content through a Writer.
+ *
+ * The source text is parsed considering each line beginning with '$$' (as default pattern) as JEXL script code
+ * and all others as Unified JEXL expressions; those expressions will be invoked from the script during
+ * evaluation and their output gathered through a writer.
+ * It is thus possible to use looping or conditional construct "around" expressions generating output.
+ *
+ * For instance:
+ *
+ * $$ for(var x : [1, 3, 5, 42, 169]) {
+ * $$ if (x == 42) {
+ * Life, the universe, and everything
+ * $$ } else if (x > 42) {
+ * The value $(x} is over forty-two
+ * $$ } else {
+ * The value ${x} is under forty-two
+ * $$ }
+ * $$ }
+ *
+ *
+ * Will evaluate as:
+ *
+ *
+ * The value 1 is under forty-two
+ * The value 3 is under forty-two
+ * The value 5 is under forty-two
+ * Life, the universe, and everything
+ * The value 169 is over forty-two
+ *
+ *
+ * During evaluation, the template context exposes its writer as '$jexl' which is safe to use in this case.
+ * This allows writing directly through the writer without adding new-lines as in:
+ *
+ *
+ * $$ for(var cell : cells) { $jexl.print(cell); $jexl.print(';') }
+ *
+ *
+ * A template is expanded as one JEXL script and a list of template expressions; each template expression is
+ * being replaced in the script by a call to jexl:print(expr) (the expr is in fact the expr number in the template).
+ * This integration uses a specialized JexlContext (TemplateContext) that serves as a namespace (for jexl:)
+ * and stores the template expression array and the writer (java.io.Writer) that the 'jexl:print(...)'
+ * delegates the output generation to.
+ *
+ * @since 3.0
+ */
+ public interface Template {
+
+ /**
+ * Recreate the template source from its inner components.
+ *
+ * @return the template source rewritten
+ */
+ String asString();
+
+ /**
+ * Evaluates this template.
+ *
+ * @param context the context to use during evaluation
+ * @param writer the writer to use for output
+ */
+ void evaluate(JexlContext context, Writer writer);
+
+ /**
+ * Evaluates this template.
+ *
+ * @param context the context to use during evaluation
+ * @param writer the writer to use for output
+ * @param args the arguments
+ */
+ void evaluate(JexlContext context, Writer writer, Object... args);
+
+ /**
+ * Prepares this template by expanding any contained deferred TemplateExpression.
+ *
+ * @param context the context to prepare against
+ * @return the prepared version of the template
+ */
+ Template prepare(JexlContext context);
+
+ /**
+ * Gets the list of variables accessed by this template.
+ * This method will visit all nodes of the sub-expressions and extract all variables whether they
+ * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to a['b']).
+ *
+ * @return the set of variables, each as a list of strings (ant-ish variables use more than 1 string)
+ * or the empty set if no variables are used
+ */
+ Set> getVariables();
+
+ /**
+ * Gets the list of parameters expected by this template.
+ *
+ * @return the parameter names array
+ */
+ String[] getParameters();
+
+ /**
+ * Gets this script pragmas.
+ *
+ * @return the (non null, may be empty) pragmas map
+ * @since 3.1
+ */
+ Map getPragmas();
+ }
+
+ /**
+ * Creates a new template.
+ *
+ * @param info the jexl info (file, line, column)
+ * @param prefix the directive prefix
+ * @param source the source
+ * @param parms the parameter names
+ * @return the template
+ */
+ public abstract Template createTemplate(JexlInfo info, String prefix, Reader source, String... parms);
+
+ /**
+ * Creates a new template.
+ *
+ * @param info the source info
+ * @param parms the parameter names
+ * @param source the source
+ * @return the template
+ */
+ public Template createTemplate(final JexlInfo info, final String source, final String... parms) {
+ return createTemplate(info, "$$", new StringReader(source), parms);
+ }
+
+ /**
+ * Creates a new template.
+ *
+ * @param info the source info
+ * @param source the source
+ * @return the template
+ */
+ public Template createTemplate(final JexlInfo info, final String source) {
+ return createTemplate(info, "$$", new StringReader(source), (String[]) null);
+ }
+
+ /**
+ * Creates a new template.
+ *
+ * @param prefix the directive prefix
+ * @param source the source
+ * @param parms the parameter names
+ * @return the template
+ */
+ public Template createTemplate(final String prefix, final Reader source, final String... parms) {
+ return createTemplate(null, prefix, source, parms);
+ }
+
+ /**
+ * Creates a new template.
+ *
+ * @param source the source
+ * @param parms the parameter names
+ * @return the template
+ */
+ public Template createTemplate(final String source, final String... parms) {
+ return createTemplate(null, source, parms);
+ }
+
+ /**
+ * Creates a new template.
+ *
+ * @param source the source
+ * @return the template
+ */
+ public Template createTemplate(final String source) {
+ return createTemplate(null, source);
+ }
+
+ /**
+ * Gets the {@link JexlEngine} underlying this template engine.
+ *
+ * @return the JexlEngine
+ */
+ public abstract JexlEngine getEngine();
+
+ /**
+ * Clears the cache.
+ */
+ public abstract void clearCache();
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/MapContext.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/MapContext.java
new file mode 100644
index 0000000..8b7f2b8
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/MapContext.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Wraps a map in a context.
+ * Each entry in the map is considered a variable name, value pair.
+ */
+public class MapContext implements JexlContext {
+
+ /**
+ * The wrapped variable map.
+ */
+ private final Map map;
+
+ /**
+ * Creates a MapContext on an automatically allocated underlying HashMap.
+ */
+ public MapContext() {
+ this(null);
+ }
+
+ /**
+ * Creates a MapContext wrapping an existing user provided map.
+ *
+ * @param vars the variable map
+ */
+ public MapContext(final Map vars) {
+ map = vars == null ? new HashMap() : vars;
+ }
+
+ @Override
+ public boolean has(final String name) {
+ return map.containsKey(name);
+ }
+
+ @Override
+ public Object get(final String name) {
+ return map.get(name);
+ }
+
+ @Override
+ public void set(final String name, final Object value) {
+ map.put(name, value);
+ }
+
+ /**
+ * Clears all variables.
+ */
+ public void clear() {
+ map.clear();
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/ObjectContext.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/ObjectContext.java
new file mode 100644
index 0000000..b9fa659
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/ObjectContext.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertySet;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertyGet;
+
+/**
+ * Wraps an Object as a JEXL context and NamespaceResolver.
+ *
+ * @param the wrapped object type to use
+ * @since 3.0
+ */
+public class ObjectContext implements JexlContext, JexlContext.NamespaceResolver {
+
+ /** The property solving jexl engine. */
+ private final JexlEngine jexl;
+
+ /** The object serving as context provider. */
+ private final T object;
+
+ /**
+ * @return the Jexl engine
+ */
+ protected JexlEngine getJexl() {
+ return jexl;
+ }
+
+ /**
+ * @return the object exposed by this context
+ */
+ protected T getObject() {
+ return object;
+ }
+
+ /**
+ * Creates a new ObjectContext.
+ *
+ * @param engine the jexl engine to use to solve properties
+ * @param wrapped the object to wrap in this context
+ */
+ public ObjectContext(final JexlEngine engine, final T wrapped) {
+ this.jexl = engine;
+ this.object = wrapped;
+ }
+
+ @Override
+ public Object get(final String name) {
+ final JexlPropertyGet jget = jexl.getUberspect().getPropertyGet(object, name);
+ if (jget != null) {
+ try {
+ return jget.invoke(object);
+ } catch (final Exception xany) {
+ if (jexl.isStrict()) {
+ throw new JexlException.Property(null, name, true, xany);
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void set(final String name, final Object value) {
+ final JexlPropertySet jset = jexl.getUberspect().getPropertySet(object, name, value);
+ if (jset != null) {
+ try {
+ jset.invoke(object, value);
+ } catch (final Exception xany) {
+ // ignore
+ if (jexl.isStrict()) {
+ throw new JexlException.Property(null, name, true, xany);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean has(final String name) {
+ final JexlPropertyGet jget = jexl.getUberspect().getPropertyGet(object, name);
+ try {
+ return jget != null && jget.invoke(object) != null;
+ } catch (final Exception xany) {
+ return false;
+ }
+ }
+
+ @Override
+ public Object resolveNamespace(final String name) {
+ if (name == null || name.isEmpty()) {
+ return object;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/annotations/NoJexl.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/annotations/NoJexl.java
new file mode 100644
index 0000000..e2cd813
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/annotations/NoJexl.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.annotations;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlSandbox;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates JEXL Introspection should not see this element.
+ *
+ * This allows to completely hide a package, class, interface, constructor, method or field from
+ * JEXL; a NoJexl annotated element will not be usable through any kind of JEXL expression or script.
+ *
+ * See {@link JexlSandbox} for another way to restrict JEXL access.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.PACKAGE})
+public @interface NoJexl {
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/annotations/package.html b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/annotations/package.html
new file mode 100644
index 0000000..2748ab8
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/annotations/package.html
@@ -0,0 +1,27 @@
+
+
+
+ Package Documentation for org.apache.commons.jexl3.annotations Package
+
+
+ Provides annotation for introspection services.
+ The only annotation in this package allows to restrict what JEXL
+ can introspect and expose through scripting.
+
+
+
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/ArrayBuilder.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/ArrayBuilder.java
new file mode 100644
index 0000000..a621b1b
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/ArrayBuilder.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlArithmetic;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Arrays;
+
+/**
+ * Helper class to create typed arrays.
+ */
+public class ArrayBuilder implements JexlArithmetic.ArrayBuilder {
+ /** The number of primitive types. */
+ private static final int PRIMITIVE_SIZE = 8;
+ /** The boxing types to primitive conversion map. */
+ private static final Map, Class>> BOXING_CLASSES;
+ static {
+ BOXING_CLASSES = new IdentityHashMap, Class>>(PRIMITIVE_SIZE);
+ BOXING_CLASSES.put(Boolean.class, Boolean.TYPE);
+ BOXING_CLASSES.put(Byte.class, Byte.TYPE);
+ BOXING_CLASSES.put(Character.class, Character.TYPE);
+ BOXING_CLASSES.put(Double.class, Double.TYPE);
+ BOXING_CLASSES.put(Float.class, Float.TYPE);
+ BOXING_CLASSES.put(Integer.class, Integer.TYPE);
+ BOXING_CLASSES.put(Long.class, Long.TYPE);
+ BOXING_CLASSES.put(Short.class, Short.TYPE);
+ }
+
+ /**
+ * Gets the primitive type of a given class (when it exists).
+ * @param parm a class
+ * @return the primitive type or null it the argument is not unboxable
+ */
+ protected static Class> unboxingClass(final Class> parm) {
+ final Class> prim = BOXING_CLASSES.get(parm);
+ return prim == null ? parm : prim;
+ }
+
+ /** The intended class array. */
+ protected Class> commonClass = null;
+ /** Whether the array stores numbers. */
+ protected boolean isNumber = true;
+ /** Whether we can try unboxing. */
+ protected boolean unboxing = true;
+ /** The untyped list of items being added. */
+ protected final Object[] untyped;
+ /** Number of added items. */
+ protected int added = 0;
+
+ /**
+ * Creates a new builder.
+ * @param size the exact array size
+ */
+ public ArrayBuilder(final int size) {
+ untyped = new Object[size];
+ }
+
+ @Override
+ public void add(final Object value) {
+ // for all children after first...
+ if (!Object.class.equals(commonClass)) {
+ if (value == null) {
+ isNumber = false;
+ unboxing = false;
+ } else {
+ Class> eclass = value.getClass();
+ // base common class on first non-null entry
+ if (commonClass == null) {
+ commonClass = eclass;
+ isNumber = isNumber && Number.class.isAssignableFrom(commonClass);
+ } else if (!commonClass.equals(eclass)) {
+ // if both are numbers...
+ if (isNumber && Number.class.isAssignableFrom(eclass)) {
+ commonClass = Number.class;
+ } else {
+ // attempt to find valid superclass
+ do {
+ eclass = eclass.getSuperclass();
+ if (eclass == null) {
+ commonClass = Object.class;
+ break;
+ }
+ } while (!commonClass.isAssignableFrom(eclass));
+ }
+ }
+ }
+ }
+ if (added >= untyped.length) {
+ throw new IllegalArgumentException("add() over size");
+ }
+ untyped[added++] = value;
+ }
+
+ @Override
+ public Object create(final boolean extended) {
+ if (untyped == null) {
+ return new Object[0];
+ }
+ if (extended) {
+ final List list = new ArrayList(added);
+ list.addAll(Arrays.asList(untyped).subList(0, added));
+ return list;
+ }
+ // convert untyped array to the common class if not Object.class
+ if ((commonClass == null) || Object.class.equals(commonClass)) {
+ return untyped.clone();
+ }
+ final int size = added;
+ // if the commonClass is a number, it has an equivalent primitive type, get it
+ if (unboxing) {
+ commonClass = unboxingClass(commonClass);
+ }
+ // allocate and fill up the typed array
+ final Object typed = Array.newInstance(commonClass, size);
+ for (int i = 0; i < size; ++i) {
+ Array.set(typed, i, untyped[i]);
+ }
+ return typed;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Closure.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Closure.java
new file mode 100644
index 0000000..0ed2801
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Closure.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlContext;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTJexlLambda;
+
+import java.util.Objects;
+
+/**
+ * A Script closure.
+ */
+public class Closure extends Script {
+ /** The frame. */
+ protected final Frame frame;
+
+ /**
+ * Creates a closure.
+ * @param theCaller the calling interpreter
+ * @param lambda the lambda
+ */
+ protected Closure(final Interpreter theCaller, final ASTJexlLambda lambda) {
+ super(theCaller.jexl, null, lambda);
+ frame = lambda.createFrame(theCaller.frame);
+ }
+
+ /**
+ * Creates a curried version of a script.
+ * @param base the base script
+ * @param args the script arguments
+ */
+ protected Closure(final Script base, final Object[] args) {
+ super(base.jexl, base.source, base.script);
+ final Frame sf = (base instanceof Closure) ? ((Closure) base).frame : null;
+ frame = sf == null
+ ? script.createFrame(args)
+ : sf.assign(args);
+ }
+
+ @Override
+ public int hashCode() {
+ // CSOFF: Magic number
+ int hash = 17;
+ hash = 31 * hash + (this.jexl != null ? this.jexl.hashCode() : 0);
+ hash = 31 * hash + (this.source != null ? this.source.hashCode() : 0);
+ hash = 31 * hash + (this.frame != null ? this.frame.hashCode() : 0);
+ // CSON: Magic number
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Closure other = (Closure) obj;
+ if (this.jexl != other.jexl) {
+ return false;
+ }
+ if (!Objects.equals(this.source, other.source)) {
+ return false;
+ }
+ if (!Objects.equals(this.frame, other.frame)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String[] getUnboundParameters() {
+ return frame.getUnboundParameters();
+ }
+
+ /**
+ * Sets the captured index of a given symbol, ie the target index of a parent
+ * captured symbol in this closure's frame.
+ * This is meant to allow a locally defined function to "see" and call
+ * itself as a local (captured) variable;
+ * in other words, this allows recursive call of a function.
+ * @param symbol the symbol index (in the caller of this closure)
+ * @param value the value to set in the local frame
+ */
+ public void setCaptured(final int symbol, final Object value) {
+ if (script instanceof ASTJexlLambda) {
+ final ASTJexlLambda lambda = (ASTJexlLambda) script;
+ final Scope scope = lambda.getScope();
+ if (scope != null) {
+ final Integer reg = scope.getCaptured(symbol);
+ if (reg != null) {
+ frame.set(reg, value);
+ }
+ }
+ }
+ }
+
+ @Override
+ public Object evaluate(final JexlContext context) {
+ return execute(context, (Object[])null);
+ }
+
+ @Override
+ public Object execute(final JexlContext context) {
+ return execute(context, (Object[])null);
+ }
+
+ @Override
+ public Object execute(final JexlContext context, final Object... args) {
+ final Frame local = frame != null? frame.assign(args) : null;
+ final Interpreter interpreter = createInterpreter(context, local);
+ return interpreter.runClosure(this, null);
+ }
+
+ @Override
+ public Callable callable(final JexlContext context, final Object... args) {
+ final Frame local = frame != null? frame.assign(args) : null;
+ return new Callable(createInterpreter(context, local)) {
+ @Override
+ public Object interpret() {
+ return interpreter.runClosure(Closure.this, null);
+ }
+ };
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Debugger.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Debugger.java
new file mode 100644
index 0000000..d80a437
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Debugger.java
@@ -0,0 +1,1082 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlExpression;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlInfo;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlScript;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAddNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAndNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTArguments;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTArrayAccess;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTArrayLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAssignment;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBlock;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBreak;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTConstructorNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTContinue;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTDivNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTDoWhileStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTEQNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTERNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTEWNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTEmptyFunction;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTExtendedLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTFalseNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTForeachStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTFunctionNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTGENode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTGTNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTIdentifier;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTIdentifierAccess;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTIfStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTJexlLambda;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTJexlScript;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTJxltLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTLENode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTLTNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTMapEntry;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTMapLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTMethodNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTModNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTMulNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNENode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNEWNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNRNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNSWNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNotNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNullLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNumberLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTOrNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTRangeNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTReference;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTReferenceExpression;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTRegexLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTReturnStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSWNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetAddNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetAndNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetDivNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetModNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetMultNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetOrNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetSubNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetXorNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSizeFunction;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTStringLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSubNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTTernaryNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTTrueNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTVar;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTWhileStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAnnotation;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNullpNode;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.JexlNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ParserVisitor;
+
+import java.util.regex.Pattern;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTUnaryPlusNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.StringParser;
+
+/**
+ * Helps pinpoint the cause of problems in expressions that fail during evaluation.
+ *
+ * It rebuilds an expression string from the tree and the start/end offsets of the cause in that string.
+ * This implies that exceptions during evaluation do always carry the node that's causing the error.
+ *
+ * @since 2.0
+ */
+public class Debugger extends ParserVisitor implements JexlInfo.Detail {
+ /** The builder to compose messages. */
+ protected final StringBuilder builder = new StringBuilder();
+ /** The cause of the issue to debug. */
+ protected JexlNode cause = null;
+ /** The starting character location offset of the cause in the builder. */
+ protected int start = 0;
+ /** The ending character location offset of the cause in the builder. */
+ protected int end = 0;
+ /** The indentation level. */
+ protected int indentLevel = 0;
+ /** Perform indentation?. */
+ protected int indent = 2;
+ /** accept() relative depth. */
+ protected int depth = Integer.MAX_VALUE;
+
+ /**
+ * Creates a Debugger.
+ */
+ public Debugger() {
+ }
+
+ /**
+ * Resets this debugger state.
+ */
+ public void reset() {
+ builder.setLength(0);
+ cause = null;
+ start = 0;
+ end = 0;
+ indentLevel = 0;
+ indent = 2;
+ depth = Integer.MAX_VALUE;
+ }
+
+ /**
+ * Position the debugger on the root of an expression.
+ * @param jscript the expression
+ * @return true if the expression was a {@link Script} instance, false otherwise
+ */
+ public boolean debug(final JexlExpression jscript) {
+ if (jscript instanceof Script) {
+ return debug(((Script) jscript).script);
+ }
+ return false;
+ }
+
+ /**
+ * Position the debugger on the root of a script.
+ * @param jscript the script
+ * @return true if the script was a {@link Script} instance, false otherwise
+ */
+ public boolean debug(final JexlScript jscript) {
+ if (jscript instanceof Script) {
+ return debug(((Script) jscript).script);
+ }
+ return false;
+ }
+
+ /**
+ * Seeks the location of an error cause (a node) in an expression.
+ * @param node the node to debug
+ * @return true if the cause was located, false otherwise
+ */
+ public boolean debug(final JexlNode node) {
+ return debug(node, true);
+ }
+
+ /**
+ * Seeks the location of an error cause (a node) in an expression.
+ * @param node the node to debug
+ * @param r whether we should actively find the root node of the debugged node
+ * @return true if the cause was located, false otherwise
+ */
+ public boolean debug(final JexlNode node, final boolean r) {
+ start = 0;
+ end = 0;
+ indentLevel = 0;
+ if (node != null) {
+ builder.setLength(0);
+ cause = node;
+ // make arg cause become the root cause
+ JexlNode walk = node;
+ if (r) {
+ while (walk.jjtGetParent() != null) {
+ walk = walk.jjtGetParent();
+ }
+ }
+ accept(walk, null);
+ }
+ return end > 0;
+ }
+
+ /**
+ * @return The rebuilt expression
+ */
+ @Override
+ public String toString() {
+ return builder.toString();
+ }
+
+ /**
+ * Rebuilds an expression from a JEXL node.
+ * @param node the node to rebuilt from
+ * @return the rebuilt expression
+ * @since 3.0
+ */
+ public String data(final JexlNode node) {
+ start = 0;
+ end = 0;
+ indentLevel = 0;
+ if (node != null) {
+ builder.setLength(0);
+ cause = node;
+ accept(node, null);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * @return The starting offset location of the cause in the expression
+ */
+ @Override
+ public int start() {
+ return start;
+ }
+
+ /**
+ * @return The end offset location of the cause in the expression
+ */
+ @Override
+ public int end() {
+ return end;
+ }
+
+ /**
+ * Sets the indentation level.
+ * @param level the number of spaces for indentation, none if less or equal to zero
+ */
+ public void setIndentation(final int level) {
+ indentation(level);
+ }
+
+ /**
+ * Sets the indentation level.
+ * @param level the number of spaces for indentation, none if less or equal to zero
+ * @return this debugger instance
+ */
+ public Debugger indentation(final int level) {
+ indent = Math.max(level, 0);
+ indentLevel = 0;
+ return this;
+ }
+
+ /**
+ * Sets this debugger relative maximum depth.
+ * @param rdepth the maximum relative depth from the debugged node
+ * @return this debugger instance
+ */
+ public Debugger depth(final int rdepth) {
+ this.depth = rdepth;
+ return this;
+ }
+
+ /**
+ * Checks if a child node is the cause to debug & adds its representation to the rebuilt expression.
+ * @param node the child node
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ protected Object accept(final JexlNode node, final Object data) {
+ if (depth <= 0) {
+ builder.append("...");
+ return data;
+ }
+ if (node == cause) {
+ start = builder.length();
+ }
+ depth -= 1;
+ final Object value = node.jjtAccept(this, data);
+ depth += 1;
+ if (node == cause) {
+ end = builder.length();
+ }
+ return value;
+ }
+
+ /**
+ * Adds a statement node to the rebuilt expression.
+ * @param child the child node
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ protected Object acceptStatement(final JexlNode child, final Object data) {
+ final JexlNode parent = child.jjtGetParent();
+ if (indent > 0 && (parent instanceof ASTBlock || parent instanceof ASTJexlScript)) {
+ for (int i = 0; i < indentLevel; ++i) {
+ for(int s = 0; s < indent; ++s) {
+ builder.append(' ');
+ }
+ }
+ }
+ depth -= 1;
+ final Object value = accept(child, data);
+ depth += 1;
+ // blocks, if, for & while don't need a ';' at end
+ if (!(child instanceof ASTJexlScript
+ || child instanceof ASTBlock
+ || child instanceof ASTIfStatement
+ || child instanceof ASTForeachStatement
+ || child instanceof ASTWhileStatement
+ || child instanceof ASTDoWhileStatement
+ || child instanceof ASTAnnotation)) {
+ builder.append(';');
+ if (indent > 0) {
+ builder.append('\n');
+ } else {
+ builder.append(' ');
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Checks if a terminal node is the cause to debug & adds its representation to the rebuilt expression.
+ * @param node the child node
+ * @param image the child node token image (may be null)
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ protected Object check(final JexlNode node, final String image, final Object data) {
+ if (node == cause) {
+ start = builder.length();
+ }
+ if (image != null) {
+ builder.append(image);
+ } else {
+ builder.append(node.toString());
+ }
+ if (node == cause) {
+ end = builder.length();
+ }
+ return data;
+ }
+
+ /**
+ * Checks if the children of a node using infix notation is the cause to debug, adds their representation to the
+ * rebuilt expression.
+ * @param node the child node
+ * @param infix the child node token
+ * @param paren whether the child should be parenthesized
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ protected Object infixChildren(final JexlNode node, final String infix, final boolean paren, final Object data) {
+ final int num = node.jjtGetNumChildren(); //child.jjtGetNumChildren() > 1;
+ if (paren) {
+ builder.append('(');
+ }
+ for (int i = 0; i < num; ++i) {
+ if (i > 0) {
+ builder.append(infix);
+ }
+ accept(node.jjtGetChild(i), data);
+ }
+ if (paren) {
+ builder.append(')');
+ }
+ return data;
+ }
+
+ /**
+ * Checks if the child of a node using prefix notation is the cause to debug, adds their representation to the
+ * rebuilt expression.
+ * @param node the node
+ * @param prefix the node token
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ protected Object prefixChild(final JexlNode node, final String prefix, final Object data) {
+ final boolean paren = node.jjtGetChild(0).jjtGetNumChildren() > 1;
+ builder.append(prefix);
+ if (paren) {
+ builder.append('(');
+ }
+ accept(node.jjtGetChild(0), data);
+ if (paren) {
+ builder.append(')');
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTAddNode node, final Object data) {
+ return additiveNode(node, " + ", data);
+ }
+
+ @Override
+ protected Object visit(final ASTSubNode node, final Object data) {
+ return additiveNode(node, " - ", data);
+ }
+
+ /**
+ * Rebuilds an additive expression.
+ * @param node the node
+ * @param op the operator
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ protected Object additiveNode(final JexlNode node, final String op, final Object data) {
+ // need parenthesis if not in operator precedence order
+ final boolean paren = node.jjtGetParent() instanceof ASTMulNode
+ || node.jjtGetParent() instanceof ASTDivNode
+ || node.jjtGetParent() instanceof ASTModNode;
+ final int num = node.jjtGetNumChildren();
+ if (paren) {
+ builder.append('(');
+ }
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ builder.append(op);
+ accept(node.jjtGetChild(i), data);
+ }
+ if (paren) {
+ builder.append(')');
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTAndNode node, final Object data) {
+ return infixChildren(node, " && ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTArrayAccess node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ for (int i = 0; i < num; ++i) {
+ builder.append('[');
+ accept(node.jjtGetChild(i), data);
+ builder.append(']');
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTExtendedLiteral node, final Object data) {
+ builder.append("...");
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTArrayLiteral node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ builder.append("[ ");
+ if (num > 0) {
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ builder.append(", ");
+ accept(node.jjtGetChild(i), data);
+ }
+ }
+ builder.append(" ]");
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTRangeNode node, final Object data) {
+ return infixChildren(node, " .. ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTAssignment node, final Object data) {
+ return infixChildren(node, " = ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseAndNode node, final Object data) {
+ return infixChildren(node, " & ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseComplNode node, final Object data) {
+ return prefixChild(node, "~", data);
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseOrNode node, final Object data) {
+ final boolean paren = node.jjtGetParent() instanceof ASTBitwiseAndNode;
+ return infixChildren(node, " | ", paren, data);
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseXorNode node, final Object data) {
+ final boolean paren = node.jjtGetParent() instanceof ASTBitwiseAndNode;
+ return infixChildren(node, " ^ ", paren, data);
+ }
+
+ @Override
+ protected Object visit(final ASTBlock node, final Object data) {
+ builder.append('{');
+ if (indent > 0) {
+ indentLevel += 1;
+ builder.append('\n');
+ } else {
+ builder.append(' ');
+ }
+ final int num = node.jjtGetNumChildren();
+ for (int i = 0; i < num; ++i) {
+ final JexlNode child = node.jjtGetChild(i);
+ acceptStatement(child, data);
+ }
+ if (indent > 0) {
+ indentLevel -= 1;
+ for (int i = 0; i < indentLevel; ++i) {
+ for(int s = 0; s < indent; ++s) {
+ builder.append(' ');
+ }
+ }
+ }
+ builder.append('}');
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTDivNode node, final Object data) {
+ return infixChildren(node, " / ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTEmptyFunction node, final Object data) {
+ builder.append("empty ");
+ accept(node.jjtGetChild(0), data);
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTEQNode node, final Object data) {
+ return infixChildren(node, " == ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTERNode node, final Object data) {
+ return infixChildren(node, " =~ ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSWNode node, final Object data) {
+ return infixChildren(node, " =^ ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTEWNode node, final Object data) {
+ return infixChildren(node, " =$ ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNSWNode node, final Object data) {
+ return infixChildren(node, " !^ ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNEWNode node, final Object data) {
+ return infixChildren(node, " !$ ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTFalseNode node, final Object data) {
+ return check(node, "false", data);
+ }
+
+ @Override
+ protected Object visit(final ASTContinue node, final Object data) {
+ return check(node, "continue", data);
+ }
+
+ @Override
+ protected Object visit(final ASTBreak node, final Object data) {
+ return check(node, "break", data);
+ }
+
+ @Override
+ protected Object visit(final ASTForeachStatement node, final Object data) {
+ builder.append("for(");
+ accept(node.jjtGetChild(0), data);
+ builder.append(" : ");
+ accept(node.jjtGetChild(1), data);
+ builder.append(") ");
+ if (node.jjtGetNumChildren() > 2) {
+ acceptStatement(node.jjtGetChild(2), data);
+ } else {
+ builder.append(';');
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTGENode node, final Object data) {
+ return infixChildren(node, " >= ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTGTNode node, final Object data) {
+ return infixChildren(node, " > ", false, data);
+ }
+
+ /** Checks identifiers that contain spaces or punctuation
+ * (but underscore, at-sign, sharp-sign and dollar).
+ */
+ protected static final Pattern QUOTED_IDENTIFIER =
+ Pattern.compile("[\\s]|[\\p{Punct}&&[^@#\\$_]]");
+
+ /**
+ * Checks whether an identifier should be quoted or not.
+ * @param str the identifier
+ * @return true if needing quotes, false otherwise
+ */
+ protected boolean needQuotes(final String str) {
+ return QUOTED_IDENTIFIER.matcher(str).find()
+ || "size".equals(str)
+ || "empty".equals(str);
+ }
+
+ @Override
+ protected Object visit(final ASTIdentifier node, final Object data) {
+ final String ns = node.getNamespace();
+ final String image = StringParser.escapeIdentifier(node.getName());
+ if (ns == null) {
+ return check(node, image, data);
+ }
+ final String nsid = StringParser.escapeIdentifier(ns) + ":" + image;
+ return check(node, nsid, data);
+ }
+
+ @Override
+ protected Object visit(final ASTIdentifierAccess node, final Object data) {
+ builder.append(node.isSafe() ? "?." : ".");
+ final String image = node.getName();
+ if (node.isExpression()) {
+ builder.append('`');
+ builder.append(image.replace("`", "\\`"));
+ builder.append('`');
+ } else if (needQuotes(image)) {
+ // quote it
+ builder.append('\'');
+ builder.append(image.replace("'", "\\'"));
+ builder.append('\'');
+ } else {
+ builder.append(image);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTIfStatement node, final Object data) {
+ final int numChildren = node.jjtGetNumChildren();
+ // if (...) ...
+ builder.append("if (");
+ accept(node.jjtGetChild(0), data);
+ builder.append(") ");
+ acceptStatement(node.jjtGetChild(1), data);
+ //.. else if (...) ...
+ for(int c = 2; c < numChildren - 1; c += 2) {
+ builder.append(" else if (");
+ accept(node.jjtGetChild(c), data);
+ builder.append(") ");
+ acceptStatement(node.jjtGetChild(c + 1), data);
+ }
+ // else... (if odd)
+ if ((numChildren & 1) == 1) {
+ builder.append(" else ");
+ acceptStatement(node.jjtGetChild(numChildren - 1), data);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTNumberLiteral node, final Object data) {
+ return check(node, node.toString(), data);
+ }
+
+ /**
+ * A pseudo visitor for parameters.
+ * @param p the parameter name
+ * @param data the visitor argument
+ * @return the parameter name to use
+ */
+ protected String visitParameter(final String p, final Object data) {
+ return p;
+ }
+
+ @Override
+ protected Object visit(final ASTJexlScript node, Object data) {
+ // if lambda, produce parameters
+ if (node instanceof ASTJexlLambda) {
+ final JexlNode parent = node.jjtGetParent();
+ // use lambda syntax if not assigned
+ final boolean named = parent instanceof ASTAssignment;
+ if (named) {
+ builder.append("function");
+ }
+ builder.append('(');
+ final String[] params = node.getParameters();
+ if (params != null && params.length > 0) {
+ builder.append(visitParameter(params[0], data));
+ for (int p = 1; p < params.length; ++p) {
+ builder.append(", ");
+ builder.append(visitParameter(params[p], data));
+ }
+ }
+ builder.append(')');
+ if (named) {
+ builder.append(' ');
+ } else {
+ builder.append("->");
+ }
+ // we will need a block...
+ }
+ // no parameters or done with them
+ final int num = node.jjtGetNumChildren();
+ if (num == 1 && !(node instanceof ASTJexlLambda)) {
+ data = accept(node.jjtGetChild(0), data);
+ } else {
+ for (int i = 0; i < num; ++i) {
+ final JexlNode child = node.jjtGetChild(i);
+ acceptStatement(child, data);
+ }
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTLENode node, final Object data) {
+ return infixChildren(node, " <= ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTLTNode node, final Object data) {
+ return infixChildren(node, " < ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTMapEntry node, final Object data) {
+ accept(node.jjtGetChild(0), data);
+ builder.append(" : ");
+ accept(node.jjtGetChild(1), data);
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTSetLiteral node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ builder.append("{ ");
+ if (num > 0) {
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ builder.append(",");
+ accept(node.jjtGetChild(i), data);
+ }
+ }
+ builder.append(" }");
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTMapLiteral node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ builder.append("{ ");
+ if (num > 0) {
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ builder.append(",");
+ accept(node.jjtGetChild(i), data);
+ }
+ } else {
+ builder.append(':');
+ }
+ builder.append(" }");
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTConstructorNode node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ builder.append("new(");
+ if (num > 0) {
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ builder.append(", ");
+ accept(node.jjtGetChild(i), data);
+ }
+ }
+ builder.append(")");
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTFunctionNode node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ if (num == 3) {
+ accept(node.jjtGetChild(0), data);
+ builder.append(":");
+ accept(node.jjtGetChild(1), data);
+ accept(node.jjtGetChild(2), data);
+ } else if (num == 2) {
+ accept(node.jjtGetChild(0), data);
+ accept(node.jjtGetChild(1), data);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTMethodNode node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ if (num == 2) {
+ accept(node.jjtGetChild(0), data);
+ accept(node.jjtGetChild(1), data);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTArguments node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ builder.append("(");
+ if (num > 0) {
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ builder.append(", ");
+ accept(node.jjtGetChild(i), data);
+ }
+ }
+ builder.append(")");
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTModNode node, final Object data) {
+ return infixChildren(node, " % ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTMulNode node, final Object data) {
+ return infixChildren(node, " * ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNENode node, final Object data) {
+ return infixChildren(node, " != ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNRNode node, final Object data) {
+ return infixChildren(node, " !~ ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNotNode node, final Object data) {
+ builder.append("!");
+ accept(node.jjtGetChild(0), data);
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTNullLiteral node, final Object data) {
+ check(node, "null", data);
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTOrNode node, final Object data) {
+ // need parenthesis if not in operator precedence order
+ final boolean paren = node.jjtGetParent() instanceof ASTAndNode;
+ return infixChildren(node, " || ", paren, data);
+ }
+
+ @Override
+ protected Object visit(final ASTReference node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ for (int i = 0; i < num; ++i) {
+ accept(node.jjtGetChild(i), data);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTReferenceExpression node, final Object data) {
+ final JexlNode first = node.jjtGetChild(0);
+ builder.append('(');
+ accept(first, data);
+ builder.append(')');
+ final int num = node.jjtGetNumChildren();
+ for (int i = 1; i < num; ++i) {
+ builder.append("[");
+ accept(node.jjtGetChild(i), data);
+ builder.append("]");
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTReturnStatement node, final Object data) {
+ builder.append("return ");
+ accept(node.jjtGetChild(0), data);
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTSizeFunction node, final Object data) {
+ builder.append("size ");
+ accept(node.jjtGetChild(0), data);
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTStringLiteral node, final Object data) {
+ final String img = node.getLiteral().replace("'", "\\'");
+ return check(node, "'" + img + "'", data);
+ }
+
+ @Override
+ protected Object visit(final ASTRegexLiteral node, final Object data) {
+ final String img = node.toString().replace("/", "\\/");
+ return check(node, "~/" + img + "/", data);
+ }
+
+ @Override
+ protected Object visit(final ASTTernaryNode node, final Object data) {
+ accept(node.jjtGetChild(0), data);
+ if (node.jjtGetNumChildren() > 2) {
+ builder.append("? ");
+ accept(node.jjtGetChild(1), data);
+ builder.append(" : ");
+ accept(node.jjtGetChild(2), data);
+ } else {
+ builder.append("?: ");
+ accept(node.jjtGetChild(1), data);
+
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTNullpNode node, final Object data) {
+ accept(node.jjtGetChild(0), data);
+ builder.append("??");
+ accept(node.jjtGetChild(1), data);
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTTrueNode node, final Object data) {
+ check(node, "true", data);
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTUnaryMinusNode node, final Object data) {
+ return prefixChild(node, "-", data);
+ }
+
+ @Override
+ protected Object visit(final ASTUnaryPlusNode node, final Object data) {
+ return prefixChild(node, "+", data);
+ }
+
+ @Override
+ protected Object visit(final ASTVar node, final Object data) {
+ builder.append("var ");
+ check(node, node.getName(), data);
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTWhileStatement node, final Object data) {
+ builder.append("while (");
+ accept(node.jjtGetChild(0), data);
+ builder.append(") ");
+ if (node.jjtGetNumChildren() > 1) {
+ acceptStatement(node.jjtGetChild(1), data);
+ } else {
+ builder.append(';');
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTDoWhileStatement node, final Object data) {
+ builder.append("do ");
+ final int nc = node.jjtGetNumChildren();
+ if (nc > 1) {
+ acceptStatement(node.jjtGetChild(0), data);
+ } else {
+ builder.append(";");
+ }
+ builder.append(" while (");
+ accept(node.jjtGetChild(nc - 1), data);
+ builder.append(")");
+ return data;
+ }
+
+ @Override
+ protected Object visit(final ASTSetAddNode node, final Object data) {
+ return infixChildren(node, " += ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetSubNode node, final Object data) {
+ return infixChildren(node, " -= ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetMultNode node, final Object data) {
+ return infixChildren(node, " *= ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetDivNode node, final Object data) {
+ return infixChildren(node, " /= ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetModNode node, final Object data) {
+ return infixChildren(node, " %= ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetAndNode node, final Object data) {
+ return infixChildren(node, " &= ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetOrNode node, final Object data) {
+ return infixChildren(node, " |= ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetXorNode node, final Object data) {
+ return infixChildren(node, " ^= ", false, data);
+ }
+
+ @Override
+ protected Object visit(final ASTJxltLiteral node, final Object data) {
+ final String img = node.getLiteral().replace("`", "\\`");
+ return check(node, "`" + img + "`", data);
+ }
+
+ @Override
+ protected Object visit(final ASTAnnotation node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ builder.append('@');
+ builder.append(node.getName());
+ if (num > 0) {
+ accept(node.jjtGetChild(0), data); // zut
+ }
+ return null;
+ }
+
+ @Override
+ protected Object visit(final ASTAnnotatedStatement node, final Object data) {
+ final int num = node.jjtGetNumChildren();
+ for (int i = 0; i < num; ++i) {
+ if (i > 0) {// && child instanceof ASTBlock) {
+ builder.append(' ');
+ }
+ final JexlNode child = node.jjtGetChild(i);
+ acceptStatement(child, data);
+ }
+ return data;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Engine.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Engine.java
new file mode 100644
index 0000000..0a622e1
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Engine.java
@@ -0,0 +1,925 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.*;
+import aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection.SandboxUberspect;
+import aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection.Uberspect;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlMethod;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlSandbox;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlUberspect;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.*;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
+
+/**
+ * A JexlEngine implementation.
+ *
+ * @since 2.0
+ */
+public class Engine extends JexlEngine {
+ /**
+ * Gets the default instance of Uberspect.
+ * This is lazily initialized to avoid building a default instance if there
+ * is no use for it. The main reason for not using the default Uberspect instance is to
+ * be able to use a (low level) introspector created with a given logger
+ * instead of the default one.
+ * Implemented as on demand holder idiom.
+ */
+ private static final class UberspectHolder {
+ /** The default uberspector that handles all introspection patterns. */
+ private static final Uberspect UBERSPECT =
+ new Uberspect(LogFactory.getLog(JexlEngine.class), JexlUberspect.JEXL_STRATEGY);
+
+ /** Non-instantiable. */
+ private UberspectHolder() {
+ }
+ }
+
+ /**
+ * The name of the options pragma.
+ */
+ protected static final String PRAGMA_OPTIONS = "jexl.options";
+ /**
+ * The prefix of a namespace pragma.
+ */
+ protected static final String PRAGMA_JEXLNS = "jexl.namespace.";
+ /**
+ * The Log to which all JexlEngine messages will be logged.
+ */
+ protected final Log logger;
+ /**
+ * The JexlUberspect instance.
+ */
+ protected final JexlUberspect uberspect;
+ /**
+ * The {@link JexlArithmetic} instance.
+ */
+ protected final JexlArithmetic arithmetic;
+ /**
+ * The map of 'prefix:function' to object implementing the namespaces.
+ */
+ protected final Map functions;
+ /**
+ * The maximum stack height.
+ */
+ protected final int stackOverflow;
+ /**
+ * Whether this engine considers unknown variables, methods and constructors as errors.
+ */
+ protected final boolean strict;
+ /**
+ * Whether this engine considers null in navigation expression as errors.
+ */
+ protected final boolean safe;
+ /**
+ * Whether expressions evaluated by this engine will throw exceptions (false) or return null (true) on errors.
+ * Default is false.
+ */
+ protected final boolean silent;
+ /**
+ * Whether expressions evaluated by this engine will throw JexlException.Cancel (true) or return null (false) when
+ * interrupted.
+ * Default is true when not silent and strict.
+ */
+ protected final boolean cancellable;
+ /**
+ * Whether error messages will carry debugging information.
+ */
+ protected final boolean debug;
+ /**
+ * The set of default script parsing features.
+ */
+ protected final JexlFeatures scriptFeatures;
+ /**
+ * The set of default expression parsing features.
+ */
+ protected final JexlFeatures expressionFeatures;
+ /**
+ * The default charset.
+ */
+ protected final Charset charset;
+ /**
+ * The atomic parsing flag; true whilst parsing.
+ */
+ protected final AtomicBoolean parsing = new AtomicBoolean(false);
+ /**
+ * The {@link Parser}; when parsing expressions, this engine uses the parser if it
+ * is not already in use otherwise it will create a new temporary one.
+ */
+ protected final Parser parser = new Parser(new StringProvider(";")); //$NON-NLS-1$
+ /**
+ * The expression max length to hit the cache.
+ */
+ protected final int cacheThreshold;
+ /**
+ * The expression cache.
+ */
+ protected final SoftCache cache;
+ /**
+ * The default jxlt engine.
+ */
+ protected volatile TemplateEngine jxlt = null;
+ /**
+ * Collect all or only dot references.
+ */
+ protected final int collectMode;
+ /**
+ * A cached version of the options.
+ */
+ protected final JexlOptions options;
+
+ /**
+ * Creates an engine with default arguments.
+ */
+ public Engine() {
+ this(new JexlBuilder());
+ }
+
+ /**
+ * Creates a JEXL engine using the provided {@link JexlBuilder}.
+ *
+ * @param conf the builder
+ */
+ public Engine(final JexlBuilder conf) {
+ // options:
+ this.options = conf.options().copy();
+ this.strict = options.isStrict();
+ this.safe = options.isSafe();
+ this.silent = options.isSilent();
+ this.cancellable = option(conf.cancellable(), !silent && strict);
+ options.setCancellable(cancellable);
+ this.debug = option(conf.debug(), true);
+ this.collectMode = conf.collectMode();
+ this.stackOverflow = conf.stackOverflow() > 0 ? conf.stackOverflow() : Integer.MAX_VALUE;
+ // core properties:
+ final JexlUberspect uber = conf.uberspect() == null ? getUberspect(conf.logger(), conf.strategy()) : conf.uberspect();
+ final ClassLoader loader = conf.loader();
+ if (loader != null) {
+ uber.setClassLoader(loader);
+ }
+ final JexlSandbox sandbox = conf.sandbox();
+ if (sandbox == null) {
+ this.uberspect = uber;
+ } else {
+ this.uberspect = new SandboxUberspect(uber, sandbox);
+ }
+ this.logger = conf.logger() == null ? LogFactory.getLog(JexlEngine.class) : conf.logger();
+ this.arithmetic = conf.arithmetic() == null ? new JexlArithmetic(this.strict) : conf.arithmetic();
+ options.setMathContext(arithmetic.getMathContext());
+ options.setMathScale(arithmetic.getMathScale());
+ options.setStrictArithmetic(arithmetic.isStrict());
+ this.functions = conf.namespaces() == null ? Collections.emptyMap() : conf.namespaces();
+ // parsing & features:
+ final JexlFeatures features = conf.features() == null ? DEFAULT_FEATURES : conf.features();
+ Predicate nsTest = features.namespaceTest();
+ final Set nsNames = functions.keySet();
+ if (!nsNames.isEmpty()) {
+ nsTest = nsTest == JexlFeatures.TEST_STR_FALSE ? nsNames::contains : nsTest.or(nsNames::contains);
+ }
+ this.expressionFeatures = new JexlFeatures(features).script(false).namespaceTest(nsTest);
+ this.scriptFeatures = new JexlFeatures(features).script(true).namespaceTest(nsTest);
+ this.charset = conf.charset();
+ // caching:
+ this.cache = conf.cache() <= 0 ? null : new SoftCache(conf.cache());
+ this.cacheThreshold = conf.cacheThreshold();
+ if (uberspect == null) {
+ throw new IllegalArgumentException("uberspect can not be null");
+ }
+ }
+
+
+ /**
+ * Gets the default instance of Uberspect.
+ * This is lazily initialized to avoid building a default instance if there
+ * is no use for it. The main reason for not using the default Uberspect instance is to
+ * be able to use a (low level) introspector created with a given logger
+ * instead of the default one.
+ *
+ * @param logger the logger to use for the underlying Uberspect
+ * @param strategy the property resolver strategy
+ * @return Uberspect the default uberspector instance.
+ */
+ public static Uberspect getUberspect(final Log logger, final JexlUberspect.ResolverStrategy strategy) {
+ if ((logger == null || logger.equals(LogFactory.getLog(JexlEngine.class)))
+ && (strategy == null || strategy == JexlUberspect.JEXL_STRATEGY)) {
+ return UberspectHolder.UBERSPECT;
+ }
+ return new Uberspect(logger, strategy);
+ }
+
+ @Override
+ public JexlUberspect getUberspect() {
+ return uberspect;
+ }
+
+ @Override
+ public JexlArithmetic getArithmetic() {
+ return arithmetic;
+ }
+
+ @Override
+ public boolean isDebug() {
+ return this.debug;
+ }
+
+ @Override
+ public boolean isSilent() {
+ return this.silent;
+ }
+
+ @Override
+ public boolean isStrict() {
+ return this.strict;
+ }
+
+ @Override
+ public boolean isCancellable() {
+ return this.cancellable;
+ }
+
+ @Override
+ public void setClassLoader(final ClassLoader loader) {
+ jxlt = null;
+ uberspect.setClassLoader(loader);
+ if (functions != null) {
+ final List names = new ArrayList(functions.keySet());
+ for (final String name : names) {
+ final Object functor = functions.get(name);
+ if (functor instanceof Class>) {
+ final Class> fclass = ((Class>) functor);
+ try {
+ final Class> nclass = loader.loadClass(fclass.getName());
+ if (nclass != fclass) {
+ functions.put(name, nclass);
+ }
+ } catch (final ClassNotFoundException xany) {
+ functions.put(name, fclass.getName());
+ }
+ }
+ }
+ }
+ if (cache != null) {
+ cache.clear();
+ }
+ }
+
+ @Override
+ public Charset getCharset() {
+ return charset;
+ }
+
+ /**
+ * Solves an optional option.
+ *
+ * @param conf the option as configured, may be null
+ * @param def the default value if null, shall not be null
+ * @param the option type
+ * @return conf or def
+ */
+ private static T option(final T conf, final T def) {
+ return conf == null ? def : conf;
+ }
+
+ /**
+ * Extracts the engine evaluation options from context if available, the engine
+ * options otherwise.
+ * If the context is a options handle and the handled options shared instance flag
+ * is false, this method creates a copy of the options making them immutable during execution.
+ *
+ * @param context the context
+ * @return the options if any
+ */
+ protected JexlOptions options(final JexlContext context) {
+ // Make a copy of the handled options if any
+ if (context instanceof JexlContext.OptionsHandle) {
+ final JexlOptions jexlo = ((JexlContext.OptionsHandle) context).getEngineOptions();
+ if (jexlo != null) {
+ return jexlo.isSharedInstance() ? jexlo : jexlo.copy();
+ }
+ } else if (context instanceof Options) {
+ // This condition and block for compatibility between 3.1 and 3.2
+ final JexlOptions jexlo = options.copy();
+ final JexlEngine jexl = this;
+ final Options opts = (Options) context;
+ jexlo.setCancellable(option(opts.isCancellable(), jexl.isCancellable()));
+ jexlo.setSilent(option(opts.isSilent(), jexl.isSilent()));
+ jexlo.setStrict(option(opts.isStrict(), jexl.isStrict()));
+ final JexlArithmetic jexla = jexl.getArithmetic();
+ jexlo.setStrictArithmetic(option(opts.isStrictArithmetic(), jexla.isStrict()));
+ jexlo.setMathContext(opts.getArithmeticMathContext());
+ jexlo.setMathScale(opts.getArithmeticMathScale());
+ return jexlo;
+ }
+ return options;
+ }
+
+ /**
+ * Compute a script options for evaluation.
+ *
This calls processPragma(...).
+ *
+ * @param script the script
+ * @param context the context
+ * @return the options
+ */
+ protected JexlOptions options(final ASTJexlScript script, final JexlContext context) {
+ final JexlOptions opts = options(context);
+ if (opts != options) {
+ // when feature lexical, try hard to run lexical
+ if (scriptFeatures.isLexical()) {
+ opts.setLexical(true);
+ }
+ if (scriptFeatures.isLexicalShade()) {
+ opts.setLexicalShade(true);
+ }
+ }
+ if (script != null) {
+ // process script pragmas if any
+ processPragmas(script, context, opts);
+ }
+ return opts;
+ }
+
+ /**
+ * Processes a script pragmas.
+ *
Only called from options(...)
+ *
+ * @param script the script
+ * @param context the context
+ * @param opts the options
+ */
+ protected void processPragmas(final ASTJexlScript script, final JexlContext context, final JexlOptions opts) {
+ final Map pragmas = script.getPragmas();
+ if (pragmas != null && !pragmas.isEmpty()) {
+ final JexlContext.PragmaProcessor processor =
+ context instanceof JexlContext.PragmaProcessor
+ ? (JexlContext.PragmaProcessor) context
+ : null;
+ Map ns = null;
+ for (final Map.Entry pragma : pragmas.entrySet()) {
+ final String key = pragma.getKey();
+ final Object value = pragma.getValue();
+ if (value instanceof String) {
+ if (PRAGMA_OPTIONS.equals(key)) {
+ // jexl.options
+ final String[] vs = value.toString().split(" ");
+ opts.setFlags(vs);
+ } else if (key.startsWith(PRAGMA_JEXLNS)) {
+ // jexl.namespace.***
+ final String nsname = key.substring(PRAGMA_JEXLNS.length());
+ if (nsname != null && !nsname.isEmpty()) {
+ if (ns == null) {
+ ns = new HashMap<>(functions);
+ }
+ final String nsclass = value.toString();
+ try {
+ ns.put(nsname, uberspect.getClassLoader().loadClass(nsclass));
+ } catch (final ClassNotFoundException e) {
+ ns.put(nsname, nsclass);
+ }
+ }
+ }
+ }
+ if (processor != null) {
+ processor.processPragma(key, value);
+ }
+ }
+ if (ns != null) {
+ opts.setNamespaces(ns);
+ }
+ }
+ }
+
+ /**
+ * Sets options from this engine options.
+ *
+ * @param opts the options to set
+ * @return the options
+ */
+ public JexlOptions optionsSet(final JexlOptions opts) {
+ if (opts != null) {
+ opts.set(options);
+ }
+ return opts;
+ }
+
+ @Override
+ public TemplateEngine createJxltEngine(final boolean noScript, final int cacheSize, final char immediate, final char deferred) {
+ return new TemplateEngine(this, noScript, cacheSize, immediate, deferred);
+ }
+
+ @Override
+ public void clearCache() {
+ if (cache != null) {
+ cache.clear();
+ }
+ }
+
+ /**
+ * Creates an interpreter.
+ *
+ * @param context a JexlContext; if null, the empty context is used instead.
+ * @param frame the interpreter frame
+ * @param opts the evaluation options
+ * @return an Interpreter
+ */
+ protected Interpreter createInterpreter(final JexlContext context, final Frame frame, final JexlOptions opts) {
+ return new Interpreter(this, opts, context, frame);
+ }
+
+
+ @Override
+ public Script createExpression(final JexlInfo info, final String expression) {
+ return createScript(expressionFeatures, info, expression, null);
+ }
+
+ @Override
+ public Script createScript(final JexlFeatures features, final JexlInfo info, final String scriptText, final String... names) {
+ if (scriptText == null) {
+ throw new NullPointerException("source is null");
+ }
+ final String source = trimSource(scriptText);
+ final Scope scope = names == null || names.length == 0 ? null : new Scope(null, names);
+ final JexlFeatures ftrs = features == null ? scriptFeatures : features;
+ final ASTJexlScript tree = parse(info, ftrs, source, scope);
+ return new Script(this, source, tree);
+ }
+
+ /**
+ * The features allowed for property set/get methods.
+ */
+ protected static final JexlFeatures PROPERTY_FEATURES = new JexlFeatures()
+ .localVar(false)
+ .loops(false)
+ .lambda(false)
+ .script(false)
+ .arrayReferenceExpr(false)
+ .methodCall(false)
+ .register(true);
+
+ @Override
+ public Object getProperty(final Object bean, final String expr) {
+ return getProperty(null, bean, expr);
+ }
+
+ @Override
+ public Object getProperty(JexlContext context, final Object bean, final String expr) {
+ if (context == null) {
+ context = EMPTY_CONTEXT;
+ }
+ // synthesize expr using register
+ String src = trimSource(expr);
+ src = "#0" + (src.charAt(0) == '[' ? "" : ".") + src;
+ try {
+ final Scope scope = new Scope(null, "#0");
+ final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope);
+ final JexlNode node = script.jjtGetChild(0);
+ final Frame frame = script.createFrame(bean);
+ final Interpreter interpreter = createInterpreter(context, frame, options);
+ return interpreter.visitLexicalNode(node, null);
+ } catch (final JexlException xjexl) {
+ if (silent) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ return null;
+ }
+ throw xjexl.clean();
+ }
+ }
+
+ @Override
+ public void setProperty(final Object bean, final String expr, final Object value) {
+ setProperty(null, bean, expr, value);
+ }
+
+ @Override
+ public void setProperty(JexlContext context, final Object bean, final String expr, final Object value) {
+ if (context == null) {
+ context = EMPTY_CONTEXT;
+ }
+ // synthesize expr using register
+ String src = trimSource(expr);
+ src = "#0" + (src.charAt(0) == '[' ? "" : ".") + src + "=" + "#1";
+ try {
+ final Scope scope = new Scope(null, "#0", "#1");
+ final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope);
+ final JexlNode node = script.jjtGetChild(0);
+ final Frame frame = script.createFrame(bean, value);
+ final Interpreter interpreter = createInterpreter(context, frame, options);
+ interpreter.visitLexicalNode(node, null);
+ } catch (final JexlException xjexl) {
+ if (silent) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ return;
+ }
+ throw xjexl.clean();
+ }
+ }
+
+ @Override
+ public Object invokeMethod(final Object obj, final String meth, final Object... args) {
+ JexlException xjexl = null;
+ Object result = null;
+ final JexlInfo info = debug ? createInfo() : null;
+ try {
+ JexlMethod method = uberspect.getMethod(obj, meth, args);
+ if (method == null && arithmetic.narrowArguments(args)) {
+ method = uberspect.getMethod(obj, meth, args);
+ }
+ if (method != null) {
+ result = method.invoke(obj, args);
+ } else {
+ xjexl = new JexlException.Method(info, meth, args);
+ }
+ } catch (final JexlException xany) {
+ xjexl = xany;
+ } catch (final Exception xany) {
+ xjexl = new JexlException.Method(info, meth, args, xany);
+ }
+ if (xjexl != null) {
+ if (!silent) {
+ throw xjexl.clean();
+ }
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ result = null;
+ }
+ return result;
+ }
+
+ @Override
+ public T newInstance(final Class extends T> clazz, final Object... args) {
+ return clazz.cast(doCreateInstance(clazz, args));
+ }
+
+ @Override
+ public Object newInstance(final String clazz, final Object... args) {
+ return doCreateInstance(clazz, args);
+ }
+
+ /**
+ * Creates a new instance of an object using the most appropriate constructor
+ * based on the arguments.
+ *
+ * @param clazz the class to instantiate
+ * @param args the constructor arguments
+ * @return the created object instance or null on failure when silent
+ */
+ protected Object doCreateInstance(final Object clazz, final Object... args) {
+ JexlException xjexl = null;
+ Object result = null;
+ final JexlInfo info = debug ? createInfo() : null;
+ try {
+ JexlMethod ctor = uberspect.getConstructor(clazz, args);
+ if (ctor == null && arithmetic.narrowArguments(args)) {
+ ctor = uberspect.getConstructor(clazz, args);
+ }
+ if (ctor != null) {
+ result = ctor.invoke(clazz, args);
+ } else {
+ xjexl = new JexlException.Method(info, clazz.toString(), args);
+ }
+ } catch (final JexlException xany) {
+ xjexl = xany;
+ } catch (final Exception xany) {
+ xjexl = new JexlException.Method(info, clazz.toString(), args, xany);
+ }
+ if (xjexl != null) {
+ if (silent) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ return null;
+ }
+ throw xjexl.clean();
+ }
+ return result;
+ }
+
+ /**
+ * Swaps the current thread local context.
+ *
+ * @param tls the context or null
+ * @return the previous thread local context
+ */
+ protected JexlContext.ThreadLocal putThreadLocal(final JexlContext.ThreadLocal tls) {
+ final JexlContext.ThreadLocal local = CONTEXT.get();
+ CONTEXT.set(tls);
+ return local;
+ }
+
+ /**
+ * Swaps the current thread local engine.
+ *
+ * @param jexl the engine or null
+ * @return the previous thread local engine
+ */
+ protected JexlEngine putThreadEngine(final JexlEngine jexl) {
+ final JexlEngine pjexl = ENGINE.get();
+ ENGINE.set(jexl);
+ return pjexl;
+ }
+
+ /**
+ * Gets the list of variables accessed by a script.
+ * This method will visit all nodes of a script and extract all variables whether they
+ * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to a['b']).
+ *
+ * @param script the script
+ * @return the set of variables, each as a list of strings (ant-ish variables use more than 1 string)
+ * or the empty set if no variables are used
+ */
+ protected Set> getVariables(final ASTJexlScript script) {
+ final VarCollector collector = varCollector();
+ getVariables(script, script, collector);
+ return collector.collected();
+ }
+
+ /**
+ * Creates a collector instance.
+ *
+ * @return a collector instance
+ */
+ protected VarCollector varCollector() {
+ return new VarCollector(this.collectMode);
+ }
+
+ /**
+ * Utility class to collect variables.
+ */
+ protected static class VarCollector {
+ /**
+ * The collected variables represented as a set of list of strings.
+ */
+ private final Set> refs = new LinkedHashSet>();
+ /**
+ * The current variable being collected.
+ */
+ private List ref = new ArrayList();
+ /**
+ * The node that started the collect.
+ */
+ private JexlNode root = null;
+ /**
+ * Whether constant array-access is considered equivalent to dot-access;
+ * if so, > 1 means collect any constant (set,map,...) instead of just
+ * strings and numbers.
+ */
+ private int mode = 1;
+
+ /**
+ * Constructor.
+ *
+ * @param constaa whether constant array-access is considered equivalent to dot-access
+ */
+ protected VarCollector(final int constaa) {
+ mode = constaa;
+ }
+
+ /**
+ * Starts/stops a variable collect.
+ *
+ * @param node starts if not null, stop if null
+ */
+ public void collect(final JexlNode node) {
+ if (!ref.isEmpty()) {
+ refs.add(ref);
+ ref = new ArrayList();
+ }
+ root = node;
+ }
+
+ /**
+ * @return true if currently collecting a variable, false otherwise
+ */
+ public boolean isCollecting() {
+ return root instanceof ASTIdentifier;
+ }
+
+ /**
+ * Adds a 'segment' to the variable being collected.
+ *
+ * @param name the name
+ */
+ public void add(final String name) {
+ ref.add(name);
+ }
+
+ /**
+ * @return the collected variables
+ */
+ public Set> collected() {
+ return refs;
+ }
+ }
+
+ /**
+ * Fills up the list of variables accessed by a node.
+ *
+ * @param script the owning script
+ * @param node the node
+ * @param collector the variable collector
+ */
+ protected void getVariables(final ASTJexlScript script, final JexlNode node, final VarCollector collector) {
+ if (node instanceof ASTIdentifier) {
+ final JexlNode parent = node.jjtGetParent();
+ if (parent instanceof ASTMethodNode || parent instanceof ASTFunctionNode) {
+ // skip identifiers for methods and functions
+ collector.collect(null);
+ return;
+ }
+ final ASTIdentifier identifier = (ASTIdentifier) node;
+ final int symbol = identifier.getSymbol();
+ // symbols that are captured are considered "global" variables
+ if (symbol >= 0 && script != null && !script.isCapturedSymbol(symbol)) {
+ collector.collect(null);
+ } else {
+ // start collecting from identifier
+ collector.collect(identifier);
+ collector.add(identifier.getName());
+ }
+ } else if (node instanceof ASTIdentifierAccess) {
+ final JexlNode parent = node.jjtGetParent();
+ if (parent instanceof ASTMethodNode || parent instanceof ASTFunctionNode) {
+ // skip identifiers for methods and functions
+ collector.collect(null);
+ return;
+ }
+ // belt and suspender since an identifier should have been seen first
+ if (collector.isCollecting()) {
+ collector.add(((ASTIdentifierAccess) node).getName());
+ }
+ } else if (node instanceof ASTArrayAccess && collector.mode > 0) {
+ final int num = node.jjtGetNumChildren();
+ // collect only if array access is const and follows an identifier
+ boolean collecting = collector.isCollecting();
+ for (int i = 0; i < num; ++i) {
+ final JexlNode child = node.jjtGetChild(i);
+ if (collecting && child.isConstant()) {
+ // collect all constants or only string and number literals
+ final boolean collect = collector.mode > 1
+ || (child instanceof ASTStringLiteral || child instanceof ASTNumberLiteral);
+ if (collect) {
+ final String image = child.toString();
+ collector.add(image);
+ }
+ } else {
+ collecting = false;
+ collector.collect(null);
+ getVariables(script, child, collector);
+ collector.collect(null);
+ }
+ }
+ } else {
+ final int num = node.jjtGetNumChildren();
+ for (int i = 0; i < num; ++i) {
+ getVariables(script, node.jjtGetChild(i), collector);
+ }
+ collector.collect(null);
+ }
+ }
+
+ /**
+ * Gets the array of parameters from a script.
+ *
+ * @param script the script
+ * @return the parameters which may be empty (but not null) if no parameters were defined
+ * @since 3.0
+ */
+ protected String[] getParameters(final JexlScript script) {
+ return script.getParameters();
+ }
+
+ /**
+ * Gets the array of local variable from a script.
+ *
+ * @param script the script
+ * @return the local variables array which may be empty (but not null) if no local variables were defined
+ * @since 3.0
+ */
+ protected String[] getLocalVariables(final JexlScript script) {
+ return script.getLocalVariables();
+ }
+
+ /**
+ * Parses an expression.
+ *
+ * @param info information structure
+ * @param expr whether we parse an expression or a feature
+ * @param src the expression to parse
+ * @param scope the script frame
+ * @return the parsed tree
+ * @throws JexlException if any error occurred during parsing
+ */
+ protected ASTJexlScript parse(final JexlInfo info, final boolean expr, final String src, final Scope scope) {
+ return parse(info, expr ? this.expressionFeatures : this.scriptFeatures, src, scope);
+ }
+
+ /**
+ * Parses an expression.
+ *
+ * @param info information structure
+ * @param parsingf the set of parsing features
+ * @param src the expression to parse
+ * @param scope the script frame
+ * @return the parsed tree
+ * @throws JexlException if any error occurred during parsing
+ */
+ protected ASTJexlScript parse(final JexlInfo info, final JexlFeatures parsingf, final String src, final Scope scope) {
+ final boolean cached = src.length() < cacheThreshold && cache != null;
+ JexlFeatures features = parsingf != null ? parsingf : DEFAULT_FEATURES;
+ // if (features.getNameSpaces().isEmpty() && !functions.isEmpty()) {
+ // features = new JexlFeatures(features).nameSpaces(functions.keySet());
+ // }
+ final Source source = cached ? new Source(features, src) : null;
+ ASTJexlScript script = null;
+ if (source != null) {
+ script = cache.get(source);
+ if (script != null) {
+ final Scope f = script.getScope();
+ if ((f == null && scope == null) || (f != null && f.equals(scope))) {
+ return script;
+ }
+ }
+ }
+ final JexlInfo ninfo = info == null && debug ? createInfo() : info;
+ // if parser not in use...
+ if (parsing.compareAndSet(false, true)) {
+ try {
+ // lets parse
+ script = parser.parse(ninfo, features, src, scope);
+ } finally {
+ // no longer in use
+ parsing.set(false);
+ }
+ } else {
+ // ...otherwise parser was in use, create a new temporary one
+ final Parser lparser = new Parser(new StringProvider(";"));
+ script = lparser.parse(ninfo, features, src, scope);
+ }
+ if (source != null) {
+ cache.put(source, script);
+ }
+ return script;
+ }
+
+ /**
+ * Trims the source from front and ending spaces.
+ *
+ * @param str expression to clean
+ * @return trimmed expression ending in a semicolon
+ */
+ protected String trimSource(final CharSequence str) {
+ if (str != null) {
+ int start = 0;
+ int end = str.length();
+ if (end > 0) {
+ // trim front spaces
+ while (start < end && Character.isSpaceChar(str.charAt(start))) {
+ ++start;
+ }
+ // trim ending spaces; end is > 0 since start >= 0
+ while (end > start && Character.isSpaceChar(str.charAt(end - 1))) {
+ --end;
+ }
+ return str.subSequence(start, end).toString();
+ }
+ return "";
+ }
+ return null;
+ }
+
+ /**
+ * Gets and/or creates a default template engine.
+ *
+ * @return a template engine
+ */
+ protected TemplateEngine jxlt() {
+ TemplateEngine e = jxlt;
+ if (e == null) {
+ synchronized (this) {
+ e = jxlt;
+ if (e == null) {
+ e = new TemplateEngine(this, true, 0, '$', '#');
+ jxlt = e;
+ }
+ }
+ }
+ return e;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Frame.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Frame.java
new file mode 100644
index 0000000..3b2cdd2
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Frame.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import java.util.Arrays;
+
+/**
+ * A call frame, created from a scope, stores the arguments and local variables in a "stack frame" (sic).
+ * @since 3.0
+ */
+public final class Frame {
+ /** The scope. */
+ private final Scope scope;
+ /** The actual stack frame. */
+ private final Object[] stack;
+ /** Number of curried parameters. */
+ private final int curried;
+
+ /**
+ * Creates a new frame.
+ * @param s the scope
+ * @param r the stack frame
+ * @param c the number of curried parameters
+ */
+ Frame(final Scope s, final Object[] r, final int c) {
+ scope = s;
+ stack = r;
+ curried = c;
+ }
+
+ /**
+ * Gets this script unbound parameters, i.e. parameters not bound through curry().
+ * @return the parameter names
+ */
+ public String[] getUnboundParameters() {
+ return scope.getParameters(curried);
+ }
+
+ /**
+ * Gets the scope.
+ * @return this frame scope
+ */
+ public Scope getScope() {
+ return scope;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.deepHashCode(this.stack);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Frame other = (Frame) obj;
+ return Arrays.deepEquals(this.stack, other.stack);
+ }
+
+ /**
+ * Gets a value.
+ * @param s the offset in this frame
+ * @return the stacked value
+ */
+ Object get(final int s) {
+ return stack[s];
+ }
+
+ /**
+ * Whether this frame defines a symbol, ie declared it and assigned it a value.
+ * @param s the offset in this frame
+ * @return true if this symbol has been assigned a value, false otherwise
+ */
+ boolean has(final int s) {
+ return s >= 0 && s < stack.length && stack[s] != Scope.UNDECLARED;
+ }
+
+ /**
+ * Sets a value.
+ * @param r the offset in this frame
+ * @param value the value to set in this frame
+ */
+ void set(final int r, final Object value) {
+ stack[r] = value;
+ }
+
+ /**
+ * Assign values to this frame.
+ * @param values the values
+ * @return this frame
+ */
+ Frame assign(final Object... values) {
+ if (stack != null) {
+ final int nparm = scope.getArgCount();
+ final Object[] copy = stack.clone();
+ int ncopy = 0;
+ if (values != null && values.length > 0) {
+ ncopy = Math.min(nparm - curried, Math.min(nparm, values.length));
+ System.arraycopy(values, 0, copy, curried, ncopy);
+ }
+ // unbound parameters are defined as null
+ Arrays.fill(copy, curried + ncopy, nparm, null);
+ return new Frame(scope, copy, curried + ncopy);
+ }
+ return this;
+ }
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/IntegerRange.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/IntegerRange.java
new file mode 100644
index 0000000..30191f3
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/IntegerRange.java
@@ -0,0 +1,318 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A range of integers.
+ */
+public abstract class IntegerRange implements Collection {
+ /** The lower boundary. */
+ protected final int min;
+ /** The upper boundary. */
+ protected final int max;
+
+ /**
+ * Creates a range, ascending or descending depending on boundaries order.
+ *
+ * @param from the lower inclusive boundary
+ * @param to the higher inclusive boundary
+ * @return a range
+ */
+ public static IntegerRange create(final int from, final int to) {
+ if (from <= to) {
+ return new Ascending(from, to);
+ }
+ return new Descending(to, from);
+ }
+
+ /**
+ * Creates a new range.
+ *
+ * @param from the lower inclusive boundary
+ * @param to the higher inclusive boundary
+ */
+ public IntegerRange(final int from, final int to) {
+ min = from;
+ max = to;
+ }
+
+ /**
+ * Gets the interval minimum value.
+ *
+ * @return the low boundary
+ */
+ public int getMin() {
+ return min;
+ }
+
+ /**
+ * Gets the interval maximum value.
+ *
+ * @return the high boundary
+ */
+ public int getMax() {
+ return max;
+ }
+
+
+ @Override
+ public int hashCode() {
+ int hash = getClass().hashCode();
+ // CSOFF: MagicNumber
+ hash = 13 * hash + this.min;
+ hash = 13 * hash + this.max;
+ // CSON: MagicNumber
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final IntegerRange other = (IntegerRange) obj;
+ if (this.min != other.min) {
+ return false;
+ }
+ return this.max == other.max;
+ }
+
+ @Override
+ public abstract Iterator iterator();
+
+ @Override
+ public int size() {
+ return max - min + 1;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ if (o instanceof Number) {
+ final long v = ((Number) o).intValue();
+ return min <= v && v <= max;
+ }
+ return false;
+ }
+
+ @Override
+ public Object[] toArray() {
+ final int size = size();
+ final Object[] array = new Object[size];
+ for (int a = 0; a < size; ++a) {
+ array[a] = min + a;
+ }
+ return array;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T[] toArray(final T[] array) {
+ final Class> ct = array.getClass().getComponentType();
+ final int length = size();
+ T[] copy = array;
+ if (ct.isAssignableFrom(Integer.class)) {
+ if (array.length < length) {
+ copy = (T[]) Array.newInstance(ct, length);
+ }
+ for (int a = 0; a < length; ++a) {
+ Array.set(copy, a, min + a);
+ }
+ if (length < copy.length) {
+ copy[length] = null;
+ }
+ return copy;
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean containsAll(final Collection> c) {
+ for (final Object cc : c) {
+ if (!contains(cc)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean add(final Integer e) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(final Collection extends Integer> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeAll(final Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean retainAll(final Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Ascending integer range.
+ */
+ public static class Ascending extends IntegerRange {
+ /**
+ * Constructor.
+ *
+ * @param from lower boundary
+ * @param to upper boundary
+ */
+ protected Ascending(final int from, final int to) {
+ super(from, to);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new AscIntegerIterator(min, max);
+ }
+ }
+
+ /**
+ * Descending integer range.
+ */
+ public static class Descending extends IntegerRange {
+ /**
+ * Constructor.
+ *
+ * @param from upper boundary
+ * @param to lower boundary
+ */
+ protected Descending(final int from, final int to) {
+ super(from, to);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new DescIntegerIterator(min, max);
+ }
+ }
+}
+
+/**
+ * An ascending iterator on an integer range.
+ */
+class AscIntegerIterator implements Iterator {
+ /** The lower boundary. */
+ private final int min;
+ /** The upper boundary. */
+ private final int max;
+ /** The current value. */
+ private int cursor;
+
+ /**
+ * Creates a iterator on the range.
+ *
+ * @param l low boundary
+ * @param h high boundary
+ */
+ public AscIntegerIterator(final int l, final int h) {
+ min = l;
+ max = h;
+ cursor = min;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return cursor <= max;
+ }
+
+ @Override
+ public Integer next() {
+ if (cursor <= max) {
+ return cursor++;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+}
+
+/**
+ * A descending iterator on an integer range.
+ */
+class DescIntegerIterator implements Iterator {
+ /** The lower boundary. */
+ private final int min;
+ /** The upper boundary. */
+ private final int max;
+ /** The current value. */
+ private int cursor;
+
+ /**
+ * Creates a iterator on the range.
+ *
+ * @param l low boundary
+ * @param h high boundary
+ */
+ public DescIntegerIterator(final int l, final int h) {
+ min = l;
+ max = h;
+ cursor = max;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return cursor >= min;
+ }
+
+ @Override
+ public Integer next() {
+ if (cursor >= min) {
+ return cursor--;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Interpreter.java
new file mode 100644
index 0000000..a5ad945
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Interpreter.java
@@ -0,0 +1,1855 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// CSOFF: FileLength
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.*;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlMethod;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertyGet;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.*;
+
+import java.util.Iterator;
+import java.util.concurrent.Callable;
+
+/**
+ * An interpreter of JEXL syntax.
+ *
+ * @since 2.0
+ */
+public class Interpreter extends InterpreterBase {
+ /** Frame height. */
+ protected int fp = 0;
+ /** Symbol values. */
+ protected final Frame frame;
+ /** Block micro-frames. */
+ protected LexicalFrame block = null;
+
+ /**
+ * The thread local interpreter.
+ */
+ protected static final ThreadLocal INTER =
+ new ThreadLocal();
+
+ /**
+ * Creates an interpreter.
+ *
+ * @param engine the engine creating this interpreter
+ * @param aContext the evaluation context, global variables, methods and functions
+ * @param opts the evaluation options, flags modifying evaluation behavior
+ * @param eFrame the evaluation frame, arguments and local variables
+ */
+ protected Interpreter(final Engine engine, final JexlOptions opts, final JexlContext aContext, final Frame eFrame) {
+ super(engine, opts, aContext);
+ this.frame = eFrame;
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param ii the interpreter to copy
+ * @param jexla the arithmetic instance to use (or null)
+ */
+ protected Interpreter(final Interpreter ii, final JexlArithmetic jexla) {
+ super(ii, jexla);
+ frame = ii.frame;
+ block = ii.block != null ? new LexicalFrame(ii.block) : null;
+ }
+
+ /**
+ * Swaps the current thread local interpreter.
+ *
+ * @param inter the interpreter or null
+ * @return the previous thread local interpreter
+ */
+ protected Interpreter putThreadInterpreter(final Interpreter inter) {
+ final Interpreter pinter = INTER.get();
+ INTER.set(inter);
+ return pinter;
+ }
+
+ /**
+ * Interpret the given script/expression.
+ *
+ * If the underlying JEXL engine is silent, errors will be logged through
+ * its logger as warning.
+ *
+ * @param node the script or expression to interpret.
+ * @return the result of the interpretation.
+ * @throws JexlException if any error occurs during interpretation.
+ */
+ public Object interpret(final JexlNode node) {
+ JexlContext.ThreadLocal tcontext = null;
+ JexlEngine tjexl = null;
+ Interpreter tinter = null;
+ try {
+ tinter = putThreadInterpreter(this);
+ if (tinter != null) {
+ fp = tinter.fp + 1;
+ }
+ if (context instanceof JexlContext.ThreadLocal) {
+ tcontext = jexl.putThreadLocal((JexlContext.ThreadLocal) context);
+ }
+ tjexl = jexl.putThreadEngine(jexl);
+ if (fp > jexl.stackOverflow) {
+ throw new JexlException.StackOverflow(node.jexlInfo(), "jexl (" + jexl.stackOverflow + ")", null);
+ }
+ cancelCheck(node);
+ return node.jjtAccept(this, null);
+ } catch (final StackOverflowError xstack) {
+ final JexlException xjexl = new JexlException.StackOverflow(node.jexlInfo(), "jvm", xstack);
+ if (!isSilent()) {
+ throw xjexl.clean();
+ }
+ if (logger.isWarnEnabled()) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ }
+ } catch (final JexlException.Return xreturn) {
+ return xreturn.getValue();
+ } catch (final JexlException.Cancel xcancel) {
+ // cancelled |= Thread.interrupted();
+ cancelled.weakCompareAndSet(false, Thread.interrupted());
+ if (isCancellable()) {
+ throw xcancel.clean();
+ }
+ } catch (final JexlException xjexl) {
+ if (!isSilent()) {
+ throw xjexl.clean();
+ }
+ if (logger.isWarnEnabled()) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ }
+ } finally {
+ synchronized (this) {
+ if (functors != null) {
+ for (final Object functor : functors.values()) {
+ closeIfSupported(functor);
+ }
+ functors.clear();
+ functors = null;
+ }
+ }
+ jexl.putThreadEngine(tjexl);
+ if (context instanceof JexlContext.ThreadLocal) {
+ jexl.putThreadLocal(tcontext);
+ }
+ if (tinter != null) {
+ fp = tinter.fp - 1;
+ }
+ putThreadInterpreter(tinter);
+ }
+ return null;
+ }
+
+ /**
+ * Gets an attribute of an object.
+ *
+ * @param object to retrieve value from
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
+ * @return the attribute value
+ */
+ public Object getAttribute(final Object object, final Object attribute) {
+ return getAttribute(object, attribute, null);
+ }
+
+ /**
+ * Sets an attribute of an object.
+ *
+ * @param object to set the value to
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
+ * @param value the value to assign to the object's attribute
+ */
+ public void setAttribute(final Object object, final Object attribute, final Object value) {
+ setAttribute(object, attribute, value, null);
+ }
+
+ @Override
+ protected Object visit(final ASTAddNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.ADD, left, right);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.add(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "+ error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTSubNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.SUBTRACT, left, right);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.subtract(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "- error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTMulNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.MULTIPLY, left, right);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.multiply(left, right);
+ } catch (final ArithmeticException xrt) {
+ final JexlNode xnode = findNullOperand(xrt, node, left, right);
+ throw new JexlException(xnode, "* error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTDivNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.DIVIDE, left, right);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.divide(left, right);
+ } catch (final ArithmeticException xrt) {
+ if (!arithmetic.isStrict()) {
+ return 0.0d;
+ }
+ final JexlNode xnode = findNullOperand(xrt, node, left, right);
+ throw new JexlException(xnode, "/ error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTModNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.MOD, left, right);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.mod(left, right);
+ } catch (final ArithmeticException xrt) {
+ if (!arithmetic.isStrict()) {
+ return 0.0d;
+ }
+ final JexlNode xnode = findNullOperand(xrt, node, left, right);
+ throw new JexlException(xnode, "% error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseAndNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.AND, left, right);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.and(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "& error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseOrNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.OR, left, right);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.or(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "| error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseXorNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.XOR, left, right);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.xor(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "^ error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTEQNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.EQ, left, right);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.equals(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "== error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTNENode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.EQ, left, right);
+ return result != JexlEngine.TRY_FAILED
+ ? !arithmetic.toBoolean(result)
+ : !arithmetic.equals(left, right);
+ } catch (final ArithmeticException xrt) {
+ final JexlNode xnode = findNullOperand(xrt, node, left, right);
+ throw new JexlException(xnode, "!= error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTGENode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.GTE, left, right);
+ return result != JexlEngine.TRY_FAILED
+ ? result
+ : arithmetic.greaterThanOrEqual(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, ">= error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTGTNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.GT, left, right);
+ return result != JexlEngine.TRY_FAILED
+ ? result
+ : arithmetic.greaterThan(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "> error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTLENode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.LTE, left, right);
+ return result != JexlEngine.TRY_FAILED
+ ? result
+ : arithmetic.lessThanOrEqual(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "<= error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTLTNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.LT, left, right);
+ return result != JexlEngine.TRY_FAILED
+ ? result
+ : arithmetic.lessThan(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "< error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTSWNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ return operators.startsWith(node, "^=", left, right);
+ }
+
+ @Override
+ protected Object visit(final ASTNSWNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ return !operators.startsWith(node, "^!", left, right);
+ }
+
+ @Override
+ protected Object visit(final ASTEWNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ return operators.endsWith(node, "$=", left, right);
+ }
+
+ @Override
+ protected Object visit(final ASTNEWNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ return !operators.endsWith(node, "$!", left, right);
+ }
+
+ @Override
+ protected Object visit(final ASTERNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ return operators.contains(node, "=~", right, left);
+ }
+
+ @Override
+ protected Object visit(final ASTNRNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ return !operators.contains(node, "!~", right, left);
+ }
+
+ @Override
+ protected Object visit(final ASTRangeNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.createRange(left, right);
+ } catch (final ArithmeticException xrt) {
+ final JexlNode xnode = findNullOperand(xrt, node, left, right);
+ throw new JexlException(xnode, ".. error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTUnaryMinusNode node, final Object data) {
+ // use cached value if literal
+ final Object value = node.jjtGetValue();
+ if (value != null && !(value instanceof JexlMethod)) {
+ return value;
+ }
+ final JexlNode valNode = node.jjtGetChild(0);
+ final Object val = valNode.jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.NEGATE, val);
+ if (result != JexlEngine.TRY_FAILED) {
+ return result;
+ }
+ Object number = arithmetic.negate(val);
+ // attempt to recoerce to literal class
+ // cache if number literal and negate is idempotent
+ if (number instanceof Number && valNode instanceof ASTNumberLiteral) {
+ number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass());
+ if (arithmetic.isNegateStable()) {
+ node.jjtSetValue(number);
+ }
+ }
+ return number;
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(valNode, "- error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTUnaryPlusNode node, final Object data) {
+ // use cached value if literal
+ final Object value = node.jjtGetValue();
+ if (value != null && !(value instanceof JexlMethod)) {
+ return value;
+ }
+ final JexlNode valNode = node.jjtGetChild(0);
+ final Object val = valNode.jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.POSITIVIZE, val);
+ if (result != JexlEngine.TRY_FAILED) {
+ return result;
+ }
+ final Object number = arithmetic.positivize(val);
+ if (valNode instanceof ASTNumberLiteral
+ && number instanceof Number
+ && arithmetic.isPositivizeStable()) {
+ node.jjtSetValue(number);
+ }
+ return number;
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(valNode, "- error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseComplNode node, final Object data) {
+ final Object arg = node.jjtGetChild(0).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.COMPLEMENT, arg);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.complement(arg);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "~ error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTNotNode node, final Object data) {
+ final Object val = node.jjtGetChild(0).jjtAccept(this, data);
+ try {
+ final Object result = operators.tryOverload(node, JexlOperator.NOT, val);
+ return result != JexlEngine.TRY_FAILED ? result : arithmetic.not(val);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, "! error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTIfStatement node, final Object data) {
+ final int n = 0;
+ final int numChildren = node.jjtGetNumChildren();
+ try {
+ Object result = null;
+ // pairs of { conditions , 'then' statement }
+ for (int ifElse = 0; ifElse < (numChildren - 1); ifElse += 2) {
+ final Object condition = node.jjtGetChild(ifElse).jjtAccept(this, null);
+ if (arithmetic.toBoolean(condition)) {
+ // first objectNode is true statement
+ return node.jjtGetChild(ifElse + 1).jjtAccept(this, null);
+ }
+ }
+ // if odd...
+ if ((numChildren & 1) == 1) {
+ // if there is an else, there are an odd number of children in the statement and it is the last child,
+ // execute it.
+ result = node.jjtGetChild(numChildren - 1).jjtAccept(this, null);
+ }
+ return result;
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node.jjtGetChild(n), "if error", xrt);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTVar node, final Object data) {
+ final int symbol = node.getSymbol();
+ // if we have a var, we have a scope thus a frame
+ if (!options.isLexical()) {
+ if (frame.has(symbol)) {
+ return frame.get(symbol);
+ }
+ } else if (!defineVariable(node, block)) {
+ return redefinedVariable(node, node.getName());
+ }
+ frame.set(symbol, null);
+ return null;
+ }
+
+ @Override
+ protected Object visit(final ASTBlock node, final Object data) {
+ final int cnt = node.getSymbolCount();
+ if (!options.isLexical() || cnt <= 0) {
+ return visitBlock(node, data);
+ }
+ try {
+ block = new LexicalFrame(frame, block);
+ return visitBlock(node, data);
+ } finally {
+ block = block.pop();
+ }
+ }
+
+ /**
+ * Base visitation for blocks.
+ *
+ * @param node the block
+ * @param data the usual data
+ * @return the result of the last expression evaluation
+ */
+ private Object visitBlock(final ASTBlock node, final Object data) {
+ final int numChildren = node.jjtGetNumChildren();
+ Object result = null;
+ for (int i = 0; i < numChildren; i++) {
+ cancelCheck(node);
+ result = node.jjtGetChild(i).jjtAccept(this, data);
+ }
+ return result;
+ }
+
+ @Override
+ protected Object visit(final ASTReturnStatement node, final Object data) {
+ final Object val = node.jjtGetChild(0).jjtAccept(this, data);
+ cancelCheck(node);
+ throw new JexlException.Return(node, null, val);
+ }
+
+ @Override
+ protected Object visit(final ASTContinue node, final Object data) {
+ throw new JexlException.Continue(node);
+ }
+
+ @Override
+ protected Object visit(final ASTBreak node, final Object data) {
+ throw new JexlException.Break(node);
+ }
+
+ @Override
+ protected Object visit(final ASTForeachStatement node, final Object data) {
+ Object result = null;
+ /* first objectNode is the loop variable */
+ final ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
+ final ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
+ final int symbol = loopVariable.getSymbol();
+ final boolean lexical = options.isLexical();// && node.getSymbolCount() > 0;
+ final LexicalFrame locals = lexical ? new LexicalFrame(frame, block) : null;
+ final boolean loopSymbol = symbol >= 0 && loopVariable instanceof ASTVar;
+ if (lexical) {
+ // create lexical frame
+ // it may be a local previously declared
+ if (loopSymbol && !defineVariable((ASTVar) loopVariable, locals)) {
+ return redefinedVariable(node, loopVariable.getName());
+ }
+ block = locals;
+ }
+ Object forEach = null;
+ try {
+ /* second objectNode is the variable to iterate */
+ final Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
+ // make sure there is a value to iterate upon
+ if (iterableValue != null) {
+ /* third objectNode is the statement to execute */
+ final JexlNode statement = node.jjtGetNumChildren() >= 3 ? node.jjtGetChild(2) : null;
+ // get an iterator for the collection/array etc via the introspector.
+ forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, iterableValue);
+ final Iterator> itemsIterator = forEach instanceof Iterator
+ ? (Iterator>) forEach
+ : uberspect.getIterator(iterableValue);
+ if (itemsIterator != null) {
+ int cnt = 0;
+ while (itemsIterator.hasNext()) {
+ cancelCheck(node);
+ // reset loop variable
+ if (lexical && cnt++ > 0) {
+ // clean up but remain current
+ block.pop();
+ // unlikely to fail
+ if (loopSymbol && !defineVariable((ASTVar) loopVariable, locals)) {
+ return redefinedVariable(node, loopVariable.getName());
+ }
+ }
+ // set loopVariable to value of iterator
+ final Object value = itemsIterator.next();
+ if (symbol < 0) {
+ setContextVariable(node, loopVariable.getName(), value);
+ } else {
+ frame.set(symbol, value);
+ }
+ if (statement != null) {
+ try {
+ // execute statement
+ result = statement.jjtAccept(this, data);
+ } catch (final JexlException.Break stmtBreak) {
+ break;
+ } catch (final JexlException.Continue stmtContinue) {
+ // continue;
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ // closeable iterator handling
+ closeIfSupported(forEach);
+ // restore lexical frame
+ if (lexical) {
+ block = block.pop();
+ }
+ }
+ return result;
+ }
+
+ @Override
+ protected Object visit(final ASTWhileStatement node, final Object data) {
+ Object result = null;
+ /* first objectNode is the condition */
+ final Node condition = node.jjtGetChild(0);
+ while (arithmetic.toBoolean(condition.jjtAccept(this, data))) {
+ cancelCheck(node);
+ if (node.jjtGetNumChildren() > 1) {
+ try {
+ // execute statement
+ result = node.jjtGetChild(1).jjtAccept(this, data);
+ } catch (final JexlException.Break stmtBreak) {
+ break;
+ } catch (final JexlException.Continue stmtContinue) {
+ // continue;
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ protected Object visit(final ASTDoWhileStatement node, final Object data) {
+ Object result = null;
+ final int nc = node.jjtGetNumChildren();
+ /* last objectNode is the condition */
+ final Node condition = node.jjtGetChild(nc - 1);
+ do {
+ cancelCheck(node);
+ if (nc > 1) {
+ try {
+ // execute statement
+ result = node.jjtGetChild(0).jjtAccept(this, data);
+ } catch (final JexlException.Break stmtBreak) {
+ break;
+ } catch (final JexlException.Continue stmtContinue) {
+ // continue;
+ }
+ }
+ } while (arithmetic.toBoolean(condition.jjtAccept(this, data)));
+ return result;
+ }
+
+ @Override
+ protected Object visit(final ASTAndNode node, final Object data) {
+ /*
+ * The pattern for exception mgmt is to let the child*.jjtAccept out of the try/catch loop so that if one fails,
+ * the ex will traverse up to the interpreter. In cases where this is not convenient/possible, JexlException
+ * must be caught explicitly and rethrown.
+ */
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ try {
+ final boolean leftValue = arithmetic.toBoolean(left);
+ if (!leftValue) {
+ return Boolean.FALSE;
+ }
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
+ }
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final boolean rightValue = arithmetic.toBoolean(right);
+ if (!rightValue) {
+ return Boolean.FALSE;
+ }
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
+ }
+ return Boolean.TRUE;
+ }
+
+ @Override
+ protected Object visit(final ASTOrNode node, final Object data) {
+ final Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ try {
+ final boolean leftValue = arithmetic.toBoolean(left);
+ if (leftValue) {
+ return Boolean.TRUE;
+ }
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
+ }
+ final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ final boolean rightValue = arithmetic.toBoolean(right);
+ if (rightValue) {
+ return Boolean.TRUE;
+ }
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
+ }
+ return Boolean.FALSE;
+ }
+
+ @Override
+ protected Object visit(final ASTNullLiteral node, final Object data) {
+ return null;
+ }
+
+ @Override
+ protected Object visit(final ASTTrueNode node, final Object data) {
+ return Boolean.TRUE;
+ }
+
+ @Override
+ protected Object visit(final ASTFalseNode node, final Object data) {
+ return Boolean.FALSE;
+ }
+
+ @Override
+ protected Object visit(final ASTNumberLiteral node, final Object data) {
+ if (data != null && node.isInteger()) {
+ return getAttribute(data, node.getLiteral(), node);
+ }
+ return node.getLiteral();
+ }
+
+ @Override
+ protected Object visit(final ASTStringLiteral node, final Object data) {
+ if (data != null) {
+ return getAttribute(data, node.getLiteral(), node);
+ }
+ return node.getLiteral();
+ }
+
+ @Override
+ protected Object visit(final ASTRegexLiteral node, final Object data) {
+ return node.getLiteral();
+ }
+
+ @Override
+ protected Object visit(final ASTArrayLiteral node, final Object data) {
+ final int childCount = node.jjtGetNumChildren();
+ final JexlArithmetic.ArrayBuilder ab = arithmetic.arrayBuilder(childCount);
+ boolean extended = false;
+ for (int i = 0; i < childCount; i++) {
+ cancelCheck(node);
+ final JexlNode child = node.jjtGetChild(i);
+ if (child instanceof ASTExtendedLiteral) {
+ extended = true;
+ } else {
+ final Object entry = node.jjtGetChild(i).jjtAccept(this, data);
+ ab.add(entry);
+ }
+ }
+ return ab.create(extended);
+ }
+
+ @Override
+ protected Object visit(final ASTExtendedLiteral node, final Object data) {
+ return node;
+ }
+
+ @Override
+ protected Object visit(final ASTSetLiteral node, final Object data) {
+ final int childCount = node.jjtGetNumChildren();
+ final JexlArithmetic.SetBuilder mb = arithmetic.setBuilder(childCount);
+ for (int i = 0; i < childCount; i++) {
+ cancelCheck(node);
+ final Object entry = node.jjtGetChild(i).jjtAccept(this, data);
+ mb.add(entry);
+ }
+ return mb.create();
+ }
+
+ @Override
+ protected Object visit(final ASTMapLiteral node, final Object data) {
+ final int childCount = node.jjtGetNumChildren();
+ final JexlArithmetic.MapBuilder mb = arithmetic.mapBuilder(childCount);
+ for (int i = 0; i < childCount; i++) {
+ cancelCheck(node);
+ final Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
+ mb.put(entry[0], entry[1]);
+ }
+ return mb.create();
+ }
+
+ @Override
+ protected Object visit(final ASTMapEntry node, final Object data) {
+ final Object key = node.jjtGetChild(0).jjtAccept(this, data);
+ final Object value = node.jjtGetChild(1).jjtAccept(this, data);
+ return new Object[]{key, value};
+ }
+
+ @Override
+ protected Object visit(final ASTTernaryNode node, final Object data) {
+ Object condition;
+ try {
+ condition = node.jjtGetChild(0).jjtAccept(this, data);
+ } catch (final JexlException xany) {
+ if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) {
+ throw xany;
+ }
+ condition = null;
+ }
+ // ternary as in "x ? y : z"
+ if (node.jjtGetNumChildren() == 3) {
+ if (condition != null && arithmetic.toBoolean(condition)) {
+ return node.jjtGetChild(1).jjtAccept(this, data);
+ }
+ return node.jjtGetChild(2).jjtAccept(this, data);
+ }
+ // elvis as in "x ?: z"
+ if (condition != null && arithmetic.toBoolean(condition)) {
+ return condition;
+ }
+ return node.jjtGetChild(1).jjtAccept(this, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNullpNode node, final Object data) {
+ Object lhs;
+ try {
+ lhs = node.jjtGetChild(0).jjtAccept(this, data);
+ } catch (final JexlException xany) {
+ if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) {
+ throw xany;
+ }
+ lhs = null;
+ }
+ // null elision as in "x ?? z"
+ return lhs != null ? lhs : node.jjtGetChild(1).jjtAccept(this, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSizeFunction node, final Object data) {
+ try {
+ final Object val = node.jjtGetChild(0).jjtAccept(this, data);
+ return operators.size(node, val);
+ } catch (final JexlException xany) {
+ return 0;
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTEmptyFunction node, final Object data) {
+ try {
+ final Object value = node.jjtGetChild(0).jjtAccept(this, data);
+ return operators.empty(node, value);
+ } catch (final JexlException xany) {
+ return true;
+ }
+ }
+
+ /**
+ * Runs a node.
+ *
+ * @param node the node
+ * @param data the usual data
+ * @return the return value
+ */
+ protected Object visitLexicalNode(final JexlNode node, final Object data) {
+ block = new LexicalFrame(frame, null);
+ try {
+ return node.jjtAccept(this, data);
+ } finally {
+ block = block.pop();
+ }
+ }
+
+ /**
+ * Runs a closure.
+ *
+ * @param closure the closure
+ * @param data the usual data
+ * @return the closure return value
+ */
+ protected Object runClosure(final Closure closure, final Object data) {
+ final ASTJexlScript script = closure.getScript();
+ block = new LexicalFrame(frame, block).defineArgs();
+ try {
+ final JexlNode body = script.jjtGetChild(script.jjtGetNumChildren() - 1);
+ return interpret(body);
+ } finally {
+ block = block.pop();
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTJexlScript script, final Object data) {
+ if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
+ return new Closure(this, (ASTJexlLambda) script);
+ }
+ block = new LexicalFrame(frame, block).defineArgs();
+ try {
+ final int numChildren = script.jjtGetNumChildren();
+ Object result = null;
+ for (int i = 0; i < numChildren; i++) {
+ final JexlNode child = script.jjtGetChild(i);
+ result = child.jjtAccept(this, data);
+ cancelCheck(child);
+ }
+ return result;
+ } finally {
+ block = block.pop();
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTReferenceExpression node, final Object data) {
+ return node.jjtGetChild(0).jjtAccept(this, data);
+ }
+
+ @Override
+ protected Object visit(final ASTIdentifier identifier, final Object data) {
+ cancelCheck(identifier);
+ return data != null
+ ? getAttribute(data, identifier.getName(), identifier)
+ : getVariable(frame, block, identifier);
+ }
+
+ @Override
+ protected Object visit(final ASTArrayAccess node, final Object data) {
+ // first objectNode is the identifier
+ Object object = data;
+ // can have multiple nodes - either an expression, integer literal or reference
+ final int numChildren = node.jjtGetNumChildren();
+ for (int i = 0; i < numChildren; i++) {
+ final JexlNode nindex = node.jjtGetChild(i);
+ if (object == null) {
+ return unsolvableProperty(nindex, stringifyProperty(nindex), false, null);
+ }
+ final Object index = nindex.jjtAccept(this, null);
+ cancelCheck(node);
+ object = getAttribute(object, index, nindex);
+ }
+ return object;
+ }
+
+ /**
+ * Evaluates an access identifier based on the 2 main implementations;
+ * static (name or numbered identifier) or dynamic (jxlt).
+ *
+ * @param node the identifier access node
+ * @return the evaluated identifier
+ */
+ private Object evalIdentifier(final ASTIdentifierAccess node) {
+ if (!(node instanceof ASTIdentifierAccessJxlt)) {
+ return node.getIdentifier();
+ }
+ final ASTIdentifierAccessJxlt accessJxlt = (ASTIdentifierAccessJxlt) node;
+ final String src = node.getName();
+ Throwable cause = null;
+ TemplateEngine.TemplateExpression expr = (TemplateEngine.TemplateExpression) accessJxlt.getExpression();
+ try {
+ if (expr == null) {
+ final TemplateEngine jxlt = jexl.jxlt();
+ expr = jxlt.parseExpression(node.jexlInfo(), src, frame != null ? frame.getScope() : null);
+ accessJxlt.setExpression(expr);
+ }
+ if (expr != null) {
+ final Object name = expr.evaluate(frame, context);
+ if (name != null) {
+ final Integer id = ASTIdentifierAccess.parseIdentifier(name.toString());
+ return id != null ? id : name;
+ }
+ }
+ } catch (final JxltEngine.Exception xjxlt) {
+ cause = xjxlt;
+ }
+ return node.isSafe() ? null : unsolvableProperty(node, src, true, cause);
+ }
+
+ @Override
+ protected Object visit(final ASTIdentifierAccess node, final Object data) {
+ if (data == null) {
+ return null;
+ }
+ final Object id = evalIdentifier(node);
+ return getAttribute(data, id, node);
+ }
+
+ @Override
+ protected Object visit(final ASTReference node, final Object data) {
+ cancelCheck(node);
+ final int numChildren = node.jjtGetNumChildren();
+ final JexlNode parent = node.jjtGetParent();
+ // pass first piece of data in and loop through children
+ Object object = null;
+ JexlNode objectNode = null;
+ JexlNode ptyNode = null;
+ StringBuilder ant = null;
+ boolean antish = !(parent instanceof ASTReference);
+ int v = 1;
+ main:
+ for (int c = 0; c < numChildren; c++) {
+ objectNode = node.jjtGetChild(c);
+ if (objectNode instanceof ASTMethodNode) {
+ antish = false;
+ if (object == null) {
+ // we may be performing a method call on an antish var
+ if (ant != null) {
+ final JexlNode child = objectNode.jjtGetChild(0);
+ if (child instanceof ASTIdentifierAccess) {
+ final int alen = ant.length();
+ ant.append('.');
+ ant.append(((ASTIdentifierAccess) child).getName());
+ object = context.get(ant.toString());
+ if (object != null) {
+ object = visit((ASTMethodNode) objectNode, object, context);
+ continue;
+ }
+ // remove method name from antish
+ ant.delete(alen, ant.length());
+ ptyNode = objectNode;
+ }
+ }
+ break;
+ }
+ } else if (objectNode instanceof ASTArrayAccess) {
+ antish = false;
+ if (object == null) {
+ ptyNode = objectNode;
+ break;
+ }
+ }
+ // attempt to evaluate the property within the object (visit(ASTIdentifierAccess node))
+ object = objectNode.jjtAccept(this, object);
+ cancelCheck(node);
+ if (object != null) {
+ // disallow mixing antish variable & bean with same root; avoid ambiguity
+ antish = false;
+ } else if (antish) {
+ // create first from first node
+ if (ant == null) {
+ // if we still have a null object, check for an antish variable
+ final JexlNode first = node.jjtGetChild(0);
+ if (!(first instanceof ASTIdentifier)) {
+ // not an identifier, not antish
+ ptyNode = objectNode;
+ break;
+ }
+ final ASTIdentifier afirst = (ASTIdentifier) first;
+ ant = new StringBuilder(afirst.getName());
+ // skip the else...*
+ // *... and continue
+ if (!options.isAntish()) {
+ antish = false;
+ continue;
+ }
+ // skip the first node case since it was trialed in jjtAccept above and returned null
+ if (c == 0) {
+ continue;
+ }
+ }
+ // catch up to current node
+ for (; v <= c; ++v) {
+ final JexlNode child = node.jjtGetChild(v);
+ if (!(child instanceof ASTIdentifierAccess)) {
+ // not an identifier, not antish
+ ptyNode = objectNode;
+ break main;
+ }
+ final ASTIdentifierAccess achild = (ASTIdentifierAccess) child;
+ if (achild.isSafe() || achild.isExpression()) {
+ break main;
+ }
+ ant.append('.');
+ ant.append(achild.getName());
+ }
+ // solve antish
+ object = context.get(ant.toString());
+ } else if (c != numChildren - 1) {
+ // only the last one may be null
+ ptyNode = objectNode;
+ break; //
+ }
+ }
+ // dealing with null
+ if (object == null) {
+ if (ptyNode != null) {
+ if (ptyNode.isSafeLhs(isSafe())) {
+ return null;
+ }
+ if (ant != null) {
+ final String aname = ant.toString();
+ final boolean defined = isVariableDefined(frame, block, aname);
+ return unsolvableVariable(node, aname, !defined);
+ }
+ return unsolvableProperty(node,
+ stringifyProperty(ptyNode), ptyNode == objectNode, null);
+ }
+ if (antish) {
+ if (node.isSafeLhs(isSafe())) {
+ return null;
+ }
+ final String aname = ant != null ? ant.toString() : "?";
+ final boolean defined = isVariableDefined(frame, block, aname);
+ // defined but null; arg of a strict operator?
+ if (defined && (!arithmetic.isStrict() || !node.jjtGetParent().isStrictOperator())) {
+ return null;
+ }
+ return unsolvableVariable(node, aname, !defined);
+ }
+ }
+ return object;
+ }
+
+ @Override
+ protected Object visit(final ASTAssignment node, final Object data) {
+ return executeAssign(node, null, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetAddNode node, final Object data) {
+ return executeAssign(node, JexlOperator.SELF_ADD, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetSubNode node, final Object data) {
+ return executeAssign(node, JexlOperator.SELF_SUBTRACT, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetMultNode node, final Object data) {
+ return executeAssign(node, JexlOperator.SELF_MULTIPLY, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetDivNode node, final Object data) {
+ return executeAssign(node, JexlOperator.SELF_DIVIDE, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetModNode node, final Object data) {
+ return executeAssign(node, JexlOperator.SELF_MOD, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetAndNode node, final Object data) {
+ return executeAssign(node, JexlOperator.SELF_AND, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetOrNode node, final Object data) {
+ return executeAssign(node, JexlOperator.SELF_OR, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetXorNode node, final Object data) {
+ return executeAssign(node, JexlOperator.SELF_XOR, data);
+ }
+
+ /**
+ * Executes an assignment with an optional side-effect operator.
+ *
+ * @param node the node
+ * @param assignop the assignment operator or null if simply assignment
+ * @param data the data
+ * @return the left hand side
+ */
+ protected Object executeAssign(final JexlNode node, final JexlOperator assignop, final Object data) { // CSOFF: MethodLength
+ cancelCheck(node);
+ // left contains the reference to assign to
+ final JexlNode left = node.jjtGetChild(0);
+ ASTIdentifier var = null;
+ Object object = null;
+ int symbol = -1;
+ // check var decl with assign is ok
+ if (left instanceof ASTIdentifier) {
+ var = (ASTIdentifier) left;
+ symbol = var.getSymbol();
+ if (symbol >= 0 && options.isLexical()) {
+ if (var instanceof ASTVar) {
+ if (!defineVariable((ASTVar) var, block)) {
+ return redefinedVariable(var, var.getName());
+ }
+ } else if (options.isLexicalShade() && var.isShaded()) {
+ return undefinedVariable(var, var.getName());
+ }
+ }
+ }
+ boolean antish = options.isAntish();
+ // 0: determine initial object & property:
+ final int last = left.jjtGetNumChildren() - 1;
+ // right is the value expression to assign
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ // a (var?) v = ... expression
+ if (var != null) {
+ if (symbol >= 0) {
+ // check we are not assigning a symbol itself
+ if (last < 0) {
+ if (assignop != null) {
+ final Object self = getVariable(frame, block, var);
+ right = operators.tryAssignOverload(node, assignop, self, right);
+ if (right == JexlOperator.ASSIGN) {
+ return self;
+ }
+ }
+ frame.set(symbol, right);
+ // make the closure accessible to itself, ie capture the currently set variable after frame creation
+ if (right instanceof Closure) {
+ ((Closure) right).setCaptured(symbol, right);
+ }
+ return right; // 1
+ }
+ object = getVariable(frame, block, var);
+ // top level is a symbol, can not be an antish var
+ antish = false;
+ } else {
+ // check we are not assigning direct global
+ if (last < 0) {
+ if (assignop != null) {
+ final Object self = context.get(var.getName());
+ right = operators.tryAssignOverload(node, assignop, self, right);
+ if (right == JexlOperator.ASSIGN) {
+ return self;
+ }
+ }
+ setContextVariable(node, var.getName(), right);
+ return right; // 2
+ }
+ object = context.get(var.getName());
+ // top level accesses object, can not be an antish var
+ if (object != null) {
+ antish = false;
+ }
+ }
+ } else if (!(left instanceof ASTReference)) {
+ throw new JexlException(left, "illegal assignment form 0");
+ }
+ // 1: follow children till penultimate, resolve dot/array
+ JexlNode objectNode = null;
+ StringBuilder ant = null;
+ int v = 1;
+ // start at 1 if symbol
+ main:
+ for (int c = symbol >= 0 ? 1 : 0; c < last; ++c) {
+ objectNode = left.jjtGetChild(c);
+ object = objectNode.jjtAccept(this, object);
+ if (object != null) {
+ // disallow mixing antish variable & bean with same root; avoid ambiguity
+ antish = false;
+ } else if (antish) {
+ // initialize if first time
+ if (ant == null) {
+ final JexlNode first = left.jjtGetChild(0);
+ final ASTIdentifier firstId = first instanceof ASTIdentifier
+ ? (ASTIdentifier) first
+ : null;
+ if ((firstId == null) || (firstId.getSymbol() >= 0)) {
+ // ant remains null, object is null, stop solving
+ antish = false;
+ break;
+ }
+ ant = new StringBuilder(firstId.getName());
+ }
+ // catch up to current child
+ for (; v <= c; ++v) {
+ final JexlNode child = left.jjtGetChild(v);
+ final ASTIdentifierAccess aid = child instanceof ASTIdentifierAccess
+ ? (ASTIdentifierAccess) child
+ : null;
+ // remain antish only if unsafe navigation
+ if ((aid == null) || aid.isSafe() || aid.isExpression()) {
+ antish = false;
+ break main;
+ }
+ ant.append('.');
+ ant.append(aid.getName());
+ }
+ // solve antish
+ object = context.get(ant.toString());
+ } else {
+ throw new JexlException(objectNode, "illegal assignment form");
+ }
+ }
+ // 2: last objectNode will perform assignement in all cases
+ Object property = null;
+ JexlNode propertyNode = left.jjtGetChild(last);
+ final ASTIdentifierAccess propertyId = propertyNode instanceof ASTIdentifierAccess
+ ? (ASTIdentifierAccess) propertyNode
+ : null;
+ if (propertyId != null) {
+ // deal with creating/assignining antish variable
+ if (antish && ant != null && object == null && !propertyId.isSafe() && !propertyId.isExpression()) {
+ if (last > 0) {
+ ant.append('.');
+ }
+ ant.append(propertyId.getName());
+ if (assignop != null) {
+ final Object self = context.get(ant.toString());
+ right = operators.tryAssignOverload(node, assignop, self, right);
+ if (right == JexlOperator.ASSIGN) {
+ return self;
+ }
+ }
+ setContextVariable(propertyNode, ant.toString(), right);
+ return right; // 3
+ }
+ // property of an object ?
+ property = evalIdentifier(propertyId);
+ } else if (propertyNode instanceof ASTArrayAccess) {
+ // can have multiple nodes - either an expression, integer literal or reference
+ final int numChildren = propertyNode.jjtGetNumChildren() - 1;
+ for (int i = 0; i < numChildren; i++) {
+ final JexlNode nindex = propertyNode.jjtGetChild(i);
+ final Object index = nindex.jjtAccept(this, null);
+ object = getAttribute(object, index, nindex);
+ }
+ propertyNode = propertyNode.jjtGetChild(numChildren);
+ property = propertyNode.jjtAccept(this, null);
+ } else {
+ throw new JexlException(objectNode, "illegal assignment form");
+ }
+ // we may have a null property as in map[null], no check needed.
+ // we can not *have* a null object though.
+ if (object == null) {
+ // no object, we fail
+ return unsolvableProperty(objectNode, ".>", true, null);
+ }
+ // 3: one before last, assign
+ if (assignop != null) {
+ final Object self = getAttribute(object, property, propertyNode);
+ right = operators.tryAssignOverload(node, assignop, self, right);
+ if (right == JexlOperator.ASSIGN) {
+ return self;
+ }
+ }
+ setAttribute(object, property, right, propertyNode);
+ return right; // 4
+ }
+
+ @Override
+ protected Object[] visit(final ASTArguments node, final Object data) {
+ final int argc = node.jjtGetNumChildren();
+ final Object[] argv = new Object[argc];
+ for (int i = 0; i < argc; i++) {
+ argv[i] = node.jjtGetChild(i).jjtAccept(this, data);
+ }
+ return argv;
+ }
+
+ @Override
+ protected Object visit(final ASTMethodNode node, final Object data) {
+ return visit(node, null, data);
+ }
+
+ /**
+ * Execute a method call, ie syntactically written as name.call(...).
+ *
+ * @param node the actual method call node
+ * @param object non null when name.call is an antish variable
+ * @param data the context
+ * @return the method call result
+ */
+ private Object visit(final ASTMethodNode node, Object object, final Object data) {
+ // left contains the reference to the method
+ final JexlNode methodNode = node.jjtGetChild(0);
+ Object method;
+ // 1: determine object and method or functor
+ if (methodNode instanceof ASTIdentifierAccess) {
+ method = methodNode;
+ if (object == null) {
+ object = data;
+ if (object == null) {
+ // no object, we fail
+ return node.isSafeLhs(isSafe())
+ ? null
+ : unsolvableMethod(methodNode, ".>(...)");
+ }
+ } else {
+ // edge case of antish var used as functor
+ method = object;
+ }
+ } else {
+ method = methodNode.jjtAccept(this, data);
+ }
+ Object result = method;
+ for (int a = 1; a < node.jjtGetNumChildren(); ++a) {
+ if (result == null) {
+ // no method, we fail// variable unknown in context and not a local
+ return node.isSafeLhs(isSafe())
+ ? null
+ : unsolvableMethod(methodNode, ">.(...)");
+ }
+ final ASTArguments argNode = (ASTArguments) node.jjtGetChild(a);
+ result = call(node, object, result, argNode);
+ object = result;
+ }
+ return result;
+ }
+
+ @Override
+ protected Object visit(final ASTFunctionNode node, final Object data) {
+ final ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0);
+ final String nsid = functionNode.getNamespace();
+ final Object namespace = (nsid != null) ? resolveNamespace(nsid, node) : context;
+ final ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
+ return call(node, namespace, functionNode, argNode);
+ }
+
+ /**
+ * Calls a method (or function).
+ *
+ * Method resolution is a follows:
+ * 1 - attempt to find a method in the target passed as parameter;
+ * 2 - if this fails, seeks a JexlScript or JexlMethod or a duck-callable* as a property of that target;
+ * 3 - if this fails, narrow the arguments and try again 1
+ * 4 - if this fails, seeks a context or arithmetic method with the proper name taking the target as first argument;
+ *
+ * *duck-callable: an object where a "call" function exists
+ *
+ * @param node the method node
+ * @param target the target of the method, what it should be invoked upon
+ * @param functor the object carrying the method or function or the method identifier
+ * @param argNode the node carrying the arguments
+ * @return the result of the method invocation
+ */
+ protected Object call(final JexlNode node, final Object target, Object functor, final ASTArguments argNode) {
+ cancelCheck(node);
+ // evaluate the arguments
+ final Object[] argv = visit(argNode, null);
+ // get the method name if identifier
+ final int symbol;
+ final String methodName;
+ boolean cacheable = cache;
+ boolean isavar = false;
+ if (functor instanceof ASTIdentifier) {
+ // function call, target is context or namespace (if there was one)
+ final ASTIdentifier methodIdentifier = (ASTIdentifier) functor;
+ symbol = methodIdentifier.getSymbol();
+ methodName = methodIdentifier.getName();
+ functor = null;
+ // is it a global or local variable ?
+ if (target == context) {
+ if (frame != null && frame.has(symbol)) {
+ functor = frame.get(symbol);
+ isavar = functor != null;
+ } else if (context.has(methodName)) {
+ functor = context.get(methodName);
+ isavar = functor != null;
+ }
+ // name is a variable, can't be cached
+ cacheable &= !isavar;
+ }
+ } else if (functor instanceof ASTIdentifierAccess) {
+ // a method call on target
+ methodName = ((ASTIdentifierAccess) functor).getName();
+ symbol = -1;
+ functor = null;
+ cacheable = true;
+ } else if (functor != null) {
+ // ...(x)(y)
+ symbol = -1 - 1; // -2;
+ methodName = null;
+ cacheable = false;
+ } else if (!node.isSafeLhs(isSafe())) {
+ return unsolvableMethod(node, "?(...)");
+ } else {
+ // safe lhs
+ return null;
+ }
+
+ // solving the call site
+ final CallDispatcher call = new CallDispatcher(node, cacheable);
+ try {
+ // do we have a cached version method/function name ?
+ final Object eval = call.tryEval(target, methodName, argv);
+ if (JexlEngine.TRY_FAILED != eval) {
+ return eval;
+ }
+ boolean functorp = false;
+ boolean narrow = false;
+ // pseudo loop to try acquiring methods without and with argument narrowing
+ while (true) {
+ call.narrow = narrow;
+ // direct function or method call
+ if (functor == null || functorp) {
+ // try a method or function from context
+ if (call.isTargetMethod(target, methodName, argv)) {
+ return call.eval(methodName);
+ }
+ if (target == context) {
+ // solve 'null' namespace
+ final Object namespace = resolveNamespace(null, node);
+ if (namespace != null
+ && namespace != context
+ && call.isTargetMethod(namespace, methodName, argv)) {
+ return call.eval(methodName);
+ }
+ // do not try context function since this was attempted
+ // 10 lines above...; solve as an arithmetic function
+ if (call.isArithmeticMethod(methodName, argv)) {
+ return call.eval(methodName);
+ }
+ // could not find a method, try as a property of a non-context target (performed once)
+ } else {
+ // try prepending target to arguments and look for
+ // applicable method in context...
+ final Object[] pargv = functionArguments(target, narrow, argv);
+ if (call.isContextMethod(methodName, pargv)) {
+ return call.eval(methodName);
+ }
+ // ...or arithmetic
+ if (call.isArithmeticMethod(methodName, pargv)) {
+ return call.eval(methodName);
+ }
+ // the method may also be a functor stored in a property of the target
+ if (!narrow) {
+ final JexlPropertyGet get = uberspect.getPropertyGet(target, methodName);
+ if (get != null) {
+ functor = get.tryInvoke(target, methodName);
+ functorp = functor != null;
+ }
+ }
+ }
+ }
+ // this may happen without the above when we are chaining call like x(a)(b)
+ // or when a var/symbol or antish var is used as a "function" name
+ if (functor != null) {
+ // lambda, script or jexl method will do
+ if (functor instanceof JexlScript) {
+ return ((JexlScript) functor).execute(context, argv);
+ }
+ if (functor instanceof JexlMethod) {
+ return ((JexlMethod) functor).invoke(target, argv);
+ }
+ final String mCALL = "call";
+ // may be a generic callable, try a 'call' method
+ if (call.isTargetMethod(functor, mCALL, argv)) {
+ return call.eval(mCALL);
+ }
+ // functor is a var, may be method is a global one ?
+ if (isavar && target == context) {
+ if (call.isContextMethod(methodName, argv)) {
+ return call.eval(methodName);
+ }
+ if (call.isArithmeticMethod(methodName, argv)) {
+ return call.eval(methodName);
+ }
+ }
+ // try prepending functor to arguments and look for
+ // context or arithmetic function called 'call'
+ final Object[] pargv = functionArguments(functor, narrow, argv);
+ if (call.isContextMethod(mCALL, pargv)) {
+ return call.eval(mCALL);
+ }
+ if (call.isArithmeticMethod(mCALL, pargv)) {
+ return call.eval(mCALL);
+ }
+ }
+ // if we did not find an exact method by name and we haven't tried yet,
+ // attempt to narrow the parameters and if this succeeds, try again in next loop
+ if (narrow || !arithmetic.narrowArguments(argv)) {
+ break;
+ }
+ narrow = true;
+ // continue;
+ }
+ } catch (JexlException.Method xmethod) {
+ // ignore and handle at end; treat as an inner discover that fails
+ } catch (final JexlException.TryFailed xany) {
+ throw invocationException(node, methodName, xany);
+ } catch (final JexlException xthru) {
+ throw xthru;
+ } catch (final Exception xany) {
+ throw invocationException(node, methodName, xany);
+ }
+ // we have either evaluated and returned or no method was found
+ return node.isSafeLhs(isSafe())
+ ? null
+ : unsolvableMethod(node, methodName, argv);
+ }
+
+ @Override
+ protected Object visit(final ASTConstructorNode node, final Object data) {
+ if (isCancelled()) {
+ throw new JexlException.Cancel(node);
+ }
+ // first child is class or class name
+ final Object target = node.jjtGetChild(0).jjtAccept(this, data);
+ // get the ctor args
+ final int argc = node.jjtGetNumChildren() - 1;
+ Object[] argv = new Object[argc];
+ for (int i = 0; i < argc; i++) {
+ argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, data);
+ }
+
+ try {
+ final boolean cacheable = cache;
+ // attempt to reuse last funcall cached in volatile JexlNode.value
+ if (cacheable) {
+ final Object cached = node.jjtGetValue();
+ if (cached instanceof Funcall) {
+ final Object eval = ((Funcall) cached).tryInvoke(this, null, target, argv);
+ if (JexlEngine.TRY_FAILED != eval) {
+ return eval;
+ }
+ }
+ }
+ boolean narrow = false;
+ JexlMethod ctor = null;
+ Funcall funcall = null;
+ while (true) {
+ // try as stated
+ ctor = uberspect.getConstructor(target, argv);
+ if (ctor != null) {
+ if (cacheable && ctor.isCacheable()) {
+ funcall = new Funcall(ctor, narrow);
+ }
+ break;
+ }
+ // try with prepending context as first argument
+ final Object[] nargv = callArguments(context, narrow, argv);
+ ctor = uberspect.getConstructor(target, nargv);
+ if (ctor != null) {
+ if (cacheable && ctor.isCacheable()) {
+ funcall = new ContextualCtor(ctor, narrow);
+ }
+ argv = nargv;
+ break;
+ }
+ // if we did not find an exact method by name and we haven't tried yet,
+ // attempt to narrow the parameters and if this succeeds, try again in next loop
+ if (!narrow && arithmetic.narrowArguments(argv)) {
+ narrow = true;
+ continue;
+ }
+ // we are done trying
+ break;
+ }
+ // we have either evaluated and returned or might have found a ctor
+ if (ctor != null) {
+ final Object eval = ctor.invoke(target, argv);
+ // cache executor in volatile JexlNode.value
+ if (funcall != null) {
+ node.jjtSetValue(funcall);
+ }
+ return eval;
+ }
+ final String tstr = target != null ? target.toString() : "?";
+ return unsolvableMethod(node, tstr, argv);
+ } catch (final JexlException.Method xmethod) {
+ throw xmethod;
+ } catch (final Exception xany) {
+ final String tstr = target != null ? target.toString() : "?";
+ throw invocationException(node, tstr, xany);
+ }
+ }
+
+ @Override
+ protected Object visit(final ASTJxltLiteral node, final Object data) {
+ TemplateEngine.TemplateExpression tp = (TemplateEngine.TemplateExpression) node.jjtGetValue();
+ if (tp == null) {
+ final TemplateEngine jxlt = jexl.jxlt();
+ JexlInfo info = node.jexlInfo();
+ if (this.block != null) {
+ info = new JexlNode.Info(node, info);
+ }
+ tp = jxlt.parseExpression(info, node.getLiteral(), frame != null ? frame.getScope() : null);
+ node.jjtSetValue(tp);
+ }
+ if (tp != null) {
+ return tp.evaluate(frame, context);
+ }
+ return null;
+ }
+
+ @Override
+ protected Object visit(final ASTAnnotation node, final Object data) {
+ throw new UnsupportedOperationException(ASTAnnotation.class.getName() + ": Not supported.");
+ }
+
+ @Override
+ protected Object visit(final ASTAnnotatedStatement node, final Object data) {
+ return processAnnotation(node, 0, data);
+ }
+
+ /**
+ * An annotated call.
+ */
+ public class AnnotatedCall implements Callable {
+ /** The statement. */
+ private final ASTAnnotatedStatement stmt;
+ /** The child index. */
+ private final int index;
+ /** The data. */
+ private final Object data;
+ /** Tracking whether we processed the annotation. */
+ private boolean processed = false;
+
+ /**
+ * Simple ctor.
+ *
+ * @param astmt the statement
+ * @param aindex the index
+ * @param adata the data
+ */
+ AnnotatedCall(final ASTAnnotatedStatement astmt, final int aindex, final Object adata) {
+ stmt = astmt;
+ index = aindex;
+ data = adata;
+ }
+
+
+ @Override
+ public Object call() throws Exception {
+ processed = true;
+ try {
+ return processAnnotation(stmt, index, data);
+ } catch (JexlException.Return | JexlException.Break | JexlException.Continue xreturn) {
+ return xreturn;
+ }
+ }
+
+ /**
+ * @return whether the statement has been processed
+ */
+ public boolean isProcessed() {
+ return processed;
+ }
+
+ /**
+ * @return the actual statement.
+ */
+ public Object getStatement() {
+ return stmt;
+ }
+ }
+
+ /**
+ * Processes an annotated statement.
+ *
+ * @param stmt the statement
+ * @param index the index of the current annotation being processed
+ * @param data the contextual data
+ * @return the result of the statement block evaluation
+ */
+ protected Object processAnnotation(final ASTAnnotatedStatement stmt, final int index, final Object data) {
+ // are we evaluating the block ?
+ final int last = stmt.jjtGetNumChildren() - 1;
+ if (index == last) {
+ final JexlNode cblock = stmt.jjtGetChild(last);
+ // if the context has changed, might need a new interpreter
+ final JexlArithmetic jexla = arithmetic.options(context);
+ if (jexla == arithmetic) {
+ return cblock.jjtAccept(Interpreter.this, data);
+ }
+ if (!arithmetic.getClass().equals(jexla.getClass())) {
+ logger.warn("expected arithmetic to be " + arithmetic.getClass().getSimpleName()
+ + ", got " + jexla.getClass().getSimpleName()
+ );
+ }
+ final Interpreter ii = new Interpreter(Interpreter.this, jexla);
+ final Object r = cblock.jjtAccept(ii, data);
+ if (ii.isCancelled()) {
+ Interpreter.this.cancel();
+ }
+ return r;
+ }
+ // tracking whether we processed the annotation
+ final AnnotatedCall jstmt = new AnnotatedCall(stmt, index + 1, data);
+ // the annotation node and name
+ final ASTAnnotation anode = (ASTAnnotation) stmt.jjtGetChild(index);
+ final String aname = anode.getName();
+ // evaluate the arguments
+ final Object[] argv = anode.jjtGetNumChildren() > 0
+ ? visit((ASTArguments) anode.jjtGetChild(0), null) : null;
+ // wrap the future, will recurse through annotation processor
+ Object result;
+ try {
+ result = processAnnotation(aname, argv, jstmt);
+ // not processing an annotation is an error
+ if (!jstmt.isProcessed()) {
+ return annotationError(anode, aname, null);
+ }
+ } catch (final JexlException xany) {
+ throw xany;
+ } catch (final Exception xany) {
+ return annotationError(anode, aname, xany);
+ }
+ // the caller may return a return, break or continue
+ if (result instanceof JexlException) {
+ throw (JexlException) result;
+ }
+ return result;
+ }
+
+ /**
+ * Delegates the annotation processing to the JexlContext if it is an AnnotationProcessor.
+ *
+ * @param annotation the annotation name
+ * @param args the annotation arguments
+ * @param stmt the statement / block that was annotated
+ * @return the result of statement.call()
+ * @throws Exception if anything goes wrong
+ */
+ protected Object processAnnotation(final String annotation, final Object[] args, final Callable stmt) throws Exception {
+ return context instanceof JexlContext.AnnotationProcessor
+ ? ((JexlContext.AnnotationProcessor) context).processAnnotation(annotation, args, stmt)
+ : stmt.call();
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/InterpreterBase.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/InterpreterBase.java
new file mode 100644
index 0000000..92c16bb
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -0,0 +1,1061 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlMethod;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertyGet;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertySet;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlUberspect;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTVar;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.JexlNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlArithmetic;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlContext;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlEngine;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlOperator;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlOptions;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTArrayAccess;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAssignment;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTFunctionNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTIdentifier;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTIdentifierAccess;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTMethodNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTReference;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ParserVisitor;
+
+
+import org.apache.commons.logging.Log;
+
+/**
+ * The helper base of an interpreter of JEXL syntax.
+ * @since 3.0
+ */
+public abstract class InterpreterBase extends ParserVisitor {
+ /** The JEXL engine. */
+ protected final Engine jexl;
+ /** The logger. */
+ protected final Log logger;
+ /** The uberspect. */
+ protected final JexlUberspect uberspect;
+ /** The arithmetic handler. */
+ protected final JexlArithmetic arithmetic;
+ /** The context to store/retrieve variables. */
+ protected final JexlContext context;
+ /** The options. */
+ protected final JexlOptions options;
+ /** Cache executors. */
+ protected final boolean cache;
+ /** Cancellation support. */
+ protected final AtomicBoolean cancelled;
+ /** Empty parameters for method matching. */
+ protected static final Object[] EMPTY_PARAMS = new Object[0];
+ /** The namespace resolver. */
+ protected final JexlContext.NamespaceResolver ns;
+ /** The operators evaluation delegate. */
+ protected final Operators operators;
+ /** The map of 'prefix:function' to object resolving as namespaces. */
+ protected final Map functions;
+ /** The map of dynamically creates namespaces, NamespaceFunctor or duck-types of those. */
+ protected Map functors;
+
+ /**
+ * Creates an interpreter base.
+ * @param engine the engine creating this interpreter
+ * @param opts the evaluation options
+ * @param aContext the evaluation context
+ */
+ protected InterpreterBase(final Engine engine, final JexlOptions opts, final JexlContext aContext) {
+ this.jexl = engine;
+ this.logger = jexl.logger;
+ this.uberspect = jexl.uberspect;
+ this.context = aContext != null ? aContext : Engine.EMPTY_CONTEXT;
+ this.cache = engine.cache != null;
+ final JexlArithmetic jexla = jexl.arithmetic;
+ this.options = opts == null? engine.options(aContext) : opts;
+ this.arithmetic = jexla.options(options);
+ if (arithmetic != jexla && !arithmetic.getClass().equals(jexla.getClass())) {
+ logger.warn("expected arithmetic to be " + jexla.getClass().getSimpleName()
+ + ", got " + arithmetic.getClass().getSimpleName()
+ );
+ }
+ if (this.context instanceof JexlContext.NamespaceResolver) {
+ ns = ((JexlContext.NamespaceResolver) context);
+ } else {
+ ns = Engine.EMPTY_NS;
+ }
+ AtomicBoolean acancel = null;
+ if (this.context instanceof JexlContext.CancellationHandle) {
+ acancel = ((JexlContext.CancellationHandle) context).getCancellation();
+ }
+ this.cancelled = acancel != null? acancel : new AtomicBoolean(false);
+ final Map ons = options.getNamespaces();
+ this.functions = ons.isEmpty()? jexl.functions : ons;
+ this.functors = null;
+ this.operators = new Operators(this);
+ }
+
+ /**
+ * Copy constructor.
+ * @param ii the base to copy
+ * @param jexla the arithmetic instance to use (or null)
+ */
+ protected InterpreterBase(final InterpreterBase ii, final JexlArithmetic jexla) {
+ jexl = ii.jexl;
+ logger = ii.logger;
+ uberspect = ii.uberspect;
+ arithmetic = jexla;
+ context = ii.context;
+ options = ii.options.copy();
+ cache = ii.cache;
+ ns = ii.ns;
+ operators = ii.operators;
+ cancelled = ii.cancelled;
+ functions = ii.functions;
+ functors = ii.functors;
+ }
+
+ /**
+ * Attempt to call close() if supported.
+ * This is used when dealing with auto-closeable (duck-like) objects
+ * @param closeable the object we'd like to close
+ */
+ protected void closeIfSupported(final Object closeable) {
+ if (closeable != null) {
+ final JexlMethod mclose = uberspect.getMethod(closeable, "close", EMPTY_PARAMS);
+ if (mclose != null) {
+ try {
+ mclose.invoke(closeable, EMPTY_PARAMS);
+ } catch (final Exception xignore) {
+ logger.warn(xignore);
+ }
+ }
+ }
+ }
+
+ /**
+ * Resolves a namespace, eventually allocating an instance using context as constructor argument.
+ *
+ * The lifetime of such instances span the current expression or script evaluation.
+ * @param prefix the prefix name (may be null for global namespace)
+ * @param node the AST node
+ * @return the namespace instance
+ */
+ protected Object resolveNamespace(final String prefix, final JexlNode node) {
+ Object namespace;
+ // check whether this namespace is a functor
+ synchronized (this) {
+ if (functors != null) {
+ namespace = functors.get(prefix);
+ if (namespace != null) {
+ return namespace;
+ }
+ }
+ }
+ // check if namespace is a resolver
+ namespace = ns.resolveNamespace(prefix);
+ if (namespace == null) {
+ namespace = functions.get(prefix);
+ if (prefix != null && namespace == null) {
+ throw new JexlException(node, "no such function namespace " + prefix, null);
+ }
+ }
+ // shortcut if ns is known to be not-a-functor
+ final boolean cacheable = cache;
+ final Object cached = cacheable ? node.jjtGetValue() : null;
+ if (cached != JexlContext.NamespaceFunctor.class) {
+ // allow namespace to instantiate a functor with context if possible, not an error otherwise
+ Object functor = null;
+ if (namespace instanceof JexlContext.NamespaceFunctor) {
+ functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
+ } else if (namespace instanceof Class> || namespace instanceof String) {
+ // attempt to reuse last ctor cached in volatile JexlNode.value
+ if (cached instanceof JexlMethod) {
+ try {
+ final Object eval = ((JexlMethod) cached).tryInvoke(null, context);
+ if (JexlEngine.TRY_FAILED != eval) {
+ functor = eval;
+ }
+ } catch (final JexlException.TryFailed xtry) {
+ throw new JexlException(node, "unable to instantiate namespace " + prefix, xtry.getCause());
+ }
+ }
+ // find a ctor with that context class
+ if (functor == null) {
+ JexlMethod ctor = uberspect.getConstructor(namespace, context);
+ if (ctor != null) {
+ try {
+ functor = ctor.invoke(namespace, context);
+ if (cacheable && ctor.isCacheable()) {
+ node.jjtSetValue(ctor);
+ }
+ } catch (final Exception xinst) {
+ throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
+ }
+ }
+ // try again; find a ctor with no arg
+ if (functor == null) {
+ ctor = uberspect.getConstructor(namespace);
+ if (ctor != null) {
+ try {
+ functor = ctor.invoke(namespace);
+ } catch (final Exception xinst) {
+ throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
+ }
+ }
+ // try again; use a class, namespace of static methods
+ // try to find a class with that name
+ if (functor == null && namespace instanceof String) {
+ try {
+ namespace = uberspect.getClassLoader().loadClass((String) namespace);
+ } catch (final ClassNotFoundException xignore) {
+ // not a class
+ namespace = null;
+ }
+ } // we know it's a class
+ }
+ }
+ }
+ // got a functor, store it and return it
+ if (functor != null) {
+ synchronized (this) {
+ if (functors == null) {
+ functors = new HashMap<>();
+ }
+ functors.put(prefix, functor);
+ }
+ return functor;
+ }
+ // use the NamespaceFunctor class to tag this node as not-a-functor
+ node.jjtSetValue(JexlContext.NamespaceFunctor.class);
+ }
+ return namespace;
+ }
+
+ /**
+ * Defines a variable.
+ * @param var the variable to define
+ * @param frame the frame in which it will be defined
+ * @return true if definition succeeded, false otherwise
+ */
+ protected boolean defineVariable(final ASTVar var, final LexicalFrame frame) {
+ final int symbol = var.getSymbol();
+ if (symbol < 0) {
+ return false;
+ }
+ if (var.isRedefined()) {
+ return false;
+ }
+ return frame.defineSymbol(symbol, var.isCaptured());
+ }
+
+ /**
+ * Checks whether a variable is defined.
+ * The var may be either a local variable declared in the frame and
+ * visible from the block or defined in the context.
+ * @param frame the frame
+ * @param block the block
+ * @param name the variable name
+ * @return true if variable is defined, false otherwise
+ */
+ protected boolean isVariableDefined(final Frame frame, final LexicalScope block, final String name) {
+ if (frame != null && block != null) {
+ final Integer ref = frame.getScope().getSymbol(name);
+ final int symbol = ref != null? ref : -1;
+ if (symbol >= 0 && block.hasSymbol(symbol)) {
+ final Object value = frame.get(symbol);
+ return value != Scope.UNDEFINED && value != Scope.UNDECLARED;
+ }
+ }
+ return context.has(name);
+ }
+
+ /**
+ * Gets a value of a defined local variable or from the context.
+ * @param frame the local frame
+ * @param block the lexical block if any
+ * @param identifier the variable node
+ * @return the value
+ */
+ protected Object getVariable(final Frame frame, final LexicalScope block, final ASTIdentifier identifier) {
+ final int symbol = identifier.getSymbol();
+ final String name = identifier.getName();
+ // if we have a symbol, we have a scope thus a frame
+ if (options.isLexicalShade() && identifier.isShaded()) {
+ return undefinedVariable(identifier, name);
+ }
+ // a local var ?
+ if ((symbol >= 0) && frame.has(symbol)) {
+ final Object value = frame.get(symbol);
+ // not out of scope with no lexical shade ?
+ if (value != Scope.UNDEFINED) {
+ // null argument of an arithmetic operator ?
+ if (value == null && arithmetic.isStrict() && identifier.jjtGetParent().isStrictOperator()) {
+ return unsolvableVariable(identifier, name, false); // defined but null
+ }
+ return value;
+ }
+ }
+ // consider global
+ final Object value = context.get(name);
+ // is it null ?
+ if (value == null) {
+ // is it defined ?
+ if (!context.has(name)) {
+ // not defined, ignore in some cases...
+ final boolean ignore =
+ (isSafe() && (symbol >= 0 || identifier.jjtGetParent() instanceof ASTAssignment))
+ || (identifier.jjtGetParent() instanceof ASTReference);
+ if (!ignore) {
+ return undefinedVariable(identifier, name); // undefined
+ }
+ } else if (arithmetic.isStrict() && identifier.jjtGetParent().isStrictOperator()) {
+ return unsolvableVariable(identifier, name, false); // defined but null
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Sets a variable in the global context.
+ *
If interpretation applies lexical shade, the variable must exist (ie
+ * the context has(...) method returns true) otherwise an error occurs.
+ * @param node the node
+ * @param name the variable name
+ * @param value the variable value
+ */
+ protected void setContextVariable(final JexlNode node, final String name, final Object value) {
+ if (options.isLexicalShade() && !context.has(name)) {
+ throw new JexlException.Variable(node, name, true);
+ }
+ try {
+ context.set(name, value);
+ } catch (final UnsupportedOperationException xsupport) {
+ throw new JexlException(node, "context is readonly", xsupport);
+ }
+ }
+
+ /**
+ * Whether this interpreter is currently evaluating with a strict engine flag.
+ * @return true if strict engine, false otherwise
+ */
+ protected boolean isStrictEngine() {
+ return options.isStrict();
+ }
+
+ /**
+ * Whether this interpreter ignores null in navigation expression as errors.
+ * @return true if safe, false otherwise
+ */
+ protected boolean isSafe() {
+ return options.isSafe();
+ }
+
+ /**
+ * Whether this interpreter is currently evaluating with a silent mode.
+ * @return true if silent, false otherwise
+ */
+ protected boolean isSilent() {
+ return options.isSilent();
+ }
+
+ /**
+ * @return true if interrupt throws a JexlException.Cancel.
+ */
+ protected boolean isCancellable() {
+ return options.isCancellable();
+ }
+
+ /**
+ * Finds the node causing a NPE for diadic operators.
+ * @param xrt the RuntimeException
+ * @param node the parent node
+ * @param left the left argument
+ * @param right the right argument
+ * @return the left, right or parent node
+ */
+ protected JexlNode findNullOperand(final RuntimeException xrt, final JexlNode node, final Object left, final Object right) {
+ if (xrt instanceof JexlArithmetic.NullOperand) {
+ if (left == null) {
+ return node.jjtGetChild(0);
+ }
+ if (right == null) {
+ return node.jjtGetChild(1);
+ }
+ }
+ return node;
+ }
+
+ /**
+ * Triggered when a variable can not be resolved.
+ * @param node the node where the error originated from
+ * @param var the variable name
+ * @param undef whether the variable is undefined or null
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object unsolvableVariable(final JexlNode node, final String var, final boolean undef) {
+ return variableError(node, var, undef? JexlException.VariableIssue.UNDEFINED : JexlException.VariableIssue.NULLVALUE);
+ }
+
+ /**
+ * Triggered when a variable is lexically known as undefined.
+ * @param node the node where the error originated from
+ * @param var the variable name
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object undefinedVariable(final JexlNode node, final String var) {
+ return variableError(node, var, JexlException.VariableIssue.UNDEFINED);
+ }
+
+ /**
+ * Triggered when a variable is lexically known as being redefined.
+ * @param node the node where the error originated from
+ * @param var the variable name
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object redefinedVariable(final JexlNode node, final String var) {
+ return variableError(node, var, JexlException.VariableIssue.REDEFINED);
+ }
+
+ /**
+ * Triggered when a variable generates an issue.
+ * @param node the node where the error originated from
+ * @param var the variable name
+ * @param issue the issue type
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object variableError(final JexlNode node, final String var, final JexlException.VariableIssue issue) {
+ if (isStrictEngine() && !node.isTernaryProtected()) {
+ throw new JexlException.Variable(node, var, issue);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.variableError(node, var, issue));
+ }
+ return null;
+ }
+ /**
+ * Triggered when a method can not be resolved.
+ * @param node the node where the error originated from
+ * @param method the method name
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object unsolvableMethod(final JexlNode node, final String method) {
+ return unsolvableMethod(node, method, null);
+ }
+
+ /**
+ * Triggered when a method can not be resolved.
+ * @param node the node where the error originated from
+ * @param method the method name
+ * @param args the method arguments
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object unsolvableMethod(final JexlNode node, final String method, final Object[] args) {
+ if (isStrictEngine()) {
+ throw new JexlException.Method(node, method, args);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.methodError(node, method, args));
+ }
+ return null;
+ }
+
+ /**
+ * Triggered when a property can not be resolved.
+ * @param node the node where the error originated from
+ * @param property the property node
+ * @param cause the cause if any
+ * @param undef whether the property is undefined or null
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object unsolvableProperty(final JexlNode node, final String property, final boolean undef, final Throwable cause) {
+ if (isStrictEngine() && !node.isTernaryProtected()) {
+ throw new JexlException.Property(node, property, undef, cause);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.propertyError(node, property, undef));
+ }
+ return null;
+ }
+
+ /**
+ * Checks whether a reference child node holds a local variable reference.
+ * @param node the reference node
+ * @param which the child we are checking
+ * @return true if child is local variable, false otherwise
+ */
+ protected boolean isLocalVariable(final ASTReference node, final int which) {
+ return (node.jjtGetNumChildren() > which
+ && node.jjtGetChild(which) instanceof ASTIdentifier
+ && ((ASTIdentifier) node.jjtGetChild(which)).getSymbol() >= 0);
+ }
+
+ /**
+ * Checks whether a reference child node holds a function call.
+ * @param node the reference node
+ * @return true if child is function call, false otherwise
+ */
+ protected boolean isFunctionCall(final ASTReference node) {
+ return (node.jjtGetNumChildren() > 0
+ && node.jjtGetChild(0) instanceof ASTFunctionNode);
+ }
+
+ /**
+ * Pretty-prints a failing property (de)reference.
+ *
Used by calls to unsolvableProperty(...).
+ * @param node the property node
+ * @return the (pretty) string
+ */
+ protected String stringifyProperty(final JexlNode node) {
+ if (node instanceof ASTArrayAccess) {
+ return "["
+ + stringifyPropertyValue(node.jjtGetChild(0))
+ + "]";
+ }
+ if (node instanceof ASTMethodNode) {
+ return stringifyPropertyValue(node.jjtGetChild(0));
+ }
+ if (node instanceof ASTFunctionNode) {
+ return stringifyPropertyValue(node.jjtGetChild(0));
+ }
+ if (node instanceof ASTIdentifier) {
+ return ((ASTIdentifier) node).getName();
+ }
+ if (node instanceof ASTReference) {
+ return stringifyProperty(node.jjtGetChild(0));
+ }
+ return stringifyPropertyValue(node);
+ }
+
+ /**
+ * Pretty-prints a failing property value (de)reference.
+ * Used by calls to unsolvableProperty(...).
+ * @param node the property node
+ * @return the (pretty) string value
+ */
+ protected static String stringifyPropertyValue(final JexlNode node) {
+ return node != null? new Debugger().depth(1).data(node) : "???";
+ }
+
+ /**
+ * Triggered when an operator fails.
+ * @param node the node where the error originated from
+ * @param operator the method name
+ * @param cause the cause of error (if any)
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object operatorError(final JexlNode node, final JexlOperator operator, final Throwable cause) {
+ if (isStrictEngine()) {
+ throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
+ }
+ return null;
+ }
+
+ /**
+ * Triggered when an annotation processing fails.
+ * @param node the node where the error originated from
+ * @param annotation the annotation name
+ * @param cause the cause of error (if any)
+ * @return throws a JexlException if strict and not silent, null otherwise
+ */
+ protected Object annotationError(final JexlNode node, final String annotation, final Throwable cause) {
+ if (isStrictEngine()) {
+ throw new JexlException.Annotation(node, annotation, cause);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.annotationError(node, annotation), cause);
+ }
+ return null;
+ }
+
+ /**
+ * Triggered when method, function or constructor invocation fails with an exception.
+ * @param node the node triggering the exception
+ * @param methodName the method/function name
+ * @param xany the cause
+ * @return a JexlException that will be thrown
+ */
+ protected JexlException invocationException(final JexlNode node, final String methodName, final Throwable xany) {
+ final Throwable cause = xany.getCause();
+ if (cause instanceof JexlException) {
+ return (JexlException) cause;
+ }
+ if (cause instanceof InterruptedException) {
+ return new JexlException.Cancel(node);
+ }
+ return new JexlException(node, methodName, xany);
+ }
+
+ /**
+ * Cancels this evaluation, setting the cancel flag that will result in a JexlException.Cancel to be thrown.
+ * @return false if already cancelled, true otherwise
+ */
+ protected boolean cancel() {
+ return cancelled.compareAndSet(false, true);
+ }
+
+ /**
+ * Checks whether this interpreter execution was cancelled due to thread interruption.
+ * @return true if cancelled, false otherwise
+ */
+ protected boolean isCancelled() {
+ return cancelled.get() | Thread.currentThread().isInterrupted();
+ }
+
+ /**
+ * Throws a JexlException.Cancel if script execution was cancelled.
+ * @param node the node being evaluated
+ */
+ protected void cancelCheck(final JexlNode node) {
+ if (isCancelled()) {
+ throw new JexlException.Cancel(node);
+ }
+ }
+
+ /**
+ * Concatenate arguments in call(...).
+ * When target == context, we are dealing with a global namespace function call
+ * @param target the pseudo-method owner, first to-be argument
+ * @param narrow whether we should attempt to narrow number arguments
+ * @param args the other (non null) arguments
+ * @return the arguments array
+ */
+ protected Object[] functionArguments(final Object target, final boolean narrow, final Object[] args) {
+ // when target == context, we are dealing with the null namespace
+ if (target == null || target == context) {
+ if (narrow) {
+ arithmetic.narrowArguments(args);
+ }
+ return args;
+ }
+ // makes target 1st args, copy others - optionally narrow numbers
+ final Object[] nargv = new Object[args.length + 1];
+ if (narrow) {
+ nargv[0] = functionArgument(true, target);
+ for (int a = 1; a <= args.length; ++a) {
+ nargv[a] = functionArgument(true, args[a - 1]);
+ }
+ } else {
+ nargv[0] = target;
+ System.arraycopy(args, 0, nargv, 1, args.length);
+ }
+ return nargv;
+ }
+
+ /**
+ * Concatenate arguments in call(...).
+ * @param target the pseudo-method owner, first to-be argument
+ * @param narrow whether we should attempt to narrow number arguments
+ * @param args the other (non null) arguments
+ * @return the arguments array
+ */
+ protected Object[] callArguments(final Object target, final boolean narrow, final Object[] args) {
+ // makes target 1st args, copy others - optionally narrow numbers
+ final Object[] nargv = new Object[args.length + 1];
+ if (narrow) {
+ nargv[0] = functionArgument(true, target);
+ for (int a = 1; a <= args.length; ++a) {
+ nargv[a] = functionArgument(true, args[a - 1]);
+ }
+ } else {
+ nargv[0] = target;
+ System.arraycopy(args, 0, nargv, 1, args.length);
+ }
+ return nargv;
+ }
+
+ /**
+ * Optionally narrows an argument for a function call.
+ * @param narrow whether narrowing should occur
+ * @param arg the argument
+ * @return the narrowed argument
+ */
+ protected Object functionArgument(final boolean narrow, final Object arg) {
+ return narrow && arg instanceof Number ? arithmetic.narrow((Number) arg) : arg;
+ }
+
+ /**
+ * Cached function call.
+ */
+ protected static class Funcall implements JexlNode.Funcall {
+ /** Whether narrow should be applied to arguments. */
+ protected final boolean narrow;
+ /** The JexlMethod to delegate the call to. */
+ protected final JexlMethod me;
+ /**
+ * Constructor.
+ * @param jme the method
+ * @param flag the narrow flag
+ */
+ protected Funcall(final JexlMethod jme, final boolean flag) {
+ this.me = jme;
+ this.narrow = flag;
+ }
+
+ /**
+ * Try invocation.
+ * @param ii the interpreter
+ * @param name the method name
+ * @param target the method target
+ * @param args the method arguments
+ * @return the method invocation result (or JexlEngine.TRY_FAILED)
+ */
+ protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
+ return me.tryInvoke(name, target, ii.functionArguments(null, narrow, args));
+ }
+ }
+
+ /**
+ * Cached arithmetic function call.
+ */
+ protected static class ArithmeticFuncall extends Funcall {
+ /**
+ * Constructor.
+ * @param jme the method
+ * @param flag the narrow flag
+ */
+ protected ArithmeticFuncall(final JexlMethod jme, final boolean flag) {
+ super(jme, flag);
+ }
+
+ @Override
+ protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
+ return me.tryInvoke(name, ii.arithmetic, ii.functionArguments(target, narrow, args));
+ }
+ }
+
+ /**
+ * Cached context function call.
+ */
+ protected static class ContextFuncall extends Funcall {
+ /**
+ * Constructor.
+ * @param jme the method
+ * @param flag the narrow flag
+ */
+ protected ContextFuncall(final JexlMethod jme, final boolean flag) {
+ super(jme, flag);
+ }
+
+ @Override
+ protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
+ return me.tryInvoke(name, ii.context, ii.functionArguments(target, narrow, args));
+ }
+ }
+
+ /**
+ * A ctor that needs a context as 1st argument.
+ */
+ protected static class ContextualCtor extends Funcall {
+ /**
+ * Constructor.
+ * @param jme the method
+ * @param flag the narrow flag
+ */
+ protected ContextualCtor(final JexlMethod jme, final boolean flag) {
+ super(jme, flag);
+ }
+
+ @Override
+ protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
+ return me.tryInvoke(name, target, ii.callArguments(ii.context, narrow, args));
+ }
+ }
+
+ /**
+ * Helping dispatch function calls.
+ */
+ protected class CallDispatcher {
+ /**
+ * The syntactic node.
+ */
+ final JexlNode node;
+ /**
+ * Whether solution is cacheable.
+ */
+ boolean cacheable = true;
+ /**
+ * Whether arguments have been narrowed.
+ */
+ boolean narrow = false;
+ /**
+ * The method to call.
+ */
+ JexlMethod vm = null;
+ /**
+ * The method invocation target.
+ */
+ Object target = null;
+ /**
+ * The actual arguments.
+ */
+ Object[] argv = null;
+ /**
+ * The cacheable funcall if any.
+ */
+ Funcall funcall = null;
+
+ /**
+ * Dispatcher ctor.
+ *
+ * @param anode the syntactic node.
+ * @param acacheable whether resolution can be cached
+ */
+ CallDispatcher(final JexlNode anode, final boolean acacheable) {
+ this.node = anode;
+ this.cacheable = acacheable;
+ }
+
+ /**
+ * Whether the method is a target method.
+ *
+ * @param ntarget the target instance
+ * @param mname the method name
+ * @param arguments the method arguments
+ * @return true if arithmetic, false otherwise
+ */
+ protected boolean isTargetMethod(final Object ntarget, final String mname, final Object[] arguments) {
+ // try a method
+ vm = uberspect.getMethod(ntarget, mname, arguments);
+ if (vm != null) {
+ argv = arguments;
+ target = ntarget;
+ if (cacheable && vm.isCacheable()) {
+ funcall = new Funcall(vm, narrow);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Whether the method is a context method.
+ *
+ * @param mname the method name
+ * @param arguments the method arguments
+ * @return true if arithmetic, false otherwise
+ */
+ protected boolean isContextMethod(final String mname, final Object[] arguments) {
+ vm = uberspect.getMethod(context, mname, arguments);
+ if (vm != null) {
+ argv = arguments;
+ target = context;
+ if (cacheable && vm.isCacheable()) {
+ funcall = new ContextFuncall(vm, narrow);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Whether the method is an arithmetic method.
+ *
+ * @param mname the method name
+ * @param arguments the method arguments
+ * @return true if arithmetic, false otherwise
+ */
+ protected boolean isArithmeticMethod(final String mname, final Object[] arguments) {
+ vm = uberspect.getMethod(arithmetic, mname, arguments);
+ if (vm != null) {
+ argv = arguments;
+ target = arithmetic;
+ if (cacheable && vm.isCacheable()) {
+ funcall = new ArithmeticFuncall(vm, narrow);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempt to reuse last funcall cached in volatile JexlNode.value (if
+ * it was cacheable).
+ *
+ * @param ntarget the target instance
+ * @param mname the method name
+ * @param arguments the method arguments
+ * @return TRY_FAILED if invocation was not possible or failed, the
+ * result otherwise
+ */
+ protected Object tryEval(final Object ntarget, final String mname, final Object[] arguments) {
+ // do we have a method/function name ?
+ // attempt to reuse last funcall cached in volatile JexlNode.value (if it was not a variable)
+ if (mname != null && cacheable && ntarget != null) {
+ final Object cached = node.jjtGetValue();
+ if (cached instanceof Funcall) {
+ return ((Funcall) cached).tryInvoke(InterpreterBase.this, mname, ntarget, arguments);
+ }
+ }
+ return JexlEngine.TRY_FAILED;
+ }
+
+ /**
+ * Evaluates the method previously dispatched.
+ *
+ * @param mname the method name
+ * @return the method invocation result
+ * @throws Exception when invocation fails
+ */
+ protected Object eval(final String mname) throws Exception {
+ // we have either evaluated and returned or might have found a method
+ if (vm != null) {
+ // vm cannot be null if xjexl is null
+ final Object eval = vm.invoke(target, argv);
+ // cache executor in volatile JexlNode.value
+ if (funcall != null) {
+ node.jjtSetValue(funcall);
+ }
+ return eval;
+ }
+ return unsolvableMethod(node, mname, argv);
+ }
+ }
+
+ /**
+ * Gets an attribute of an object.
+ *
+ * @param object to retrieve value from
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
+ * @param node the node that evaluated as the object
+ * @return the attribute value
+ */
+ protected Object getAttribute(final Object object, final Object attribute, final JexlNode node) {
+ if (object == null) {
+ throw new JexlException(node, "object is null");
+ }
+ cancelCheck(node);
+ final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
+ ? JexlOperator.ARRAY_GET : JexlOperator.PROPERTY_GET;
+ final Object result = operators.tryOverload(node, operator, object, attribute);
+ if (result != JexlEngine.TRY_FAILED) {
+ return result;
+ }
+ Exception xcause = null;
+ try {
+ // attempt to reuse last executor cached in volatile JexlNode.value
+ if (node != null && cache) {
+ final Object cached = node.jjtGetValue();
+ if (cached instanceof JexlPropertyGet) {
+ final JexlPropertyGet vg = (JexlPropertyGet) cached;
+ final Object value = vg.tryInvoke(object, attribute);
+ if (!vg.tryFailed(value)) {
+ return value;
+ }
+ }
+ }
+ // resolve that property
+ final List resolvers = uberspect.getResolvers(operator, object);
+ final JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
+ if (vg != null) {
+ final Object value = vg.invoke(object);
+ // cache executor in volatile JexlNode.value
+ if (node != null && cache && vg.isCacheable()) {
+ node.jjtSetValue(vg);
+ }
+ return value;
+ }
+ } catch (final Exception xany) {
+ xcause = xany;
+ }
+ // lets fail
+ if (node == null) {
+ // direct call
+ final String error = "unable to get object property"
+ + ", class: " + object.getClass().getName()
+ + ", property: " + attribute;
+ throw new UnsupportedOperationException(error, xcause);
+ }
+ final boolean safe = (node instanceof ASTIdentifierAccess) && ((ASTIdentifierAccess) node).isSafe();
+ if (safe) {
+ return null;
+ }
+ final String attrStr = attribute != null ? attribute.toString() : null;
+ return unsolvableProperty(node, attrStr, true, xcause);
+ }
+
+ /**
+ * Sets an attribute of an object.
+ *
+ * @param object to set the value to
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
+ * @param value the value to assign to the object's attribute
+ * @param node the node that evaluated as the object
+ */
+ protected void setAttribute(final Object object, final Object attribute, final Object value, final JexlNode node) {
+ cancelCheck(node);
+ final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
+ ? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
+ final Object result = operators.tryOverload(node, operator, object, attribute, value);
+ if (result != JexlEngine.TRY_FAILED) {
+ return;
+ }
+ Exception xcause = null;
+ try {
+ // attempt to reuse last executor cached in volatile JexlNode.value
+ if (node != null && cache) {
+ final Object cached = node.jjtGetValue();
+ if (cached instanceof JexlPropertySet) {
+ final JexlPropertySet setter = (JexlPropertySet) cached;
+ final Object eval = setter.tryInvoke(object, attribute, value);
+ if (!setter.tryFailed(eval)) {
+ return;
+ }
+ }
+ }
+ final List resolvers = uberspect.getResolvers(operator, object);
+ JexlPropertySet vs = uberspect.getPropertySet(resolvers, object, attribute, value);
+ // if we can't find an exact match, narrow the value argument and try again
+ if (vs == null) {
+ // replace all numbers with the smallest type that will fit
+ final Object[] narrow = {value};
+ if (arithmetic.narrowArguments(narrow)) {
+ vs = uberspect.getPropertySet(resolvers, object, attribute, narrow[0]);
+ }
+ }
+ if (vs != null) {
+ // cache executor in volatile JexlNode.value
+ vs.invoke(object, value);
+ if (node != null && cache && vs.isCacheable()) {
+ node.jjtSetValue(vs);
+ }
+ return;
+ }
+ } catch (final Exception xany) {
+ xcause = xany;
+ }
+ // lets fail
+ if (node == null) {
+ // direct call
+ final String error = "unable to set object property"
+ + ", class: " + object.getClass().getName()
+ + ", property: " + attribute
+ + ", argument: " + value.getClass().getSimpleName();
+ throw new UnsupportedOperationException(error, xcause);
+ }
+ final String attrStr = attribute != null ? attribute.toString() : null;
+ unsolvableProperty(node, attrStr, true, xcause);
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/LexicalFrame.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/LexicalFrame.java
new file mode 100644
index 0000000..68f0919
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/LexicalFrame.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * The set of valued symbols defined in a lexical frame.
+ * The symbol identifiers are determined by the functional scope.
+ */
+public class LexicalFrame extends LexicalScope {
+ /**
+ * The script frame.
+ */
+ private final Frame frame;
+ /**
+ * Previous frame.
+ */
+ protected final LexicalFrame previous;
+ /**
+ * The stack of values in the lexical frame.
+ */
+ private Deque stack = null;
+
+ /**
+ * Lexical frame ctor.
+ *
+ * @param scriptf the script frame
+ * @param outerf the previous lexical frame
+ */
+ public LexicalFrame(final Frame scriptf, final LexicalFrame outerf) {
+ this.previous = outerf;
+ this.frame = scriptf;
+ }
+
+ /**
+ * Copy ctor.
+ *
+ * @param src the frame to copy
+ */
+ public LexicalFrame(final LexicalFrame src) {
+ super(src.symbols, src.moreSymbols);
+ frame = src.frame;
+ previous = src.previous;
+ stack = src.stack != null ? new ArrayDeque<>(src.stack) : null;
+ }
+
+ /**
+ * Define the arguments.
+ *
+ * @return this frame
+ */
+ public LexicalFrame defineArgs() {
+ if (frame != null) {
+ final int argc = frame.getScope().getArgCount();
+ for (int a = 0; a < argc; ++a) {
+ super.addSymbol(a);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Defines a symbol.
+ *
+ * @param symbol the symbol to define
+ * @param capture whether this redefines a captured symbol
+ * @return true if symbol is defined, false otherwise
+ */
+ public boolean defineSymbol(final int symbol, final boolean capture) {
+ final boolean declared = addSymbol(symbol);
+ if (declared && capture) {
+ if (stack == null) {
+ stack = new ArrayDeque<>();
+ }
+ stack.push(symbol);
+ Object value = frame.get(symbol);
+ if (value == null) {
+ value = this;
+ }
+ stack.push(value);
+ }
+ return declared;
+ }
+
+ /**
+ * Pops back values and lexical frame.
+ *
+ * @return the previous frame
+ */
+ public LexicalFrame pop() {
+ // undefine all symbols
+ clearSymbols(s -> frame.set(s, Scope.UNDEFINED) );
+ // restore values of captured symbols that were overwritten
+ if (stack != null) {
+ while (!stack.isEmpty()) {
+ Object value = stack.pop();
+ if (value == Scope.UNDECLARED) {
+ value = Scope.UNDEFINED;
+ } else if (value == this) {
+ value = null;
+ }
+ final int symbol = (Integer) stack.pop();
+ frame.set(symbol, value);
+ }
+ }
+ return previous;
+ }
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/LexicalScope.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/LexicalScope.java
new file mode 100644
index 0000000..3436594
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/LexicalScope.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import java.util.BitSet;
+
+/**
+ * The set of symbols declared in a lexical scope.
+ * The symbol identifiers are determined by the functional scope.
+ */
+public class LexicalScope {
+ /**
+ * Number of bits in a long.
+ */
+ protected static final int LONGBITS = 64;
+ /**
+ * The mask of symbols in the frame.
+ */
+ protected long symbols = 0L;
+ /**
+ * Symbols after 64.
+ */
+ protected BitSet moreSymbols = null;
+
+ /**
+ * Create a scope.
+ */
+ public LexicalScope() {
+ }
+
+ /**
+ * Frame copy ctor base.
+ *
+ * @param s the symbols mask
+ * @param ms the more symbols bitset
+ */
+ protected LexicalScope(final long s, final BitSet ms) {
+ symbols = s;
+ moreSymbols = ms != null ? (BitSet) ms.clone() : null;
+ }
+
+ /**
+ * Ensure more symbpls can be stored.
+ *
+ * @return the set of more symbols
+ */
+ protected final BitSet moreSymbols() {
+ if (moreSymbols == null) {
+ moreSymbols = new BitSet();
+ }
+ return moreSymbols;
+ }
+
+ /**
+ * Checks whether a symbol has already been declared.
+ *
+ * @param symbol the symbol
+ * @return true if declared, false otherwise
+ */
+ public boolean hasSymbol(final int symbol) {
+ if (symbol < LONGBITS) {
+ return (symbols & (1L << symbol)) != 0L;
+ }
+ return moreSymbols != null && moreSymbols.get(symbol - LONGBITS);
+ }
+
+ /**
+ * Adds a symbol in this scope.
+ *
+ * @param symbol the symbol
+ * @return true if registered, false if symbol was already registered
+ */
+ public boolean addSymbol(final int symbol) {
+ if (symbol < LONGBITS) {
+ if ((symbols & (1L << symbol)) != 0L) {
+ return false;
+ }
+ symbols |= (1L << symbol);
+ } else {
+ final int s = symbol - LONGBITS;
+ final BitSet ms = moreSymbols();
+ if (ms.get(s)) {
+ return false;
+ }
+ ms.set(s, true);
+ }
+ return true;
+ }
+
+ /**
+ * Clear all symbols.
+ *
+ * @param cleanSymbol a (optional, may be null) functor to call for each cleaned symbol
+ */
+ public final void clearSymbols(final java.util.function.IntConsumer cleanSymbol) {
+ // undefine symbols getting out of scope
+ if (cleanSymbol != null) {
+ long clean = symbols;
+ while (clean != 0L) {
+ final int s = Long.numberOfTrailingZeros(clean);
+ clean &= ~(1L << s);
+ cleanSymbol.accept(s);
+ }
+ }
+ symbols = 0L;
+ if (moreSymbols != null) {
+ if (cleanSymbol != null) {
+ for (int s = moreSymbols.nextSetBit(0); s != -1; s = moreSymbols.nextSetBit(s + 1)) {
+ cleanSymbol.accept(s + LONGBITS);
+ }
+ }
+ moreSymbols.clear();
+ }
+ }
+
+ /**
+ * @return the number of symbols defined in this scope.
+ */
+ public int getSymbolCount() {
+ return Long.bitCount(symbols) + (moreSymbols == null ? 0 : moreSymbols.cardinality());
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/LongRange.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/LongRange.java
new file mode 100644
index 0000000..85e689f
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/LongRange.java
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A range of longs.
+ *
+ * Behaves as a readonly collection of longs.
+ */
+public abstract class LongRange implements Collection {
+ /** The lower boundary. */
+ protected final long min;
+ /** The upper boundary. */
+ protected final long max;
+
+ /**
+ * Creates a range, ascending or descending depending on boundaries order.
+ *
+ * @param from the lower inclusive boundary
+ * @param to the higher inclusive boundary
+ * @return a range
+ */
+ public static LongRange create(final long from, final long to) {
+ if (from <= to) {
+ return new Ascending(from, to);
+ }
+ return new Descending(to, from);
+ }
+
+ /**
+ * Creates a new range.
+ *
+ * @param from the lower inclusive boundary
+ * @param to the higher inclusive boundary
+ */
+ protected LongRange(final long from, final long to) {
+ min = from;
+ max = to;
+ }
+
+ /**
+ * Gets the interval minimum value.
+ *
+ * @return the low boundary
+ */
+ public long getMin() {
+ return min;
+ }
+
+ /**
+ * Gets the interval maximum value.
+ *
+ * @return the high boundary
+ */
+ public long getMax() {
+ return max;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = getClass().hashCode();
+ // CSOFF: MagicNumber
+ hash = 13 * hash + (int) (this.min ^ (this.min >>> 32));
+ hash = 13 * hash + (int) (this.max ^ (this.max >>> 32));
+ // CSON: MagicNumber
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final LongRange other = (LongRange) obj;
+ if (this.min != other.min) {
+ return false;
+ }
+ return this.max == other.max;
+ }
+
+ @Override
+ public abstract Iterator iterator();
+
+ @Override
+ public int size() {
+ return (int) (max - min + 1);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ if (o instanceof Number) {
+ final long v = ((Number) o).longValue();
+ return min <= v && v <= max;
+ }
+ return false;
+ }
+
+ @Override
+ public Object[] toArray() {
+ final int size = size();
+ final Object[] array = new Object[size];
+ for (int a = 0; a < size; ++a) {
+ array[a] = min + a;
+ }
+ return array;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T[] toArray(final T[] array) {
+ final Class> ct = array.getClass().getComponentType();
+ final int length = size();
+ T[] copy = array;
+ if (ct.isAssignableFrom(Long.class)) {
+ if (array.length < length) {
+ copy = (T[]) Array.newInstance(ct, length);
+ }
+ for (int a = 0; a < length; ++a) {
+ Array.set(copy, a, min + a);
+ }
+ if (length < copy.length) {
+ copy[length] = null;
+ }
+ return copy;
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean containsAll(final Collection> c) {
+ for (final Object cc : c) {
+ if (!contains(cc)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean add(final Long e) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(final Collection extends Long> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeAll(final Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean retainAll(final Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Ascending long range.
+ */
+ public static class Ascending extends LongRange {
+ /**
+ * Constructor.
+ *
+ * @param from lower boundary
+ * @param to upper boundary
+ */
+ protected Ascending(final long from, final long to) {
+ super(from, to);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new AscLongIterator(min, max);
+ }
+ }
+
+ /**
+ * Descending long range.
+ */
+ public static class Descending extends LongRange {
+ /**
+ * Constructor.
+ *
+ * @param from upper boundary
+ * @param to lower boundary
+ */
+ protected Descending(final long from, final long to) {
+ super(from, to);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new DescLongIterator(min, max);
+ }
+ }
+}
+
+/**
+ * An iterator on a long range.
+ */
+class AscLongIterator implements Iterator {
+ /** The lower boundary. */
+ private final long min;
+ /** The upper boundary. */
+ private final long max;
+ /** The current value. */
+ private long cursor;
+
+ /**
+ * Creates a iterator on the range.
+ *
+ * @param l low boundary
+ * @param h high boundary
+ */
+ AscLongIterator(final long l, final long h) {
+ min = l;
+ max = h;
+ cursor = min;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return cursor <= max;
+ }
+
+ @Override
+ public Long next() {
+ if (cursor <= max) {
+ return cursor++;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
+
+/**
+ * An iterator on a long range.
+ */
+class DescLongIterator implements Iterator {
+ /** The lower boundary. */
+ private final long min;
+ /** The upper boundary. */
+ private final long max;
+ /** The current value. */
+ private long cursor;
+
+ /**
+ * Creates a iterator on the range.
+ *
+ * @param l low boundary
+ * @param h high boundary
+ */
+ DescLongIterator(final long l, final long h) {
+ min = l;
+ max = h;
+ cursor = max;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return cursor >= min;
+ }
+
+ @Override
+ public Long next() {
+ if (cursor >= min) {
+ return cursor--;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/MapBuilder.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/MapBuilder.java
new file mode 100644
index 0000000..709face
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/MapBuilder.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlArithmetic;
+
+/**
+ * Helper class to create map literals.
+ */
+public class MapBuilder implements JexlArithmetic.MapBuilder {
+ /** The map being created. */
+ protected final Map map;
+
+ /**
+ * Creates a new builder.
+ * @param size the expected map size
+ */
+ public MapBuilder(final int size) {
+ map = new HashMap(size);
+ }
+
+ @Override
+ public void put(final Object key, final Object value) {
+ map.put(key, value);
+ }
+
+ @Override
+ public Map create() {
+ return map;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Operators.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Operators.java
new file mode 100644
index 0000000..93d950a
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Operators.java
@@ -0,0 +1,410 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import java.lang.reflect.Method;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlArithmetic;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlEngine;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlOperator;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlMethod;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlUberspect;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.JexlNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection.MethodExecutor;
+
+/**
+ * Helper class to deal with operator overloading and specifics.
+ * @since 3.0
+ */
+public class Operators {
+ /** The owner. */
+ protected final InterpreterBase interpreter;
+ /** The overloaded arithmetic operators. */
+ protected final JexlArithmetic.Uberspect operators;
+
+ /**
+ * Constructor.
+ * @param owner the owning interpreter
+ */
+ protected Operators(final InterpreterBase owner) {
+ final JexlArithmetic arithmetic = owner.arithmetic;
+ final JexlUberspect uberspect = owner.uberspect;
+ this.interpreter = owner;
+ this.operators = uberspect.getArithmetic(arithmetic);
+ }
+
+ /**
+ * Checks whether a method returns a boolean or a Boolean.
+ * @param vm the JexlMethod (may be null)
+ * @return true of false
+ */
+ private boolean returnsBoolean(final JexlMethod vm) {
+ if (vm !=null) {
+ final Class> rc = vm.getReturnType();
+ return Boolean.TYPE.equals(rc) || Boolean.class.equals(rc);
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether a method returns an int or an Integer.
+ * @param vm the JexlMethod (may be null)
+ * @return true of false
+ */
+ private boolean returnsInteger(final JexlMethod vm) {
+ if (vm !=null) {
+ final Class> rc = vm.getReturnType();
+ return Integer.TYPE.equals(rc) || Integer.class.equals(rc);
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether a method is a JexlArithmetic method.
+ * @param vm the JexlMethod (may be null)
+ * @return true of false
+ */
+ private boolean isArithmetic(final JexlMethod vm) {
+ if (vm instanceof MethodExecutor) {
+ final Method method = ((MethodExecutor) vm).getMethod();
+ return JexlArithmetic.class.equals(method.getDeclaringClass());
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to call an operator.
+ *
+ * This takes care of finding and caching the operator method when appropriate
+ * @param node the syntactic node
+ * @param operator the operator
+ * @param args the arguments
+ * @return the result of the operator evaluation or TRY_FAILED
+ */
+ protected Object tryOverload(final JexlNode node, final JexlOperator operator, final Object... args) {
+ if (operators != null && operators.overloads(operator)) {
+ final JexlArithmetic arithmetic = interpreter.arithmetic;
+ final boolean cache = interpreter.cache;
+ try {
+ if (cache) {
+ final Object cached = node.jjtGetValue();
+ if (cached instanceof JexlMethod) {
+ final JexlMethod me = (JexlMethod) cached;
+ final Object eval = me.tryInvoke(operator.getMethodName(), arithmetic, args);
+ if (!me.tryFailed(eval)) {
+ return eval;
+ }
+ }
+ }
+ final JexlMethod vm = operators.getOperator(operator, args);
+ if (vm != null && !isArithmetic(vm)) {
+ final Object result = vm.invoke(arithmetic, args);
+ if (cache) {
+ node.jjtSetValue(vm);
+ }
+ return result;
+ }
+ } catch (final Exception xany) {
+ return interpreter.operatorError(node, operator, xany);
+ }
+ }
+ return JexlEngine.TRY_FAILED;
+ }
+
+ /**
+ * Evaluates an assign operator.
+ *
+ * This takes care of finding and caching the operator method when appropriate.
+ * If an overloads returns Operator.ASSIGN, it means the side-effect is complete.
+ * Otherwise, a += b <=> a = a + b
+ *
+ * @param node the syntactic node
+ * @param operator the operator
+ * @param args the arguments, the first one being the target of assignment
+ * @return JexlOperator.ASSIGN if operation assignment has been performed,
+ * JexlEngine.TRY_FAILED if no operation was performed,
+ * the value to use as the side effect argument otherwise
+ */
+ protected Object tryAssignOverload(final JexlNode node, final JexlOperator operator, final Object...args) {
+ final JexlArithmetic arithmetic = interpreter.arithmetic;
+ if (args.length != operator.getArity()) {
+ return JexlEngine.TRY_FAILED;
+ }
+ // try to call overload with side effect
+ Object result = tryOverload(node, operator, args);
+ if (result != JexlEngine.TRY_FAILED) {
+ return result;
+ }
+ // call base operator
+ final JexlOperator base = operator.getBaseOperator();
+ if (base == null) {
+ throw new IllegalArgumentException("must be called with a side-effect operator");
+ }
+ if (operators != null && operators.overloads(base)) {
+ // in case there is an overload on the base operator
+ try {
+ final JexlMethod vm = operators.getOperator(base, args);
+ if (vm != null) {
+ result = vm.invoke(arithmetic, args);
+ if (result != JexlEngine.TRY_FAILED) {
+ return result;
+ }
+ }
+ } catch (final Exception xany) {
+ interpreter.operatorError(node, base, xany);
+ }
+ }
+ // base eval
+ try {
+ switch (operator) {
+ case SELF_ADD:
+ return arithmetic.add(args[0], args[1]);
+ case SELF_SUBTRACT:
+ return arithmetic.subtract(args[0], args[1]);
+ case SELF_MULTIPLY:
+ return arithmetic.multiply(args[0], args[1]);
+ case SELF_DIVIDE:
+ return arithmetic.divide(args[0], args[1]);
+ case SELF_MOD:
+ return arithmetic.mod(args[0], args[1]);
+ case SELF_AND:
+ return arithmetic.and(args[0], args[1]);
+ case SELF_OR:
+ return arithmetic.or(args[0], args[1]);
+ case SELF_XOR:
+ return arithmetic.xor(args[0], args[1]);
+ default:
+ // unexpected, new operator added?
+ throw new UnsupportedOperationException(operator.getOperatorSymbol());
+ }
+ } catch (final Exception xany) {
+ interpreter.operatorError(node, base, xany);
+ }
+ return JexlEngine.TRY_FAILED;
+ }
+
+ /**
+ * The 'startsWith' operator implementation.
+ * @param node the node
+ * @param operator the calling operator, $= or $!
+ * @param left the left operand
+ * @param right the right operand
+ * @return true if left starts with right, false otherwise
+ */
+ protected boolean startsWith(final JexlNode node, final String operator, final Object left, final Object right) {
+ final JexlArithmetic arithmetic = interpreter.arithmetic;
+ final JexlUberspect uberspect = interpreter.uberspect;
+ try {
+ // try operator overload
+ final Object result = tryOverload(node, JexlOperator.STARTSWITH, left, right);
+ if (result instanceof Boolean) {
+ return (Boolean) result;
+ }
+ // use arithmetic / pattern matching ?
+ final Boolean matched = arithmetic.startsWith(left, right);
+ if (matched != null) {
+ return matched;
+ }
+ // try a startsWith method (duck type)
+ try {
+ final Object[] argv = {right};
+ JexlMethod vm = uberspect.getMethod(left, "startsWith", argv);
+ if (returnsBoolean(vm)) {
+ return (Boolean) vm.invoke(left, argv);
+ }
+ if (arithmetic.narrowArguments(argv)) {
+ vm = uberspect.getMethod(left, "startsWith", argv);
+ if (returnsBoolean(vm)) {
+ return (Boolean) vm.invoke(left, argv);
+ }
+ }
+ } catch (final Exception e) {
+ throw new JexlException(node, operator + " error", e);
+ }
+ // defaults to equal
+ return arithmetic.equals(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, operator + " error", xrt);
+ }
+ }
+
+ /**
+ * The 'endsWith' operator implementation.
+ * @param node the node
+ * @param operator the calling operator, ^= or ^!
+ * @param left the left operand
+ * @param right the right operand
+ * @return true if left ends with right, false otherwise
+ */
+ protected boolean endsWith(final JexlNode node, final String operator, final Object left, final Object right) {
+ final JexlArithmetic arithmetic = interpreter.arithmetic;
+ final JexlUberspect uberspect = interpreter.uberspect;
+ try {
+ // try operator overload
+ final Object result = tryOverload(node, JexlOperator.ENDSWITH, left, right);
+ if (result instanceof Boolean) {
+ return (Boolean) result;
+ }
+ // use arithmetic / pattern matching ?
+ final Boolean matched = arithmetic.endsWith(left, right);
+ if (matched != null) {
+ return matched;
+ }
+ // try a endsWith method (duck type)
+ try {
+ final Object[] argv = {right};
+ JexlMethod vm = uberspect.getMethod(left, "endsWith", argv);
+ if (returnsBoolean(vm)) {
+ return (Boolean) vm.invoke(left, argv);
+ }
+ if (arithmetic.narrowArguments(argv)) {
+ vm = uberspect.getMethod(left, "endsWith", argv);
+ if (returnsBoolean(vm)) {
+ return (Boolean) vm.invoke(left, argv);
+ }
+ }
+ } catch (final Exception e) {
+ throw new JexlException(node, operator + " error", e);
+ }
+ // defaults to equal
+ return arithmetic.equals(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, operator + " error", xrt);
+ }
+ }
+
+ /**
+ * The 'match'/'in' operator implementation.
+ *
+ * Note that 'x in y' or 'x matches y' means 'y contains x' ;
+ * the JEXL operator arguments order syntax is the reverse of this method call.
+ *
+ * @param node the node
+ * @param op the calling operator, =~ or !~
+ * @param right the left operand
+ * @param left the right operand
+ * @return true if left matches right, false otherwise
+ */
+ protected boolean contains(final JexlNode node, final String op, final Object left, final Object right) {
+ final JexlArithmetic arithmetic = interpreter.arithmetic;
+ final JexlUberspect uberspect = interpreter.uberspect;
+ try {
+ // try operator overload
+ final Object result = tryOverload(node, JexlOperator.CONTAINS, left, right);
+ if (result instanceof Boolean) {
+ return (Boolean) result;
+ }
+ // use arithmetic / pattern matching ?
+ final Boolean matched = arithmetic.contains(left, right);
+ if (matched != null) {
+ return matched;
+ }
+ // try a contains method (duck type set)
+ try {
+ final Object[] argv = {right};
+ JexlMethod vm = uberspect.getMethod(left, "contains", argv);
+ if (returnsBoolean(vm)) {
+ return (Boolean) vm.invoke(left, argv);
+ }
+ if (arithmetic.narrowArguments(argv)) {
+ vm = uberspect.getMethod(left, "contains", argv);
+ if (returnsBoolean(vm)) {
+ return (Boolean) vm.invoke(left, argv);
+ }
+ }
+ } catch (final Exception e) {
+ throw new JexlException(node, op + " error", e);
+ }
+ // defaults to equal
+ return arithmetic.equals(left, right);
+ } catch (final ArithmeticException xrt) {
+ throw new JexlException(node, op + " error", xrt);
+ }
+ }
+
+ /**
+ * Check for emptyness of various types: Collection, Array, Map, String, and anything that has a boolean isEmpty()
+ * method.
+ * Note that the result may not be a boolean.
+ *
+ * @param node the node holding the object
+ * @param object the object to check the emptyness of
+ * @return the evaluation result
+ */
+ protected Object empty(final JexlNode node, final Object object) {
+ if (object == null) {
+ return true;
+ }
+ Object result = tryOverload(node, JexlOperator.EMPTY, object);
+ if (result != JexlEngine.TRY_FAILED) {
+ return result;
+ }
+ final JexlArithmetic arithmetic = interpreter.arithmetic;
+ result = arithmetic.isEmpty(object, null);
+ if (result == null) {
+ final JexlUberspect uberspect = interpreter.uberspect;
+ result = false;
+ // check if there is an isEmpty method on the object that returns a
+ // boolean and if so, just use it
+ final JexlMethod vm = uberspect.getMethod(object, "isEmpty", Interpreter.EMPTY_PARAMS);
+ if (returnsBoolean(vm)) {
+ try {
+ result = vm.invoke(object, Interpreter.EMPTY_PARAMS);
+ } catch (final Exception xany) {
+ interpreter.operatorError(node, JexlOperator.EMPTY, xany);
+ }
+ }
+ }
+ return !(result instanceof Boolean) || (Boolean) result;
+ }
+
+ /**
+ * Calculate the size
of various types:
+ * Collection, Array, Map, String, and anything that has a int size() method.
+ *
Note that the result may not be an integer.
+ *
+ * @param node the node that gave the value to size
+ * @param object the object to get the size of
+ * @return the evaluation result
+ */
+ protected Object size(final JexlNode node, final Object object) {
+ if (object == null) {
+ return 0;
+ }
+ Object result = tryOverload(node, JexlOperator.SIZE, object);
+ if (result != JexlEngine.TRY_FAILED) {
+ return result;
+ }
+ final JexlArithmetic arithmetic = interpreter.arithmetic;
+ result = arithmetic.size(object, null);
+ if (result == null) {
+ final JexlUberspect uberspect = interpreter.uberspect;
+ // check if there is a size method on the object that returns an
+ // integer and if so, just use it
+ final JexlMethod vm = uberspect.getMethod(object, "size", Interpreter.EMPTY_PARAMS);
+ if (returnsInteger(vm)) {
+ try {
+ result = vm.invoke(object, Interpreter.EMPTY_PARAMS);
+ } catch (final Exception xany) {
+ interpreter.operatorError(node, JexlOperator.SIZE, xany);
+ }
+ }
+ }
+ return result instanceof Number ? ((Number) result).intValue() : 0;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Scope.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Scope.java
new file mode 100644
index 0000000..b295e89
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Scope.java
@@ -0,0 +1,318 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A script scope, stores the declaration of parameters and local variables as symbols.
+ *
This also acts as the functional scope and variable definition store.
+ * @since 3.0
+ */
+public final class Scope {
+ /**
+ * The value of an as-yet undeclared but variable, for instance: x; before var x;.
+ */
+ static final Object UNDECLARED = new Object() {
+ @Override public String toString() {
+ return "??";
+ }
+ };
+ /**
+ * The value of a declared but undefined variable, for instance: var x;.
+ */
+ static final Object UNDEFINED = new Object() {
+ @Override public String toString() {
+ return "?";
+ }
+ };
+ /**
+ * The parent scope.
+ */
+ private final Scope parent;
+ /**
+ * The number of parameters.
+ */
+ private int parms;
+ /**
+ * The number of local variables.
+ */
+ private int vars;
+ /**
+ * The map of named variables aka script parameters and local variables.
+ * Each parameter is associated to a symbol and is materialized as an offset in the stacked array used
+ * during evaluation.
+ */
+ private Map namedVariables = null;
+ /**
+ * The map of local captured variables to parent scope variables, ie closure.
+ */
+ private Map capturedVariables = null;
+ /**
+ * The empty string array.
+ */
+ private static final String[] EMPTY_STRS = new String[0];
+
+ /**
+ * Creates a new scope with a list of parameters.
+ * @param scope the parent scope if any
+ * @param parameters the list of parameters
+ */
+ public Scope(final Scope scope, final String... parameters) {
+ if (parameters != null) {
+ parms = parameters.length;
+ namedVariables = new LinkedHashMap();
+ for (int p = 0; p < parms; ++p) {
+ namedVariables.put(parameters[p], p);
+ }
+ } else {
+ parms = 0;
+ }
+ vars = 0;
+ parent = scope;
+ }
+
+ @Override
+ public int hashCode() {
+ return namedVariables == null ? 0 : parms ^ namedVariables.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Scope)) {
+ return false;
+ }
+ final Scope scope = (Scope) o;
+ if (parms != scope.parms) {
+ return false;
+ }
+ if (namedVariables == null) {
+ return scope.namedVariables == null;
+ }
+ return namedVariables.equals(scope.namedVariables);
+ }
+
+ /**
+ * Checks whether an identifier is a local variable or argument, ie a symbol.
+ * If this fails, look in parents for symbol that can be captured.
+ * @param name the symbol name
+ * @return the symbol index
+ */
+ public Integer getSymbol(final String name) {
+ return getSymbol(name, true);
+ }
+
+ /**
+ * Checks whether an identifier is a local variable or argument, ie a symbol.
+ * @param name the symbol name
+ * @param capture whether solving by capturing a parent symbol is allowed
+ * @return the symbol index
+ */
+ private Integer getSymbol(final String name, final boolean capture) {
+ Integer register = namedVariables != null ? namedVariables.get(name) : null;
+ if (register == null && capture && parent != null) {
+ final Integer pr = parent.getSymbol(name, true);
+ if (pr != null) {
+ if (capturedVariables == null) {
+ capturedVariables = new LinkedHashMap();
+ }
+ if (namedVariables == null) {
+ namedVariables = new LinkedHashMap();
+ }
+ register = namedVariables.size();
+ namedVariables.put(name, register);
+ capturedVariables.put(register, pr);
+ }
+ }
+ return register;
+ }
+
+ /**
+ * Checks whether a given symbol is captured.
+ * @param symbol the symbol number
+ * @return true if captured, false otherwise
+ */
+ public boolean isCapturedSymbol(final int symbol) {
+ return capturedVariables != null && capturedVariables.containsKey(symbol);
+ }
+
+ /**
+ * Declares a parameter.
+ *
+ * This method creates an new entry in the symbol map.
+ *
+ * @param name the parameter name
+ * @return the register index storing this variable
+ */
+ public int declareParameter(final String name) {
+ if (namedVariables == null) {
+ namedVariables = new LinkedHashMap();
+ } else if (vars > 0) {
+ throw new IllegalStateException("cant declare parameters after variables");
+ }
+ Integer register = namedVariables.get(name);
+ if (register == null) {
+ register = namedVariables.size();
+ namedVariables.put(name, register);
+ parms += 1;
+ }
+ return register;
+ }
+
+ /**
+ * Declares a local variable.
+ *
+ * This method creates an new entry in the symbol map.
+ *
+ * @param name the variable name
+ * @return the register index storing this variable
+ */
+ public int declareVariable(final String name) {
+ if (namedVariables == null) {
+ namedVariables = new LinkedHashMap();
+ }
+ Integer register = namedVariables.get(name);
+ if (register == null) {
+ register = namedVariables.size();
+ namedVariables.put(name, register);
+ vars += 1;
+ // check if local is redefining captured
+ if (parent != null) {
+ final Integer pr = parent.getSymbol(name, true);
+ if (pr != null) {
+ if (capturedVariables == null) {
+ capturedVariables = new LinkedHashMap();
+ }
+ capturedVariables.put(register, pr);
+ }
+ }
+ }
+ return register;
+ }
+
+ /**
+ * Creates a frame by copying values up to the number of parameters.
+ * This captures the captured variables values.
+ * @param frame the caller frame
+ * @param args the arguments
+ * @return the arguments array
+ */
+ public Frame createFrame(final Frame frame, final Object...args) {
+ if (namedVariables == null) {
+ return null;
+ }
+ final Object[] arguments = new Object[namedVariables.size()];
+ Arrays.fill(arguments, UNDECLARED);
+ if (frame != null && capturedVariables != null && parent != null) {
+ for (final Map.Entry capture : capturedVariables.entrySet()) {
+ final Integer target = capture.getKey();
+ final Integer source = capture.getValue();
+ final Object arg = frame.get(source);
+ arguments[target] = arg;
+ }
+ }
+ return new Frame(this, arguments, 0).assign(args);
+ }
+
+ /**
+ * Gets the captured index of a given symbol, ie the target index of a symbol in a child frame.
+ * @param symbol the symbol index
+ * @return the target symbol index or null if the symbol is not captured
+ */
+ public Integer getCaptured(final int symbol) {
+ if (capturedVariables != null) {
+ for (final Map.Entry capture : capturedVariables.entrySet()) {
+ final Integer source = capture.getValue();
+ if (source == symbol) {
+ return capture.getKey();
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the (maximum) number of arguments this script expects.
+ * @return the number of parameters
+ */
+ public int getArgCount() {
+ return parms;
+ }
+
+ /**
+ * Gets this script symbols names, i.e. parameters and local variables.
+ * @return the symbol names
+ */
+ public String[] getSymbols() {
+ return namedVariables != null ? namedVariables.keySet().toArray(new String[0]) : EMPTY_STRS;
+ }
+
+ /**
+ * Gets this script parameters, i.e. symbols assigned before creating local variables.
+ * @return the parameter names
+ */
+ public String[] getParameters() {
+ return getParameters(0);
+ }
+
+ /**
+ * Gets this script parameters.
+ * @param bound number of known bound parameters (curry)
+ * @return the parameter names
+ */
+ protected String[] getParameters(final int bound) {
+ final int unbound = parms - bound;
+ if ((namedVariables == null) || (unbound <= 0)) {
+ return EMPTY_STRS;
+ }
+ final String[] pa = new String[unbound];
+ int p = 0;
+ for (final Map.Entry entry : namedVariables.entrySet()) {
+ final int argn = entry.getValue();
+ if (argn >= bound && argn < parms) {
+ pa[p++] = entry.getKey();
+ }
+ }
+ return pa;
+ }
+
+ /**
+ * Gets this script local variable, i.e. symbols assigned to local variables excluding captured variables.
+ * @return the local variable names
+ */
+ public String[] getLocalVariables() {
+ if ((namedVariables == null) || (vars <= 0)) {
+ return EMPTY_STRS;
+ }
+ final List locals = new ArrayList(vars);
+ for (final Map.Entry entry : namedVariables.entrySet()) {
+ final int symnum = entry.getValue();
+ if (symnum >= parms && (capturedVariables == null || !capturedVariables.containsKey(symnum))) {
+ locals.add(entry.getKey());
+ }
+ }
+ return locals.toArray(new String[locals.size()]);
+ }
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Script.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Script.java
new file mode 100644
index 0000000..6a77ce8
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Script.java
@@ -0,0 +1,342 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlContext;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlFeatures;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlInfo;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlOptions;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlEngine;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlScript;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlExpression;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTJexlScript;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+/**
+ * A JexlScript implementation.
+ * @since 1.1
+ */
+public class Script implements JexlScript, JexlExpression {
+ /**
+ * The engine for this expression.
+ */
+ protected final Engine jexl;
+ /**
+ * Original expression stripped from leading and trailing spaces.
+ */
+ protected final String source;
+ /**
+ * The resulting AST we can interpret.
+ */
+ protected final ASTJexlScript script;
+ /**
+ * The engine version (as class loader change count) that last evaluated this script.
+ */
+ protected int version;
+
+ /**
+ * @return the script AST
+ */
+ protected ASTJexlScript getScript() {
+ return script;
+ }
+
+ /**
+ * Do not let this be generally instantiated with a 'new'.
+ *
+ * @param engine the interpreter to evaluate the expression
+ * @param expr the expression source.
+ * @param ref the parsed expression.
+ */
+ protected Script(final Engine engine, final String expr, final ASTJexlScript ref) {
+ jexl = engine;
+ source = expr;
+ script = ref;
+ version = jexl.getUberspect().getVersion();
+ }
+
+ /**
+ * Checks that this script cached methods (wrt introspection) matches the engine version.
+ *
+ * If the engine class loader has changed since we last evaluated this script, the script local cache
+ * is invalidated to drop references to obsolete methods. It is not strictly necessary since the tryExecute
+ * will fail because the class won't match but it seems cleaner nevertheless.
+ *
+ */
+ protected void checkCacheVersion() {
+ final int uberVersion = jexl.getUberspect().getVersion();
+ if (version != uberVersion) {
+ // version 0 of the uberSpect is an illusion due to order of construction; no need to clear cache
+ if (version > 0) {
+ script.clearCache();
+ }
+ version = uberVersion;
+ }
+ }
+
+ /**
+ * Creates this script frame for evaluation.
+ * @param args the arguments to bind to parameters
+ * @return the frame (may be null)
+ */
+ protected Frame createFrame(final Object[] args) {
+ return script.createFrame(args);
+ }
+
+ /**
+ * Creates this script interpreter.
+ * @param context the context
+ * @param frame the calling frame
+ * @return the interpreter
+ */
+ protected Interpreter createInterpreter(final JexlContext context, final Frame frame) {
+ final JexlOptions opts = jexl.options(script, context);
+ return jexl.createInterpreter(context, frame, opts);
+ }
+
+ /**
+ * @return the engine that created this script
+ */
+ public JexlEngine getEngine() {
+ return jexl;
+ }
+
+ @Override
+ public String getSourceText() {
+ return source;
+ }
+
+ @Override
+ public String getParsedText() {
+ return getParsedText(2);
+ }
+
+ @Override
+ public String getParsedText(final int indent) {
+ final Debugger debug = new Debugger();
+ debug.setIndentation(indent);
+ debug.debug(script, false);
+ return debug.toString();
+ }
+
+ @Override
+ public String toString() {
+ CharSequence src = source;
+ if (src == null) {
+ final Debugger debug = new Debugger();
+ debug.debug(script, false);
+ src = debug.toString();
+ }
+ return src.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ // CSOFF: Magic number
+ int hash = 17;
+ hash = 31 * hash + (this.jexl != null ? this.jexl.hashCode() : 0);
+ hash = 31 * hash + (this.source != null ? this.source.hashCode() : 0);
+ return hash;
+ // CSON: Magic number
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Script other = (Script) obj;
+ if (this.jexl != other.jexl) {
+ return false;
+ }
+ if (!Objects.equals(this.source, other.source)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public Object evaluate(final JexlContext context) {
+ return execute(context);
+ }
+
+ @Override
+ public Object execute(final JexlContext context) {
+ checkCacheVersion();
+ final Frame frame = createFrame(null);
+ final Interpreter interpreter = createInterpreter(context, frame);
+ return interpreter.interpret(script);
+ }
+
+ @Override
+ public Object execute(final JexlContext context, final Object... args) {
+ checkCacheVersion();
+ final Frame frame = createFrame(args != null && args.length > 0 ? args : null);
+ final Interpreter interpreter = createInterpreter(context, frame);
+ return interpreter.interpret(script);
+ }
+
+ @Override
+ public JexlScript curry(final Object... args) {
+ final String[] parms = script.getParameters();
+ if (parms == null || parms.length == 0) {
+ return this;
+ }
+ return new Closure(this, args);
+ }
+
+ @Override
+ public String[] getParameters() {
+ return script.getParameters();
+ }
+
+ @Override
+ public String[] getUnboundParameters() {
+ return getParameters();
+ }
+
+ @Override
+ public String[] getLocalVariables() {
+ return script.getLocalVariables();
+ }
+
+ /**
+ * @return the info
+ */
+ public JexlInfo getInfo() {
+ return script.jexlInfo();
+ }
+
+ /**
+ * @return the script features
+ */
+ public JexlFeatures getFeatures() {
+ return script.getFeatures();
+ }
+
+ /**
+ * Gets this script variables.
+ * Note that since variables can be in an ant-ish form (ie foo.bar.quux), each variable is returned as
+ * a list of strings where each entry is a fragment of the variable ({"foo", "bar", "quux"} in the example.
+ * @return the variables or null
+ */
+ @Override
+ public Set> getVariables() {
+ return jexl.getVariables(script);
+ }
+
+ /**
+ * Get this script pragmas
+ * Pragma keys are ant-ish variables, their values are scalar literals..
+ * @return the pragmas
+ */
+ @Override
+ public Map getPragmas() {
+ return script.getPragmas();
+ }
+
+ /**
+ * Creates a Callable from this script.
+ * This allows to submit it to an executor pool and provides support for asynchronous calls.
+ * The interpreter will handle interruption/cancellation gracefully if needed.
+ * @param context the context
+ * @return the callable
+ */
+ @Override
+ public Callable callable(final JexlContext context) {
+ return callable(context, (Object[]) null);
+ }
+
+ /**
+ * Creates a Callable from this script.
+ * This allows to submit it to an executor pool and provides support for asynchronous calls.
+ * The interpreter will handle interruption/cancellation gracefully if needed.
+ * @param context the context
+ * @param args the script arguments
+ * @return the callable
+ */
+ @Override
+ public Callable callable(final JexlContext context, final Object... args) {
+ return new Callable(createInterpreter(context, script.createFrame(args)));
+ }
+
+ /**
+ * Implements the Future and Callable interfaces to help delegation.
+ */
+ public class Callable implements java.util.concurrent.Callable {
+ /** The actual interpreter. */
+ protected final Interpreter interpreter;
+ /** Use interpreter as marker for not having run. */
+ protected volatile Object result;
+
+ /**
+ * The base constructor.
+ * @param intrprtr the interpreter to use
+ */
+ protected Callable(final Interpreter intrprtr) {
+ this.interpreter = intrprtr;
+ this.result = intrprtr;
+ }
+
+ /**
+ * Run the interpreter.
+ * @return the evaluation result
+ */
+ protected Object interpret() {
+ return interpreter.interpret(script);
+ }
+
+ @Override
+ public Object call() throws Exception {
+ synchronized(this) {
+ if (result == interpreter) {
+ checkCacheVersion();
+ result = interpret();
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Soft cancel the execution.
+ * @return true if cancel was successful, false otherwise
+ */
+ public boolean cancel() {
+ return interpreter.cancel();
+ }
+
+ /**
+ * @return true if evaluation was cancelled, false otherwise
+ */
+ public boolean isCancelled() {
+ return interpreter.isCancelled();
+ }
+
+ /**
+ * @return true if interruption will throw a JexlException.Cancel, false otherwise
+ */
+ public boolean isCancellable() {
+ return interpreter.isCancellable();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/ScriptVisitor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/ScriptVisitor.java
new file mode 100644
index 0000000..3442d82
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/ScriptVisitor.java
@@ -0,0 +1,502 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlExpression;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlScript;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAddNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAndNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAnnotation;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTArguments;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTArrayAccess;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTArrayLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTAssignment;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBlock;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTBreak;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTConstructorNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTContinue;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTDivNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTDoWhileStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTEQNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTERNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTEWNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTEmptyFunction;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTExtendedLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTFalseNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTForeachStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTFunctionNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTGENode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTGTNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTIdentifier;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTIdentifierAccess;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTIfStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTJexlScript;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTJxltLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTLENode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTLTNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTMapEntry;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTMapLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTMethodNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTModNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTMulNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNENode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNEWNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNRNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNSWNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNotNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNullLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNullpNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNumberLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTOrNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTRangeNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTReference;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTReferenceExpression;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTRegexLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTReturnStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSWNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetAddNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetAndNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetDivNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetModNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetMultNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetOrNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetSubNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSetXorNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSizeFunction;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTStringLiteral;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTSubNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTTernaryNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTTrueNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTUnaryPlusNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTVar;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTWhileStatement;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.JexlNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ParserVisitor;
+
+/**
+ * Fully abstract to avoid public interface exposition.
+ */
+public class ScriptVisitor extends ParserVisitor {
+ /**
+ * Visits all AST constituents of a JEXL expression.
+ * @param jscript the expression
+ * @param data some data context
+ * @return the visit result or null if jscript was not a Script implementation
+ */
+ public Object visitExpression (final JexlExpression jscript, final Object data) {
+ if (jscript instanceof Script) {
+ return ((Script) jscript).getScript().jjtAccept(this, data);
+ }
+ return null;
+ }
+
+ /**
+ * Visits all AST constituents of a JEXL script.
+ * @param jscript the expression
+ * @param data some data context
+ * @return the visit result or null if jscript was not a Script implementation
+ */
+ public Object visitScript(final JexlScript jscript, final Object data) {
+ if (jscript instanceof Script) {
+ return ((Script) jscript).getScript().jjtAccept(this, data);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a node.
+ * Default implementation visits all its children.
+ * @param node the node to visit
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ protected Object visitNode(final JexlNode node, final Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ protected Object visit(final ASTJexlScript node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTBlock node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTIfStatement node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTWhileStatement node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTDoWhileStatement node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTContinue node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTBreak node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTForeachStatement node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTReturnStatement node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTAssignment node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTVar node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTReference node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTTernaryNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNullpNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTOrNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTAndNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseOrNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseXorNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseAndNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTEQNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNENode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTLTNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTGTNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTLENode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTGENode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTERNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNRNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSWNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNSWNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTEWNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNEWNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTAddNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSubNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTMulNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTDivNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTModNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTUnaryMinusNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTUnaryPlusNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTBitwiseComplNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNotNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTIdentifier node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNullLiteral node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTTrueNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTFalseNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTNumberLiteral node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTStringLiteral node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTRegexLiteral node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetLiteral node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTExtendedLiteral node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTArrayLiteral node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTRangeNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTMapLiteral node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTMapEntry node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTEmptyFunction node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSizeFunction node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTFunctionNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTMethodNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTConstructorNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTArrayAccess node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTIdentifierAccess node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTArguments node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTReferenceExpression node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetAddNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetSubNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetMultNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetDivNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetModNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetAndNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetOrNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTSetXorNode node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTJxltLiteral node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTAnnotation node, final Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTAnnotatedStatement node, final Object data) {
+ return visitNode(node, data);
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/SetBuilder.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/SetBuilder.java
new file mode 100644
index 0000000..5dfa477
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/SetBuilder.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlArithmetic;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Helper class to create set literals.
+ */
+public class SetBuilder implements JexlArithmetic.SetBuilder {
+ /** The set being created. */
+ protected final Set set;
+
+ /**
+ * Creates a new builder.
+ * @param size the expected set size
+ */
+ public SetBuilder(final int size) {
+ set = new HashSet(size);
+ }
+
+ @Override
+ public void add(final Object value) {
+ set.add(value);
+ }
+
+ @Override
+ public Object create() {
+ return set;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/SoftCache.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/SoftCache.java
new file mode 100644
index 0000000..c754849
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/SoftCache.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * A soft referenced cache.
+ *
+ * The actual cache is held through a soft reference, allowing it to be GCed
+ * under memory pressure.
+ *
+ * @param the cache key entry type
+ * @param the cache key value type
+ */
+public class SoftCache {
+ /**
+ * The default cache load factor.
+ */
+ private static final float LOAD_FACTOR = 0.75f;
+ /**
+ * The cache size.
+ */
+ private final int size;
+ /**
+ * The soft reference to the cache map.
+ */
+ private SoftReference> ref = null;
+ /**
+ * The cache r/w lock.
+ */
+ private final ReadWriteLock lock;
+
+ /**
+ * Creates a new instance of a soft cache.
+ *
+ * @param theSize the cache size
+ */
+ SoftCache(final int theSize) {
+ size = theSize;
+ lock = new ReentrantReadWriteLock();
+ }
+
+ /**
+ * Returns the cache size.
+ *
+ * @return the cache size
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Clears the cache.
+ */
+ public void clear() {
+ lock.writeLock().lock();
+ try {
+ ref = null;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Gets a value from cache.
+ *
+ * @param key the cache entry key
+ * @return the cache entry value
+ */
+ public V get(final K key) {
+ lock.readLock().lock();
+ try {
+ final Map map = ref != null ? ref.get() : null;
+ return map != null ? map.get(key) : null;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ /**
+ * Puts a value in cache.
+ *
+ * @param key the cache entry key
+ * @param script the cache entry value
+ */
+ public void put(final K key, final V script) {
+ lock.writeLock().lock();
+ try {
+ Map map = ref != null ? ref.get() : null;
+ if (map == null) {
+ map = createCache(size);
+ ref = new SoftReference>(map);
+ }
+ map.put(key, script);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Produces the cache entry set.
+ *
+ * For testing only, perform deep copy of cache entries
+ *
+ * @return the cache entry list
+ */
+ public List> entries() {
+ lock.readLock().lock();
+ try {
+ final Map map = ref != null ? ref.get() : null;
+ if (map == null) {
+ return Collections.emptyList();
+ }
+ final Set> set = map.entrySet();
+ final List> entries = new ArrayList>(set.size());
+ for (final Map.Entry e : set) {
+ entries.add(new SoftCacheEntry(e));
+ }
+ return entries;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ /**
+ * Creates the cache store.
+ *
+ * @param the key type
+ * @param the value type
+ * @param cacheSize the cache size, must be > 0
+ * @return a Map usable as a cache bounded to the given size
+ */
+ public Map createCache(final int cacheSize) {
+ return new java.util.LinkedHashMap(cacheSize, LOAD_FACTOR, true) {
+ /**
+ * Serial version UID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected boolean removeEldestEntry(final Map.Entry eldest) {
+ return super.size() > cacheSize;
+ }
+ };
+ }
+}
+
+/**
+ * A soft cache entry.
+ *
+ * @param key type
+ * @param value type
+ */
+class SoftCacheEntry implements Map.Entry {
+ /**
+ * The entry key.
+ */
+ private final K key;
+ /**
+ * The entry value.
+ */
+ private final V value;
+
+ /**
+ * Creates an entry clone.
+ *
+ * @param e the entry to clone
+ */
+ SoftCacheEntry(final Map.Entry e) {
+ key = e.getKey();
+ value = e.getValue();
+ }
+
+ @Override
+ public K getKey() {
+ return key;
+ }
+
+ @Override
+ public V getValue() {
+ return value;
+ }
+
+ @Override
+ public V setValue(final V v) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+}
+
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Source.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Source.java
new file mode 100644
index 0000000..7e0091f
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/Source.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlFeatures;
+
+import java.util.Objects;
+
+/**
+ * Maintains the set of allowed features associated with a script/expression source.
+ * This is meant for caching scripts using their 'source' as key but still distinguishing
+ * scripts with different features and prevent false sharing.
+ */
+public final class Source {
+ /** The hash code, pre-computed for fast op. */
+ private final int hashCode;
+ /** The set of features. */
+ private final JexlFeatures features;
+ /** The actual source script/expression. */
+ private final String str;
+
+ /**
+ * Default constructor.
+ * @param theFeatures the features
+ * @param theStr the script source
+ */
+ Source(final JexlFeatures theFeatures, final String theStr) { // CSOFF: MagicNumber
+ this.features = theFeatures;
+ this.str = theStr;
+ int hash = 3;
+ hash = 37 * hash + features.hashCode();
+ hash = 37 * hash + str.hashCode() ;
+ this.hashCode = hash;
+ }
+
+ /**
+ * @return the length of the script source
+ */
+ int length() {
+ return str.length();
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Source other = (Source) obj;
+ if (!Objects.equals(this.features, other.features)) {
+ return false;
+ }
+ if (!Objects.equals(this.str, other.str)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return str;
+ }
+
+ /**
+ * @return the features associated with the source
+ */
+ public JexlFeatures getFeatures() {
+ return features;
+ }
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateDebugger.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateDebugger.java
new file mode 100644
index 0000000..a07eb5b
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateDebugger.java
@@ -0,0 +1,314 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JxltEngine;
+import aiyh.utils.tool.org.apache.commons.jexl3.internal.TemplateEngine.*;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.*;
+
+/**
+ * A visitor for templates.
+ *
A friend (ala C++) of template engine.
+ */
+public class TemplateDebugger extends Debugger {
+ /** The outer script. */
+ private ASTJexlScript script;
+ /** The expressions called by the script through jexl:print. */
+ private TemplateExpression[] exprs;
+
+ /**
+ * Default ctor.
+ */
+ public TemplateDebugger() {
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ // so we can use it more than one time
+ exprs = null;
+ script = null;
+ }
+
+ /**
+ * Position the debugger on the root of a template expression.
+ *
+ * @param je the expression
+ * @return true if the expression was a {@link TemplateExpression} instance, false otherwise
+ */
+ public boolean debug(final JxltEngine.Expression je) {
+ if (je instanceof TemplateExpression) {
+ final TemplateExpression te = (TemplateExpression) je;
+ return visit(te, this) != null;
+ }
+ return false;
+ }
+
+ /**
+ * Position the debugger on the root of a template script.
+ *
+ * @param jt the template
+ * @return true if the template was a {@link TemplateScript} instance, false otherwise
+ */
+ public boolean debug(final JxltEngine.Template jt) {
+ if (!(jt instanceof TemplateScript)) {
+ return false;
+ }
+ final TemplateScript ts = (TemplateScript) jt;
+ // ensure expr is not null for templates
+ this.exprs = ts.getExpressions() == null ? new TemplateExpression[0] : ts.getExpressions();
+ this.script = ts.getScript();
+ start = 0;
+ end = 0;
+ indentLevel = 0;
+ builder.setLength(0);
+ cause = script;
+ final int num = script.jjtGetNumChildren();
+ for (int i = 0; i < num; ++i) {
+ final JexlNode child = script.jjtGetChild(i);
+ acceptStatement(child, null);
+ }
+ // the last line
+ if (builder.length() > 0 && builder.charAt(builder.length() - 1) != '\n') {
+ builder.append('\n');
+ }
+ end = builder.length();
+ return end > 0;
+ }
+
+
+ @Override
+ protected Object visit(final ASTBlock node, final Object data) {
+ // if not really a template, must use super impl
+ if (exprs == null) {
+ return super.visit(node, data);
+ }
+ // open the block
+ builder.append('{');
+ if (indent > 0) {
+ indentLevel += 1;
+ builder.append('\n');
+ } else {
+ builder.append(' ');
+ }
+ final int num = node.jjtGetNumChildren();
+ for (int i = 0; i < num; ++i) {
+ final JexlNode child = node.jjtGetChild(i);
+ acceptStatement(child, data);
+ }
+ // before we close this block node, $$ might be needed
+ newJexlLine();
+ if (indent > 0) {
+ indentLevel -= 1;
+ for (int i = 0; i < indentLevel; ++i) {
+ for (int s = 0; s < indent; ++s) {
+ builder.append(' ');
+ }
+ }
+ }
+ builder.append('}');
+ // closed the block
+ return data;
+ }
+
+ @Override
+ protected Object acceptStatement(final JexlNode child, final Object data) {
+ // if not really a template, must use super impl
+ if (exprs == null) {
+ return super.acceptStatement(child, data);
+ }
+ final TemplateExpression te = getPrintStatement(child);
+ if (te != null) {
+ // if statement is a jexl:print(...), may need to prepend '\n'
+ newJxltLine();
+ return visit(te, data);
+ }
+ // if statement is not a jexl:print(...), need to prepend '$$'
+ newJexlLine();
+ return super.acceptStatement(child, data);
+ }
+
+ /**
+ * In a template, any statement that is not 'jexl:print(n)' must be prefixed by "$$".
+ *
+ * @param child the node to check
+ * @return the expression number or -1 if the node is not a jexl:print
+ */
+ private TemplateExpression getPrintStatement(final JexlNode child) {
+ if (exprs != null && child instanceof ASTFunctionNode) {
+ final ASTFunctionNode node = (ASTFunctionNode) child;
+ final ASTIdentifier ns = (ASTIdentifier) node.jjtGetChild(0);
+ final JexlNode args = node.jjtGetChild(1);
+ if ("jexl".equals(ns.getNamespace())
+ && "print".equals(ns.getName())
+ && args.jjtGetNumChildren() == 1
+ && args.jjtGetChild(0) instanceof ASTNumberLiteral) {
+ final ASTNumberLiteral exprn = (ASTNumberLiteral) args.jjtGetChild(0);
+ final int n = exprn.getLiteral().intValue();
+ if (n >= 0 && n < exprs.length) {
+ return exprs[n];
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Insert $$ and \n when needed.
+ */
+ private void newJexlLine() {
+ final int length = builder.length();
+ if (length == 0) {
+ builder.append("$$ ");
+ } else {
+ for (int i = length - 1; i >= 0; --i) {
+ final char c = builder.charAt(i);
+ switch (c) {
+ case '\n':
+ builder.append("$$ ");
+ return;
+ case '}':
+ builder.append("\n$$ ");
+ return;
+ case ' ':
+ case ';':
+ return;
+ default: // continue
+ }
+ }
+ }
+ }
+
+ /**
+ * Insert \n when needed.
+ */
+ private void newJxltLine() {
+ final int length = builder.length();
+ for (int i = length - 1; i >= 0; --i) {
+ final char c = builder.charAt(i);
+ switch (c) {
+ case '\n':
+ case ';':
+ return;
+ case '}':
+ builder.append('\n');
+ return;
+ default: // continue
+ }
+ }
+ }
+
+ /**
+ * Visit a template expression.
+ *
+ * @param expr the constant expression
+ * @param data the visitor argument
+ * @return the visitor argument
+ */
+ private Object visit(final TemplateExpression expr, final Object data) {
+ Object r;
+ switch (expr.getType()) {
+ case CONSTANT:
+ r = visit((ConstantExpression) expr, data);
+ break;
+ case IMMEDIATE:
+ r = visit((ImmediateExpression) expr, data);
+ break;
+ case DEFERRED:
+ r = visit((DeferredExpression) expr, data);
+ break;
+ case NESTED:
+ r = visit((NestedExpression) expr, data);
+ break;
+ case COMPOSITE:
+ r = visit((CompositeExpression) expr, data);
+ break;
+ default:
+ r = null;
+ }
+ return r;
+ }
+
+ /**
+ * Visit a constant expression.
+ *
+ * @param expr the constant expression
+ * @param data the visitor argument
+ * @return the visitor argument
+ */
+ private Object visit(final ConstantExpression expr, final Object data) {
+ expr.asString(builder);
+ return data;
+ }
+
+ /**
+ * Visit an immediate expression.
+ *
+ * @param expr the immediate expression
+ * @param data the visitor argument
+ * @return the visitor argument
+ */
+ private Object visit(final ImmediateExpression expr, final Object data) {
+ builder.append(expr.isImmediate() ? '$' : '#');
+ builder.append('{');
+ super.accept(expr.node, data);
+ builder.append('}');
+ return data;
+ }
+
+ /**
+ * Visit a deferred expression.
+ *
+ * @param expr the deferred expression
+ * @param data the visitor argument
+ * @return the visitor argument
+ */
+ private Object visit(final DeferredExpression expr, final Object data) {
+ builder.append(expr.isImmediate() ? '$' : '#');
+ builder.append('{');
+ super.accept(expr.node, data);
+ builder.append('}');
+ return data;
+ }
+
+ /**
+ * Visit a nested expression.
+ *
+ * @param expr the nested expression
+ * @param data the visitor argument
+ * @return the visitor argument
+ */
+ private Object visit(final NestedExpression expr, final Object data) {
+ super.accept(expr.node, data);
+ return data;
+ }
+
+ /**
+ * Visit a composite expression.
+ *
+ * @param expr the composite expression
+ * @param data the visitor argument
+ * @return the visitor argument
+ */
+ private Object visit(final CompositeExpression expr, final Object data) {
+ for (final TemplateExpression ce : expr.exprs) {
+ visit(ce, data);
+ }
+ return data;
+ }
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateEngine.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateEngine.java
new file mode 100644
index 0000000..4fb869b
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateEngine.java
@@ -0,0 +1,1232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.*;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTJexlScript;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.JexlNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.StringParser;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.*;
+
+/**
+ * A JxltEngine implementation.
+ *
+ * @since 3.0
+ */
+public final class TemplateEngine extends JxltEngine {
+ /** The TemplateExpression cache. */
+ private final SoftCache cache;
+ /** The JEXL engine instance. */
+ private final Engine jexl;
+ /** The first character for immediate expressions. */
+ private final char immediateChar;
+ /** The first character for deferred expressions. */
+ private final char deferredChar;
+ /** Whether expressions can use JEXL script or only expressions (ie, no for, var, etc). */
+ private boolean noscript = true;
+
+ /**
+ * Creates a new instance of {@link JxltEngine} creating a local cache.
+ *
+ * @param aJexl the JexlEngine to use.
+ * @param noScript whether this engine only allows JEXL expressions or scripts
+ * @param cacheSize the number of expressions in this cache, default is 256
+ * @param immediate the immediate template expression character, default is '$'
+ * @param deferred the deferred template expression character, default is '#'
+ */
+ public TemplateEngine(final Engine aJexl,
+ final boolean noScript,
+ final int cacheSize,
+ final char immediate,
+ final char deferred) {
+ this.jexl = aJexl;
+ this.cache = new SoftCache<>(cacheSize);
+ immediateChar = immediate;
+ deferredChar = deferred;
+ noscript = noScript;
+ }
+
+ /**
+ * @return the immediate character
+ */
+ char getImmediateChar() {
+ return immediateChar;
+ }
+
+ /**
+ * @return the deferred character
+ */
+ char getDeferredChar() {
+ return deferredChar;
+ }
+
+ /**
+ * Types of expressions.
+ * Each instance carries a counter index per (composite sub-) template expression type.
+ *
+ * @see ExpressionBuilder
+ */
+ enum ExpressionType {
+ /** Constant TemplateExpression, count index 0. */
+ CONSTANT(0),
+ /** Immediate TemplateExpression, count index 1. */
+ IMMEDIATE(1),
+ /** Deferred TemplateExpression, count index 2. */
+ DEFERRED(2),
+ /**
+ * Nested (which are deferred) expressions, count
+ * index 2.
+ */
+ NESTED(2),
+ /** Composite expressions are not counted, index -1. */
+ COMPOSITE(-1);
+ /** The index in arrays of TemplateExpression counters for composite expressions. */
+ private final int index;
+
+ /**
+ * Creates an ExpressionType.
+ *
+ * @param idx the index for this type in counters arrays.
+ */
+ ExpressionType(final int idx) {
+ this.index = idx;
+ }
+ }
+
+ /**
+ * A helper class to build expressions.
+ * Keeps count of sub-expressions by type.
+ */
+ static final class ExpressionBuilder {
+ /** Per TemplateExpression type counters. */
+ private final int[] counts;
+ /** The list of expressions. */
+ private final ArrayList expressions;
+
+ /**
+ * Creates a builder.
+ *
+ * @param size the initial TemplateExpression array size
+ */
+ private ExpressionBuilder(final int size) {
+ counts = new int[]{0, 0, 0};
+ expressions = new ArrayList<>(size <= 0 ? 3 : size);
+ }
+
+ /**
+ * Adds an TemplateExpression to the list of expressions, maintain per-type counts.
+ *
+ * @param expr the TemplateExpression to add
+ */
+ private void add(final TemplateExpression expr) {
+ counts[expr.getType().index] += 1;
+ expressions.add(expr);
+ }
+
+ @Override
+ public String toString() {
+ return toString(new StringBuilder()).toString();
+ }
+
+ /**
+ * Base for to-string.
+ *
+ * @param error the builder to fill
+ * @return the builder
+ */
+ private StringBuilder toString(final StringBuilder error) {
+ error.append("exprs{");
+ error.append(expressions.size());
+ error.append(", constant:");
+ error.append(counts[ExpressionType.CONSTANT.index]);
+ error.append(", immediate:");
+ error.append(counts[ExpressionType.IMMEDIATE.index]);
+ error.append(", deferred:");
+ error.append(counts[ExpressionType.DEFERRED.index]);
+ error.append("}");
+ return error;
+ }
+
+ /**
+ * Builds an TemplateExpression from a source, performs checks.
+ *
+ * @param el the unified el instance
+ * @param source the source TemplateExpression
+ * @return an TemplateExpression
+ */
+ private TemplateExpression build(final TemplateEngine el, final TemplateExpression source) {
+ int sum = 0;
+ for (final int count : counts) {
+ sum += count;
+ }
+ if (expressions.size() != sum) {
+ final StringBuilder error = new StringBuilder("parsing algorithm error: ");
+ throw new IllegalStateException(toString(error).toString());
+ }
+ // if only one sub-expr, no need to create a composite
+ if (expressions.size() == 1) {
+ return expressions.get(0);
+ }
+ return el.new CompositeExpression(counts, expressions, source);
+ }
+ }
+
+ /**
+ * Gets the JexlEngine underlying this JxltEngine.
+ *
+ * @return the JexlEngine
+ */
+ @Override
+ public Engine getEngine() {
+ return jexl;
+ }
+
+ /**
+ * Clears the cache.
+ */
+ @Override
+ public void clearCache() {
+ synchronized (cache) {
+ cache.clear();
+ }
+ }
+
+ /**
+ * The abstract base class for all unified expressions, immediate '${...}' and deferred '#{...}'.
+ */
+ abstract class TemplateExpression implements Expression {
+ /** The source of this template expression(see {@link TemplateExpression#prepare}). */
+ protected final TemplateExpression source;
+
+ /**
+ * Creates an TemplateExpression.
+ *
+ * @param src the source TemplateExpression if any
+ */
+ TemplateExpression(final TemplateExpression src) {
+ this.source = src != null ? src : this;
+ }
+
+ @Override
+ public boolean isImmediate() {
+ return true;
+ }
+
+ @Override
+ public final boolean isDeferred() {
+ return !isImmediate();
+ }
+
+ /**
+ * Gets this TemplateExpression type.
+ *
+ * @return its type
+ */
+ abstract ExpressionType getType();
+
+ /** @return the info */
+ JexlInfo getInfo() {
+ return null;
+ }
+
+ @Override
+ public final String toString() {
+ final StringBuilder strb = new StringBuilder();
+ asString(strb);
+ if (source != this) {
+ strb.append(" /*= ");
+ strb.append(source);
+ strb.append(" */");
+ }
+ return strb.toString();
+ }
+
+ @Override
+ public String asString() {
+ final StringBuilder strb = new StringBuilder();
+ asString(strb);
+ return strb.toString();
+ }
+
+ @Override
+ public Set> getVariables() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final TemplateExpression getSource() {
+ return source;
+ }
+
+ /**
+ * Fills up the list of variables accessed by this unified expression.
+ *
+ * @param collector the variable collector
+ */
+ protected void getVariables(final Engine.VarCollector collector) {
+ // nothing to do
+ }
+
+ @Override
+ public final TemplateExpression prepare(final JexlContext context) {
+ return prepare(null, context);
+ }
+
+ /**
+ * Prepares this expression.
+ *
+ * @param frame the frame storing parameters and local variables
+ * @param context the context storing global variables
+ * @return the expression value
+ * @throws JexlException
+ */
+ protected final TemplateExpression prepare(final Frame frame, final JexlContext context) {
+ try {
+ final Interpreter interpreter = jexl.createInterpreter(context, frame, jexl.options(context));
+ return prepare(interpreter);
+ } catch (final JexlException xjexl) {
+ final JexlException xuel = createException(xjexl.getInfo(), "prepare", this, xjexl);
+ if (jexl.isSilent()) {
+ jexl.logger.warn(xuel.getMessage(), xuel.getCause());
+ return null;
+ }
+ throw xuel;
+ }
+ }
+
+ /**
+ * Prepares a sub-expression for interpretation.
+ *
+ * @param interpreter a JEXL interpreter
+ * @return a prepared unified expression
+ * @throws JexlException (only for nested and composite)
+ */
+ protected TemplateExpression prepare(final Interpreter interpreter) {
+ return this;
+ }
+
+ @Override
+ public final Object evaluate(final JexlContext context) {
+ return evaluate(null, context);
+ }
+
+ /**
+ * The options to use during evaluation.
+ *
+ * @param context the context
+ * @return the options
+ */
+ protected JexlOptions options(final JexlContext context) {
+ return jexl.options(null, context);
+ }
+
+ /**
+ * Evaluates this expression.
+ *
+ * @param frame the frame storing parameters and local variables
+ * @param context the context storing global variables
+ * @return the expression value
+ * @throws JexlException
+ */
+ protected final Object evaluate(final Frame frame, final JexlContext context) {
+ try {
+ final JexlOptions options = options(context);
+ final TemplateInterpreter.Arguments args = new TemplateInterpreter
+ .Arguments(jexl)
+ .context(context)
+ .options(options)
+ .frame(frame);
+ final Interpreter interpreter = new TemplateInterpreter(args);
+ return evaluate(interpreter);
+ } catch (final JexlException xjexl) {
+ final JexlException xuel = createException(xjexl.getInfo(), "evaluate", this, xjexl);
+ if (jexl.isSilent()) {
+ jexl.logger.warn(xuel.getMessage(), xuel.getCause());
+ return null;
+ }
+ throw xuel;
+ }
+ }
+
+ /**
+ * Interprets a sub-expression.
+ *
+ * @param interpreter a JEXL interpreter
+ * @return the result of interpretation
+ * @throws JexlException (only for nested and composite)
+ */
+ protected abstract Object evaluate(Interpreter interpreter);
+
+ }
+
+ /** A constant unified expression. */
+ class ConstantExpression extends TemplateExpression {
+ /** The constant held by this unified expression. */
+ private final Object value;
+
+ /**
+ * Creates a constant unified expression.
+ *
+ * If the wrapped constant is a string, it is treated
+ * as a JEXL strings with respect to escaping.
+ *
+ *
+ * @param val the constant value
+ * @param source the source TemplateExpression if any
+ */
+ ConstantExpression(Object val, final TemplateExpression source) {
+ super(source);
+ if (val == null) {
+ throw new NullPointerException("constant can not be null");
+ }
+ if (val instanceof String) {
+ val = StringParser.buildTemplate((String) val, false);
+ }
+ this.value = val;
+ }
+
+ @Override
+ ExpressionType getType() {
+ return ExpressionType.CONSTANT;
+ }
+
+ @Override
+ public StringBuilder asString(final StringBuilder strb) {
+ if (value != null) {
+ strb.append(value);
+ }
+ return strb;
+ }
+
+ @Override
+ protected Object evaluate(final Interpreter interpreter) {
+ return value;
+ }
+ }
+
+ /** The base for JEXL based unified expressions. */
+ abstract class JexlBasedExpression extends TemplateExpression {
+ /** The JEXL string for this unified expression. */
+ protected final CharSequence expr;
+ /** The JEXL node for this unified expression. */
+ protected final JexlNode node;
+
+ /**
+ * Creates a JEXL interpretable unified expression.
+ *
+ * @param theExpr the unified expression as a string
+ * @param theNode the unified expression as an AST
+ * @param theSource the source unified expression if any
+ */
+ protected JexlBasedExpression(final CharSequence theExpr, final JexlNode theNode, final TemplateExpression theSource) {
+ super(theSource);
+ this.expr = theExpr;
+ this.node = theNode;
+ }
+
+ @Override
+ public StringBuilder asString(final StringBuilder strb) {
+ strb.append(isImmediate() ? immediateChar : deferredChar);
+ strb.append("{");
+ strb.append(expr);
+ strb.append("}");
+ return strb;
+ }
+
+ @Override
+ protected JexlOptions options(final JexlContext context) {
+ return jexl.options(node instanceof ASTJexlScript ? (ASTJexlScript) node : null, context);
+ }
+
+ @Override
+ protected Object evaluate(final Interpreter interpreter) {
+ return interpreter.interpret(node);
+ }
+
+ @Override
+ public Set> getVariables() {
+ final Engine.VarCollector collector = jexl.varCollector();
+ getVariables(collector);
+ return collector.collected();
+ }
+
+ @Override
+ protected void getVariables(final Engine.VarCollector collector) {
+ jexl.getVariables(node instanceof ASTJexlScript ? (ASTJexlScript) node : null, node, collector);
+ }
+
+ @Override
+ JexlInfo getInfo() {
+ return node.jexlInfo();
+ }
+ }
+
+ /** An immediate unified expression: ${jexl}. */
+ class ImmediateExpression extends JexlBasedExpression {
+ /**
+ * Creates an immediate unified expression.
+ *
+ * @param expr the unified expression as a string
+ * @param node the unified expression as an AST
+ * @param source the source unified expression if any
+ */
+ ImmediateExpression(final CharSequence expr, final JexlNode node, final TemplateExpression source) {
+ super(expr, node, source);
+ }
+
+ @Override
+ ExpressionType getType() {
+ return ExpressionType.IMMEDIATE;
+ }
+
+ @Override
+ protected TemplateExpression prepare(final Interpreter interpreter) {
+ // evaluate immediate as constant
+ final Object value = evaluate(interpreter);
+ return value != null ? new ConstantExpression(value, source) : null;
+ }
+ }
+
+ /** A deferred unified expression: #{jexl}. */
+ class DeferredExpression extends JexlBasedExpression {
+ /**
+ * Creates a deferred unified expression.
+ *
+ * @param expr the unified expression as a string
+ * @param node the unified expression as an AST
+ * @param source the source unified expression if any
+ */
+ DeferredExpression(final CharSequence expr, final JexlNode node, final TemplateExpression source) {
+ super(expr, node, source);
+ }
+
+ @Override
+ public boolean isImmediate() {
+ return false;
+ }
+
+ @Override
+ ExpressionType getType() {
+ return ExpressionType.DEFERRED;
+ }
+
+ @Override
+ protected TemplateExpression prepare(final Interpreter interpreter) {
+ return new ImmediateExpression(expr, node, source);
+ }
+
+ @Override
+ protected void getVariables(final Engine.VarCollector collector) {
+ // noop
+ }
+ }
+
+ /**
+ * An immediate unified expression nested into a deferred unified expression.
+ * #{...${jexl}...}
+ * Note that the deferred syntax is JEXL's.
+ */
+ class NestedExpression extends JexlBasedExpression {
+ /**
+ * Creates a nested unified expression.
+ *
+ * @param expr the unified expression as a string
+ * @param node the unified expression as an AST
+ * @param source the source unified expression if any
+ */
+ NestedExpression(final CharSequence expr, final JexlNode node, final TemplateExpression source) {
+ super(expr, node, source);
+ if (this.source != this) {
+ throw new IllegalArgumentException("Nested TemplateExpression can not have a source");
+ }
+ }
+
+ @Override
+ public StringBuilder asString(final StringBuilder strb) {
+ strb.append(expr);
+ return strb;
+ }
+
+ @Override
+ public boolean isImmediate() {
+ return false;
+ }
+
+ @Override
+ ExpressionType getType() {
+ return ExpressionType.NESTED;
+ }
+
+ @Override
+ protected TemplateExpression prepare(final Interpreter interpreter) {
+ final String value = interpreter.interpret(node).toString();
+ final JexlNode dnode = jexl.parse(node.jexlInfo(), noscript, value, null);
+ return new ImmediateExpression(value, dnode, this);
+ }
+
+ @Override
+ protected Object evaluate(final Interpreter interpreter) {
+ return prepare(interpreter).evaluate(interpreter);
+ }
+ }
+
+ /** A composite unified expression: "... ${...} ... #{...} ...". */
+ class CompositeExpression extends TemplateExpression {
+ /** Bit encoded (deferred count > 0) bit 1, (immediate count > 0) bit 0. */
+ private final int meta;
+ /** The list of sub-expression resulting from parsing. */
+ protected final TemplateExpression[] exprs;
+
+ /**
+ * Creates a composite expression.
+ *
+ * @param counters counters of expressions per type
+ * @param list the sub-expressions
+ * @param src the source for this expression if any
+ */
+ CompositeExpression(final int[] counters, final ArrayList list, final TemplateExpression src) {
+ super(src);
+ this.exprs = list.toArray(new TemplateExpression[list.size()]);
+ this.meta = (counters[ExpressionType.DEFERRED.index] > 0 ? 2 : 0)
+ | (counters[ExpressionType.IMMEDIATE.index] > 0 ? 1 : 0);
+ }
+
+ @Override
+ public boolean isImmediate() {
+ // immediate if no deferred
+ return (meta & 2) == 0;
+ }
+
+ @Override
+ ExpressionType getType() {
+ return ExpressionType.COMPOSITE;
+ }
+
+ @Override
+ public StringBuilder asString(final StringBuilder strb) {
+ for (final TemplateExpression e : exprs) {
+ e.asString(strb);
+ }
+ return strb;
+ }
+
+ @Override
+ public Set> getVariables() {
+ final Engine.VarCollector collector = jexl.varCollector();
+ for (final TemplateExpression expr : exprs) {
+ expr.getVariables(collector);
+ }
+ return collector.collected();
+ }
+
+ /**
+ * Fills up the list of variables accessed by this unified expression.
+ *
+ * @param collector the variable collector
+ */
+ @Override
+ protected void getVariables(final Engine.VarCollector collector) {
+ for (final TemplateExpression expr : exprs) {
+ expr.getVariables(collector);
+ }
+ }
+
+ @Override
+ protected TemplateExpression prepare(final Interpreter interpreter) {
+ // if this composite is not its own source, it is already prepared
+ if (source != this) {
+ return this;
+ }
+ // we need to prepare all sub-expressions
+ final int size = exprs.length;
+ final ExpressionBuilder builder = new ExpressionBuilder(size);
+ // tracking whether prepare will return a different expression
+ boolean eq = true;
+ for (final TemplateExpression expr : exprs) {
+ final TemplateExpression prepared = expr.prepare(interpreter);
+ // add it if not null
+ if (prepared != null) {
+ builder.add(prepared);
+ }
+ // keep track of TemplateExpression equivalence
+ eq &= expr == prepared;
+ }
+ return eq ? this : builder.build(TemplateEngine.this, this);
+ }
+
+ @Override
+ protected Object evaluate(final Interpreter interpreter) {
+ Object value;
+ // common case: evaluate all expressions & concatenate them as a string
+ final StringBuilder strb = new StringBuilder();
+ for (final TemplateExpression expr : exprs) {
+ value = expr.evaluate(interpreter);
+ if (value != null) {
+ strb.append(value);
+ }
+ }
+ value = strb.toString();
+ return value;
+ }
+ }
+
+
+ @Override
+ public Expression createExpression(JexlInfo info, final String expression) {
+ if (info == null) {
+ info = jexl.createInfo();
+ }
+ Exception xuel = null;
+ TemplateExpression stmt = null;
+ try {
+ stmt = cache.get(expression);
+ if (stmt == null) {
+ stmt = parseExpression(info, expression, null);
+ cache.put(expression, stmt);
+ }
+ } catch (final JexlException xjexl) {
+ xuel = new Exception(xjexl.getInfo(), "failed to parse '" + expression + "'", xjexl);
+ }
+ if (xuel != null) {
+ if (!jexl.isSilent()) {
+ throw xuel;
+ }
+ jexl.logger.warn(xuel.getMessage(), xuel.getCause());
+ stmt = null;
+ }
+ return stmt;
+ }
+
+ /**
+ * Creates a JxltEngine.Exception from a JexlException.
+ *
+ * @param info the source info
+ * @param action createExpression, prepare, evaluate
+ * @param expr the template expression
+ * @param xany the exception
+ * @return an exception containing an explicit error message
+ */
+ static Exception createException(final JexlInfo info,
+ final String action,
+ final TemplateExpression expr,
+ final java.lang.Exception xany) {
+ final StringBuilder strb = new StringBuilder("failed to ");
+ strb.append(action);
+ if (expr != null) {
+ strb.append(" '");
+ strb.append(expr);
+ strb.append("'");
+ }
+ final Throwable cause = xany.getCause();
+ if (cause != null) {
+ final String causeMsg = cause.getMessage();
+ if (causeMsg != null) {
+ strb.append(", ");
+ strb.append(causeMsg);
+ }
+ }
+ return new Exception(info, strb.toString(), xany);
+ }
+
+ /** The different parsing states. */
+ private enum ParseState {
+ /** Parsing a constant. */
+ CONST,
+ /** Parsing after $ . */
+ IMMEDIATE0,
+ /** Parsing after # . */
+ DEFERRED0,
+ /** Parsing after ${ . */
+ IMMEDIATE1,
+ /** Parsing after #{ . */
+ DEFERRED1,
+ /** Parsing after \ . */
+ ESCAPE
+ }
+
+ /**
+ * Helper for expression dealing with embedded strings.
+ *
+ * @param strb the expression buffer to copy characters into
+ * @param expr the source
+ * @param position the offset into the source
+ * @param c the separator character
+ * @return the new position to read the source from
+ */
+ private static int append(final StringBuilder strb, final CharSequence expr, final int position, final char c) {
+ strb.append(c);
+ if (c != '"' && c != '\'') {
+ return position;
+ }
+ // read thru strings
+ final int end = expr.length();
+ boolean escape = false;
+ int index = position + 1;
+ for (; index < end; ++index) {
+ final char ec = expr.charAt(index);
+ strb.append(ec);
+ if (ec == '\\') {
+ escape = !escape;
+ } else if (escape) {
+ escape = false;
+ } else if (ec == c) {
+ break;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Parses a unified expression.
+ *
+ * @param info the source info
+ * @param expr the string expression
+ * @param scope the template scope
+ * @return the unified expression instance
+ * @throws JexlException if an error occur during parsing
+ */
+ TemplateExpression parseExpression(final JexlInfo info, final String expr, final Scope scope) { // CSOFF: MethodLength
+ final int size = expr.length();
+ final ExpressionBuilder builder = new ExpressionBuilder(0);
+ final StringBuilder strb = new StringBuilder(size);
+ ParseState state = ParseState.CONST;
+ int immediate1 = 0;
+ int deferred1 = 0;
+ int inner1 = 0;
+ boolean nested = false;
+ int inested = -1;
+ int lineno = info.getLine();
+ for (int column = 0; column < size; ++column) {
+ final char c = expr.charAt(column);
+ switch (state) {
+ default: // in case we ever add new unified expression type
+ throw new UnsupportedOperationException("unexpected unified expression type");
+ case CONST:
+ if (c == immediateChar) {
+ state = ParseState.IMMEDIATE0;
+ } else if (c == deferredChar) {
+ inested = column;
+ state = ParseState.DEFERRED0;
+ } else if (c == '\\') {
+ state = ParseState.ESCAPE;
+ } else {
+ // do buildup expr
+ strb.append(c);
+ }
+ break;
+ case IMMEDIATE0: // $
+ if (c == '{') {
+ state = ParseState.IMMEDIATE1;
+ // if chars in buffer, create constant
+ if (strb.length() > 0) {
+ final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
+ builder.add(cexpr);
+ strb.delete(0, Integer.MAX_VALUE);
+ }
+ } else {
+ // revert to CONST
+ strb.append(immediateChar);
+ strb.append(c);
+ state = ParseState.CONST;
+ }
+ break;
+ case DEFERRED0: // #
+ if (c == '{') {
+ state = ParseState.DEFERRED1;
+ // if chars in buffer, create constant
+ if (strb.length() > 0) {
+ final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
+ builder.add(cexpr);
+ strb.delete(0, Integer.MAX_VALUE);
+ }
+ } else {
+ // revert to CONST
+ strb.append(deferredChar);
+ strb.append(c);
+ state = ParseState.CONST;
+ }
+ break;
+ case IMMEDIATE1: // ${...
+ if (c == '}') {
+ if (immediate1 > 0) {
+ immediate1 -= 1;
+ strb.append(c);
+ } else {
+ // materialize the immediate expr
+ final String src = strb.toString();
+ final TemplateExpression iexpr = new ImmediateExpression(
+ src,
+ jexl.parse(info.at(lineno, column), noscript, src, scope),
+ null);
+ builder.add(iexpr);
+ strb.delete(0, Integer.MAX_VALUE);
+ state = ParseState.CONST;
+ }
+ } else {
+ if (c == '{') {
+ immediate1 += 1;
+ }
+ // do buildup expr
+ column = append(strb, expr, column, c);
+ }
+ break;
+ case DEFERRED1: // #{...
+ // skip inner strings (for '}')
+ if (c == '"' || c == '\'') {
+ strb.append(c);
+ column = StringParser.readString(strb, expr, column + 1, c);
+ continue;
+ }
+ // nested immediate in deferred; need to balance count of '{' & '}'
+ if (c == '{') {
+ if (expr.charAt(column - 1) == immediateChar) {
+ inner1 += 1;
+ strb.deleteCharAt(strb.length() - 1);
+ nested = true;
+ } else {
+ deferred1 += 1;
+ strb.append(c);
+ }
+ continue;
+ }
+ // closing '}'
+ if (c == '}') {
+ // balance nested immediate
+ if (deferred1 > 0) {
+ deferred1 -= 1;
+ strb.append(c);
+ } else if (inner1 > 0) {
+ inner1 -= 1;
+ } else {
+ // materialize the nested/deferred expr
+ final String src = strb.toString();
+ TemplateExpression dexpr;
+ if (nested) {
+ dexpr = new NestedExpression(
+ expr.substring(inested, column + 1),
+ jexl.parse(info.at(lineno, column), noscript, src, scope),
+ null);
+ } else {
+ dexpr = new DeferredExpression(
+ strb.toString(),
+ jexl.parse(info.at(lineno, column), noscript, src, scope),
+ null);
+ }
+ builder.add(dexpr);
+ strb.delete(0, Integer.MAX_VALUE);
+ nested = false;
+ state = ParseState.CONST;
+ }
+ } else {
+ // do buildup expr
+ column = append(strb, expr, column, c);
+ }
+ break;
+ case ESCAPE:
+ if (c == deferredChar) {
+ strb.append(deferredChar);
+ } else if (c == immediateChar) {
+ strb.append(immediateChar);
+ } else {
+ strb.append('\\');
+ strb.append(c);
+ }
+ state = ParseState.CONST;
+ }
+ if (c == '\n') {
+ lineno += 1;
+ }
+ }
+ // we should be in that state
+ if (state != ParseState.CONST) {
+ // otherwise, we ended a line with a \, $ or #
+ switch (state) {
+ case ESCAPE:
+ strb.append('\\');
+ strb.append('\\');
+ break;
+ case DEFERRED0:
+ strb.append(deferredChar);
+ break;
+ case IMMEDIATE0:
+ strb.append(immediateChar);
+ break;
+ default:
+ throw new Exception(info.at(lineno, 0), "malformed expression: " + expr, null);
+ }
+ }
+ // if any chars were buffered, add them as a constant
+ if (strb.length() > 0) {
+ final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
+ builder.add(cexpr);
+ }
+ return builder.build(this, null);
+ }
+
+ /**
+ * The enum capturing the difference between verbatim and code source fragments.
+ */
+ enum BlockType {
+ /** Block is to be output "as is" but may be a unified expression. */
+ VERBATIM,
+ /** Block is a directive, ie a fragment of JEXL code. */
+ DIRECTIVE
+ }
+
+ /**
+ * Abstract the source fragments, verbatim or immediate typed text blocks.
+ */
+ static final class Block {
+ /** The type of block, verbatim or directive. */
+ private final BlockType type;
+ /** The block start line info. */
+ private final int line;
+ /** The actual content. */
+ private final String body;
+
+ /**
+ * Creates a new block.
+ *
+ * @param theType the block type
+ * @param theLine the line number
+ * @param theBlock the content
+ */
+ Block(final BlockType theType, final int theLine, final String theBlock) {
+ type = theType;
+ line = theLine;
+ body = theBlock;
+ }
+
+ /**
+ * @return type
+ */
+ BlockType getType() {
+ return type;
+ }
+
+ /**
+ * @return line
+ */
+ int getLine() {
+ return line;
+ }
+
+ /**
+ * @return body
+ */
+ String getBody() {
+ return body;
+ }
+
+ @Override
+ public String toString() {
+ if (BlockType.VERBATIM.equals(type)) {
+ return body;
+ }
+ // CHECKSTYLE:OFF
+ final StringBuilder strb = new StringBuilder(64); // CSOFF: MagicNumber
+ // CHECKSTYLE:ON
+ final Iterator lines = readLines(new StringReader(body));
+ while (lines.hasNext()) {
+ strb.append("$$").append(lines.next());
+ }
+ return strb.toString();
+ }
+
+ /**
+ * Appends this block string representation to a builder.
+ *
+ * @param strb the string builder to append to
+ * @param prefix the line prefix (immediate or deferred)
+ */
+ void toString(final StringBuilder strb, final String prefix) {
+ if (BlockType.VERBATIM.equals(type)) {
+ strb.append(body);
+ } else {
+ final Iterator lines = readLines(new StringReader(body));
+ while (lines.hasNext()) {
+ strb.append(prefix).append(lines.next());
+ }
+ }
+ }
+ }
+
+ /**
+ * Whether a sequence starts with a given set of characters (following spaces).
+ * Space characters at beginning of line before the pattern are discarded.
+ *
+ * @param sequence the sequence
+ * @param pattern the pattern to match at start of sequence
+ * @return the first position after end of pattern if it matches, -1 otherwise
+ */
+ private int startsWith(CharSequence sequence, final CharSequence pattern) {
+ final int length = sequence.length();
+ int s = 0;
+ while (s < length && Character.isSpaceChar(sequence.charAt(s))) {
+ s += 1;
+ }
+ if (s < length && pattern.length() <= (length - s)) {
+ sequence = sequence.subSequence(s, length);
+ if (sequence.subSequence(0, pattern.length()).equals(pattern)) {
+ return s + pattern.length();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Read lines from a (buffered / mark-able) reader keeping all new-lines and line-feeds.
+ *
+ * @param reader the reader
+ * @return the line iterator
+ */
+ private static Iterator readLines(final Reader reader) {
+ if (!reader.markSupported()) {
+ throw new IllegalArgumentException("mark support in reader required");
+ }
+ return new Iterator() {
+ private CharSequence next = doNext();
+
+ private CharSequence doNext() {
+ final StringBuffer strb = new StringBuffer(64); // CSOFF: MagicNumber
+ int c;
+ boolean eol = false;
+ try {
+ while ((c = reader.read()) >= 0) {
+ if (eol) {// && (c != '\n' && c != '\r')) {
+ reader.reset();
+ break;
+ }
+ if (c == '\n') {
+ eol = true;
+ }
+ strb.append((char) c);
+ reader.mark(1);
+ }
+ } catch (final IOException xio) {
+ return null;
+ }
+ return strb.length() > 0 ? strb : null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ @Override
+ public CharSequence next() {
+ final CharSequence current = next;
+ if (current != null) {
+ next = doNext();
+ }
+ return current;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+ };
+ }
+
+ /**
+ * Reads lines of a template grouping them by typed blocks.
+ *
+ * @param prefix the directive prefix
+ * @param source the source reader
+ * @return the list of blocks
+ */
+ List readTemplate(final String prefix, final Reader source) {
+ final ArrayList blocks = new ArrayList();
+ final BufferedReader reader;
+ if (source instanceof BufferedReader) {
+ reader = (BufferedReader) source;
+ } else {
+ reader = new BufferedReader(source);
+ }
+ final StringBuilder strb = new StringBuilder();
+ BlockType type = null;
+ int prefixLen;
+ final Iterator lines = readLines(reader);
+ int lineno = 1;
+ int start = 0;
+ while (lines.hasNext()) {
+ final CharSequence line = lines.next();
+ if (line == null) {
+ break;
+ }
+ if (type == null) {
+ // determine starting type if not known yet
+ prefixLen = startsWith(line, prefix);
+ if (prefixLen >= 0) {
+ type = BlockType.DIRECTIVE;
+ strb.append(line.subSequence(prefixLen, line.length()));
+ } else {
+ type = BlockType.VERBATIM;
+ strb.append(line.subSequence(0, line.length()));
+ }
+ start = lineno;
+ } else if (type == BlockType.DIRECTIVE) {
+ // switch to verbatim if necessary
+ prefixLen = startsWith(line, prefix);
+ if (prefixLen < 0) {
+ final Block directive = new Block(BlockType.DIRECTIVE, start, strb.toString());
+ strb.delete(0, Integer.MAX_VALUE);
+ blocks.add(directive);
+ type = BlockType.VERBATIM;
+ strb.append(line.subSequence(0, line.length()));
+ start = lineno;
+ } else {
+ // still a directive
+ strb.append(line.subSequence(prefixLen, line.length()));
+ }
+ } else if (type == BlockType.VERBATIM) {
+ // switch to directive if necessary
+ prefixLen = startsWith(line, prefix);
+ if (prefixLen >= 0) {
+ final Block verbatim = new Block(BlockType.VERBATIM, start, strb.toString());
+ strb.delete(0, Integer.MAX_VALUE);
+ blocks.add(verbatim);
+ type = BlockType.DIRECTIVE;
+ strb.append(line.subSequence(prefixLen, line.length()));
+ start = lineno;
+ } else {
+ strb.append(line.subSequence(0, line.length()));
+ }
+ }
+ lineno += 1;
+ }
+ // input may be null
+ if (type != null && strb.length() > 0) {
+ final Block block = new Block(type, start, strb.toString());
+ blocks.add(block);
+ }
+ blocks.trimToSize();
+ return blocks;
+ }
+
+ @Override
+ public TemplateScript createTemplate(final JexlInfo info, final String prefix, final Reader source, final String... parms) {
+ return new TemplateScript(this, info, prefix, source, parms);
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateInterpreter.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateInterpreter.java
new file mode 100644
index 0000000..9d11802
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateInterpreter.java
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlContext;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlInfo;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlOptions;
+import aiyh.utils.tool.org.apache.commons.jexl3.JxltEngine;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlMethod;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlUberspect;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.*;
+
+import java.io.Writer;
+import java.util.Arrays;
+
+/**
+ * The type of interpreter to use during evaluation of templates.
+ * This context exposes its writer as '$jexl' to the scripts.
+ * public for introspection purpose.
+ */
+public class TemplateInterpreter extends Interpreter {
+ /** The array of template expressions. */
+ private final TemplateEngine.TemplateExpression[] exprs;
+ /** The writer used to output. */
+ private final Writer writer;
+
+ /**
+ * Helper ctor.
+ * Stores the different properties required to create a Template interpreter.
+ */
+ static class Arguments {
+ /** The engine. */
+ Engine jexl;
+ /** The options. */
+ JexlOptions options;
+ /** The context. */
+ JexlContext jcontext;
+ /** The frame. */
+ Frame jframe;
+ /** The expressions. */
+ TemplateEngine.TemplateExpression[] expressions;
+ /** The writer. */
+ Writer out;
+
+ /**
+ * Sole ctor.
+ *
+ * @param e the JEXL engine
+ */
+ Arguments(final Engine e) {
+ this.jexl = e;
+ }
+
+ /**
+ * Sets the options.
+ *
+ * @param o the options
+ * @return this instance
+ */
+ Arguments options(final JexlOptions o) {
+ this.options = o;
+ return this;
+ }
+
+ /**
+ * Sets the context.
+ *
+ * @param j the context
+ * @return this instance
+ */
+ Arguments context(final JexlContext j) {
+ this.jcontext = j;
+ return this;
+ }
+
+ /**
+ * Sets the frame.
+ *
+ * @param f the frame
+ * @return this instance
+ */
+ Arguments frame(final Frame f) {
+ this.jframe = f;
+ return this;
+ }
+
+ /**
+ * Sets the expressions.
+ *
+ * @param e the expressions
+ * @return this instance
+ */
+ Arguments expressions(final TemplateEngine.TemplateExpression[] e) {
+ this.expressions = e;
+ return this;
+ }
+
+ /**
+ * Sets the writer.
+ *
+ * @param o the writer
+ * @return this instance
+ */
+ Arguments writer(final Writer o) {
+ this.out = o;
+ return this;
+ }
+ }
+
+ /**
+ * Creates a template interpreter instance.
+ *
+ * @param args the template interpreter arguments
+ */
+ TemplateInterpreter(final Arguments args) {
+ super(args.jexl, args.options, args.jcontext, args.jframe);
+ exprs = args.expressions;
+ writer = args.out;
+ block = new LexicalFrame(frame, null);
+ }
+
+ /**
+ * Includes a call to another template.
+ *
+ * Includes another template using this template initial context and writer.
+ *
+ * @param script the TemplateScript to evaluate
+ * @param args the arguments
+ */
+ public void include(final JxltEngine.Template script, final Object... args) {
+ script.evaluate(context, writer, args);
+ }
+
+ /**
+ * Prints a unified expression evaluation result.
+ *
+ * @param e the expression number
+ */
+ public void print(final int e) {
+ if (e < 0 || e >= exprs.length) {
+ return;
+ }
+ TemplateEngine.TemplateExpression expr = exprs[e];
+ if (expr.isDeferred()) {
+ expr = expr.prepare(frame, context);
+ }
+ if (expr instanceof TemplateEngine.CompositeExpression) {
+ printComposite((TemplateEngine.CompositeExpression) expr);
+ } else {
+ doPrint(expr.getInfo(), expr.evaluate(this));
+ }
+ }
+
+ /**
+ * Prints a composite expression.
+ *
+ * @param composite the composite expression
+ */
+ private void printComposite(final TemplateEngine.CompositeExpression composite) {
+ final TemplateEngine.TemplateExpression[] cexprs = composite.exprs;
+ Object value;
+ for (final TemplateEngine.TemplateExpression cexpr : cexprs) {
+ value = cexpr.evaluate(this);
+ doPrint(cexpr.getInfo(), value);
+ }
+ }
+
+ /**
+ * Prints to output.
+ *
+ * This will dynamically try to find the best suitable method in the writer through uberspection.
+ * Subclassing Writer by adding 'print' methods should be the preferred way to specialize output.
+ *
+ *
+ * @param info the source info
+ * @param arg the argument to print out
+ */
+ private void doPrint(final JexlInfo info, final Object arg) {
+ try {
+ if (writer != null) {
+ if (arg instanceof CharSequence) {
+ writer.write(arg.toString());
+ } else if (arg != null) {
+ final Object[] value = {arg};
+ final JexlUberspect uber = jexl.getUberspect();
+ final JexlMethod method = uber.getMethod(writer, "print", value);
+ if (method != null) {
+ method.invoke(writer, value);
+ } else {
+ writer.write(arg.toString());
+ }
+ }
+ }
+ } catch (final java.io.IOException xio) {
+ throw TemplateEngine.createException(info, "call print", null, xio);
+ } catch (final Exception xany) {
+ throw TemplateEngine.createException(info, "invoke print", null, xany);
+ }
+ }
+
+ @Override
+ protected Object resolveNamespace(final String prefix, final JexlNode node) {
+ return "jexl".equals(prefix) ? this : super.resolveNamespace(prefix, node);
+ }
+
+ @Override
+ protected Object visit(final ASTIdentifier node, final Object data) {
+ final String name = node.getName();
+ if ("$jexl".equals(name)) {
+ return writer;
+ }
+ return super.visit(node, data);
+ }
+
+ /**
+ * Interprets a function node.
+ * print() and include() must be decoded by this interpreter since delegating to the Uberspect
+ * may be sandboxing the interpreter itself making it unable to call the function.
+ *
+ * @param node the function node
+ * @param data the data
+ * @return the function evaluation result.
+ */
+ @Override
+ protected Object visit(final ASTFunctionNode node, Object data) {
+ final int argc = node.jjtGetNumChildren();
+ if (argc == 2) {
+ final ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0);
+ if ("jexl".equals(functionNode.getNamespace())) {
+ final String functionName = functionNode.getName();
+ final ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
+ if ("print".equals(functionName)) {
+ // evaluate the arguments
+ Object[] argv = visit(argNode, null);
+ if (argv != null && argv.length > 0 && argv[0] instanceof Number) {
+ print(((Number) argv[0]).intValue());
+ return null;
+ }
+ }
+ if ("include".equals(functionName)) {
+ // evaluate the arguments
+ Object[] argv = visit(argNode, null);
+ if (argv != null && argv.length > 0) {
+ if (argv[0] instanceof TemplateScript) {
+ TemplateScript script = (TemplateScript) argv[0];
+ if (argv.length > 1) {
+ argv = Arrays.copyOfRange(argv, 1, argv.length);
+ } else {
+ argv = null;
+ }
+ include(script, argv);
+ return null;
+ }
+ }
+ }
+ // fail safe
+ throw new JxltEngine.Exception(node.jexlInfo(), "no callable template function " + functionName, null);
+ }
+ }
+ return super.visit(node, data);
+ }
+
+ @Override
+ protected Object visit(final ASTJexlScript script, final Object data) {
+ if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
+ return new Closure(this, (ASTJexlLambda) script) {
+ @Override
+ protected Interpreter createInterpreter(final JexlContext context, final Frame local) {
+ final JexlOptions opts = jexl.options(script, context);
+ final Arguments targs = new Arguments(jexl)
+ .context(context)
+ .options(opts)
+ .frame(local)
+ .expressions(exprs)
+ .writer(writer);
+ return new TemplateInterpreter(targs);
+ }
+ };
+ }
+ // otherwise...
+ final int numChildren = script.jjtGetNumChildren();
+ Object result = null;
+ for (int i = 0; i < numChildren; i++) {
+ final JexlNode child = script.jjtGetChild(i);
+ result = child.jjtAccept(this, data);
+ cancelCheck(child);
+ }
+ return result;
+ }
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateScript.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateScript.java
new file mode 100644
index 0000000..63b8de3
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/TemplateScript.java
@@ -0,0 +1,322 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.JexlNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlContext;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlInfo;
+import aiyh.utils.tool.org.apache.commons.jexl3.JxltEngine;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTJexlScript;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlOptions;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTArguments;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTFunctionNode;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTIdentifier;
+import aiyh.utils.tool.org.apache.commons.jexl3.parser.ASTNumberLiteral;
+
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * A Template instance.
+ */
+public final class TemplateScript implements JxltEngine.Template {
+ /** The prefix marker. */
+ private final String prefix;
+ /** The array of source blocks. */
+ private final TemplateEngine.Block[] source;
+ /** The resulting script. */
+ private final ASTJexlScript script;
+ /** The TemplateEngine expressions called by the script. */
+ private final TemplateEngine.TemplateExpression[] exprs;
+ /** The engine. */
+ private final TemplateEngine jxlt;
+
+ /**
+ * Creates a new template from an character input.
+ * @param engine the template engine
+ * @param info the source info
+ * @param directive the prefix for lines of code; can not be "$", "${", "#" or "#{"
+ since this would preclude being able to differentiate directives and jxlt expressions
+ * @param reader the input reader
+ * @param parms the parameter names
+ * @throws NullPointerException if either the directive prefix or input is null
+ * @throws IllegalArgumentException if the directive prefix is invalid
+ */
+ public TemplateScript(final TemplateEngine engine,
+ JexlInfo info,
+ final String directive,
+ final Reader reader,
+ final String... parms) {
+ if (directive == null) {
+ throw new NullPointerException("null prefix");
+ }
+ final String engineImmediateCharString = Character.toString(engine.getImmediateChar());
+ final String engineDeferredCharString = Character.toString(engine.getDeferredChar());
+
+ if (engineImmediateCharString.equals(directive)
+ || engineDeferredCharString.equals(directive)
+ || (engineImmediateCharString + "{").equals(directive)
+ || (engineDeferredCharString + "{").equals(directive)) {
+ throw new IllegalArgumentException(directive + ": is not a valid directive pattern");
+ }
+ if (reader == null) {
+ throw new NullPointerException("null input");
+ }
+ this.jxlt = engine;
+ this.prefix = directive;
+ final List blocks = jxlt.readTemplate(prefix, reader);
+ final List uexprs = new ArrayList<>();
+ final StringBuilder strb = new StringBuilder();
+ int nuexpr = 0;
+ int codeStart = -1;
+ int line = 1;
+ for (int b = 0; b < blocks.size(); ++b) {
+ final TemplateEngine.Block block = blocks.get(b);
+ final int bl = block.getLine();
+ while(line < bl) {
+ strb.append("//\n");
+ line += 1;
+ }
+ if (block.getType() == TemplateEngine.BlockType.VERBATIM) {
+ strb.append("jexl:print(");
+ strb.append(nuexpr++);
+ strb.append(");\n");
+ line += 1;
+ } else {
+ // keep track of first block of code, the frame creator
+ if (codeStart < 0) {
+ codeStart = b;
+ }
+ final String body = block.getBody();
+ strb.append(body);
+ for(int c = 0; c < body.length(); ++c) {
+ if (body.charAt(c) == '\n') {
+ line += 1;
+ }
+ }
+ }
+ }
+ // create the script
+ if (info == null) {
+ info = jxlt.getEngine().createInfo();
+ }
+ // allow lambda defining params
+ final Scope scope = parms == null ? null : new Scope(null, parms);
+ script = jxlt.getEngine().parse(info.at(1, 1), false, strb.toString(), scope).script();
+ // seek the map of expression number to scope so we can parse Unified
+ // expression blocks with the appropriate symbols
+ final Map minfo = new TreeMap<>();
+ collectPrintScope(script.script(), minfo);
+ // jexl:print(...) expression counter
+ int jpe = 0;
+ // create the exprs using the intended scopes
+ for (final TemplateEngine.Block block : blocks) {
+ if (block.getType() == TemplateEngine.BlockType.VERBATIM) {
+ final JexlNode.Info ji = minfo.get(jpe);
+ TemplateEngine.TemplateExpression te;
+ // no node info means this verbatim is surrounded by comments markers;
+ // expr at this index is never called
+ if (ji != null) {
+ te = jxlt.parseExpression(ji, block.getBody(), scopeOf(ji));
+ } else {
+ te = jxlt.new ConstantExpression(block.getBody(), null);
+ }
+ uexprs.add(te);
+ jpe += 1;
+ }
+ }
+ source = blocks.toArray(new TemplateEngine.Block[blocks.size()]);
+ exprs = uexprs.toArray(new TemplateEngine.TemplateExpression[uexprs.size()]);
+ }
+
+ /**
+ * Private ctor used to expand deferred expressions during prepare.
+ * @param engine the template engine
+ * @param thePrefix the directive prefix
+ * @param theSource the source
+ * @param theScript the script
+ * @param theExprs the expressions
+ */
+ TemplateScript(final TemplateEngine engine,
+ final String thePrefix,
+ final TemplateEngine.Block[] theSource,
+ final ASTJexlScript theScript,
+ final TemplateEngine.TemplateExpression[] theExprs) {
+ jxlt = engine;
+ prefix = thePrefix;
+ source = theSource;
+ script = theScript;
+ exprs = theExprs;
+ }
+
+ /**
+ * Gets the scope from an info.
+ * @param info the node info
+ * @return the scope
+ */
+ private static Scope scopeOf(final JexlNode.Info info) {
+ JexlNode walk = info.getNode();
+ while(walk != null) {
+ if (walk instanceof ASTJexlScript) {
+ return ((ASTJexlScript) walk).getScope();
+ }
+ walk = walk.jjtGetParent();
+ }
+ return null;
+ }
+
+ /**
+ * Collects the scope surrounding a call to jexl:print(i).
+ * This allows to later parse the blocks with the known symbols
+ * in the frame visible to the parser.
+ * @param node the visited node
+ * @param minfo the map of printed expression number to node info
+ */
+ private static void collectPrintScope(final JexlNode node, final Map minfo) {
+ final int nc = node.jjtGetNumChildren();
+ if (node instanceof ASTFunctionNode && nc == 2) {
+ // 0 must be the prefix jexl:
+ final ASTIdentifier nameNode = (ASTIdentifier) node.jjtGetChild(0);
+ if ("print".equals(nameNode.getName()) && "jexl".equals(nameNode.getNamespace())) {
+ final ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
+ if (argNode.jjtGetNumChildren() == 1) {
+ // seek the epression number
+ final JexlNode arg0 = argNode.jjtGetChild(0);
+ if (arg0 instanceof ASTNumberLiteral) {
+ final int exprNumber = ((ASTNumberLiteral) arg0).getLiteral().intValue();
+ minfo.put(exprNumber, new JexlNode.Info(nameNode));
+ return;
+ }
+ }
+ }
+ }
+ for (int c = 0; c < nc; ++c) {
+ collectPrintScope(node.jjtGetChild(c), minfo);
+ }
+ }
+
+ /**
+ * @return script
+ */
+ ASTJexlScript getScript() {
+ return script;
+ }
+
+ /**
+ * @return exprs
+ */
+ TemplateEngine.TemplateExpression[] getExpressions() {
+ return exprs;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder strb = new StringBuilder();
+ for (final TemplateEngine.Block block : source) {
+ block.toString(strb, prefix);
+ }
+ return strb.toString();
+ }
+
+ @Override
+ public String asString() {
+ final StringBuilder strb = new StringBuilder();
+ int e = 0;
+ for (final TemplateEngine.Block block : source) {
+ if (block.getType() == TemplateEngine.BlockType.DIRECTIVE) {
+ strb.append(prefix);
+ strb.append(block.getBody());
+ } else {
+ exprs[e++].asString(strb);
+ }
+ }
+ return strb.toString();
+ }
+
+ @Override
+ public TemplateScript prepare(final JexlContext context) {
+ final Engine jexl = jxlt.getEngine();
+ final JexlOptions options = jexl.options(script, context);
+ final Frame frame = script.createFrame((Object[]) null);
+ final TemplateInterpreter.Arguments targs = new TemplateInterpreter
+ .Arguments(jxlt.getEngine())
+ .context(context)
+ .options(options)
+ .frame(frame);
+ final Interpreter interpreter = new TemplateInterpreter(targs);
+ final TemplateEngine.TemplateExpression[] immediates = new TemplateEngine.TemplateExpression[exprs.length];
+ for (int e = 0; e < exprs.length; ++e) {
+ try {
+ immediates[e] = exprs[e].prepare(interpreter);
+ } catch (final JexlException xjexl) {
+ final JexlException xuel = TemplateEngine.createException(xjexl.getInfo(), "prepare", exprs[e], xjexl);
+ if (jexl.isSilent()) {
+ jexl.logger.warn(xuel.getMessage(), xuel.getCause());
+ return null;
+ }
+ throw xuel;
+ }
+ }
+ return new TemplateScript(jxlt, prefix, source, script, immediates);
+ }
+
+ @Override
+ public void evaluate(final JexlContext context, final Writer writer) {
+ evaluate(context, writer, (Object[]) null);
+ }
+
+ @Override
+ public void evaluate(final JexlContext context, final Writer writer, final Object... args) {
+ final Engine jexl = jxlt.getEngine();
+ final JexlOptions options = jexl.options(script, context);
+ final Frame frame = script.createFrame(args);
+ final TemplateInterpreter.Arguments targs = new TemplateInterpreter
+ .Arguments(jexl)
+ .context(context)
+ .options(options)
+ .frame(frame)
+ .expressions(exprs)
+ .writer(writer);
+ final Interpreter interpreter = new TemplateInterpreter(targs);
+ interpreter.interpret(script);
+ }
+
+ @Override
+ public Set> getVariables() {
+ final Engine.VarCollector collector = jxlt.getEngine().varCollector();
+ for (final TemplateEngine.TemplateExpression expr : exprs) {
+ expr.getVariables(collector);
+ }
+ return collector.collected();
+ }
+
+ @Override
+ public String[] getParameters() {
+ return script.getParameters();
+ }
+
+ @Override
+ public Map getPragmas() {
+ return script.getPragmas();
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/AbstractExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/AbstractExecutor.java
new file mode 100644
index 0000000..6f5ac8e
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/AbstractExecutor.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlEngine;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlMethod;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertyGet;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertySet;
+
+/**
+ * Abstract class that is used to execute an arbitrary
+ * method that is introspected. This is the superclass
+ * for all other AbstractExecutor classes.
+ *
+ * @since 1.0
+ */
+abstract class AbstractExecutor {
+ /** A marker for invocation failures in tryInvoke. */
+ public static final Object TRY_FAILED = JexlEngine.TRY_FAILED;
+
+ /**
+ * A helper to initialize the marker methods (array.get, list.get, etc...).
+ * @param clazz the class to introspect
+ * @param name the name of the method
+ * @param parms the parameters
+ * @return the method
+ */
+ static java.lang.reflect.Method initMarker(final Class> clazz, final String name, final Class>... parms) {
+ try {
+ return clazz.getMethod(name, parms);
+ } catch (final Exception xnever) {
+ throw new Error(xnever);
+ }
+ }
+
+ /**
+ * Coerce an Object which must be a number to an Integer.
+ * @param arg the Object to coerce
+ * @return an Integer if it can be converted, null otherwise
+ */
+ static Integer castInteger(final Object arg) {
+ return arg instanceof Number? ((Number) arg).intValue() : null;
+ }
+
+ /**
+ * Coerce an Object to a String.
+ * @param arg the Object to coerce
+ * @return a String if it can be converted, null otherwise
+ */
+ static String castString(final Object arg) {
+ return arg instanceof CharSequence || arg instanceof Integer ? arg.toString() : null;
+ }
+
+ /**
+ * Creates an arguments array.
+ * @param args the list of arguments
+ * @return the arguments array
+ */
+ static Object[] makeArgs(final Object... args) {
+ return args;
+ }
+
+ /**
+ * Gets the class of an object or Object if null.
+ * @param instance the instance
+ * @return the class
+ */
+ static Class> classOf(final Object instance) {
+ return instance == null? Object.class : instance.getClass();
+ }
+
+ /** The class this executor applies to. */
+ protected final Class> objectClass;
+ /** Method to be executed. */
+ protected final java.lang.reflect.Method method;
+
+ /**
+ * Default and sole constructor.
+ * @param theClass the class this executor applies to
+ * @param theMethod the method held by this executor
+ */
+ protected AbstractExecutor(final Class> theClass, final java.lang.reflect.Method theMethod) {
+ objectClass = theClass;
+ method = theMethod;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return this == obj || (obj instanceof AbstractExecutor && equals((AbstractExecutor) obj));
+ }
+
+ @Override
+ public int hashCode() {
+ return method.hashCode();
+ }
+
+ /**
+ * Indicates whether some other executor is equivalent to this one.
+ * @param arg the other executor to check
+ * @return true if both executors are equivalent, false otherwise
+ */
+ public boolean equals(final AbstractExecutor arg) {
+ // common equality check
+ if (!this.getClass().equals(arg.getClass())) {
+ return false;
+ }
+ if (!this.getMethod().equals(arg.getMethod())) {
+ return false;
+ }
+ if (!this.getTargetClass().equals(arg.getTargetClass())) {
+ return false;
+ }
+ // specific equality check
+ final Object lhsp = this.getTargetProperty();
+ final Object rhsp = arg.getTargetProperty();
+ if (lhsp == null && rhsp == null) {
+ return true;
+ }
+ if (lhsp != null && rhsp != null) {
+ return lhsp.equals(rhsp);
+ }
+ return false;
+ }
+
+ /**
+ * Tell whether the executor is alive by looking
+ * at the value of the method.
+ *
+ * @return boolean Whether the executor is alive.
+ */
+ public final boolean isAlive() {
+ return (method != null);
+ }
+
+ /**
+ * Specifies if this executor is cacheable and able to be reused for this
+ * class of object it was returned for.
+ *
+ * @return true if can be reused for this class, false if not
+ */
+ public boolean isCacheable() {
+ return method != null;
+ }
+
+ /**
+ * Gets the method to be executed or used as a marker.
+ * @return Method The method used by execute in derived classes.
+ */
+ public final java.lang.reflect.Method getMethod() {
+ return method;
+ }
+
+ /**
+ * Gets the object class targeted by this executor.
+ * @return the target object class
+ */
+ public final Class> getTargetClass() {
+ return objectClass;
+ }
+
+ /**
+ * Gets the property targeted by this executor.
+ * @return the target property
+ */
+ public Object getTargetProperty() {
+ return null;
+ }
+
+ /**
+ * Gets the method name used.
+ * @return method name
+ */
+ public final String getMethodName() {
+ return method.getName();
+ }
+
+ /**
+ * Checks whether a tryExecute failed or not.
+ * @param exec the value returned by tryExecute
+ * @return true if tryExecute failed, false otherwise
+ */
+ public final boolean tryFailed(final Object exec) {
+ return exec == JexlEngine.TRY_FAILED;
+ }
+
+ /**
+ * Abstract class that is used to execute an arbitrary 'get' method.
+ */
+ public abstract static class Get extends AbstractExecutor implements JexlPropertyGet {
+ /**
+ * Default and sole constructor.
+ * @param theClass the class this executor applies to
+ * @param theMethod the method held by this executor
+ */
+ protected Get(final Class> theClass, final java.lang.reflect.Method theMethod) {
+ super(theClass, theMethod);
+ }
+ }
+
+ /**
+ * Abstract class that is used to execute an arbitrary 'set' method.
+ */
+ public abstract static class Set extends AbstractExecutor implements JexlPropertySet {
+ /**
+ * Default and sole constructor.
+ * @param theClass the class this executor applies to
+ * @param theMethod the method held by this executor
+ */
+ protected Set(final Class> theClass, final java.lang.reflect.Method theMethod) {
+ super(theClass, theMethod);
+ }
+ }
+
+ /**
+ * Abstract class that is used to execute an arbitrary method.
+ */
+ public abstract static class Method extends AbstractExecutor implements JexlMethod {
+ /** The method key discovered from the arguments. */
+ protected final MethodKey key;
+
+ /**
+ * Creates a new instance.
+ * @param c the class this executor applies to
+ * @param m the method
+ * @param k the MethodKey
+ */
+ protected Method(final Class> c, final java.lang.reflect.Method m, final MethodKey k) {
+ super(c, m);
+ key = k;
+ }
+
+ @Override
+ public Object getTargetProperty() {
+ return key;
+ }
+
+ @Override
+ public final Class> getReturnType() {
+ return method.getReturnType();
+ }
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ArrayIterator.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ArrayIterator.java
new file mode 100644
index 0000000..670e59f
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ArrayIterator.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.reflect.Array;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ *
+ * An Iterator wrapper for an Object[]. This will
+ * allow us to deal with all array like structures
+ * in a consistent manner.
+ *
+ *
+ * WARNING : this class's operations are NOT synchronized.
+ * It is meant to be used in a single thread, newly created
+ * for each use in the #foreach() directive.
+ * If this is used or shared, synchronize in the
+ * next() method.
+ *
+ *
+ * @since 1.0
+ */
+public class ArrayIterator implements Iterator {
+ /** The objects to iterate over. */
+ private final Object array;
+ /** The size of the array. */
+ private final int size;
+ /** The current position and size in the array. */
+ private int pos;
+
+ /**
+ * Creates a new iterator instance for the specified array.
+ * @param arr The array for which an iterator is desired.
+ */
+ public ArrayIterator(final Object arr) {
+ if (arr == null) {
+ array = null;
+ pos = 0;
+ size = 0;
+ } else if (!arr.getClass().isArray()) {
+ throw new IllegalArgumentException(arr.getClass() + " is not an array");
+ } else {
+ array = arr;
+ pos = 0;
+ size = Array.getLength(array);
+ }
+ }
+
+ /**
+ * Move to next element in the array.
+ *
+ * @return The next object in the array.
+ */
+ @Override
+ public Object next() {
+ if (pos < size) {
+ return Array.get(array, pos++);
+ }
+ // we screwed up...
+ throw new NoSuchElementException("No more elements: " + pos
+ + " / " + size);
+ }
+
+ /**
+ * Check to see if there is another element in the array.
+ *
+ * @return Whether there is another element.
+ */
+ @Override
+ public boolean hasNext() {
+ return (pos < size);
+ }
+
+ /**
+ * No op--merely added to satify the Iterator
interface.
+ */
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ArrayListWrapper.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ArrayListWrapper.java
new file mode 100644
index 0000000..450d0c5
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ArrayListWrapper.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.reflect.Array;
+import java.util.AbstractList;
+import java.util.RandomAccess;
+
+/**
+ * A class that wraps an array within an AbstractList.
+ *
+ * It overrides some methods because introspection uses this class a a marker for wrapped arrays; the declared class
+ * for these method is thus ArrayListWrapper.
+ * The methods are get/set/size/contains and indexOf because it is used by contains.
+ *
+ */
+public class ArrayListWrapper extends AbstractList implements RandomAccess {
+ /** the array to wrap. */
+ private final Object array;
+
+ /**
+ * Create the wrapper.
+ * @param anArray {@link #array}
+ */
+ public ArrayListWrapper(final Object anArray) {
+ if (!anArray.getClass().isArray()) {
+ throw new IllegalArgumentException(anArray.getClass() + " is not an array");
+ }
+ this.array = anArray;
+ }
+
+ @Override
+ public Object get(final int index) {
+ return Array.get(array, index);
+ }
+
+ @Override
+ public Object set(final int index, final Object element) {
+ final Object old = Array.get(array, index);
+ Array.set(array, index, element);
+ return old;
+ }
+
+ @Override
+ public int size() {
+ return Array.getLength(array);
+ }
+
+ @Override
+ public int indexOf(final Object o) {
+ final int size = size();
+ if (o == null) {
+ for (int i = 0; i < size; i++) {
+ if (get(i) == null) {
+ return i;
+ }
+ }
+ } else {
+ for (int i = 0; i < size; i++) {
+ if (o.equals(get(i))) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ return indexOf(o) != -1;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/BooleanGetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/BooleanGetExecutor.java
new file mode 100644
index 0000000..bb6feea
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/BooleanGetExecutor.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+import java.lang.reflect.InvocationTargetException;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
+
+/**
+ * Specialized executor to get a boolean property from an object.
+ * @since 2.0
+ */
+public final class BooleanGetExecutor extends AbstractExecutor.Get {
+ /** The property. */
+ private final String property;
+
+ /**
+ * Discovers a BooleanGetExecutor.
+ * The method to be found should be named "is{P,p}property and return a boolean.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param property the property name
+ * @return the executor if found, null otherwise
+ */
+ public static BooleanGetExecutor discover(final Introspector is, final Class> clazz, final String property) {
+ if (property != null && !property.isEmpty()) {
+ final java.lang.reflect.Method m = PropertyGetExecutor.discoverGet(is, "is", clazz, property);
+ if (m != null && (m.getReturnType() == Boolean.TYPE || m.getReturnType() == Boolean.class)) {
+ return new BooleanGetExecutor(clazz, m, property);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates an instance by attempting discovery of the get method.
+ * @param clazz the class to introspect
+ * @param method the method held by this executor
+ * @param key the property to get
+ */
+ private BooleanGetExecutor(final Class> clazz, final java.lang.reflect.Method method, final String key) {
+ super(clazz, method);
+ property = key;
+ }
+
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ @Override
+ public Object invoke(final Object obj) throws IllegalAccessException, InvocationTargetException {
+ return method == null ? null : method.invoke(obj, (Object[]) null);
+ }
+
+ @Override
+ public Object tryInvoke(final Object obj, final Object key) {
+ if (obj != null && method != null
+ // ensure method name matches the property name
+ && property.equals(key)
+ && objectClass.equals(obj.getClass())) {
+ try {
+ return method.invoke(obj, (Object[]) null);
+ } catch (final IllegalAccessException xill) {
+ return TRY_FAILED;// fail
+ } catch (final InvocationTargetException xinvoke) {
+ throw JexlException.tryFailed(xinvoke); // throw
+ }
+ }
+ return TRY_FAILED;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ClassMap.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ClassMap.java
new file mode 100644
index 0000000..18fbad9
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ClassMap.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import org.apache.commons.logging.Log;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A cache of introspection information for a specific class instance.
+ * Keys objects by an aggregation of the method name and the classes
+ * that make up the parameters.
+ *
+ * Originally taken from the Velocity tree so we can be self-sufficient.
+ *
+ *
+ * @see MethodKey
+ * @since 1.0
+ */
+final class ClassMap {
+ /**
+ * A method that returns itself used as a marker for cache miss,
+ * allows the underlying cache map to be strongly typed.
+ *
+ * @return itself as a method
+ */
+ public static Method cacheMiss() {
+ try {
+ return ClassMap.class.getMethod("cacheMiss");
+ } catch (final Exception xio) {
+ // this really can't make an error...
+ return null;
+ }
+ }
+
+ /**
+ * The cache miss marker method.
+ */
+ private static final Method CACHE_MISS = cacheMiss();
+ /**
+ * This is the cache to store and look up the method information.
+ *
+ * It stores the association between:
+ * - a key made of a method name and an array of argument types.
+ * - a method.
+ *
+ *
+ * Since the invocation of the associated method is dynamic, there is no need (nor way) to differentiate between
+ * foo(int,int) and foo(Integer,Integer) since in practice only the latter form will be used through a call.
+ * This of course, applies to all 8 primitive types.
+ *
+ * Uses ConcurrentMap since 3.0, marginally faster than 2.1 under contention.
+ */
+ private final ConcurrentMap byKey = new ConcurrentHashMap<>();
+ /**
+ * Keep track of all methods with the same name; this is not modified after creation.
+ */
+ private final Map byName = new HashMap<>();
+ /**
+ * Cache of fields.
+ */
+ private final Map fieldCache;
+
+ /**
+ * Standard constructor.
+ *
+ * @param aClass the class to deconstruct.
+ * @param permissions the permissions to apply during introspection
+ * @param log the logger.
+ */
+ @SuppressWarnings("LeakingThisInConstructor")
+ ClassMap(final Class> aClass, final Permissions permissions, final Log log) {
+ // eagerly cache methods
+ create(this, permissions, aClass, log);
+ // eagerly cache public fields
+ final Field[] fields = aClass.getFields();
+ if (fields.length > 0) {
+ final Map cache = new HashMap<>();
+ for (final Field field : fields) {
+ if (permissions.allow(field)) {
+ cache.put(field.getName(), field);
+ }
+ }
+ fieldCache = cache;
+ } else {
+ fieldCache = Collections.emptyMap();
+ }
+ }
+
+ /**
+ * Find a Field using its name.
+ *
+ * @param fname the field name
+ * @return A Field object representing the field to invoke or null.
+ */
+ Field getField(final String fname) {
+ return fieldCache.get(fname);
+ }
+
+ /**
+ * Gets the field names cached by this map.
+ *
+ * @return the array of field names
+ */
+ String[] getFieldNames() {
+ return fieldCache.keySet().toArray(new String[fieldCache.size()]);
+ }
+
+ /**
+ * Gets the methods names cached by this map.
+ *
+ * @return the array of method names
+ */
+ String[] getMethodNames() {
+ return byName.keySet().toArray(new String[byName.size()]);
+ }
+
+ /**
+ * Gets all the methods with a given name from this map.
+ *
+ * @param methodName the seeked methods name
+ * @return the array of methods (null or non-empty)
+ */
+ Method[] getMethods(final String methodName) {
+ final Method[] lm = byName.get(methodName);
+ if (lm != null && lm.length > 0) {
+ return lm.clone();
+ }
+ return null;
+ }
+
+ /**
+ * Find a Method using the method name and parameter objects.
+ *
+ * Look in the methodMap for an entry. If found,
+ * it'll either be a CACHE_MISS, in which case we
+ * simply give up, or it'll be a Method, in which
+ * case, we return it.
+ *
+ *
+ * If nothing is found, then we must actually go
+ * and introspect the method from the MethodMap.
+ *
+ *
+ * @param methodKey the method key
+ * @return A Method object representing the method to invoke or null.
+ * @throws MethodKey.AmbiguousException When more than one method is a match for the parameters.
+ */
+ Method getMethod(final MethodKey methodKey) throws MethodKey.AmbiguousException {
+ // Look up by key
+ Method cacheEntry = byKey.get(methodKey);
+ // We looked this up before and failed.
+ if (cacheEntry == CACHE_MISS) {
+ return null;
+ }
+ if (cacheEntry == null) {
+ try {
+ // That one is expensive...
+ final Method[] methodList = byName.get(methodKey.getMethod());
+ if (methodList != null) {
+ cacheEntry = methodKey.getMostSpecificMethod(methodList);
+ }
+ if (cacheEntry == null) {
+ byKey.put(methodKey, CACHE_MISS);
+ } else {
+ byKey.put(methodKey, cacheEntry);
+ }
+ } catch (final MethodKey.AmbiguousException ae) {
+ // that's a miss :-)
+ byKey.put(methodKey, CACHE_MISS);
+ throw ae;
+ }
+ }
+
+ // Yes, this might just be null.
+ return cacheEntry;
+ }
+
+ /**
+ * Populate the Map of direct hits. These are taken from all the public methods
+ * that our class, its parents and their implemented interfaces provide.
+ *
+ * @param cache the ClassMap instance we create
+ * @param permissions the permissions to apply during introspection
+ * @param classToReflect the class to cache
+ * @param log the Log
+ */
+ private static void create(final ClassMap cache, final Permissions permissions, Class> classToReflect, final Log log) {
+ //
+ // Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
+ // with the actual declaring class and its interfaces and then move up (superclass etc.) until we
+ // hit java.lang.Object. That is important because it will give us the methods of the declaring class
+ // which might in turn be abstract further up the tree.
+ //
+ // We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions.
+ //
+ for (; classToReflect != null; classToReflect = classToReflect.getSuperclass()) {
+ if (Modifier.isPublic(classToReflect.getModifiers())) {
+ populateWithClass(cache, permissions, classToReflect, log);
+ }
+ final Class>[] interfaces = classToReflect.getInterfaces();
+ for (final Class> anInterface : interfaces) {
+ populateWithInterface(cache, permissions, anInterface, log);
+ }
+ }
+ // now that we've got all methods keyed in, lets organize them by name
+ if (!cache.byKey.isEmpty()) {
+ final List lm = new ArrayList<>(cache.byKey.size());
+ lm.addAll(cache.byKey.values());
+ // sort all methods by name
+ lm.sort(Comparator.comparing(Method::getName));
+ // put all lists of methods with same name in byName cache
+ int start = 0;
+ while (start < lm.size()) {
+ final String name = lm.get(start).getName();
+ int end = start + 1;
+ while (end < lm.size()) {
+ final String walk = lm.get(end).getName();
+ if (!walk.equals(name)) {
+ break;
+ }
+ end += 1;
+ }
+ final Method[] lmn = lm.subList(start, end).toArray(new Method[end - start]);
+ cache.byName.put(name, lmn);
+ start = end;
+ }
+ }
+ }
+
+ /**
+ * Recurses up interface hierarchy to get all super interfaces.
+ *
+ * @param cache the cache to fill
+ * @param permissions the permissions to apply during introspection
+ * @param iface the interface to populate the cache from
+ * @param log the Log
+ */
+ private static void populateWithInterface(final ClassMap cache,
+ final Permissions permissions,
+ final Class> iface,
+ final Log log) {
+ if (Modifier.isPublic(iface.getModifiers())) {
+ populateWithClass(cache, permissions, iface, log);
+ final Class>[] supers = iface.getInterfaces();
+ for (final Class> aSuper : supers) {
+ populateWithInterface(cache, permissions, aSuper, log);
+ }
+ }
+ }
+
+ /**
+ * Recurses up class hierarchy to get all super classes.
+ *
+ * @param cache the cache to fill
+ * @param permissions the permissions to apply during introspection
+ * @param clazz the class to populate the cache from
+ * @param log the Log
+ */
+ private static void populateWithClass(final ClassMap cache,
+ final Permissions permissions,
+ final Class> clazz,
+ final Log log) {
+ try {
+ final Method[] methods = clazz.getDeclaredMethods();
+ for (final Method mi : methods) {
+ // add method to byKey cache; do not override
+ final MethodKey key = new MethodKey(mi);
+ final Method pmi = cache.byKey.putIfAbsent(key, permissions.allow(mi) ? mi : CACHE_MISS);
+ if (pmi != null && pmi != CACHE_MISS && log.isDebugEnabled() && !key.equals(new MethodKey(pmi))) {
+ // foo(int) and foo(Integer) have the same signature for JEXL
+ log.debug("Method " + pmi + " is already registered, key: " + key.debugString());
+ }
+ }
+ } catch (final SecurityException se) {
+ // Everybody feels better with...
+ if (log.isDebugEnabled()) {
+ log.debug("While accessing methods of " + clazz + ": ", se);
+ }
+ }
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java
new file mode 100644
index 0000000..d046cd9
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.beans.IntrospectionException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlMethod;
+
+/**
+ * A JexlMethod that wraps a constructor.
+ */
+public final class ConstructorMethod implements JexlMethod {
+ /** The wrapped constructor. */
+ private final Constructor> ctor;
+
+ /**
+ * Discovers a class constructor and wrap it as a JexlMethod.
+ * @param is the introspector
+ * @param ctorHandle a class or class name
+ * @param args constructor arguments
+ * @return a {@link JexlMethod}
+ */
+ public static ConstructorMethod discover(final Introspector is, final Object ctorHandle, final Object... args) {
+ String className;
+ Class> clazz = null;
+ if (ctorHandle instanceof Class>) {
+ clazz = (Class>) ctorHandle;
+ className = clazz.getName();
+ } else if (ctorHandle != null) {
+ className = ctorHandle.toString();
+ } else {
+ return null;
+ }
+ final Constructor> ctor = is.getConstructor(clazz, new MethodKey(className, args));
+ if (ctor != null) {
+ return new ConstructorMethod(ctor);
+ }
+ return null;
+ }
+ /**
+ * Creates a constructor method.
+ * @param theCtor the constructor to wrap
+ */
+ ConstructorMethod(final Constructor> theCtor) {
+ this.ctor = theCtor;
+ }
+
+ @Override
+ public Object invoke(final Object obj, final Object... params) throws Exception {
+ final Class> ctorClass = ctor.getDeclaringClass();
+ boolean invoke = true;
+ if (obj != null) {
+ if (obj instanceof Class>) {
+ invoke = ctorClass.equals(obj);
+ } else {
+ invoke = ctorClass.getName().equals(obj.toString());
+ }
+ }
+ if (invoke) {
+ return ctor.newInstance(params);
+ }
+ throw new IntrospectionException("constructor resolution error");
+ }
+
+ @Override
+ public Object tryInvoke(final String name, final Object obj, final Object... params) {
+ try {
+ final Class> ctorClass = ctor.getDeclaringClass();
+ boolean invoke = true;
+ if (obj != null) {
+ if (obj instanceof Class>) {
+ invoke = ctorClass.equals(obj);
+ } else {
+ invoke = ctorClass.getName().equals(obj.toString());
+ }
+ }
+ invoke &= name == null || ctorClass.getName().equals(name);
+ if (invoke) {
+ return ctor.newInstance(params);
+ }
+ } catch (InstantiationException | IllegalArgumentException | IllegalAccessException xinstance) {
+ return Uberspect.TRY_FAILED;
+ } catch (final InvocationTargetException xinvoke) {
+ throw JexlException.tryFailed(xinvoke); // throw
+ }
+ return Uberspect.TRY_FAILED;
+ }
+
+ @Override
+ public boolean tryFailed(final Object rval) {
+ return rval == Uberspect.TRY_FAILED;
+ }
+
+ @Override
+ public boolean isCacheable() {
+ return true;
+ }
+
+ @Override
+ public Class> getReturnType() {
+ return ctor.getDeclaringClass();
+ }
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/DuckGetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/DuckGetExecutor.java
new file mode 100644
index 0000000..b59de15
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/DuckGetExecutor.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.reflect.InvocationTargetException;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
+
+/**
+ * Specialized executor to get a property from an object.
+ * Duck as in duck-typing for an interface like:
+ *
+ * interface Get {
+ * Object get(Object key);
+ * }
+ *
+ *
+ * @since 2.0
+ */
+public final class DuckGetExecutor extends AbstractExecutor.Get {
+ /** The property, may be null. */
+ private final Object property;
+
+ /**
+ * Attempts to discover a DuckGetExecutor.
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param identifier the key to use as an argument to the get method
+ * @return the executor if found, null otherwise
+ */
+ public static DuckGetExecutor discover(final Introspector is, final Class> clazz, final Object identifier) {
+ final java.lang.reflect.Method method = is.getMethod(clazz, "get", makeArgs(identifier));
+ return method == null? null : new DuckGetExecutor(clazz, method, identifier);
+ }
+
+ /**
+ * Creates an instance.
+ * @param clazz he class the get method applies to
+ * @param method the method held by this executor
+ * @param identifier the property to get
+ */
+ private DuckGetExecutor(final Class> clazz, final java.lang.reflect.Method method, final Object identifier) {
+ super(clazz, method);
+ property = identifier;
+ }
+
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ @Override
+ public Object invoke(final Object obj) throws IllegalAccessException, InvocationTargetException {
+ final Object[] args = {property};
+ return method == null ? null : method.invoke(obj, args);
+ }
+
+ @Override
+ public Object tryInvoke(final Object obj, final Object key) {
+ if (obj != null
+ && objectClass.equals(obj.getClass())
+ // ensure method name matches the property name
+ && method != null
+ && ((property == null && key == null)
+ || (property != null && property.equals(key)))) {
+ try {
+ final Object[] args = {property};
+ return method.invoke(obj, args);
+ } catch (IllegalAccessException | IllegalArgumentException xill) {
+ return TRY_FAILED;// fail
+ } catch (final InvocationTargetException xinvoke) {
+ throw JexlException.tryFailed(xinvoke); // throw
+ }
+ }
+ return TRY_FAILED;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/DuckSetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/DuckSetExecutor.java
new file mode 100644
index 0000000..1c28983
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/DuckSetExecutor.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.reflect.InvocationTargetException;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
+
+/**
+ * Specialized executor to set a property of an object.
+ * Duck as in duck-typing for an interface like:
+ *
+ * interface Setable {
+ * Object set(Object property, Object value);
+ * }
+ *
+ * or
+ *
+ * interface Putable {
+ * Object put(Object property, Object value);
+ * }
+ *
+ *
+ * @since 2.0
+ */
+public final class DuckSetExecutor extends AbstractExecutor.Set {
+ /** The property, may be null. */
+ private final Object property;
+ /** The property value class. */
+ private final Class> valueClass;
+
+ /**
+ * Discovers a DuckSetExecutor.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the set method from
+ * @param key the key to use as 1st argument to the set method
+ * @param value the value to use as 2nd argument to the set method
+ * @return the executor if found, null otherwise
+ */
+ public static DuckSetExecutor discover(final Introspector is, final Class> clazz, final Object key, final Object value) {
+ java.lang.reflect.Method method = is.getMethod(clazz, "set", makeArgs(key, value));
+ if (method == null) {
+ method = is.getMethod(clazz, "put", makeArgs(key, value));
+ }
+ return method == null? null : new DuckSetExecutor(clazz, method, key, value);
+ }
+
+ /**
+ * Creates an instance.
+ * @param clazz the class the set method applies to
+ * @param method the method called through this executor
+ * @param key the key to use as 1st argument to the set method
+ * @param value the value to use as 2nd argument to the set method
+ */
+ private DuckSetExecutor(final Class> clazz, final java.lang.reflect.Method method, final Object key, final Object value) {
+ super(clazz, method);
+ property = key;
+ valueClass = classOf(value);
+ }
+
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ @Override
+ public Object invoke(final Object obj, final Object value) throws IllegalAccessException, InvocationTargetException {
+ final Object[] pargs = {property, value};
+ if (method != null) {
+ method.invoke(obj, pargs);
+ }
+ return value;
+ }
+
+ @Override
+ public Object tryInvoke(final Object obj, final Object key, final Object value) {
+ if (obj != null
+ && objectClass.equals(obj.getClass())
+ && method != null
+ && ((property != null && property.equals(key))
+ || (property == null && key == null))
+ && valueClass.equals(classOf(value))) {
+ try {
+ final Object[] args = {property, value};
+ method.invoke(obj, args);
+ return value;
+ } catch (IllegalAccessException | IllegalArgumentException xill) {
+ return TRY_FAILED;// fail
+ } catch (final InvocationTargetException xinvoke) {
+ throw JexlException.tryFailed(xinvoke); // throw
+ }
+ }
+ return TRY_FAILED;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/EnumerationIterator.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/EnumerationIterator.java
new file mode 100644
index 0000000..10e7405
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/EnumerationIterator.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+
+import java.util.Iterator;
+import java.util.Enumeration;
+
+/**
+ * An Iterator wrapper for an Enumeration.
+ * @param the type of object this iterator returns
+ * @since 1.0
+ */
+public class EnumerationIterator implements Iterator {
+ /**
+ * The enumeration to iterate over.
+ */
+ private final Enumeration enumeration;
+
+ /**
+ * Creates a new iteratorwrapper instance for the specified
+ * Enumeration.
+ *
+ * @param enumer The Enumeration to wrap.
+ */
+ public EnumerationIterator(final Enumeration enumer) {
+ enumeration = enumer;
+ }
+
+ @Override
+ public T next() {
+ return enumeration.nextElement();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return enumeration.hasMoreElements();
+ }
+
+ @Override
+ public void remove() {
+ // not implemented
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/FieldGetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/FieldGetExecutor.java
new file mode 100644
index 0000000..e8a8605
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/FieldGetExecutor.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.reflect.Field;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertyGet;
+
+/**
+ * A JexlPropertyGet for public fields.
+ */
+public final class FieldGetExecutor implements JexlPropertyGet {
+ /**
+ * The public field.
+ */
+ private final Field field;
+
+ /**
+ * Attempts to discover a FieldGetExecutor.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param identifier the key to use as an argument to the get method
+ * @return the executor if found, null otherwise
+ */
+ public static JexlPropertyGet discover(final Introspector is, final Class> clazz, final String identifier) {
+ if (identifier != null) {
+ final Field field = is.getField(clazz, identifier);
+ if (field != null) {
+ return new FieldGetExecutor(field);
+ }
+ }
+ return null;
+ }
+ /**
+ * Creates a new instance of FieldPropertyGet.
+ * @param theField the class public field
+ */
+ private FieldGetExecutor(final Field theField) {
+ field = theField;
+ }
+
+ @Override
+ public Object invoke(final Object obj) throws Exception {
+ return field.get(obj);
+ }
+
+ @Override
+ public Object tryInvoke(final Object obj, final Object key) {
+ if (obj.getClass().equals(field.getDeclaringClass()) && key.equals(field.getName())) {
+ try {
+ return field.get(obj);
+ } catch (final IllegalAccessException xill) {
+ return Uberspect.TRY_FAILED;
+ }
+ }
+ return Uberspect.TRY_FAILED;
+ }
+
+ @Override
+ public boolean tryFailed(final Object rval) {
+ return rval == Uberspect.TRY_FAILED;
+ }
+
+ @Override
+ public boolean isCacheable() {
+ return true;
+ }
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/FieldSetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/FieldSetExecutor.java
new file mode 100644
index 0000000..3a05410
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/FieldSetExecutor.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertySet;
+
+/**
+ * A JexlPropertySet for public fields.
+ */
+public final class FieldSetExecutor implements JexlPropertySet {
+ /**
+ * The public field.
+ */
+ private final Field field;
+
+ /**
+ * Attempts to discover a FieldSetExecutor.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param identifier the key to use as an argument to the get method
+ * @param value the value to set the field to
+ * @return the executor if found, null otherwise
+ */
+ public static JexlPropertySet discover(final Introspector is,
+ final Class> clazz,
+ final String identifier,
+ final Object value) {
+ if (identifier != null) {
+ final Field field = is.getField(clazz, identifier);
+ if (field != null
+ && !Modifier.isFinal(field.getModifiers())
+ && (value == null || MethodKey.isInvocationConvertible(field.getType(), value.getClass(), false))) {
+ return new FieldSetExecutor(field);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new instance of FieldPropertySet.
+ * @param theField the class public field
+ */
+ private FieldSetExecutor(final Field theField) {
+ field = theField;
+ }
+
+ @Override
+ public Object invoke(final Object obj, final Object arg) throws Exception {
+ field.set(obj, arg);
+ return arg;
+ }
+
+ @Override
+ public Object tryInvoke(final Object obj, final Object key, final Object value) {
+ if (obj.getClass().equals(field.getDeclaringClass())
+ && key.equals(field.getName())
+ && (value == null || MethodKey.isInvocationConvertible(field.getType(), value.getClass(), false))) {
+ try {
+ field.set(obj, value);
+ return value;
+ } catch (final IllegalAccessException xill) {
+ return Uberspect.TRY_FAILED;
+ }
+ }
+ return Uberspect.TRY_FAILED;
+ }
+
+ @Override
+ public boolean tryFailed(final Object rval) {
+ return rval == Uberspect.TRY_FAILED;
+ }
+
+ @Override
+ public boolean isCacheable() {
+ return true;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/IndexedType.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/IndexedType.java
new file mode 100644
index 0000000..70094aa
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/IndexedType.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertyGet;
+
+import java.lang.reflect.Method;
+import java.beans.IntrospectionException;
+
+/**
+ * Abstract an indexed property container.
+ * This allows getting properties from expressions like var.container.property
.
+ * This stores the container name and class as well as the list of available getter and setter methods.
+ * It implements JexlPropertyGet since such a container can only be accessed from its owning instance (not set).
+ */
+public final class IndexedType implements JexlPropertyGet {
+ /** The container name. */
+ private final String container;
+ /** The container class. */
+ private final Class> clazz;
+ /** The array of getter methods. */
+ private final Method[] getters;
+ /** Last get method used. */
+ private volatile Method get = null;
+ /** The array of setter methods. */
+ private final Method[] setters;
+ /** Last set method used. */
+ private volatile Method set = null;
+
+ /**
+ * Attempts to find an indexed-property getter in an object.
+ * The code attempts to find the list of methods getXXX() and setXXX().
+ * Note that this is not equivalent to the strict bean definition of indexed properties; the type of the key
+ * is not necessarily an int and the set/get arrays are not resolved.
+ *
+ * @param is the introspector
+ * @param object the object
+ * @param name the container name
+ * @return a JexlPropertyGet is successful, null otherwise
+ */
+ public static JexlPropertyGet discover(final Introspector is, final Object object, final String name) {
+ if (object != null && name != null && !name.isEmpty()) {
+ final String base = name.substring(0, 1).toUpperCase() + name.substring(1);
+ final String container = name;
+ final Class> clazz = object.getClass();
+ final Method[] getters = is.getMethods(object.getClass(), "get" + base);
+ final Method[] setters = is.getMethods(object.getClass(), "set" + base);
+ if (getters != null) {
+ return new IndexedType(container, clazz, getters, setters);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * A generic indexed property container, exposes get(key) and set(key, value)
+ * and solves method call dynamically based on arguments.
+ *
Must remain public for introspection purpose.
+ */
+ public static final class IndexedContainer {
+ /** The container instance. */
+ private final Object container;
+ /** The container type instance. */
+ private final IndexedType type;
+
+ /**
+ * Creates a new duck container.
+ * @param theType the container type
+ * @param theContainer the container instance
+ */
+ private IndexedContainer(final IndexedType theType, final Object theContainer) {
+ this.type = theType;
+ this.container = theContainer;
+ }
+
+ /**
+ * Gets the property container name.
+ * @return the container name
+ */
+ public String getContainerName() {
+ return type.container;
+ }
+
+ /**
+ * Gets the property container class.
+ * @return the container class
+ */
+ public Class> getContainerClass() {
+ return type.clazz;
+ }
+
+ /**
+ * Gets a property from this indexed container.
+ * @param key the property key
+ * @return the property value
+ * @throws Exception if inner invocation fails
+ */
+ public Object get(final Object key) throws Exception {
+ return type.invokeGet(container, key);
+ }
+
+ /**
+ * Sets a property in this indexed container.
+ * @param key the property key
+ * @param value the property value
+ * @return the invocation result (frequently null)
+ * @throws Exception if inner invocation fails
+ */
+ public Object set(final Object key, final Object value) throws Exception {
+ return type.invokeSet(container, key, value);
+ }
+ }
+
+ /**
+ * Creates a new indexed property container type.
+ * @param name the container name
+ * @param c the owning class
+ * @param gets the array of getter methods
+ * @param sets the array of setter methods
+ */
+ private IndexedType(final String name, final Class> c, final Method[] gets, final Method[] sets) {
+ this.container = name;
+ this.clazz = c;
+ this.getters = gets;
+ this.setters = sets;
+ }
+
+ @Override
+ public Object invoke(final Object obj) throws Exception {
+ if (obj != null && clazz.equals(obj.getClass())) {
+ return new IndexedContainer(this, obj);
+ }
+ throw new IntrospectionException("property resolution error");
+ }
+
+ @Override
+ public Object tryInvoke(final Object obj, final Object key) {
+ if (obj != null && key != null
+ && clazz.equals(obj.getClass())
+ && container.equals(key.toString())) {
+ return new IndexedContainer(this, obj);
+ }
+ return Uberspect.TRY_FAILED;
+ }
+
+ @Override
+ public boolean tryFailed(final Object rval) {
+ return rval == Uberspect.TRY_FAILED;
+ }
+
+ @Override
+ public boolean isCacheable() {
+ return true;
+ }
+
+ /**
+ * Gets the value of a property from a container.
+ * @param object the container instance (not null)
+ * @param key the property key (not null)
+ * @return the property value
+ * @throws Exception if invocation failed;
+ * IntrospectionException if a property getter could not be found
+ */
+ private Object invokeGet(final Object object, final Object key) throws Exception {
+ if (getters != null && getters.length > 0) {
+ Method jm = get;
+ if (jm != null) {
+ final Class>[] ptypes = jm.getParameterTypes();
+ if (ptypes[0].isAssignableFrom(key.getClass())) {
+ return jm.invoke(object, key);
+ }
+ }
+ final Object[] args = {key};
+ final String mname = getters[0].getName();
+ final MethodKey km = new MethodKey(mname, args);
+ jm = km.getMostSpecificMethod(getters);
+ if (jm != null) {
+ final Object invoked = jm.invoke(object, args);
+ get = jm;
+ return invoked;
+ }
+ }
+ throw new IntrospectionException("property get error: "
+ + object.getClass().toString()
+ + "@" + key.toString());
+ }
+
+ /**
+ * Sets the value of a property in a container.
+ * @param object the container instance (not null)
+ * @param key the property key (not null)
+ * @param value the property value (not null)
+ * @return the result of the method invocation (frequently null)
+ * @throws Exception if invocation failed;
+ * IntrospectionException if a property setter could not be found
+ */
+ private Object invokeSet(final Object object, final Object key, final Object value) throws Exception {
+ if (setters != null && setters.length > 0) {
+ Method jm = set;
+ if (jm != null) {
+ final Class>[] ptypes = jm.getParameterTypes();
+ if (ptypes[0].isAssignableFrom(key.getClass())
+ && (value == null
+ || ptypes[1].isAssignableFrom(value.getClass()))) {
+ return jm.invoke(object, key, value);
+ }
+ }
+ final Object[] args = {key, value};
+ final String mname = setters[0].getName();
+ final MethodKey km = new MethodKey(mname, args);
+ jm = km.getMostSpecificMethod(setters);
+ if (jm != null) {
+ final Object invoked = jm.invoke(object, args);
+ set = jm;
+ return invoked;
+ }
+ }
+ throw new IntrospectionException("property set error: "
+ + object.getClass().toString()
+ + "@" + key.toString());
+ }
+
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/Introspector.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/Introspector.java
new file mode 100644
index 0000000..04fc268
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/Introspector.java
@@ -0,0 +1,392 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import org.apache.commons.logging.Log;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * This basic function of this class is to return a Method object for a
+ * particular class given the name of a method and the parameters to the method
+ * in the form of an Object[].
+ *
+ * The first time the Introspector sees a class it creates a class method map
+ * for the class in question.
+ * Basically the class method map is a Hashtable where Method objects are keyed by the aggregation of
+ * the method name and the array of parameters classes.
+ * This mapping is performed for all the public methods of a class and stored.
+ *
+ * @since 1.0
+ */
+public final class Introspector {
+ /**
+ * A Constructor get cache-miss.
+ */
+ private static class CacheMiss {
+ /** The constructor used as cache-miss. */
+ @SuppressWarnings("unused")
+ public CacheMiss() {
+ }
+ }
+ /**
+ * The cache-miss marker for the constructors map.
+ */
+ private static final Constructor> CTOR_MISS = CacheMiss.class.getConstructors()[0];
+ /**
+ * the logger.
+ */
+ protected final Log logger;
+ /**
+ * The class loader used to solve constructors if needed.
+ */
+ private ClassLoader loader;
+ /**
+ * The permissions.
+ */
+ private final Permissions permissions;
+ /**
+ * The read/write lock.
+ */
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ /**
+ * Holds the method maps for the classes we know about, keyed by Class.
+ */
+ private final Map, ClassMap> classMethodMaps = new HashMap, ClassMap>();
+ /**
+ * Holds the map of classes ctors we know about as well as unknown ones.
+ */
+ private final Map> constructorsMap = new HashMap>();
+ /**
+ * Holds the set of classes we have introspected.
+ */
+ private final Map> constructibleClasses = new HashMap>();
+
+ /**
+ * Create the introspector.
+ * @param log the logger to use
+ * @param cloader the class loader
+ */
+ public Introspector(final Log log, final ClassLoader cloader) {
+ this(log, cloader, null);
+ }
+
+ /**
+ * Create the introspector.
+ * @param log the logger to use
+ * @param cloader the class loader
+ * @param perms the permissions
+ */
+ public Introspector(final Log log, final ClassLoader cloader, final Permissions perms) {
+ this.logger = log;
+ this.loader = cloader;
+ this.permissions = perms != null? perms : Permissions.DEFAULT;
+ }
+
+ /**
+ * Gets a class by name through this introspector class loader.
+ * @param className the class name
+ * @return the class instance or null if it could not be found
+ */
+ public Class> getClassByName(final String className) {
+ try {
+ return Class.forName(className, false, loader);
+ } catch (final ClassNotFoundException xignore) {
+ return null;
+ }
+ }
+
+ /**
+ * Gets a method defined by a class, a name and a set of parameters.
+ * @param c the class
+ * @param name the method name
+ * @param params the method parameters
+ * @return the desired method object
+ * @throws MethodKey.AmbiguousException if no unambiguous method could be found through introspection
+ */
+ public Method getMethod(final Class> c, final String name, final Object[] params) {
+ return getMethod(c, new MethodKey(name, params));
+ }
+
+ /**
+ * Gets the method defined by the MethodKey
for the class c
.
+ *
+ * @param c Class in which the method search is taking place
+ * @param key Key of the method being searched for
+ * @return The desired method object
+ * @throws MethodKey.AmbiguousException if no unambiguous method could be found through introspection
+ */
+ public Method getMethod(final Class> c, final MethodKey key) {
+ try {
+ return getMap(c).getMethod(key);
+ } catch (final MethodKey.AmbiguousException xambiguous) {
+ // whoops. Ambiguous and not benign. Make a nice log message and return null...
+ if (logger != null && xambiguous.isSevere() && logger.isInfoEnabled()) {
+ logger.info("ambiguous method invocation: "
+ + c.getName() + "."
+ + key.debugString(), xambiguous);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Gets the field named by key
for the class c
.
+ *
+ * @param c Class in which the field search is taking place
+ * @param key Name of the field being searched for
+ * @return the desired field or null if it does not exist or is not accessible
+ */
+ public Field getField(final Class> c, final String key) {
+ return getMap(c).getField(key);
+ }
+
+ /**
+ * Gets the array of accessible field names known for a given class.
+ * @param c the class
+ * @return the class field names
+ */
+ public String[] getFieldNames(final Class> c) {
+ if (c == null) {
+ return new String[0];
+ }
+ final ClassMap classMap = getMap(c);
+ return classMap.getFieldNames();
+ }
+
+ /**
+ * Gets the array of accessible methods names known for a given class.
+ * @param c the class
+ * @return the class method names
+ */
+ public String[] getMethodNames(final Class> c) {
+ if (c == null) {
+ return new String[0];
+ }
+ final ClassMap classMap = getMap(c);
+ return classMap.getMethodNames();
+ }
+
+ /**
+ * Gets the array of accessible method known for a given class.
+ * @param c the class
+ * @param methodName the method name
+ * @return the array of methods (null or not empty)
+ */
+ public Method[] getMethods(final Class> c, final String methodName) {
+ if (c == null) {
+ return null;
+ }
+ final ClassMap classMap = getMap(c);
+ return classMap.getMethods(methodName);
+ }
+
+ /**
+ * Gets the constructor defined by the MethodKey
.
+ *
+ * @param key Key of the constructor being searched for
+ * @return The desired constructor object
+ * or null if no unambiguous constructor could be found through introspection.
+ */
+ public Constructor> getConstructor(final MethodKey key) {
+ return getConstructor(null, key);
+ }
+
+ /**
+ * Gets the constructor defined by the MethodKey
.
+ * @param c the class we want to instantiate
+ * @param key Key of the constructor being searched for
+ * @return The desired constructor object
+ * or null if no unambiguous constructor could be found through introspection.
+ */
+ public Constructor> getConstructor(final Class> c, final MethodKey key) {
+ Constructor> ctor;
+ lock.readLock().lock();
+ try {
+ ctor = constructorsMap.get(key);
+ if (ctor != null) {
+ // miss or not?
+ return CTOR_MISS.equals(ctor) ? null : ctor;
+ }
+ } finally {
+ lock.readLock().unlock();
+ }
+ // let's introspect...
+ lock.writeLock().lock();
+ try {
+ // again for kicks
+ ctor = constructorsMap.get(key);
+ if (ctor != null) {
+ // miss or not?
+ return CTOR_MISS.equals(ctor) ? null : ctor;
+ }
+ final String cname = key.getMethod();
+ // do we know about this class?
+ Class> clazz = constructibleClasses.get(cname);
+ try {
+ // do find the most specific ctor
+ if (clazz == null) {
+ if (c != null && c.getName().equals(key.getMethod())) {
+ clazz = c;
+ } else {
+ clazz = loader.loadClass(cname);
+ }
+ // add it to list of known loaded classes
+ constructibleClasses.put(cname, clazz);
+ }
+ final List> l = new ArrayList<>();
+ for (final Constructor> ictor : clazz.getConstructors()) {
+ if (permissions.allow(ictor)) {
+ l.add(ictor);
+ }
+ }
+ // try to find one
+ ctor = key.getMostSpecificConstructor(l.toArray(new Constructor>[l.size()]));
+ if (ctor != null) {
+ constructorsMap.put(key, ctor);
+ } else {
+ constructorsMap.put(key, CTOR_MISS);
+ }
+ } catch (final ClassNotFoundException xnotfound) {
+ if (logger != null && logger.isDebugEnabled()) {
+ logger.debug("unable to find class: "
+ + cname + "."
+ + key.debugString(), xnotfound);
+ }
+ ctor = null;
+ } catch (final MethodKey.AmbiguousException xambiguous) {
+ if (logger != null && xambiguous.isSevere() && logger.isInfoEnabled()) {
+ logger.info("ambiguous constructor invocation: "
+ + cname + "."
+ + key.debugString(), xambiguous);
+ }
+ ctor = null;
+ }
+ return ctor;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Gets the ClassMap for a given class.
+ * @param c the class
+ * @return the class map
+ */
+ private ClassMap getMap(final Class> c) {
+ ClassMap classMap;
+ lock.readLock().lock();
+ try {
+ classMap = classMethodMaps.get(c);
+ } finally {
+ lock.readLock().unlock();
+ }
+ if (classMap == null) {
+ lock.writeLock().lock();
+ try {
+ // try again
+ classMap = classMethodMaps.get(c);
+ if (classMap == null) {
+ classMap = new ClassMap(c, permissions, logger);
+ classMethodMaps.put(c, classMap);
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+
+ }
+ return classMap;
+ }
+
+ /**
+ * Sets the class loader used to solve constructors.
+ * Also cleans the constructors and methods caches.
+ * @param cloader the class loader; if null, use this instance class loader
+ */
+ public void setLoader(ClassLoader cloader) {
+ final ClassLoader previous = loader;
+ if (cloader == null) {
+ cloader = getClass().getClassLoader();
+ }
+ if (!cloader.equals(loader)) {
+ lock.writeLock().lock();
+ try {
+ // clean up constructor and class maps
+ final Iterator>> mentries = constructorsMap.entrySet().iterator();
+ while (mentries.hasNext()) {
+ final Map.Entry> entry = mentries.next();
+ final Class> clazz = entry.getValue().getDeclaringClass();
+ if (isLoadedBy(previous, clazz)) {
+ mentries.remove();
+ // the method name is the name of the class
+ constructibleClasses.remove(entry.getKey().getMethod());
+ }
+ }
+ // clean up method maps
+ final Iterator, ClassMap>> centries = classMethodMaps.entrySet().iterator();
+ while (centries.hasNext()) {
+ final Map.Entry, ClassMap> entry = centries.next();
+ final Class> clazz = entry.getKey();
+ if (isLoadedBy(previous, clazz)) {
+ centries.remove();
+ }
+ }
+ loader = cloader;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+ }
+
+ /**
+ * Gets the class loader used by this introspector.
+ * @return the class loader
+ */
+ public ClassLoader getLoader() {
+ return loader;
+ }
+
+ /**
+ * Checks whether a class is loaded through a given class loader or one of its ascendants.
+ * @param loader the class loader
+ * @param clazz the class to check
+ * @return true if clazz was loaded through the loader, false otherwise
+ */
+ private static boolean isLoadedBy(final ClassLoader loader, final Class> clazz) {
+ if (loader != null) {
+ ClassLoader cloader = clazz.getClassLoader();
+ while (cloader != null) {
+ if (cloader.equals(loader)) {
+ return true;
+ }
+ cloader = cloader.getParent();
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ListGetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ListGetExecutor.java
new file mode 100644
index 0000000..b16a7be
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ListGetExecutor.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.util.List;
+import java.lang.reflect.Array;
+
+/**
+ * Specialized executor to get a property from a List or array.
+ * @since 2.0
+ */
+public final class ListGetExecutor extends AbstractExecutor.Get {
+ /** The java.lang.reflect.Array.get method used as an active marker in ListGet. */
+ private static final java.lang.reflect.Method ARRAY_GET =
+ initMarker(Array.class, "get", Object.class, Integer.TYPE);
+ /** The java.util.obj.get method used as an active marker in ListGet. */
+ private static final java.lang.reflect.Method LIST_GET =
+ initMarker(List.class, "get", Integer.TYPE);
+ /** The property. */
+ private final Integer property;
+
+ /**
+ * Attempts to discover a ListGetExecutor.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param index the index to use as an argument to the get method
+ * @return the executor if found, null otherwise
+ */
+ public static ListGetExecutor discover(final Introspector is, final Class> clazz, final Integer index) {
+ if (index != null) {
+ if (clazz.isArray()) {
+ return new ListGetExecutor(clazz, ARRAY_GET, index);
+ }
+ if (List.class.isAssignableFrom(clazz)) {
+ return new ListGetExecutor(clazz, LIST_GET, index);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates an instance.
+ * @param clazz he class the get method applies to
+ * @param method the method held by this executor
+ * @param index the index to use as an argument to the get method
+ */
+ private ListGetExecutor(final Class> clazz, final java.lang.reflect.Method method, final Integer index) {
+ super(clazz, method);
+ property = index;
+ }
+
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ @Override
+ public Object invoke(final Object obj) {
+ if (method == ARRAY_GET) {
+ return Array.get(obj, property);
+ }
+ return ((List>) obj).get(property);
+ }
+
+ @Override
+ public Object tryInvoke(final Object obj, final Object identifier) {
+ final Integer index = castInteger(identifier);
+ if (obj != null && method != null
+ && objectClass.equals(obj.getClass())
+ && index != null) {
+ if (method == ARRAY_GET) {
+ return Array.get(obj, index);
+ }
+ return ((List>) obj).get(index);
+ }
+ return TRY_FAILED;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ListSetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ListSetExecutor.java
new file mode 100644
index 0000000..b75fc51
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/ListSetExecutor.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.util.List;
+import java.lang.reflect.Array;
+
+/**
+ * Specialized executor to set a property in a List or array.
+ * @since 2.0
+ */
+public final class ListSetExecutor extends AbstractExecutor.Set {
+ /** The java.lang.reflect.Array.get method used as an active marker in ListGet. */
+ private static final java.lang.reflect.Method ARRAY_SET =
+ initMarker(Array.class, "set", Object.class, Integer.TYPE, Object.class);
+ /** The java.util.obj.set method used as an active marker in ListSet. */
+ private static final java.lang.reflect.Method LIST_SET =
+ initMarker(List.class, "set", Integer.TYPE, Object.class);
+ /** The property. */
+ private final Integer property;
+
+ /**
+ * Attempts to discover a ListSetExecutor.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param identifier the key to use as an argument to the get method
+ * @param value the value to use as argument in list.put(key,value)
+ * @return the executor if found, null otherwise
+ */
+ public static ListSetExecutor discover(final Introspector is,
+ final Class> clazz,
+ final Object identifier,
+ final Object value) {
+ final Integer index = castInteger(identifier);
+ if (index != null) {
+ if (clazz.isArray()) {
+ // we could verify if the call can be performed but it does not change
+ // the fact we would fail...
+ // Class> formal = clazz.getComponentType();
+ // Class> actual = value == null? Object.class : value.getClass();
+ // if (IntrospectionUtils.isMethodInvocationConvertible(formal, actual, false)) {
+ return new ListSetExecutor(clazz, ARRAY_SET, index);
+ // }
+ }
+ if (List.class.isAssignableFrom(clazz)) {
+ return new ListSetExecutor(clazz, LIST_SET, index);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * @param clazz the class the set method applies to
+ * @param method the method called through this executor
+ * @param key the key to use as 1st argument to the set method
+ */
+ private ListSetExecutor(final Class> clazz, final java.lang.reflect.Method method, final Integer key) {
+ super(clazz, method);
+ property = key;
+ }
+
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ @Override
+ public Object invoke(final Object obj, final Object value) {
+ if (method == ARRAY_SET) {
+ Array.set(obj, property, value);
+ } else {
+ @SuppressWarnings("unchecked") // LSE should only be created for array or list types
+ final List list = (List) obj;
+ list.set(property, value);
+ }
+ return value;
+ }
+
+ @Override
+ public Object tryInvoke(final Object obj, final Object key, final Object value) {
+ final Integer index = castInteger(key);
+ if (obj != null && method != null
+ && objectClass.equals(obj.getClass())
+ && index != null) {
+ if (method == ARRAY_SET) {
+ Array.set(obj, index, value);
+ } else {
+ @SuppressWarnings("unchecked") // LSE should only be created for array or list types
+ final List list = (List) obj;
+ list.set(index, value);
+ }
+ return value;
+ }
+ return TRY_FAILED;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MapGetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MapGetExecutor.java
new file mode 100644
index 0000000..6517159
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MapGetExecutor.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.util.Map;
+
+/**
+ * Specialized executor to get a property from a Map.
+ * @since 2.0
+ */
+public final class MapGetExecutor extends AbstractExecutor.Get {
+ /** The java.util.map.get method used as an active marker in MapGet. */
+ private static final java.lang.reflect.Method MAP_GET =
+ initMarker(Map.class, "get", Object.class);
+ /** The property. */
+ private final Object property;
+
+ /**
+ * Attempts to discover a MapGetExecutor.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param identifier the key to use as an argument to the get method
+ * @return the executor if found, null otherwise
+ */
+ public static MapGetExecutor discover(final Introspector is, final Class> clazz, final Object identifier) {
+ if (Map.class.isAssignableFrom(clazz)) {
+ return new MapGetExecutor(clazz, MAP_GET, identifier);
+ }
+ return null;
+ }
+
+ /**
+ * Creates an instance.
+ * @param clazz he class the get method applies to
+ * @param method the method held by this executor
+ * @param key the property to get
+ */
+ private MapGetExecutor(final Class> clazz, final java.lang.reflect.Method method, final Object key) {
+ super(clazz, method);
+ property = key;
+ }
+
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ @Override
+ public Object invoke(final Object obj) {
+ @SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
+ final Map map = (Map) obj;
+ return map.get(property);
+ }
+
+ @Override
+ public Object tryInvoke(final Object obj, final Object key) {
+ if (obj != null
+ && method != null
+ && objectClass.equals(obj.getClass())
+ && ((property == null && key == null)
+ || (property != null && key != null && property.getClass().equals(key.getClass())))) {
+ @SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
+ final Map map = (Map) obj;
+ return map.get(key);
+ }
+ return TRY_FAILED;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MapSetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MapSetExecutor.java
new file mode 100644
index 0000000..6707b21
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MapSetExecutor.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+import java.util.Map;
+
+/**
+ * Specialized executor to set a property in a Map.
+ * @since 2.0
+ */
+public final class MapSetExecutor extends AbstractExecutor.Set {
+ /** The java.util.map.put method used as an active marker in MapSet. */
+ private static final java.lang.reflect.Method MAP_SET = initMarker(Map.class, "put", Object.class, Object.class);
+ /** The property. */
+ private final Object property;
+ /** The property value class. */
+ private final Class> valueClass;
+
+ /**
+ * Attempts to discover a MapSetExecutor.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the set method from
+ * @param identifier the key to use as an argument to the get method
+ * @param value the value to use as argument in map.put(key,value)
+ * @return the executor if found, null otherwise
+ */
+ public static MapSetExecutor discover(final Introspector is,
+ final Class> clazz,
+ final Object identifier,
+ final Object value) {
+ if (Map.class.isAssignableFrom(clazz)) {
+ return new MapSetExecutor(clazz, MAP_SET, identifier, value);
+ }
+ return null;
+ }
+
+ /**
+ * Creates an instance.
+ * @param clazz the class the set method applies to
+ * @param method the method called through this executor
+ * @param key the key to use as 1st argument to the set method
+ * @param value the value to use as 2nd argument to the set method
+ */
+ private MapSetExecutor(final Class> clazz, final java.lang.reflect.Method method, final Object key, final Object value) {
+ super(clazz, method);
+ property = key;
+ valueClass = classOf(value);
+ }
+
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ @Override
+ public Object invoke(final Object obj, final Object value) {
+ @SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
+ final Map map = ((Map) obj);
+ map.put(property, value);
+ return value;
+ }
+
+ @Override
+ public Object tryInvoke(final Object obj, final Object key, final Object value) {
+ if (obj != null
+ && method != null
+ && objectClass.equals(obj.getClass())
+ && ((property == null && key == null)
+ || (property != null && key != null && property.getClass().equals(key.getClass())))
+ && valueClass.equals(classOf(value))) {
+ @SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
+ final Map map = ((Map) obj);
+ map.put(key, value);
+ return value;
+ }
+ return TRY_FAILED;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
new file mode 100644
index 0000000..863963d
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlEngine;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Specialized executor to invoke a method on an object.
+ * @since 2.0
+ */
+public final class MethodExecutor extends AbstractExecutor.Method {
+ /** If this method is a vararg method, vaStart is the last argument index. */
+ private final int vaStart;
+ /** If this method is a vararg method, vaClass is the component type of the vararg array. */
+ private final Class> vaClass;
+
+ /**
+ * Discovers a {@link MethodExecutor}.
+ *
+ * If the object is an array, an attempt will be made to find the
+ * method in a List (see {@link ArrayListWrapper})
+ *
+ *
+ * If the object is a class, an attempt will be made to find the
+ * method as a static method of that class.
+ *
+ * @param is the introspector used to discover the method
+ * @param obj the object to introspect
+ * @param method the name of the method to find
+ * @param args the method arguments
+ * @return a filled up parameter (may contain a null method)
+ */
+ public static MethodExecutor discover(final Introspector is, final Object obj, final String method, final Object[] args) {
+ final Class> clazz = obj.getClass();
+ final MethodKey key = new MethodKey(method, args);
+ java.lang.reflect.Method m = is.getMethod(clazz, key);
+ if (m == null && clazz.isArray()) {
+ // check for support via our array->list wrapper
+ m = is.getMethod(ArrayListWrapper.class, key);
+ }
+ if (m == null && obj instanceof Class>) {
+ m = is.getMethod((Class>) obj, key);
+ }
+ return m == null? null : new MethodExecutor(clazz, m, key);
+ }
+
+ /**
+ * Creates a new instance.
+ * @param c the class this executor applies to
+ * @param m the method
+ * @param k the MethodKey
+ */
+ private MethodExecutor(final Class> c, final java.lang.reflect.Method m, final MethodKey k) {
+ super(c, m, k);
+ int vastart = -1;
+ Class> vaclass = null;
+ if (MethodKey.isVarArgs(method)) {
+ // if the last parameter is an array, the method is considered as vararg
+ final Class>[] formal = method.getParameterTypes();
+ vastart = formal.length - 1;
+ vaclass = formal[vastart].getComponentType();
+ }
+ vaStart = vastart;
+ vaClass = vaclass;
+ }
+
+ @Override
+ public Object invoke(final Object o, Object... args) throws IllegalAccessException, InvocationTargetException {
+ if (vaClass != null && args != null) {
+ args = handleVarArg(args);
+ }
+ if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) {
+ return method.invoke(new ArrayListWrapper(o), args);
+ }
+ return method.invoke(o, args);
+ }
+
+ @Override
+ public Object tryInvoke(final String name, final Object obj, final Object... args) {
+ final MethodKey tkey = new MethodKey(name, args);
+ // let's assume that invocation will fly if the declaring class is the
+ // same and arguments have the same type
+ if (objectClass.equals(obj.getClass()) && tkey.equals(key)) {
+ try {
+ return invoke(obj, args);
+ } catch (IllegalAccessException | IllegalArgumentException xill) {
+ return TRY_FAILED;// fail
+ } catch (final InvocationTargetException xinvoke) {
+ throw JexlException.tryFailed(xinvoke); // throw
+ }
+ }
+ return JexlEngine.TRY_FAILED;
+ }
+
+
+ /**
+ * Reassembles arguments if the method is a vararg method.
+ * @param actual The actual arguments being passed to this method
+ * @return The actual parameters adjusted for the varargs in order
+ * to fit the method declaration.
+ */
+ @SuppressWarnings("SuspiciousSystemArraycopy")
+ private Object[] handleVarArg(Object[] actual) {
+ final Class> vaclass = vaClass;
+ final int vastart = vaStart;
+ // variable arguments count
+ final int varargc = actual.length - vastart;
+ // if no values are being passed into the vararg, size == 0
+ if (varargc == 1) {
+ // if one non-null value is being passed into the vararg,
+ // and that arg is not the sole argument and not an array of the expected type,
+ // make the last arg an array of the expected type
+ if (actual[vastart] != null) {
+ final Class> aclazz = actual[vastart].getClass();
+ if (!aclazz.isArray() || !vaclass.isAssignableFrom(aclazz.getComponentType())) {
+ // create a 1-length array to hold and replace the last argument
+ final Object lastActual = Array.newInstance(vaclass, 1);
+ Array.set(lastActual, 0, actual[vastart]);
+ actual[vastart] = lastActual;
+ }
+ }
+ // else, the vararg is null and used as is, considered as T[]
+ } else {
+ // if no or multiple values are being passed into the vararg,
+ // put them in an array of the expected type
+ final Object varargs = Array.newInstance(vaclass, varargc);
+ System.arraycopy(actual, vastart, varargs, 0, varargc);
+ // put all arguments into a new actual array of the appropriate size
+ final Object[] newActual = new Object[vastart + 1];
+ System.arraycopy(actual, 0, newActual, 0, vastart);
+ newActual[vastart] = varargs;
+ // replace the old actual array
+ actual = newActual;
+ }
+ return actual;
+ }
+}
+
+
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MethodKey.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MethodKey.java
new file mode 100644
index 0000000..00fcaaf
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/MethodKey.java
@@ -0,0 +1,840 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.*;
+
+/**
+ * A method key usable by the introspector cache.
+ *
+ * This stores a method (or class) name and parameters.
+ *
+ *
+ * This replaces the original key scheme which used to build the key
+ * by concatenating the method name and parameters class names as one string
+ * with the exception that primitive types were converted to their object class equivalents.
+ *
+ *
+ * The key is still based on the same information, it is just wrapped in an object instead.
+ * Primitive type classes are converted to they object equivalent to make a key;
+ * int foo(int) and int foo(Integer) do generate the same key.
+ *
+ * A key can be constructed either from arguments (array of objects) or from parameters
+ * (array of class).
+ * Roughly 3x faster than string key to access the map and uses less memory.
+ */
+public final class MethodKey {
+ /** The initial size of the primitive conversion map. */
+ private static final int PRIMITIVE_SIZE = 11;
+ /** The hash code. */
+ private final int hashCode;
+ /** The method name. */
+ private final String method;
+ /** The parameters. */
+ private final Class>[] params;
+ /** A marker for empty parameter list. */
+ private static final Class>[] NOARGS = new Class>[0];
+ /** The hash code constants. */
+ private static final int HASH = 37;
+
+ /**
+ * Creates a key from a method name and a set of arguments.
+ *
+ * @param aMethod the method to generate the key from
+ * @param args the intended method arguments
+ */
+ public MethodKey(final String aMethod, final Object[] args) {
+ // !! keep this in sync with the other ctor (hash code) !!
+ this.method = aMethod;
+ int hash = this.method.hashCode();
+ final int size;
+ // CSOFF: InnerAssignment
+ if (args != null && (size = args.length) > 0) {
+ this.params = new Class>[size];
+ for (int p = 0; p < size; ++p) {
+ final Object arg = args[p];
+ // null arguments use void as Void.class as marker
+ final Class> parm = arg == null ? Void.class : arg.getClass();
+ hash = (HASH * hash) + parm.hashCode();
+ this.params[p] = parm;
+ }
+ } else {
+ this.params = NOARGS;
+ }
+ this.hashCode = hash;
+ }
+
+ /**
+ * Creates a key from a method.
+ *
+ * @param aMethod the method to generate the key from.
+ */
+ MethodKey(final Method aMethod) {
+ this(aMethod.getName(), aMethod.getParameterTypes());
+ }
+
+ /**
+ * Creates a key from a constructor.
+ *
+ * @param aCtor the constructor to generate the key from.
+ */
+ MethodKey(final Constructor> aCtor) {
+ this(aCtor.getDeclaringClass().getName(), aCtor.getParameterTypes());
+ }
+
+ /**
+ * Creates a key from a method name and a set of parameters.
+ *
+ * @param aMethod the method to generate the key from
+ * @param args the intended method parameters
+ */
+ MethodKey(final String aMethod, final Class>[] args) {
+ // !! keep this in sync with the other ctor (hash code) !!
+ this.method = aMethod.intern();
+ int hash = this.method.hashCode();
+ final int size;
+ // CSOFF: InnerAssignment
+ if (args != null && (size = args.length) > 0) {
+ this.params = new Class>[size];
+ for (int p = 0; p < size; ++p) {
+ final Class> parm = primitiveClass(args[p]);
+ hash = (HASH * hash) + parm.hashCode();
+ this.params[p] = parm;
+ }
+ } else {
+ this.params = NOARGS;
+ }
+ this.hashCode = hash;
+ }
+
+ /**
+ * Gets this key's method name.
+ *
+ * @return the method name
+ */
+ String getMethod() {
+ return method;
+ }
+
+ /**
+ * Gets this key's method parameter classes.
+ *
+ * @return the parameters
+ */
+ Class>[] getParameters() {
+ return params;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof MethodKey) {
+ final MethodKey key = (MethodKey) obj;
+ return method.equals(key.method) && Arrays.equals(params, key.params);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder(method);
+ for (final Class> c : params) {
+ builder.append(c == Void.class ? "null" : c.getName());
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Outputs a human readable debug representation of this key.
+ *
+ * @return method(p0, p1, ...)
+ */
+ public String debugString() {
+ final StringBuilder builder = new StringBuilder(method);
+ builder.append('(');
+ for (int i = 0; i < params.length; i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ builder.append(Void.class == params[i] ? "null" : params[i].getName());
+ }
+ builder.append(')');
+ return builder.toString();
+ }
+
+ /**
+ * Checks whether a method accepts a variable number of arguments.
+ * May be due to a subtle bug in some JVMs, if a varargs method is an override, depending on (may be) the
+ * class introspection order, the isVarargs flag on the method itself will be false.
+ * To circumvent the potential problem, fetch the method with the same signature from the super-classes,
+ * - which will be different if override -and get the varargs flag from it.
+ *
+ * @param method the method to check for varargs
+ * @return true if declared varargs, false otherwise
+ */
+ public static boolean isVarArgs(final Method method) {
+ if (method == null) {
+ return false;
+ }
+ if (method.isVarArgs()) {
+ return true;
+ }
+ // before climbing up the hierarchy, verify that the last parameter is an array
+ final Class>[] ptypes = method.getParameterTypes();
+ if (ptypes.length == 0 || ptypes[ptypes.length - 1].getComponentType() == null) {
+ return false;
+ }
+ final String mname = method.getName();
+ // if this is an override, was it actually declared as varargs?
+ Class> clazz = method.getDeclaringClass();
+ do {
+ try {
+ final Method m = clazz.getMethod(mname, ptypes);
+ if (m.isVarArgs()) {
+ return true;
+ }
+ } catch (final NoSuchMethodException xignore) {
+ // this should not happen...
+ }
+ clazz = clazz.getSuperclass();
+ } while (clazz != null);
+ return false;
+ }
+
+ /**
+ * Gets the most specific method that is applicable to the parameters of this key.
+ *
+ * @param methods a list of methods.
+ * @return the most specific method.
+ * @throws AmbiguousException if there is more than one.
+ */
+ public Method getMostSpecificMethod(final Method[] methods) {
+ return METHODS.getMostSpecific(this, methods);
+ }
+
+ /**
+ * Gets the most specific constructor that is applicable to the parameters of this key.
+ *
+ * @param methods a list of constructors.
+ * @return the most specific constructor.
+ * @throws AmbiguousException if there is more than one.
+ */
+ public Constructor> getMostSpecificConstructor(final Constructor>[] methods) {
+ return CONSTRUCTORS.getMostSpecific(this, methods);
+ }
+
+ /**
+ * Determines whether a type represented by a class object is
+ * convertible to another type represented by a class object using a
+ * method invocation conversion, treating object types of primitive
+ * types as if they were primitive types (that is, a Boolean actual
+ * parameter type matches boolean primitive formal type). This behavior
+ * is because this method is used to determine applicable methods for
+ * an actual parameter list, and primitive types are represented by
+ * their object duals in reflective method calls.
+ *
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @param possibleVarArg whether or not we're dealing with the last parameter
+ * in the method declaration
+ * @return true if either formal type is assignable from actual type,
+ * or formal is a primitive type and actual is its corresponding object
+ * type or an object type of a primitive type that can be converted to
+ * the formal type.
+ */
+ public static boolean isInvocationConvertible(final Class> formal,
+ final Class> actual,
+ final boolean possibleVarArg) {
+ return isInvocationConvertible(formal, actual, false, possibleVarArg);
+ }
+
+ /**
+ * Determines whether a type represented by a class object is
+ * convertible to another type represented by a class object using a
+ * method invocation conversion, without matching object and primitive
+ * types. This method is used to determine the more specific type when
+ * comparing signatures of methods.
+ *
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @param possibleVarArg whether or not we're dealing with the last parameter
+ * in the method declaration
+ * @return true if either formal type is assignable from actual type,
+ * or formal and actual are both primitive types and actual can be
+ * subject to widening conversion to formal.
+ */
+ public static boolean isStrictInvocationConvertible(final Class> formal,
+ final Class> actual,
+ final boolean possibleVarArg) {
+ return isInvocationConvertible(formal, actual, true, possibleVarArg);
+ }
+
+ /**
+ * Converts a primitive type to its corresponding class.
+ *
+ * If the argument type is primitive then we want to convert our
+ * primitive type signature to the corresponding Object type so
+ * introspection for methods with primitive types will work
+ * correctly.
+ *
+ *
+ * @param parm a may-be primitive type class
+ * @return the equivalent object class
+ */
+ static Class> primitiveClass(final Class> parm) {
+ // it was marginally faster to get from the map than call isPrimitive...
+ // if (!parm.isPrimitive()) return parm;
+ final Class>[] prim = CONVERTIBLES.get(parm);
+ return prim == null ? parm : prim[0];
+ }
+
+ /**
+ * Helper to build class arrays.
+ *
+ * @param args the classes
+ * @return the array
+ */
+ private static Class>[] asArray(final Class>... args) {
+ return args;
+ }
+
+ /**
+ * Maps from primitive types to invocation compatible classes.
+ * Considering the key as a parameter type, the value is the list of argument classes that are invocation
+ * compatible with the parameter. Example is Long is invocation convertible to long.
+ */
+ private static final Map, Class>[]> CONVERTIBLES;
+
+ static {
+ CONVERTIBLES = new HashMap, Class>[]>(PRIMITIVE_SIZE);
+ CONVERTIBLES.put(Boolean.TYPE,
+ asArray(Boolean.class));
+ CONVERTIBLES.put(Character.TYPE,
+ asArray(Character.class));
+ CONVERTIBLES.put(Byte.TYPE,
+ asArray(Byte.class));
+ CONVERTIBLES.put(Short.TYPE,
+ asArray(Short.class, Byte.class));
+ CONVERTIBLES.put(Integer.TYPE,
+ asArray(Integer.class, Short.class, Byte.class));
+ CONVERTIBLES.put(Long.TYPE,
+ asArray(Long.class, Integer.class, Short.class, Byte.class));
+ CONVERTIBLES.put(Float.TYPE,
+ asArray(Float.class, Long.class, Integer.class, Short.class, Byte.class));
+ CONVERTIBLES.put(Double.TYPE,
+ asArray(Double.class, Float.class, Long.class, Integer.class, Short.class, Byte.class));
+ }
+
+ /**
+ * Maps from primitive types to invocation compatible primitive types.
+ * Considering the key as a parameter type, the value is the list of argument types that are invocation
+ * compatible with the parameter. Example is 'int' is invocation convertible to 'long'.
+ */
+ private static final Map, Class>[]> STRICT_CONVERTIBLES;
+
+ static {
+ STRICT_CONVERTIBLES = new HashMap, Class>[]>(PRIMITIVE_SIZE);
+ STRICT_CONVERTIBLES.put(Short.TYPE,
+ asArray(Byte.TYPE));
+ STRICT_CONVERTIBLES.put(Integer.TYPE,
+ asArray(Short.TYPE, Byte.TYPE));
+ STRICT_CONVERTIBLES.put(Long.TYPE,
+ asArray(Integer.TYPE, Short.TYPE, Byte.TYPE));
+ STRICT_CONVERTIBLES.put(Float.TYPE,
+ asArray(Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE));
+ STRICT_CONVERTIBLES.put(Double.TYPE,
+ asArray(Float.TYPE, Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE));
+ }
+
+ /**
+ * Determines parameter-argument invocation compatibility.
+ *
+ * @param formal the formal parameter type
+ * @param actual the argument type
+ * @param strict whether the check is strict or not
+ * @param possibleVarArg whether or not we're dealing with the last parameter in the method declaration
+ * @return true if compatible, false otherwise
+ */
+ private static boolean isInvocationConvertible(
+ final Class> formal, Class> actual, final boolean strict, final boolean possibleVarArg) {
+ /* if it's a null, it means the arg was null */
+ if (actual == null && !formal.isPrimitive()) {
+ return true;
+ }
+ /* system asssignable, both sides must be array or not */
+ if (actual != null && formal.isAssignableFrom(actual) && actual.isArray() == formal.isArray()) {
+ return true;
+ }
+ /* catch all... */
+ if (!strict && formal == Object.class) {
+ return true;
+ }
+ /* Primitive conversion check. */
+ if (formal.isPrimitive()) {
+ final Class>[] clist = strict ? STRICT_CONVERTIBLES.get(formal) : CONVERTIBLES.get(formal);
+ if (clist != null) {
+ for (final Class> aClass : clist) {
+ if (actual == aClass) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ /* Check for vararg conversion. */
+ if (possibleVarArg && formal.isArray()) {
+ if (actual != null && actual.isArray()) {
+ actual = actual.getComponentType();
+ }
+ return isInvocationConvertible(formal.getComponentType(), actual, strict, false);
+ }
+ return false;
+ }
+
+ /**
+ * whether a method/ctor is more specific than a previously compared one.
+ */
+ private static final int MORE_SPECIFIC = 0;
+ /**
+ * whether a method/ctor is less specific than a previously compared one.
+ */
+ private static final int LESS_SPECIFIC = 1;
+ /**
+ * A method/ctor doesn't match a previously compared one.
+ */
+ private static final int INCOMPARABLE = 2;
+
+ /**
+ * Simple distinguishable exception, used when
+ * we run across ambiguous overloading. Caught
+ * by the introspector.
+ */
+ public static class AmbiguousException extends RuntimeException {
+ /** Version Id for serializable. */
+ private static final long serialVersionUID = -201801091655L;
+ /** Whether this exception should be considered severe. */
+ private final boolean severe;
+
+ /**
+ * A severe or not ambiguous exception.
+ *
+ * @param flag logging flag
+ */
+ AmbiguousException(final boolean flag) {
+ this.severe = flag;
+ }
+
+ /**
+ * Whether this exception is considered severe or benign.
+ * Note that this is meant in the context of an ambiguous exception; benign cases can only be triggered
+ * by null arguments often related to runtime problems (not simply on overload signatures).
+ *
+ * @return true if severe, false if benign.
+ */
+ public boolean isSevere() {
+ return severe;
+ }
+ }
+
+ /**
+ * Utility for parameters matching.
+ *
+ * @param Method or Constructor
+ */
+ private abstract static class Parameters {
+ /**
+ * Extract the parameter types from its applicable argument.
+ *
+ * @param app a method or constructor
+ * @return the parameters
+ */
+ protected abstract Class>[] getParameterTypes(T app);
+
+ /**
+ * Whether a constructor or method handles varargs.
+ *
+ * @param app the constructor or method
+ * @return true if varargs, false otherwise
+ */
+ protected abstract boolean isVarArgs(T app);
+
+ // CSOFF: RedundantThrows
+
+ /**
+ * Gets the most specific method that is applicable to actual argument types.
+ * Attempts to find the most specific applicable method using the
+ * algorithm described in the JLS section 15.12.2 (with the exception that it can't
+ * distinguish a primitive type argument from an object type argument, since in reflection
+ * primitive type arguments are represented by their object counterparts, so for an argument of
+ * type (say) java.lang.Integer, it will not be able to decide between a method that takes int and a
+ * method that takes java.lang.Integer as a parameter.
+ *
+ *
+ * This turns out to be a relatively rare case where this is needed - however, functionality
+ * like this is needed.
+ *
+ *
+ * @param key a method key, esp its parameters
+ * @param methods a list of methods
+ * @return the most specific method.
+ * @throws AmbiguousException if there is more than one.
+ */
+ private T getMostSpecific(final MethodKey key, final T[] methods) {
+ final Class>[] args = key.params;
+ final LinkedList applicables = getApplicables(methods, args);
+ if (applicables.isEmpty()) {
+ return null;
+ }
+
+ if (applicables.size() == 1) {
+ return applicables.getFirst();
+ }
+
+ /*
+ * This list will contain the maximally specific methods. Hopefully at
+ * the end of the below loop, the list will contain exactly one method,
+ * (the most specific method) otherwise we have ambiguity.
+ */
+ final LinkedList maximals = new LinkedList();
+ for (final T app : applicables) {
+ final Class>[] parms = getParameterTypes(app);
+ boolean lessSpecific = false;
+ final Iterator maximal = maximals.iterator();
+ while (!lessSpecific && maximal.hasNext()) {
+ final T max = maximal.next();
+ switch (moreSpecific(args, parms, getParameterTypes(max))) {
+ case MORE_SPECIFIC:
+ /*
+ * This method is more specific than the previously
+ * known maximally specific, so remove the old maximum.
+ */
+ maximal.remove();
+ break;
+
+ case LESS_SPECIFIC:
+ /*
+ * This method is less specific than some of the
+ * currently known maximally specific methods, so we
+ * won't add it into the set of maximally specific
+ * methods
+ */
+
+ lessSpecific = true;
+ break;
+ default:
+ // nothing do do
+ }
+ }
+
+ if (!lessSpecific) {
+ maximals.addLast(app);
+ }
+ }
+ // if we have more than one maximally specific method, this call is ambiguous...
+ if (maximals.size() > 1) {
+ throw ambiguousException(args, applicables);
+ }
+ return maximals.getFirst();
+ } // CSON: RedundantThrows
+
+ /**
+ * Creates an ambiguous exception.
+ *
+ * This method computes the severity of the ambiguity. The only non-severe case is when there is
+ * at least one null argument and at most one applicable method or constructor has a corresponding 'Object'
+ * parameter.
+ * We thus consider that ambiguity is benign in presence of null arguments but in the case where
+ * the corresponding parameter is of type Object in more than one applicable overloads.
+ *
+ * Rephrasing:
+ *
+ * If all arguments are valid instances - no null argument -, ambiguity is severe.
+ * If there is at least one null argument, the ambiguity is severe if more than one method has a
+ * corresponding parameter of class 'Object'.
+ *
+ *
+ * @param classes the argument args
+ * @param applicables the list of applicable methods or constructors
+ * @return an ambiguous exception
+ */
+ private AmbiguousException ambiguousException(final Class>[] classes, final List applicables) {
+ boolean severe = false;
+ int instanceArgCount = 0; // count the number of valid instances, aka not null
+ for (int c = 0; c < classes.length; ++c) {
+ final Class> argClazz = classes[c];
+ if (Void.class.equals(argClazz)) {
+ // count the number of methods for which the current arg maps to an Object parameter
+ int objectParmCount = 0;
+ for (final T app : applicables) {
+ final Class>[] parmClasses = getParameterTypes(app);
+ final Class> parmClass = parmClasses[c];
+ if (Object.class.equals(parmClass) && (objectParmCount++ == 2)) {
+ severe = true;
+ break;
+ }
+ }
+ } else {
+ instanceArgCount += 1;
+ }
+ }
+ return new AmbiguousException(severe || instanceArgCount == classes.length);
+ }
+
+ /**
+ * Determines which method signature (represented by a class array) is more
+ * specific. This defines a partial ordering on the method signatures.
+ *
+ * @param a the arguments signature
+ * @param c1 first method signature to compare
+ * @param c2 second method signature to compare
+ * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
+ * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
+ */
+ private int moreSpecific(final Class>[] a, final Class>[] c1, final Class>[] c2) {
+ // compare lengths to handle comparisons where the size of the arrays
+ // doesn't match, but the methods are both applicable due to the fact
+ // that one is a varargs method
+ if (c1.length > a.length) {
+ return LESS_SPECIFIC;
+ }
+ if (c2.length > a.length) {
+ return MORE_SPECIFIC;
+ }
+ if (c1.length > c2.length) {
+ return MORE_SPECIFIC;
+ }
+ if (c2.length > c1.length) {
+ return LESS_SPECIFIC;
+ }
+ // same length, keep ultimate param offset for vararg checks
+ final int length = c1.length;
+ final int ultimate = c1.length - 1;
+
+ // ok, move on and compare those of equal lengths
+ for (int i = 0; i < length; ++i) {
+ if (c1[i] != c2[i]) {
+ final boolean last = (i == ultimate);
+ // argument is null, prefer an Object param
+ if (a[i] == Void.class) {
+ if (c1[i] == Object.class && c2[i] != Object.class) {
+ return MORE_SPECIFIC;
+ }
+ if (c1[i] != Object.class && c2[i] == Object.class) {
+ return LESS_SPECIFIC;
+ }
+ }
+ // prefer primitive on non null arg, non primitive otherwise
+ boolean c1s = isPrimitive(c1[i], last);
+ boolean c2s = isPrimitive(c2[i], last);
+ if (c1s != c2s) {
+ return (c1s == (a[i] != Void.class)) ? MORE_SPECIFIC : LESS_SPECIFIC;
+ }
+ // if c2 can be converted to c1 but not the opposite,
+ // c1 is more specific than c2
+ c1s = isStrictConvertible(c2[i], c1[i], last);
+ c2s = isStrictConvertible(c1[i], c2[i], last);
+ if (c1s != c2s) {
+ return c1s ? MORE_SPECIFIC : LESS_SPECIFIC;
+ }
+ }
+ }
+ // Incomparable due to non-related arguments (i.e.foo(Runnable) vs. foo(Serializable))
+ return INCOMPARABLE;
+ }
+
+ /**
+ * Checks whether a parameter class is a primitive.
+ *
+ * @param c the parameter class
+ * @param possibleVarArg true if this is the last parameter which can be a primitive array (vararg call)
+ * @return true if primitive, false otherwise
+ */
+ private boolean isPrimitive(final Class> c, final boolean possibleVarArg) {
+ if (c != null) {
+ if (c.isPrimitive()) {
+ return true;
+ }
+ if (possibleVarArg) {
+ final Class> t = c.getComponentType();
+ return t != null && t.isPrimitive();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns all methods that are applicable to actual argument types.
+ *
+ * @param methods list of all candidate methods
+ * @param classes the actual types of the arguments
+ * @return a list that contains only applicable methods (number of
+ * formal and actual arguments matches, and argument types are assignable
+ * to formal types through a method invocation conversion).
+ */
+ private LinkedList getApplicables(final T[] methods, final Class>[] classes) {
+ final LinkedList list = new LinkedList();
+ for (final T method : methods) {
+ if (isApplicable(method, classes)) {
+ list.add(method);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Returns true if the supplied method is applicable to actual
+ * argument types.
+ *
+ * @param method method that will be called
+ * @param actuals arguments signature for method
+ * @return true if method is applicable to arguments
+ */
+ private boolean isApplicable(final T method, final Class>[] actuals) {
+ final Class>[] formals = getParameterTypes(method);
+ // if same number or args or
+ // there's just one more methodArg than class arg
+ // and the last methodArg is an array, then treat it as a vararg
+ if (formals.length == actuals.length) {
+ // this will properly match when the last methodArg
+ // is an array/varargs and the last class is the type of array
+ // (e.g. String when the method is expecting String...)
+ for (int i = 0; i < actuals.length; ++i) {
+ if (!isConvertible(formals[i], actuals[i], false)) {
+ // if we're on the last arg and the method expects an array
+ if (i == actuals.length - 1 && formals[i].isArray()) {
+ // check to see if the last arg is convertible
+ // to the array's component type
+ return isConvertible(formals[i], actuals[i], true);
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // number of formal and actual differ, method must be vararg
+ if (!isVarArgs(method)) {
+ return false;
+ }
+
+ // less arguments than method parameters: vararg is null
+ if (formals.length > actuals.length) {
+ // only one parameter, the last (ie vararg) can be missing
+ if (formals.length - actuals.length > 1) {
+ return false;
+ }
+ // check that all present args match up to the method parms
+ for (int i = 0; i < actuals.length; ++i) {
+ if (!isConvertible(formals[i], actuals[i], false)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // more arguments given than the method accepts; check for varargs
+ if (formals.length > 0 && actuals.length > 0) {
+ // check that they all match up to the last method arg
+ for (int i = 0; i < formals.length - 1; ++i) {
+ if (!isConvertible(formals[i], actuals[i], false)) {
+ return false;
+ }
+ }
+ // check that all remaining arguments are convertible to the vararg type
+ // (last parm is an array since method is vararg)
+ final Class> vararg = formals[formals.length - 1].getComponentType();
+ for (int i = formals.length - 1; i < actuals.length; ++i) {
+ if (!isConvertible(vararg, actuals[i], false)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ // no match
+ return false;
+ }
+
+ /**
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @param possibleVarArg whether or not we're dealing with the last parameter
+ * in the method declaration
+ * @return see isMethodInvocationConvertible.
+ * @see #isInvocationConvertible(Class, Class, boolean)
+ */
+ private boolean isConvertible(final Class> formal, final Class> actual, final boolean possibleVarArg) {
+ // if we see Void.class, the argument was null
+ return isInvocationConvertible(formal, actual.equals(Void.class) ? null : actual, possibleVarArg);
+ }
+
+ /**
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @param possibleVarArg whether or not we're dealing with the last parameter
+ * in the method declaration
+ * @return see isStrictMethodInvocationConvertible.
+ * @see #isStrictInvocationConvertible(Class, Class, boolean)
+ */
+ private boolean isStrictConvertible(final Class> formal, final Class> actual,
+ final boolean possibleVarArg) {
+ // if we see Void.class, the argument was null
+ return isStrictInvocationConvertible(formal, actual.equals(Void.class) ? null : actual, possibleVarArg);
+ }
+ }
+
+ /**
+ * The parameter matching service for methods.
+ */
+ private static final Parameters METHODS = new Parameters() {
+ @Override
+ protected Class>[] getParameterTypes(final Method app) {
+ return app.getParameterTypes();
+ }
+
+ @Override
+ public boolean isVarArgs(final Method app) {
+ return MethodKey.isVarArgs(app);
+ }
+
+ };
+
+ /**
+ * The parameter matching service for constructors.
+ */
+ private static final Parameters> CONSTRUCTORS = new Parameters>() {
+ @Override
+ protected Class>[] getParameterTypes(final Constructor> app) {
+ return app.getParameterTypes();
+ }
+
+ @Override
+ public boolean isVarArgs(final Constructor> app) {
+ return app.isVarArgs();
+ }
+
+ };
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/Permissions.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/Permissions.java
new file mode 100644
index 0000000..587d54f
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/Permissions.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.annotations.NoJexl;
+
+/**
+ * Checks whether an element (ctor, field or method) is visible by JEXL introspection.
+ * Default implementation does this by checking if element has been annotated with NoJexl.
+ */
+public class Permissions {
+ /** Allow inheritance. */
+ protected Permissions() {
+ }
+ /**
+ * The default singleton.
+ */
+ public static final Permissions DEFAULT = new Permissions();
+
+ /**
+ * Checks whether a package explicitly disallows JEXL introspection.
+ * @param pack the package
+ * @return true if JEXL is allowed to introspect, false otherwise
+ */
+ public boolean allow(final Package pack) {
+ if (pack == null || pack.getAnnotation(NoJexl.class) != null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether a class or one of its super-classes or implemented interfaces
+ * explicitly disallows JEXL introspection.
+ * @param clazz the class to check
+ * @return true if JEXL is allowed to introspect, false otherwise
+ */
+ public boolean allow(final Class> clazz) {
+ return clazz != null && allow(clazz.getPackage()) && allow(clazz, true);
+ }
+
+ /**
+ * Checks whether a constructor explicitly disallows JEXL introspection.
+ * @param ctor the constructor to check
+ * @return true if JEXL is allowed to introspect, false otherwise
+ */
+ public boolean allow(final Constructor> ctor) {
+ if (ctor == null) {
+ return false;
+ }
+ if (!Modifier.isPublic(ctor.getModifiers())) {
+ return false;
+ }
+ final Class> clazz = ctor.getDeclaringClass();
+ if (!allow(clazz, false)) {
+ return false;
+ }
+ // is ctor annotated with nojexl ?
+ final NoJexl nojexl = ctor.getAnnotation(NoJexl.class);
+ if (nojexl != null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether a field explicitly disallows JEXL introspection.
+ * @param field the field to check
+ * @return true if JEXL is allowed to introspect, false otherwise
+ */
+ public boolean allow(final Field field) {
+ if (field == null) {
+ return false;
+ }
+ if (!Modifier.isPublic(field.getModifiers())) {
+ return false;
+ }
+ final Class> clazz = field.getDeclaringClass();
+ if (!allow(clazz, false)) {
+ return false;
+ }
+ // is field annotated with nojexl ?
+ final NoJexl nojexl = field.getAnnotation(NoJexl.class);
+ if (nojexl != null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether a method explicitly disallows JEXL introspection.
+ * Since methods can be overridden, this also checks that no superclass or interface
+ * explicitly disallows this methods.
+ * @param method the method to check
+ * @return true if JEXL is allowed to introspect, false otherwise
+ */
+ public boolean allow(final Method method) {
+ if (method == null) {
+ return false;
+ }
+ if (!Modifier.isPublic(method.getModifiers())) {
+ return false;
+ }
+ // is method annotated with nojexl ?
+ NoJexl nojexl = method.getAnnotation(NoJexl.class);
+ if (nojexl != null) {
+ return false;
+ }
+ // is the class annotated with nojexl ?
+ Class> clazz = method.getDeclaringClass();
+ nojexl = clazz.getAnnotation(NoJexl.class);
+ if (nojexl != null) {
+ return false;
+ }
+ // lets walk all interfaces
+ for (final Class> inter : clazz.getInterfaces()) {
+ if (!allow(inter, method)) {
+ return false;
+ }
+ }
+ // lets walk all super classes
+ clazz = clazz.getSuperclass();
+ // walk all superclasses
+ while (clazz != null) {
+ if (!allow(clazz, method)) {
+ return false;
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether a class or one of its superclasses or implemented interfaces
+ * explicitly disallows JEXL introspection.
+ * @param clazz the class to check
+ * @param interf whether interfaces should be checked as well
+ * @return true if JEXL is allowed to introspect, false otherwise
+ */
+ protected boolean allow(Class> clazz, final boolean interf) {
+ if (clazz == null) {
+ return false;
+ }
+ if (!Modifier.isPublic(clazz.getModifiers())) {
+ return false;
+ }
+ // lets walk all interfaces
+ if (interf) {
+ for (final Class> inter : clazz.getInterfaces()) {
+ // is clazz annotated with nojexl ?
+ final NoJexl nojexl = inter.getAnnotation(NoJexl.class);
+ if (nojexl != null) {
+ return false;
+ }
+ }
+ }
+ // lets walk all super classes
+ clazz = clazz.getSuperclass();
+ // walk all superclasses
+ while (clazz != null) {
+ // is clazz annotated with nojexl ?
+ final NoJexl nojexl = clazz.getAnnotation(NoJexl.class);
+ if (nojexl != null) {
+ return false;
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return true;
+ }
+
+ /**
+ * Check whether a method is allowed to be JEXL introspected in all its
+ * superclasses and interfaces.
+ * @param clazz the class
+ * @param method the method
+ * @return true if JEXL is allowed to introspect, false otherwise
+ */
+ protected boolean allow(final Class> clazz, final Method method) {
+ if (clazz != null) {
+ try {
+ // check if method in that class is different from the method argument
+ final Method wmethod = clazz.getMethod(method.getName(), method.getParameterTypes());
+ if (wmethod != null) {
+ NoJexl nojexl = clazz.getAnnotation(NoJexl.class);
+ if (nojexl != null) {
+ return false;
+ }
+ // check if parent declaring class said nojexl (transitivity)
+ nojexl = wmethod.getAnnotation(NoJexl.class);
+ if (nojexl != null) {
+ return false;
+ }
+ }
+ } catch (final NoSuchMethodException ex) {
+ // unexpected, return no
+ return true;
+ } catch (final SecurityException ex) {
+ // unexpected, can't do much
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/PropertyGetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/PropertyGetExecutor.java
new file mode 100644
index 0000000..73862a8
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/PropertyGetExecutor.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+import java.lang.reflect.InvocationTargetException;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
+
+/**
+ * Specialized executor to get a property from an object.
+ * @since 2.0
+ */
+public final class PropertyGetExecutor extends AbstractExecutor.Get {
+ /** A static signature for method(). */
+ private static final Object[] EMPTY_PARAMS = {};
+ /** The property. */
+ private final String property;
+
+ /**
+ * Discovers a PropertyGetExecutor.
+ * The method to be found should be named "get{P,p}property.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param property the property name to find
+ * @return the executor if found, null otherwise
+ */
+ public static PropertyGetExecutor discover(final Introspector is, final Class> clazz, final String property) {
+ final java.lang.reflect.Method method = discoverGet(is, "get", clazz, property);
+ return method == null? null : new PropertyGetExecutor(clazz, method, property);
+ }
+
+ /**
+ * Creates an instance.
+ * @param clazz he class the get method applies to
+ * @param method the method held by this executor
+ * @param identifier the property to get
+ */
+ private PropertyGetExecutor(final Class> clazz, final java.lang.reflect.Method method, final String identifier) {
+ super(clazz, method);
+ property = identifier;
+ }
+
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ @Override
+ public Object invoke(final Object o) throws IllegalAccessException, InvocationTargetException {
+ return method == null ? null : method.invoke(o, (Object[]) null);
+ }
+
+ @Override
+ public Object tryInvoke(final Object o, final Object identifier) {
+ if (o != null && method != null
+ && property.equals(castString(identifier))
+ && objectClass.equals(o.getClass())) {
+ try {
+ return method.invoke(o, (Object[]) null);
+ } catch (IllegalAccessException | IllegalArgumentException xill) {
+ return TRY_FAILED;// fail
+ } catch (final InvocationTargetException xinvoke) {
+ throw JexlException.tryFailed(xinvoke); // throw
+ }
+ }
+ return TRY_FAILED;
+ }
+
+ /**
+ * Base method for boolean and object property get.
+ * @param is the introspector
+ * @param which "is" or "get" for boolean or object
+ * @param clazz The class being examined.
+ * @param property The property being addressed.
+ * @return The {get,is}{p,P}roperty method if one exists, null otherwise.
+ */
+ static java.lang.reflect.Method discoverGet(final Introspector is,
+ final String which,
+ final Class> clazz,
+ final String property) {
+ if (property == null || property.isEmpty()) {
+ return null;
+ }
+ // this is gross and linear, but it keeps it straightforward.
+ java.lang.reflect.Method method;
+ final int start = which.length(); // "get" or "is" so 3 or 2 for char case switch
+ // start with get
+ final StringBuilder sb = new StringBuilder(which);
+ sb.append(property);
+ // uppercase nth char
+ final char c = sb.charAt(start);
+ sb.setCharAt(start, Character.toUpperCase(c));
+ method = is.getMethod(clazz, sb.toString(), EMPTY_PARAMS);
+ //lowercase nth char
+ if (method == null) {
+ sb.setCharAt(start, Character.toLowerCase(c));
+ method = is.getMethod(clazz, sb.toString(), EMPTY_PARAMS);
+ }
+ return method;
+ }
+}
+
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/PropertySetExecutor.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/PropertySetExecutor.java
new file mode 100644
index 0000000..df2d0f9
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/PropertySetExecutor.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertySet;
+
+/**
+ * Specialized executor to set a property in an object.
+ * @since 2.0
+ */
+public class PropertySetExecutor extends AbstractExecutor.Set {
+ /** Index of the first character of the set{p,P}roperty. */
+ private static final int SET_START_INDEX = 3;
+ /** The property. */
+ protected final String property;
+ /** The property value class. */
+ protected final Class> valueClass;
+
+ /**
+ * Discovers a PropertySetExecutor.
+ * The method to be found should be named "set{P,p}property.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param property the property name to find
+ * @param value the value to assign to the property
+ * @return the executor if found, null otherwise
+ */
+ public static PropertySetExecutor discover(final Introspector is,
+ final Class> clazz,
+ final String property,
+ final Object value) {
+ if (property == null || property.isEmpty()) {
+ return null;
+ }
+ final java.lang.reflect.Method method = discoverSet(is, clazz, property, value);
+ return method != null? new PropertySetExecutor(clazz, method, property, value) : null;
+ }
+
+ /**
+ * Creates an instance.
+ * @param clazz the class the set method applies to
+ * @param method the method called through this executor
+ * @param key the key to use as 1st argument to the set method
+ * @param value the value
+ */
+ protected PropertySetExecutor(final Class> clazz,
+ final java.lang.reflect.Method method,
+ final String key,
+ final Object value) {
+ super(clazz, method);
+ property = key;
+ valueClass = classOf(value);
+ }
+
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ @Override
+ public Object invoke(final Object o, Object arg) throws IllegalAccessException, InvocationTargetException {
+ if (method != null) {
+ // handle the empty array case
+ if (isEmptyArray(arg)) {
+ // if array is empty but its component type is different from the method first parameter component type,
+ // replace argument with a new empty array instance (of the method first parameter component type)
+ final Class> componentType = method.getParameterTypes()[0].getComponentType();
+ if (componentType != null && !componentType.equals(arg.getClass().getComponentType())) {
+ arg = Array.newInstance(componentType, 0);
+ }
+ }
+ method.invoke(o, arg);
+ }
+ return arg;
+ }
+
+ @Override
+ public Object tryInvoke(final Object o, final Object identifier, final Object value) {
+ if (o != null && method != null
+ // ensure method name matches the property name
+ && property.equals(castString(identifier))
+ // object class should be same as executor's method declaring class
+ && objectClass.equals(o.getClass())
+ // argument class should be eq
+ && valueClass.equals(classOf(value))) {
+ try {
+ return invoke(o, value);
+ } catch (IllegalAccessException | IllegalArgumentException xill) {
+ return TRY_FAILED;// fail
+ } catch (final InvocationTargetException xinvoke) {
+ throw JexlException.tryFailed(xinvoke); // throw
+ }
+ }
+ return TRY_FAILED;
+ }
+
+ /**
+ * Checks whether an argument is an empty array.
+ * @param arg the argument
+ * @return true if arg
is an empty array
+ */
+ private static boolean isEmptyArray(final Object arg) {
+ return (arg != null && arg.getClass().isArray() && Array.getLength(arg) == 0);
+ }
+
+ /**
+ * Discovers the method for a {@link JexlPropertySet}.
+ * The method to be found should be named "set{P,p}property.
+ * As a special case, any empty array will try to find a valid array-setting non-ambiguous method.
+ *
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param property the name of the property to set
+ * @param arg the value to assign to the property
+ * @return the method if found, null otherwise
+ */
+ private static java.lang.reflect.Method discoverSet(final Introspector is,
+ final Class> clazz,
+ final String property,
+ final Object arg) {
+ // first, we introspect for the set setter method
+ final Object[] params = {arg};
+ final StringBuilder sb = new StringBuilder("set");
+ sb.append(property);
+ // uppercase nth char
+ final char c = sb.charAt(SET_START_INDEX);
+ sb.setCharAt(SET_START_INDEX, Character.toUpperCase(c));
+ java.lang.reflect.Method method = is.getMethod(clazz, sb.toString(), params);
+ // lowercase nth char
+ if (method == null) {
+ sb.setCharAt(SET_START_INDEX, Character.toLowerCase(c));
+ method = is.getMethod(clazz, sb.toString(), params);
+ // uppercase nth char, try array
+ if (method == null && isEmptyArray(arg)) {
+ sb.setCharAt(SET_START_INDEX, Character.toUpperCase(c));
+ method = lookupSetEmptyArray(is, clazz, sb.toString());
+ // lowercase nth char
+ if (method == null) {
+ sb.setCharAt(SET_START_INDEX, Character.toLowerCase(c));
+ method = lookupSetEmptyArray(is, clazz, sb.toString());
+ }
+ }
+ }
+ return method;
+ }
+
+ /**
+ * Finds an empty array property setter method by methodName
.
+ * This checks only one method with that name accepts an array as sole parameter.
+ * @param is the introspector
+ * @param clazz the class to find the get method from
+ * @param mname the method name to find
+ * @return the sole method that accepts an array as parameter
+ */
+ private static java.lang.reflect.Method lookupSetEmptyArray(final Introspector is, final Class> clazz, final String mname) {
+ java.lang.reflect.Method candidate = null;
+ final java.lang.reflect.Method[] methods = is.getMethods(clazz, mname);
+ if (methods != null) {
+ for (final java.lang.reflect.Method method : methods) {
+ final Class>[] paramTypes = method.getParameterTypes();
+ if (paramTypes.length == 1 && paramTypes[0].isArray()) {
+ if (candidate != null) {
+ // because the setter method is overloaded for different parameter type,
+ // return null here to report the ambiguity.
+ return null;
+ }
+ candidate = method;
+ }
+ }
+ }
+ return candidate;
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
new file mode 100644
index 0000000..1a13f24
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlArithmetic;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlOperator;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlMethod;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertyGet;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertySet;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlSandbox;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlUberspect;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An uberspect that controls usage of properties, methods and constructors through a sandbox.
+ * @since 3.0
+ */
+public final class SandboxUberspect implements JexlUberspect {
+ /** The base uberspect. */
+ private final JexlUberspect uberspect;
+ /** The sandbox. */
+ private final JexlSandbox sandbox;
+
+ /**
+ * A constructor for JexlSandbox uberspect.
+ * @param theUberspect the JexlUberspect to sandbox
+ * @param theSandbox the sandbox which is copied to avoid changes at runtime
+ */
+ public SandboxUberspect(final JexlUberspect theUberspect, final JexlSandbox theSandbox) {
+ if (theSandbox == null) {
+ throw new NullPointerException("sandbox can not be null");
+ }
+ if (theUberspect == null) {
+ throw new NullPointerException("uberspect can not be null");
+ }
+ this.uberspect = theUberspect;
+ this.sandbox = theSandbox.copy();
+ }
+
+ @Override
+ public void setClassLoader(final ClassLoader loader) {
+ uberspect.setClassLoader(loader);
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return uberspect.getClassLoader();
+ }
+
+ @Override
+ public int getVersion() {
+ return uberspect.getVersion();
+ }
+
+ @Override
+ public JexlMethod getConstructor(final Object ctorHandle, final Object... args) {
+ final String className;
+ if (ctorHandle instanceof Class>) {
+ className = sandbox.execute((Class>) ctorHandle, "");
+ } else if (ctorHandle != null) {
+ className = sandbox.execute(ctorHandle.toString(), "");
+ } else {
+ className = null;
+ }
+ return className != null && className != JexlSandbox.NULL ? uberspect.getConstructor(className, args) : null;
+ }
+
+ @Override
+ public JexlMethod getMethod(final Object obj, final String method, final Object... args) {
+ if (obj != null && method != null) {
+ final Class> clazz = (obj instanceof Class) ? (Class>) obj : obj.getClass();
+ final String actual = sandbox.execute(clazz, method);
+ if (actual != null && actual != JexlSandbox.NULL) {
+ return uberspect.getMethod(obj, actual, args);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public List getResolvers(final JexlOperator op, final Object obj) {
+ return uberspect.getResolvers(op, obj);
+ }
+
+ @Override
+ public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier) {
+ return getPropertyGet(null, obj, identifier);
+ }
+
+ @Override
+ public JexlPropertyGet getPropertyGet(final List resolvers,
+ final Object obj,
+ final Object identifier) {
+ if (obj != null) {
+ if (identifier != null) {
+ final String property = identifier.toString();
+ final String actual = sandbox.read(obj.getClass(), property);
+ if (actual != null) {
+ // no transformation, strict equality: use identifier before string conversion
+ final Object pty = actual == property ? identifier : actual;
+ return uberspect.getPropertyGet(resolvers, obj, pty);
+ }
+ } else {
+ final String actual = sandbox.read(obj.getClass(), null);
+ if (actual != JexlSandbox.NULL) {
+ return uberspect.getPropertyGet(resolvers, obj, null);
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public JexlPropertySet getPropertySet(final Object obj, final Object identifier, final Object arg) {
+ return getPropertySet(null, obj, identifier, arg);
+ }
+
+ @Override
+ public JexlPropertySet getPropertySet(final List resolvers,
+ final Object obj,
+ final Object identifier,
+ final Object arg) {
+ if (obj != null) {
+ if (identifier != null) {
+ final String property = identifier.toString();
+ final String actual = sandbox.write(obj.getClass(), property);
+ if (actual != null) {
+ // no transformation, strict equality: use identifier before string conversion
+ final Object pty = actual == property ? identifier : actual;
+ return uberspect.getPropertySet(resolvers, obj, pty, arg);
+ }
+ } else {
+ final String actual = sandbox.write(obj.getClass(), null);
+ if (actual != JexlSandbox.NULL) {
+ return uberspect.getPropertySet(resolvers, obj, null, arg);
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Iterator> getIterator(final Object obj) {
+ return uberspect.getIterator(obj);
+ }
+
+ @Override
+ public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic arithmetic) {
+ return uberspect.getArithmetic(arithmetic);
+ }
+}
diff --git a/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/Uberspect.java b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/Uberspect.java
new file mode 100644
index 0000000..f904ef1
--- /dev/null
+++ b/src/main/java/aiyh/utils/tool/org/apache/commons/jexl3/internal/introspection/Uberspect.java
@@ -0,0 +1,479 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package aiyh.utils.tool.org.apache.commons.jexl3.internal.introspection;
+
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlArithmetic;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlEngine;
+import aiyh.utils.tool.org.apache.commons.jexl3.JexlOperator;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlMethod;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertyGet;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlPropertySet;
+import aiyh.utils.tool.org.apache.commons.jexl3.introspection.JexlUberspect;
+import org.apache.commons.logging.Log;
+
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Implementation of Uberspect to provide the default introspective
+ * functionality of JEXL.
+ *
+ * This is the class to derive to customize introspection.
+ *
+ * @since 1.0
+ */
+public class Uberspect implements JexlUberspect {
+ /** Publicly exposed special failure object returned by tryInvoke. */
+ public static final Object TRY_FAILED = JexlEngine.TRY_FAILED;
+ /** The logger to use for all warnings and errors. */
+ protected final Log logger;
+ /** The resolver strategy. */
+ private final ResolverStrategy strategy;
+ /** The permissions. */
+ private final Permissions permissions;
+ /** The introspector version. */
+ private final AtomicInteger version;
+ /** The soft reference to the introspector currently in use. */
+ private volatile Reference ref;
+ /** The class loader reference; used to recreate the introspector when necessary. */
+ private volatile Reference loader;
+ /**
+ * The map from arithmetic classes to overloaded operator sets.
+ *
+ * This keeps track of which operator methods are overloaded per JexlArithemtic classes
+ * allowing a fail fast test during interpretation by avoiding seeking a method when there is none.
+ */
+ private final Map, Set> operatorMap;
+
+ /**
+ * Creates a new Uberspect.
+ *
+ * @param runtimeLogger the logger used for all logging needs
+ * @param sty the resolver strategy
+ */
+ public Uberspect(final Log runtimeLogger, final ResolverStrategy sty) {
+ this(runtimeLogger, sty, null);
+ }
+
+ /**
+ * Creates a new Uberspect.
+ *
+ * @param runtimeLogger the logger used for all logging needs
+ * @param sty the resolver strategy
+ * @param perms the introspector permissions
+ */
+ public Uberspect(final Log runtimeLogger, final ResolverStrategy sty, final Permissions perms) {
+ logger = runtimeLogger;
+ strategy = sty == null ? JexlUberspect.JEXL_STRATEGY : sty;
+ permissions = perms;
+ ref = new SoftReference(null);
+ loader = new SoftReference(getClass().getClassLoader());
+ operatorMap = new ConcurrentHashMap, Set>();
+ version = new AtomicInteger(0);
+ }
+
+ /**
+ * Gets the current introspector base.
+ *
+ * If the reference has been collected, this method will recreate the underlying introspector.
+ *
+ * @return the introspector
+ */
+ // CSOFF: DoubleCheckedLocking
+ protected final Introspector base() {
+ Introspector intro = ref.get();
+ if (intro == null) {
+ // double checked locking is ok (fixed by Java 5 memory model).
+ synchronized (this) {
+ intro = ref.get();
+ if (intro == null) {
+ intro = new Introspector(logger, loader.get(), permissions);
+ ref = new SoftReference(intro);
+ loader = new SoftReference(intro.getLoader());
+ version.incrementAndGet();
+ }
+ }
+ }
+ return intro;
+ }
+ // CSON: DoubleCheckedLocking
+
+ @Override
+ public void setClassLoader(final ClassLoader nloader) {
+ synchronized (this) {
+ Introspector intro = ref.get();
+ if (intro != null) {
+ intro.setLoader(nloader);
+ } else {
+ intro = new Introspector(logger, nloader, permissions);
+ ref = new SoftReference(intro);
+ }
+ loader = new SoftReference(intro.getLoader());
+ operatorMap.clear();
+ version.incrementAndGet();
+ }
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return loader.get();
+ }
+
+ @Override
+ public int getVersion() {
+ return version.intValue();
+ }
+
+ /**
+ * Gets a class by name through this introspector class loader.
+ *
+ * @param className the class name
+ * @return the class instance or null if it could not be found
+ */
+ public final Class> getClassByName(final String className) {
+ return base().getClassByName(className);
+ }
+
+ /**
+ * Gets the field named by
+ * key
for the class
+ * c
.
+ *
+ * @param c Class in which the field search is taking place
+ * @param key Name of the field being searched for
+ * @return a {@link Field} or null if it does not exist or is not accessible
+ */
+ public final Field getField(final Class> c, final String key) {
+ return base().getField(c, key);
+ }
+
+ /**
+ * Gets the accessible field names known for a given class.
+ *
+ * @param c the class
+ * @return the class field names
+ */
+ public final String[] getFieldNames(final Class> c) {
+ return base().getFieldNames(c);
+ }
+
+ /**
+ * Gets the method defined by
+ * name
and
+ * params
for the Class
+ * c
.
+ *
+ * @param c Class in which the method search is taking place
+ * @param name Name of the method being searched for
+ * @param params An array of Objects (not Classes) that describe the
+ * the parameters
+ * @return a {@link Method}
+ * or null if no unambiguous method could be found through introspection.
+ */
+ public final Method getMethod(final Class> c, final String name, final Object[] params) {
+ return base().getMethod(c, new MethodKey(name, params));
+ }
+
+ /**
+ * Gets the method defined by
+ * key
and for the Class
+ * c
.
+ *
+ * @param c Class in which the method search is taking place
+ * @param key MethodKey of the method being searched for
+ * @return a {@link Method}
+ * or null if no unambiguous method could be found through introspection.
+ */
+ public final Method getMethod(final Class> c, final MethodKey key) {
+ return base().getMethod(c, key);
+ }
+
+ /**
+ * Gets the accessible methods names known for a given class.
+ *
+ * @param c the class
+ * @return the class method names
+ */
+ public final String[] getMethodNames(final Class> c) {
+ return base().getMethodNames(c);
+ }
+
+ /**
+ * Gets all the methods with a given name from this map.
+ *
+ * @param c the class
+ * @param methodName the seeked methods name
+ * @return the array of methods
+ */
+ public final Method[] getMethods(final Class> c, final String methodName) {
+ return base().getMethods(c, methodName);
+ }
+
+ @Override
+ public JexlMethod getMethod(final Object obj, final String method, final Object... args) {
+ return MethodExecutor.discover(base(), obj, method, args);
+ }
+
+ @Override
+ public List getResolvers(final JexlOperator op, final Object obj) {
+ return strategy.apply(op, obj);
+ }
+
+ @Override
+ public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier) {
+ return getPropertyGet(null, obj, identifier);
+ }
+
+ @Override
+ public JexlPropertyGet getPropertyGet(
+ final List resolvers, final Object obj, final Object identifier
+ ) {
+ final Class> claz = obj.getClass();
+ final String property = AbstractExecutor.castString(identifier);
+ final Introspector is = base();
+ final List r = resolvers == null ? strategy.apply(null, obj) : resolvers;
+ JexlPropertyGet executor = null;
+ for (final PropertyResolver resolver : r) {
+ if (resolver instanceof JexlResolver) {
+ switch ((JexlResolver) resolver) {
+ case PROPERTY:
+ // first try for a getFoo() type of property (also getfoo() )
+ executor = PropertyGetExecutor.discover(is, claz, property);
+ if (executor == null) {
+ executor = BooleanGetExecutor.discover(is, claz, property);
+ }
+ break;
+ case MAP:
+ // let's see if we are a map...
+ executor = MapGetExecutor.discover(is, claz, identifier);
+ break;
+ case LIST:
+ // let's see if this is a list or array
+ final Integer index = AbstractExecutor.castInteger(identifier);
+ if (index != null) {
+ executor = ListGetExecutor.discover(is, claz, index);
+ }
+ break;
+ case DUCK:
+ // if that didn't work, look for get(foo)
+ executor = DuckGetExecutor.discover(is, claz, identifier);
+ if (executor == null && property != null && property != identifier) {
+ // look for get("foo") if we did not try yet (just above)
+ executor = DuckGetExecutor.discover(is, claz, property);
+ }
+ break;
+ case FIELD:
+ // a field may be? (can not be a number)
+ executor = FieldGetExecutor.discover(is, claz, property);
+ // static class fields (enums included)
+ if (obj instanceof Class>) {
+ executor = FieldGetExecutor.discover(is, (Class>) obj, property);
+ }
+ break;
+ case CONTAINER:
+ // or an indexed property?
+ executor = IndexedType.discover(is, obj, property);
+ break;
+ default:
+ continue; // in case we add new ones in enum
+ }
+ } else {
+ executor = resolver.getPropertyGet(this, obj, identifier);
+ }
+ if (executor != null) {
+ return executor;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public JexlPropertySet getPropertySet(final Object obj, final Object identifier, final Object arg) {
+ return getPropertySet(null, obj, identifier, arg);
+ }
+
+ @Override
+ public JexlPropertySet getPropertySet(
+ final List resolvers, final Object obj, final Object identifier, final Object arg
+ ) {
+ final Class> claz = obj.getClass();
+ final String property = AbstractExecutor.castString(identifier);
+ final Introspector is = base();
+ final List actual = resolvers == null ? strategy.apply(null, obj) : resolvers;
+ JexlPropertySet executor = null;
+ for (final PropertyResolver resolver : actual) {
+ if (resolver instanceof JexlResolver) {
+ switch ((JexlResolver) resolver) {
+ case PROPERTY:
+ // first try for a setFoo() type of property (also setfoo() )
+ executor = PropertySetExecutor.discover(is, claz, property, arg);
+ break;
+ case MAP:
+ // let's see if we are a map...
+ executor = MapSetExecutor.discover(is, claz, identifier, arg);
+ break;
+ case LIST:
+ // let's see if we can convert the identifier to an int,
+ // if obj is an array or a list, we can still do something
+ final Integer index = AbstractExecutor.castInteger(identifier);
+ if (index != null) {
+ executor = ListSetExecutor.discover(is, claz, identifier, arg);
+ }
+ break;
+ case DUCK:
+ // if that didn't work, look for set(foo)
+ executor = DuckSetExecutor.discover(is, claz, identifier, arg);
+ if (executor == null && property != null && property != identifier) {
+ executor = DuckSetExecutor.discover(is, claz, property, arg);
+ }
+ break;
+ case FIELD:
+ // a field may be?
+ executor = FieldSetExecutor.discover(is, claz, property, arg);
+ break;
+ case CONTAINER:
+ default:
+ continue; // in case we add new ones in enum
+ }
+ } else {
+ executor = resolver.getPropertySet(this, obj, identifier, arg);
+ }
+ if (executor != null) {
+ return executor;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Iterator> getIterator(final Object obj) {
+ if (obj instanceof Iterator>) {
+ return ((Iterator>) obj);
+ }
+ if (obj.getClass().isArray()) {
+ return new ArrayIterator(obj);
+ }
+ if (obj instanceof Map, ?>) {
+ return ((Map, ?>) obj).values().iterator();
+ }
+ if (obj instanceof Enumeration>) {
+ return new EnumerationIterator((Enumeration) obj);
+ }
+ if (obj instanceof Iterable>) {
+ return ((Iterable>) obj).iterator();
+ }
+ try {
+ // look for an iterator() method to support the JDK5 Iterable
+ // interface or any user tools/DTOs that want to work in
+ // foreach without implementing the Collection interface
+ final JexlMethod it = getMethod(obj, "iterator", (Object[]) null);
+ if (it != null && Iterator.class.isAssignableFrom(it.getReturnType())) {
+ return (Iterator