Skip to content

Commit

Permalink
JEXL-425, JEXL-426 : added tests;
Browse files Browse the repository at this point in the history
- moved tests to appropriate test classes;
- ensure 3.4 compatibility through flags, remediation example with new semantics;
  • Loading branch information
henrib committed Aug 31, 2024
1 parent 788c405 commit aecb393
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 249 deletions.
32 changes: 32 additions & 0 deletions src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,38 @@ private long parseLong(final String arg) throws ArithmeticException {
throw new CoercionException("Long coercion: ("+ arg +")");
}

/**
* Parse an identifier which must be of the form:
* 0|([1-9][0-9]*)
* @param id the identifier
* @return an integer or null
*/
public static Integer parseIdentifier(final Object id) {
if (id instanceof Number) {
return ((Number) id).intValue();
}
// hand coded because the was no way to fail on leading '0's using NumberFormat
if (id instanceof CharSequence) {
final CharSequence str = (CharSequence) id;
final int length = str.length();
// can not be empty string and can not be longer than Integer.MAX_VALUE representation
if (length > 0 && length <= 10) {
int val = 0;
for (int i = 0; i < length; ++i) {
final char c = str.charAt(i);
// leading 0s but no just 0, numeric only
if ((c == '0' && val == 0 && length > 1) || (c < '0' || c > '9')) {
return null;
}
val *= 10;
val += c - '0';
}
return val;
}
}
return null;
}

/**
* Positivize value (unary plus for numbers).
* <p>C/C++/C#/Java perform integral promotion of the operand, ie
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ private <NODE extends JexlNode & JexlNode.JxltHandle> Object evalJxltHandle(NODE
if (options.isStrictInterpolation()) {
return inter;
}
final Integer id = ASTIdentifierAccess.parseIdentifier(inter);
final Integer id = JexlArithmetic.parseIdentifier(inter);
return id != null ? id : eval;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ public final Method[] getMethods(final Class<?> c, final String methodName) {
public JexlPropertyGet getPropertyGet(
final List<PropertyResolver> resolvers, final Object obj, final Object identifier
) {
final Class<?> claz = obj.getClass();
final Class<?> clazz = obj.getClass();
final String property = AbstractExecutor.castString(identifier);
final Introspector is = base();
final List<PropertyResolver> r = resolvers == null ? strategy.apply(null, obj) : resolvers;
Expand All @@ -341,33 +341,33 @@ public JexlPropertyGet getPropertyGet(
switch ((JexlResolver) resolver) {
case PROPERTY:
// first try for a getFoo() type of property (also getfoo() )
executor = PropertyGetExecutor.discover(is, claz, property);
executor = PropertyGetExecutor.discover(is, clazz, property);
if (executor == null) {
executor = BooleanGetExecutor.discover(is, claz, property);
executor = BooleanGetExecutor.discover(is, clazz, property);
}
break;
case MAP:
// let's see if we are a map...
executor = MapGetExecutor.discover(is, claz, identifier);
executor = MapGetExecutor.discover(is, clazz, 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);
executor = ListGetExecutor.discover(is, clazz, index);
}
break;
case DUCK:
// if that didn't work, look for get(foo)
executor = DuckGetExecutor.discover(is, claz, identifier);
executor = DuckGetExecutor.discover(is, clazz, 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);
executor = DuckGetExecutor.discover(is, clazz, property);
}
break;
case FIELD:
// a field may be? (can not be a number)
executor = FieldGetExecutor.discover(is, claz, property);
executor = FieldGetExecutor.discover(is, clazz, property);
// static class fields (enums included)
if (obj instanceof Class<?>) {
executor = FieldGetExecutor.discover(is, (Class<?>) obj, property);
Expand Down Expand Up @@ -399,7 +399,7 @@ public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier)
public JexlPropertySet getPropertySet(
final List<PropertyResolver> resolvers, final Object obj, final Object identifier, final Object arg
) {
final Class<?> claz = obj.getClass();
final Class<?> clazz = obj.getClass();
final String property = AbstractExecutor.castString(identifier);
final Introspector is = base();
final List<PropertyResolver> actual = resolvers == null ? strategy.apply(null, obj) : resolvers;
Expand All @@ -409,30 +409,30 @@ public JexlPropertySet getPropertySet(
switch ((JexlResolver) resolver) {
case PROPERTY:
// first try for a setFoo() type of property (also setfoo() )
executor = PropertySetExecutor.discover(is, claz, property, arg);
executor = PropertySetExecutor.discover(is, clazz, property, arg);
break;
case MAP:
// let's see if we are a map...
executor = MapSetExecutor.discover(is, claz, identifier, arg);
executor = MapSetExecutor.discover(is, clazz, 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);
executor = ListSetExecutor.discover(is, clazz, identifier, arg);
}
break;
case DUCK:
// if that didn't work, look for set(foo)
executor = DuckSetExecutor.discover(is, claz, identifier, arg);
executor = DuckSetExecutor.discover(is, clazz, identifier, arg);
if (executor == null && property != null && property != identifier) {
executor = DuckSetExecutor.discover(is, claz, property, arg);
executor = DuckSetExecutor.discover(is, clazz, property, arg);
}
break;
case FIELD:
// a field may be?
executor = FieldSetExecutor.discover(is, claz, property, arg);
executor = FieldSetExecutor.discover(is, clazz, property, arg);
break;
case CONTAINER:
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,15 @@
*/
package org.apache.commons.jexl3.parser;

import org.apache.commons.jexl3.JexlArithmetic;

/**
* Identifiers, variables and registers.
*/
public class ASTIdentifierAccess extends JexlNode {
/**
*/
private static final long serialVersionUID = 1L;
/**
* Parse an identifier which must be of the form:
* 0|([1-9][0-9]*)
* @param id the identifier
* @return an integer or null
*/
public static Integer parseIdentifier(final String id) {
// hand coded because the was no way to fail on leading '0's using NumberFormat
if (id != null) {
final int length = id.length();
int val = 0;
for (int i = 0; i < length; ++i) {
final char c = id.charAt(i);
// leading 0s but no just 0, NaN
if (c == '0') {
if (length == 1) {
return 0;
}
if (val == 0) {
return null;
}
} // any non numeric, NaN
else if (c < '0' || c > '9') {
return null;
}
val *= 10;
val += c - '0';
}
return val;
}
return null;
}

private String name;
private Integer identifier;
Expand Down Expand Up @@ -99,7 +69,7 @@ public Object jjtAccept(final ParserVisitor visitor, final Object data) {

void setIdentifier(final String id) {
name = id;
identifier = parseIdentifier(id);
identifier = JexlArithmetic.parseIdentifier(id);
}

@Override
Expand Down
62 changes: 62 additions & 0 deletions src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.math.MathContext;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -2254,4 +2255,65 @@ public void testXmlArithmetic() throws Exception {
assertTrue(getJavaVersion() > 11);
}
}

@Test void testShortCircuitAnd() {
String src = "(x, y, z) -> x && y && z";
final JexlBuilder builder = new JexlBuilder();
final JexlEngine jexl = builder.create();
JexlScript script;
Object result;
script = jexl.createScript(src);
result = script.execute(null, true, "foo", 42);
assertEquals(42, result);
result = script.execute(null, true, "", 42);
assertEquals("", result);
}

@Test void testShortCircuitOr() {
OptContext optc = new OptContext();
String src = "(x, y, z) -> x || y || z";
final JexlBuilder builder = new JexlBuilder();
final JexlEngine jexl = builder.create();
JexlOptions options = builder.options();
optc.setOptions(options);
JexlScript script;
Object result;
script = jexl.createScript(src);
result = script.execute(optc, 0, "", 42);
assertEquals(42, result);
result = script.execute(optc, true, 42, null);
assertEquals(true, result);

options.setBooleanLogical(true);
result = script.execute(optc, 0, "", Double.NaN);
assertEquals(false, result);
result = script.execute(optc, 0, "", Collections.emptySet());
assertEquals(true, result);

}

@Test void testLogicalValue() {
String src = "function sanitize(const n) { n == 0 ? NaN : n }; sanitize(x) && 420 / x";
final JexlEngine jexl = new JexlBuilder().create();
JexlScript script;
Object result;
script = jexl.createScript(src, "x");
result = script.execute(null, 10);
assertEquals(42, result);
result = script.execute(null, 0);
assertTrue(Double.isNaN(((Number) result).doubleValue()));
}

public static class OptContext extends MapContext implements JexlContext.OptionsHandle {
private JexlOptions options;

@Override
public JexlOptions getEngineOptions() {
return options;
}

void setOptions(JexlOptions options) {
this.options = options;
}
}
}
10 changes: 7 additions & 3 deletions src/test/java/org/apache/commons/jexl3/BuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private static JexlBuilder builder() {
}

@Test
public void testFlags() {
void testFlags() {
assertTrue(builder().antish(true).antish());
assertFalse(builder().antish(false).antish());
assertTrue(builder().cancellable(true).cancellable());
Expand All @@ -54,10 +54,14 @@ public void testFlags() {
assertFalse(builder().silent(false).silent());
assertTrue(builder().strict(true).strict());
assertFalse(builder().strict(false).strict());
assertTrue(builder().booleanLogical(true).options().isBooleanLogical());
assertFalse(builder().booleanLogical(false).options().isBooleanLogical());
assertTrue(builder().strictInterpolation(true).options().isStrictInterpolation());
assertFalse(builder().strictInterpolation(false).options().isStrictInterpolation());
}

@Test
public void testOther() {
void testOther() {
final ClassLoader cls = getClass().getClassLoader().getParent();
assertEquals(cls, builder().loader(cls).loader());
final Charset cs = StandardCharsets.UTF_16;
Expand All @@ -71,7 +75,7 @@ public void testOther() {
}

@Test
public void testValues() {
void testValues() {
assertEquals(1, builder().collectMode(1).collectMode());
assertEquals(0, builder().collectMode(0).collectMode());
assertEquals(32, builder().cacheThreshold(32).cacheThreshold());
Expand Down
Loading

0 comments on commit aecb393

Please sign in to comment.