Skip to content

Commit

Permalink
JEXL-428: operators improvements;
Browse files Browse the repository at this point in the history
- use compare method as base for operators when possible;
- cache resolution using method key;
JEXL-429: namespace syntax disambiguation;
- added feature flag (namespaceIdentifier);
- variable used as function name added hint of non-ns call;
  • Loading branch information
henrib committed Sep 25, 2024
1 parent 2b495df commit 2d2300e
Show file tree
Hide file tree
Showing 11 changed files with 558 additions and 280 deletions.
37 changes: 30 additions & 7 deletions src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,22 @@ public interface Uberspect {
* Gets the most specific method for an operator.
*
* @param operator the operator
* @param arg the arguments
* @param args the arguments
* @return the most specific method or null if no specific override could be found
*/
JexlMethod getOperator(JexlOperator operator, Object... arg);
JexlMethod getOperator(JexlOperator operator, Object... args);

/**
* Try to find the most specific method and evaluate an operator.
* <p>This method does not call {@link #overloads(JexlOperator)} and shall not be called with an
* assignment operator.</p>
*
* @param reference an optional cache reference storing actual method or failing signature
* @param operator the operator
* @param args the arguments
* @return TRY_FAILED if no specific method could be found, the evaluation result otherwise
*/
Object tryEval(JexlCache.Reference reference, JexlOperator operator, Object...args);

/**
* Checks whether this uberspect has overloads for a given operator.
Expand Down Expand Up @@ -476,9 +488,9 @@ public final Object bitwiseXor(final Object lhs, final Object rhs) {
protected Boolean collectionContains(final Object collection, final Object value) {
// convert arrays if needed
final Object left = arrayWrap(collection);
if (left instanceof Collection<?>) {
if (left instanceof Collection) {
final Object right = arrayWrap(value);
if (right instanceof Collection<?>) {
if (right instanceof Collection) {
return ((Collection<?>) left).containsAll((Collection<?>) right);
}
return ((Collection<?>) left).contains(value);
Expand Down Expand Up @@ -798,12 +810,23 @@ private int doCompare(final Object left, final Object right, final JexlOperator
final Comparable<Object> comparable = (Comparable<Object>) left;
try {
return comparable.compareTo(right);
} catch(final ClassCastException castException) {
} catch (final ClassCastException castException) {
// ignore it, continue in sequence
}
}
if (right instanceof Comparable<?>) {
@SuppressWarnings("unchecked") // OK because of instanceof check above
final Comparable<Object> comparable = (Comparable<Object>) right;
try {
return -comparable.compareTo(left);
} catch (final ClassCastException castException) {
// ignore it, continue in sequence
}
}
}
throw new ArithmeticException("Object comparison:(" + left + " " + operator + " " + right + ")");
throw new ArithmeticException("Object comparison:(" + left +
" " + operator.getOperatorSymbol()
+ " " + right + ")");
}

/**
Expand Down Expand Up @@ -971,7 +994,7 @@ public Boolean isEmpty(final Object object) {
* 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
* @param def the default value if object emptiness can not be determined
* @return the boolean or null if there is no arithmetic solution
*/
public Boolean isEmpty(final Object object, final Boolean def) {
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/org/apache/commons/jexl3/JexlCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,21 @@ default Collection<Map.Entry<K, V>> entries() {
* @return the cache size
*/
int size();

/**
* A cached reference.
*/
interface Reference {
/**
* Gets the referenced object.
* @return the referenced object
*/
Object getCache();

/**
* Sets the referenced object.
* @param cache the referenced object
*/
void setCache(Object cache);
}
}
37 changes: 31 additions & 6 deletions src/main/java/org/apache/commons/jexl3/JexlFeatures.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
* <li>Thin-arrow: use the thin-arrow, ie {@code ->} for lambdas as in {@code x -> x + x}
* <li>Fat-arrow: use the fat-arrow, ie {@code =>} for lambdas as in {@code x => x + x}
* <li>Namespace pragma: whether the {@code #pragma jexl.namespace.ns namespace} syntax is allowed</li>
* <li>Namespace identifier: whether the {@code ns:fun(...)} parser treats the ns:fun as one identifier, no spaces allowed</li>
* <li>Import pragma: whether the {@code #pragma jexl.import fully.qualified.class.name} syntax is allowed</li>
* <li>Comparator names: whether the comparator operator names can be used (as in {@code gt} for &gt;,
* {@code lt} for &lt;, ...)</li>
Expand All @@ -71,7 +72,7 @@ public final class JexlFeatures {
"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",
"thin-arrow", "fat-arrow", "namespace pragma", "import pragma", "comparator names", "pragma anywhere",
"thin-arrow", "fat-arrow", "namespace pragma", "namespace identifier", "import pragma", "comparator names", "pragma anywhere",
"const capture", "ref capture"
};
/** Registers feature ordinal. */
Expand Down Expand Up @@ -112,16 +113,18 @@ public final class JexlFeatures {
public static final int FAT_ARROW = 17;
/** Namespace pragma feature ordinal. */
public static final int NS_PRAGMA = 18;
/** Namespace syntax as an identifier (no space). */
public static final int NS_IDENTIFIER = 19;
/** Import pragma feature ordinal. */
public static final int IMPORT_PRAGMA = 19;
public static final int IMPORT_PRAGMA = 20;
/** Comparator names (legacy) syntax. */
public static final int COMPARATOR_NAMES = 20;
public static final int COMPARATOR_NAMES = 21;
/** The pragma anywhere feature ordinal. */
public static final int PRAGMA_ANYWHERE = 21;
public static final int PRAGMA_ANYWHERE = 22;
/** Captured variables are const. */
public static final int CONST_CAPTURE = 22;
public static final int CONST_CAPTURE = 23;
/** Captured variables are reference. */
public static final int REF_CAPTURE = 23;
public static final int REF_CAPTURE = 24;
/**
* All features.
* N.B. ensure this is updated if additional features are added.
Expand Down Expand Up @@ -574,6 +577,21 @@ public JexlFeatures namespacePragma(final boolean flag) {
return this;
}

/**
* Sets whether namespace as identifier syntax is enabled.
* <p>
* When enabled, a namespace call must be of the form <code>ns:fun(...)</code> with no
* spaces between the namespace name and the function.
* </p>
* @param flag true to enable, false to disable
* @return this features instance
* @since 3.4.1
*/
public JexlFeatures namespaceIdentifier(final boolean flag) {
setFeature(NS_IDENTIFIER, flag);
return this;
}

/**
* @return the declared namespaces test.
*/
Expand Down Expand Up @@ -828,6 +846,13 @@ public boolean supportsMethodCall() {
public boolean supportsNamespacePragma() {
return getFeature(NS_PRAGMA);
}
/**
* @return true if namespace identifier syntax is enabled, false otherwise
* @since 3.4.1
*/
public boolean supportsNamespaceIdentifier() {
return getFeature(NS_IDENTIFIER);
}

/**
* @return true if creating new instances is enabled, false otherwise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,22 +304,16 @@ protected static String stringifyPropertyValue(final JexlNode node) {
protected final JexlOptions options;
/** Cache executors. */
protected final boolean cache;

/** Cancellation support. */
protected final AtomicBoolean cancelled;

/** The namespace resolver. */
protected final JexlContext.NamespaceResolver ns;

/** The class name resolver. */
protected final JexlContext.ClassNameResolver fqcnSolver;

/** The operators evaluation delegate. */
protected final Operators operators;

/** The map of 'prefix:function' to object resolving as namespaces. */
protected final Map<String, Object> functions;

/** The map of dynamically created namespaces, NamespaceFunctor or duck-types of those. */
protected Map<String, Object> functors;

Expand Down Expand Up @@ -355,7 +349,7 @@ protected InterpreterBase(final Engine engine, final JexlOptions opts, final Jex
this.cancelled = acancel != null ? acancel : new AtomicBoolean();
this.functions = options.getNamespaces();
this.functors = null;
this.operators = new Operators(this);
this.operators = (Operators) uberspect.getArithmetic(arithmetic);
// the import package facility
final Collection<String> imports = options.getImports();
this.fqcnSolver = imports.isEmpty()
Expand Down
Loading

0 comments on commit 2d2300e

Please sign in to comment.