diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java index 76d1a7d70..ee09ebb7b 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java +++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java @@ -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 diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java index 3479c92a8..6096ff863 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java @@ -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; } } diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java index bdbfb373b..7608e7384 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java +++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java @@ -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; @@ -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); @@ -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; @@ -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: diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccess.java b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccess.java index 7943996c4..893471ee4 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccess.java +++ b/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifierAccess.java @@ -16,6 +16,8 @@ */ package org.apache.commons.jexl3.parser; +import org.apache.commons.jexl3.JexlArithmetic; + /** * Identifiers, variables and registers. */ @@ -23,38 +25,6 @@ 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; @@ -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 diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java index 6d5b90598..484041fc2 100644 --- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java +++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java @@ -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; @@ -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; + } + } } diff --git a/src/test/java/org/apache/commons/jexl3/BuilderTest.java b/src/test/java/org/apache/commons/jexl3/BuilderTest.java index 465b584e6..9f0bd222f 100644 --- a/src/test/java/org/apache/commons/jexl3/BuilderTest.java +++ b/src/test/java/org/apache/commons/jexl3/BuilderTest.java @@ -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()); @@ -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; @@ -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()); diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java b/src/test/java/org/apache/commons/jexl3/Issues400Test.java index 8ec230e40..df1417533 100644 --- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java +++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java @@ -482,132 +482,4 @@ void testSortArray() { assertEquals(9, m[1].get("type")); } - @Test void test425() { - final JexlBuilder builder = new JexlBuilder().strictInterpolation(true); - final JexlEngine jexl = builder.create(); - JexlScript script; - Object result; - script = jexl.createScript("let x = 42; let y = `${x}`; y"); - result = script.execute(null); - assertTrue(result instanceof String); - assertEquals("42", result); - } - - @Test void test426a() { - String src = "let x = 10;\n" + - "let foo = () -> {\n" + - "x += 2;\n" + - "}\n" + - "x = 40;\n" + - "foo();\n" + - "x"; - final JexlBuilder builder = new JexlBuilder().features(new JexlFeatures().constCapture(false).referenceCapture(true)); - final JexlEngine jexl = builder.create(); - JexlScript script; - Object result; - script = jexl.createScript(src); - result = script.execute(null); - assertEquals(42, result); - } - - @Test void test426b() { - String src = "let x = 10; let f = () -> { x + 2 }; x = 40; f()"; - final JexlBuilder builder = new JexlBuilder().features(new JexlFeatures().constCapture(true).referenceCapture(true)); - final JexlEngine jexl = builder.create(); - JexlScript script; - Object result; - script = jexl.createScript(src); - result = script.execute(null); - assertEquals(42, result); - } - - @Test void test426c() { - String src = "let x = 10; let f = () -> { x + 2 }; x = 40; f"; - final JexlBuilder builder = new JexlBuilder().features(new JexlFeatures().constCapture(true).referenceCapture(true)); - final JexlEngine jexl = builder.create(); - JexlScript script; - Object result; - script = jexl.createScript(src); - result = script.execute(null); - assertTrue(result instanceof JexlScript); - script = jexl.createScript("f()", "f"); - result = script.execute(null, result); - assertEquals(42, result); - } - - @Test void test426d() { - String src = "let x = 10; let f = () -> { let x = 142; x }; x = 40; f"; - final JexlBuilder builder = new JexlBuilder().features(new JexlFeatures().referenceCapture(true)); - final JexlEngine jexl = builder.create(); - JexlScript script; - Object result; - script = jexl.createScript(src); - result = script.execute(null); - assertTrue(result instanceof JexlScript); - script = jexl.createScript("f()", "f"); - result = script.execute(null, result); - assertEquals(142, result); - } - - - @Test void test427a() { - String src = "(x, y, z) -> x && y && z"; - final JexlBuilder builder = new JexlBuilder().features(new JexlFeatures().constCapture(true)); - 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 test427b() { - OptContext optc = new OptContext(); - String src = "(x, y, z) -> x || y || z"; - final JexlBuilder builder = new JexlBuilder().features(new JexlFeatures().constCapture(true)); - 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 test427c() { - 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; - } - } } diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java b/src/test/java/org/apache/commons/jexl3/JXLTTest.java index 889f6a8b9..39937ee6c 100644 --- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java +++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java @@ -30,9 +30,12 @@ import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.jexl3.internal.Debugger; @@ -44,6 +47,7 @@ import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -138,14 +142,14 @@ public String toString() { } }; - public static List<JexlBuilder> engines() { - final JexlFeatures f = new JexlFeatures(); - f.lexical(true).lexicalShade(true); - return Arrays.<JexlBuilder>asList( - new JexlBuilder().silent(false).lexical(true).lexicalShade(true).cache(128).strict(true), - new JexlBuilder().features(f).silent(false).cache(128).strict(true), - new JexlBuilder().silent(false).cache(128).strict(true)); - } + public static List<JexlBuilder> engines() { + final JexlFeatures f = new JexlFeatures(); + f.lexical(true).lexicalShade(true); + return Arrays.asList( + new JexlBuilder().silent(false).lexical(true).lexicalShade(true).cache(128).strict(true), + new JexlBuilder().features(f).silent(false).cache(128).strict(true), + new JexlBuilder().silent(false).cache(128).strict(true)); + } private static String refactor(final TemplateDebugger td, final JxltEngine.Template ts) { final boolean dbg = td.debug(ts); @@ -210,7 +214,7 @@ private boolean isLexicalShade() { @BeforeEach @Override - public void setUp() throws Exception { + public void setUp() { // ensure jul logging is only error java.util.logging.Logger.getLogger(org.apache.commons.jexl3.JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE); context = new JexlEvalContext(vars); @@ -225,7 +229,7 @@ public void tearDown() throws Exception { @ParameterizedTest @MethodSource("engines") - public void test311a(final JexlBuilder builder) throws Exception { + void test311a(final JexlBuilder builder) { init(builder); final JexlContext ctx = null; // @formatter:off @@ -243,7 +247,7 @@ public void test311a(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void test311b(final JexlBuilder builder) throws Exception { + void test311b(final JexlBuilder builder) { init(builder); final JexlContext ctx311 = new Context311(); // @formatter:off @@ -261,7 +265,7 @@ public void test311b(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void test311c(final JexlBuilder builder) throws Exception { + void test311c(final JexlBuilder builder) { init(builder); final Context311 ctx311 = new Context311(); ctx311.newOptions().setLexical(true); @@ -280,7 +284,7 @@ public void test311c(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void test311d(final JexlBuilder builder) throws Exception { + void test311d(final JexlBuilder builder) { init(builder); final Context311 ctx311 = new Context311(); ctx311.newOptions().setLexical(true); @@ -299,7 +303,7 @@ public void test311d(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void test311e(final JexlBuilder builder) throws Exception { + void test311e(final JexlBuilder builder) { init(builder); final Context311 ctx311 = new Context311(); ctx311.newOptions().setLexical(true); @@ -316,7 +320,7 @@ public void test311e(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void test311f(final JexlBuilder builder) throws Exception { + void test311f(final JexlBuilder builder) { init(builder); final Context311 ctx311 = new Context311(); ctx311.newOptions().setLexical(true); @@ -333,7 +337,7 @@ public void test311f(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void test311g(final JexlBuilder builder) throws Exception { + void test311g(final JexlBuilder builder) { init(builder); final Context311 ctx311 = new Context311(); ctx311.newOptions().setLexical(true); @@ -350,7 +354,7 @@ public void test311g(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void test311h(final JexlBuilder builder) throws Exception { + void test311h(final JexlBuilder builder) { init(builder); final Context311 ctx311 = new Context311(); ctx311.newOptions().setLexical(true); @@ -362,7 +366,7 @@ public void test311h(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void test311i(final JexlBuilder builder) throws Exception { + void test311i(final JexlBuilder builder) { init(builder); final JexlContext ctx311 = new Context311(); // @formatter:off @@ -380,7 +384,7 @@ public void test311i(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void test315(final JexlBuilder builder) throws Exception { + void test315(final JexlBuilder builder) { init(builder); String s315; StringWriter strw; @@ -411,7 +415,7 @@ public void test315(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void test42(final JexlBuilder builder) throws Exception { + void test42(final JexlBuilder builder) { init(builder); // @formatter:off final String test42 @@ -449,9 +453,141 @@ public void test42(final JexlBuilder builder) throws Exception { assertEquals(test42, refactored); } + + @Test + void testParseIdentifier() { + assertNull(JexlArithmetic.parseIdentifier(null)); + assertNull(JexlArithmetic.parseIdentifier("")); + assertNull(JexlArithmetic.parseIdentifier("za")); + assertNull(JexlArithmetic.parseIdentifier("a")); + assertNull(JexlArithmetic.parseIdentifier("00")); + assertNull(JexlArithmetic.parseIdentifier("01")); + assertNull(JexlArithmetic.parseIdentifier("001")); + assertNull(JexlArithmetic.parseIdentifier("12345678901")); + + assertEquals(0, JexlArithmetic.parseIdentifier("0")); + assertEquals(10, JexlArithmetic.parseIdentifier("10")); + assertEquals(100, JexlArithmetic.parseIdentifier("100")); + assertEquals(42, JexlArithmetic.parseIdentifier("42")); + assertEquals(42000, JexlArithmetic.parseIdentifier("42000")); + + assertEquals(42, JexlArithmetic.parseIdentifier(42)); + } + + /** + * A remediation to strict interpolation that consistently attempts to coerce to integer + * index for lists and keys for maps. (see JEXL-425) + */ + public static class Arithmetic425 extends JexlArithmetic { + public Arithmetic425(boolean astrict) { + super(astrict); + } + + public Object propertyGet(List list, String property) { + Integer id = JexlArithmetic.parseIdentifier(property); + return id == null? JexlEngine.TRY_FAILED : list.get(id); + } + + public Object propertySet(List list, String property, Object value) { + Integer id = JexlArithmetic.parseIdentifier(property); + if (id != null) { + return list.set(id, value); + } + return JexlEngine.TRY_FAILED; + } + + public Object propertyGet(Map list, String property) { + // determine if keys are integers by performing a check on first one + if (list.keySet().stream().findFirst().orElse(null) instanceof Number) { + Integer id = JexlArithmetic.parseIdentifier(property); + if (id != null) { + return list.get(id); + } + } + return JexlEngine.TRY_FAILED; + } + + public Object propertySet(Map list, String property, Object value) { + // determine if keys are integers by performing a check on first one + if (list.keySet().stream().findFirst().orElse(null) instanceof Number) { + Integer id = JexlArithmetic.parseIdentifier(property); + if (id != null) { + list.put(id, value); + return value; + } + } + return JexlEngine.TRY_FAILED; + } + } + + @Test void test425a() { + final String S42 = "fourty-two"; + final JexlBuilder builder = new JexlBuilder().strictInterpolation(true); + final JexlEngine jexl = builder.create(); + JexlScript script; + Object result; + script = jexl.createScript("let x = 42; let y = `${x}`; y"); + result = script.execute(null); + assertTrue(result instanceof String); + assertEquals("42", result); + Map<Object,Object> map = Collections.singletonMap("42", S42); + + script = jexl.createScript("let x = 42; map.`${x}`", "map"); + result = script.execute(null, map); + assertEquals(S42, result); + List<String> list = Collections.singletonList(S42); + + JexlScript finalScript = script; + assertThrows(JexlException.Property.class, () -> finalScript.execute(null, list)); + script = jexl.createScript("let x = 0; list[x]", "list"); + assertEquals(S42, result); + } + + @Test void test425b() { + final String S42 = "fourty-two"; + final JexlEngine jexl = new JexlBuilder().strictInterpolation(false).create(); + run425bc(jexl, false); + run425bc(jexl, false); + } + + @Test void test425c() { + JexlEngine jexl = new JexlBuilder() + .cache(8) + .arithmetic(new Arithmetic425(true)) + .strictInterpolation(true).create(); + run425bc(jexl, true); + run425bc(jexl, true); + } + + void run425bc(JexlEngine jexl, boolean strictInterpolation) { + final String S42 = "fourty-two"; + JexlScript script; + Object result; + script = jexl.createScript("let x = 42; let y = `${x}`; y"); + result = script.execute(null); + assertEquals(!strictInterpolation? 42 : "42", result); + Map<Object,Object> map = Collections.singletonMap(0, S42); + script = jexl.createScript("let x = 0; map.`${x}`", "map"); + result = script.execute(null, map); + assertEquals(S42, result); + List<String> list = Collections.singletonList(S42); + result = script.execute(null, list); + assertEquals(S42, result); + + map = new HashMap<>(map); + map.put(0, "nothing"); + script = jexl.createScript("let x = 0; map.`${x}` = S42", "map", "S42"); + result = script.execute(null, map, S42); + assertEquals(S42, result); + list = new ArrayList<>(list); + list.set(0, "nothing"); + result = script.execute(null, map, S42); + assertEquals(S42, result); + } + @ParameterizedTest @MethodSource("engines") - public void testAssign(final JexlBuilder builder) throws Exception { + void testAssign(final JexlBuilder builder) { init(builder); final Froboz froboz = new Froboz(32); context.set("froboz", froboz); @@ -465,7 +601,7 @@ public void testAssign(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testBadContextNested(final JexlBuilder builder) throws Exception { + void testBadContextNested(final JexlBuilder builder) { init(builder); final JxltEngine.Expression expr = JXLT.createExpression("#{${hi}+'.world'}"); final JexlContext none = null; @@ -475,7 +611,7 @@ public void testBadContextNested(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testCharAtBug(final JexlBuilder builder) throws Exception { + void testCharAtBug(final JexlBuilder builder) { init(builder); context.set("foo", "abcdef"); final JexlOptions options = context.getEngineOptions(); @@ -492,12 +628,11 @@ public void testCharAtBug(final JexlBuilder builder) throws Exception { } finally { options.setSilent(false); } - } @ParameterizedTest @MethodSource("engines") - public void testCommentedTemplate0(final JexlBuilder builder) throws Exception { + void testCommentedTemplate0(final JexlBuilder builder) { init(builder); final JexlContext ctxt = new MapContext(); final JexlEngine jexl = new JexlBuilder().create(); @@ -516,7 +651,7 @@ public void testCommentedTemplate0(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testCommentedTemplate1(final JexlBuilder builder) throws Exception { + void testCommentedTemplate1(final JexlBuilder builder) { init(builder); final JexlContext ctxt = new MapContext(); final JexlEngine jexl = new JexlBuilder().create(); @@ -540,7 +675,7 @@ public void testCommentedTemplate1(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testComposite(final JexlBuilder builder) throws Exception { + void testComposite(final JexlBuilder builder) { init(builder); final String source = "Dear ${p} ${name};"; final JxltEngine.Expression expr = JXLT.createExpression(source); @@ -558,7 +693,7 @@ public void testComposite(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testConstant0(final JexlBuilder builder) throws Exception { + void testConstant0(final JexlBuilder builder) { init(builder); final JexlContext none = null; final String source = "Hello World!"; @@ -573,7 +708,7 @@ public void testConstant0(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testConstant2(final JexlBuilder builder) throws Exception { + void testConstant2(final JexlBuilder builder) { init(builder); final JexlContext none = null; final String source = "${size({'map':123,'map2':456})}"; @@ -588,7 +723,7 @@ public void testConstant2(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testConstant3(final JexlBuilder builder) throws Exception { + void testConstant3(final JexlBuilder builder) { init(builder); final JexlContext none = null; final String source = "#{size({'map':123,'map2':456})}"; @@ -603,7 +738,7 @@ public void testConstant3(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testConstant4(final JexlBuilder builder) throws Exception { + void testConstant4(final JexlBuilder builder) { init(builder); final JexlContext none = null; final String source = "#{ ${size({'1':2,'2': 3})} }"; @@ -618,7 +753,7 @@ public void testConstant4(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testConstantTemplate(final JexlBuilder builder) { + void testConstantTemplate(final JexlBuilder builder) { init(builder); // @formatter:off final String src = "<script>\n" + @@ -642,7 +777,7 @@ public void testConstantTemplate(final JexlBuilder builder) { @ParameterizedTest @MethodSource("engines") - public void testDbgEscapes(final JexlBuilder builder) throws Exception { + void testDbgEscapes(final JexlBuilder builder) { init(builder); // @formatter:off final String[] srcs = { @@ -664,7 +799,7 @@ public void testDbgEscapes(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testDeferred(final JexlBuilder builder) throws Exception { + void testDeferred(final JexlBuilder builder) { init(builder); final JexlContext none = null; final String source = "#{'world'}"; @@ -680,7 +815,7 @@ public void testDeferred(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testEscape(final JexlBuilder builder) throws Exception { + void testEscape(final JexlBuilder builder) { init(builder); final JexlContext none = null; JxltEngine.Expression expr; @@ -696,7 +831,7 @@ public void testEscape(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testEscapeString(final JexlBuilder builder) throws Exception { + void testEscapeString(final JexlBuilder builder) { init(builder); final JxltEngine.Expression expr = JXLT.createExpression("\\\"${'world\\'s finest'}\\\""); final JexlContext none = null; @@ -706,7 +841,7 @@ public void testEscapeString(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testImmediate(final JexlBuilder builder) throws Exception { + void testImmediate(final JexlBuilder builder) { init(builder); final JexlContext none = null; final String source = "${'Hello ' + 'World!'}"; @@ -722,7 +857,7 @@ public void testImmediate(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testImmediateTemplate(final JexlBuilder builder) throws Exception { + void testImmediateTemplate(final JexlBuilder builder) { init(builder); context.set("tables", new String[]{"table1", "table2"}); context.set("w" ,"x=1"); @@ -745,7 +880,7 @@ public void testImmediateTemplate(final JexlBuilder builder) throws Exception { } @ParameterizedTest @MethodSource("engines") - public void testInheritedDebugger(final JexlBuilder builder) throws Exception { + void testInheritedDebugger(final JexlBuilder builder) { init(builder); final String src = "if ($A) { $B + 1; } else { $C - 2 }"; final JexlEngine jexl = JXLT.getEngine(); @@ -763,7 +898,7 @@ public void testInheritedDebugger(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testInterpolation(final JexlBuilder builder) throws Exception { + void testInterpolation(final JexlBuilder builder) { init(builder); final String expr = "`Hello \n${user}`"; final JexlScript script = ENGINE.createScript(expr); @@ -777,7 +912,7 @@ public void testInterpolation(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testInterpolationGlobal(final JexlBuilder builder) throws Exception { + void testInterpolationGlobal(final JexlBuilder builder) { init(builder); if (isLexicalShade()) { context.set("user", null); @@ -789,7 +924,7 @@ public void testInterpolationGlobal(final JexlBuilder builder) throws Exception @ParameterizedTest @MethodSource("engines") - public void testInterpolationLocal(final JexlBuilder builder) throws Exception { + void testInterpolationLocal(final JexlBuilder builder) { init(builder); final String expr = "var user='Henrib'; `Hello \n${user}`"; final Object value = ENGINE.createScript(expr).execute(context); @@ -798,7 +933,7 @@ public void testInterpolationLocal(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testInterpolationLvsG(final JexlBuilder builder) throws Exception { + void testInterpolationLvsG(final JexlBuilder builder) { init(builder); if (isLexicalShade()) { context.set("user", null); @@ -810,7 +945,7 @@ public void testInterpolationLvsG(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testInterpolationLvsG2(final JexlBuilder builder) throws Exception { + void testInterpolationLvsG2(final JexlBuilder builder) { init(builder); if (isLexicalShade()) { context.set("user", null); @@ -822,7 +957,7 @@ public void testInterpolationLvsG2(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testInterpolationParameter(final JexlBuilder builder) throws Exception { + void testInterpolationParameter(final JexlBuilder builder) { init(builder); final String expr = "(user)->{`Hello \n${user}`}"; final JexlScript script = ENGINE.createScript(expr); @@ -834,7 +969,7 @@ public void testInterpolationParameter(final JexlBuilder builder) throws Excepti @ParameterizedTest @MethodSource("engines") - public void testLexicalTemplate(final JexlBuilder builder) throws Exception { + void testLexicalTemplate(final JexlBuilder builder) { init(builder); final JexlOptions opts = new JexlOptions(); final JexlContext ctxt = new PragmaticContext(opts); @@ -872,7 +1007,7 @@ public void testLexicalTemplate(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testMalformed(final JexlBuilder builder) throws Exception { + void testMalformed(final JexlBuilder builder) { init(builder); final JxltEngine.Exception xjexl = assertThrows(JxltEngine.Exception.class, () -> JXLT.createExpression("${'world'"), "should be malformed"); LOGGER.debug(xjexl.getMessage()); @@ -880,7 +1015,7 @@ public void testMalformed(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testMalformedNested(final JexlBuilder builder) throws Exception { + void testMalformedNested(final JexlBuilder builder) { init(builder); final JxltEngine.Exception xjexl = assertThrows(JxltEngine.Exception.class, () -> JXLT.createExpression("#{${hi} world}"), "should be malformed"); LOGGER.debug(xjexl.getMessage()); @@ -888,7 +1023,7 @@ public void testMalformedNested(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testMalformedNested2(final JexlBuilder builder) throws Exception { + void testMalformedNested2(final JexlBuilder builder) { init(builder); final JxltEngine.Exception xjexl = assertThrows(JxltEngine.Exception.class, () -> JXLT.createExpression("#{${hi} world}"), "should be malformed"); LOGGER.debug(xjexl.getMessage()); @@ -896,7 +1031,7 @@ public void testMalformedNested2(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testNested(final JexlBuilder builder) throws Exception { + void testNested(final JexlBuilder builder) { init(builder); final String source = "#{${hi}+'.world'}"; final JxltEngine.Expression expr = JXLT.createExpression(source); @@ -916,7 +1051,7 @@ public void testNested(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testNestedTemplate(final JexlBuilder builder) throws Exception { + void testNestedTemplate(final JexlBuilder builder) { init(builder); final String source = "#{${hi}+'.world'}"; final JxltEngine.Template expr = JXLT.createTemplate(source, "hi"); @@ -932,7 +1067,7 @@ public void testNestedTemplate(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testNonEscapeString(final JexlBuilder builder) throws Exception { + void testNonEscapeString(final JexlBuilder builder) { init(builder); final JxltEngine.Expression expr = JXLT.createExpression("c:\\some\\windows\\path"); final JexlContext none = null; @@ -942,7 +1077,7 @@ public void testNonEscapeString(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testOneLiner(final JexlBuilder builder) throws Exception { + void testOneLiner(final JexlBuilder builder) { init(builder); final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader("fourty-two")); final StringWriter strw = new StringWriter(); @@ -953,7 +1088,7 @@ public void testOneLiner(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testOneLinerVar(final JexlBuilder builder) throws Exception { + void testOneLinerVar(final JexlBuilder builder) { init(builder); final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader("fourty-${x}")); final StringWriter strw = new StringWriter(); @@ -965,7 +1100,7 @@ public void testOneLinerVar(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testPrepareEvaluate(final JexlBuilder builder) throws Exception { + void testPrepareEvaluate(final JexlBuilder builder) { init(builder); final String source = "Dear #{p} ${name};"; final JxltEngine.Expression expr = JXLT.createExpression("Dear #{p} ${name};"); @@ -994,7 +1129,7 @@ public void testPrepareEvaluate(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testPrepareTemplate(final JexlBuilder builder) throws Exception { + void testPrepareTemplate(final JexlBuilder builder) { init(builder); // @formatter:off final String source @@ -1029,7 +1164,7 @@ public void testPrepareTemplate(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testReport(final JexlBuilder builder) throws Exception { + void testReport(final JexlBuilder builder) { init(builder); // @formatter:off final String rpt @@ -1058,7 +1193,7 @@ public void testReport(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testReport1(final JexlBuilder builder) throws Exception { + void testReport1(final JexlBuilder builder) { init(builder); // @formatter:off final String rpt @@ -1090,7 +1225,7 @@ public void testReport1(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testReport2(final JexlBuilder builder) throws Exception { + void testReport2(final JexlBuilder builder) { init(builder); // @formatter:off final String rpt @@ -1126,7 +1261,7 @@ public void testReport2(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testSanboxed311i(final JexlBuilder builder) throws Exception { + void testSanboxed311i(final JexlBuilder builder) { init(builder); /// this uberspect can not access jexl3 classes (besides test) final Uberspect uberspect = new Uberspect(LogFactory.getLog(JXLTTest.class), null, NOJEXL3); @@ -1149,7 +1284,7 @@ public void testSanboxed311i(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testSanboxedTemplate(final JexlBuilder builder) throws Exception { + void testSanboxedTemplate(final JexlBuilder builder) { init(builder); final String src = "Hello ${user}"; final JexlContext ctxt = new MapContext(); @@ -1171,7 +1306,7 @@ public void testSanboxedTemplate(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testStatement(final JexlBuilder builder) throws Exception { + void testStatement(final JexlBuilder builder) { init(builder); final Froboz froboz = new Froboz(32); context.set("froboz", froboz); @@ -1185,7 +1320,7 @@ public void testStatement(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testTemplate0(final JexlBuilder builder) throws Exception { + void testTemplate0(final JexlBuilder builder) { init(builder); final String source = " $$ if(x) {\nx is ${x}\n $$ } else {\n${'no x'}\n$$ }\n"; StringWriter strw; @@ -1211,7 +1346,7 @@ public void testTemplate0(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testTemplate1(final JexlBuilder builder) throws Exception { + void testTemplate1(final JexlBuilder builder) { init(builder); final String source = "$$ if(x) {\nx is ${x}\n$$ } else {\n${'no x'}\n$$ }\n"; StringWriter strw; @@ -1234,7 +1369,7 @@ public void testTemplate1(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testTemplate10(final JexlBuilder builder) throws Exception { + void testTemplate10(final JexlBuilder builder) { init(builder); final String source = "$$(x)->{ if(x) {\nx is ${x}\n$$ } else {\n${'no x'}\n$$ } }\n"; StringWriter strw; @@ -1255,7 +1390,7 @@ public void testTemplate10(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testTemplate2(final JexlBuilder builder) throws Exception { + void testTemplate2(final JexlBuilder builder) { init(builder); final String source = "The answer: ${x}"; StringWriter strw; @@ -1273,7 +1408,7 @@ public void testTemplate2(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testTemplateOutOfScope(final JexlBuilder builder) throws Exception { + void testTemplateOutOfScope(final JexlBuilder builder) { init(builder); final JexlOptions opts = new JexlOptions(); opts.setCancellable(false); @@ -1306,7 +1441,7 @@ public void testTemplateOutOfScope(final JexlBuilder builder) throws Exception { @ParameterizedTest @MethodSource("engines") - public void testTemplatePragmaPro50(final JexlBuilder builder) throws Exception { + void testTemplatePragmaPro50(final JexlBuilder builder) { init(builder); final JexlOptions opts = new JexlOptions(); opts.setCancellable(false); @@ -1330,7 +1465,7 @@ public void testTemplatePragmaPro50(final JexlBuilder builder) throws Exception @ParameterizedTest @MethodSource("engines") - public void testWriter(final JexlBuilder builder) throws Exception { + void testWriter(final JexlBuilder builder) { init(builder); final Froboz froboz = new Froboz(42); final Writer writer = new FrobozWriter(new StringWriter()); diff --git a/src/test/java/org/apache/commons/jexl3/LambdaTest.java b/src/test/java/org/apache/commons/jexl3/LambdaTest.java index 7579ef335..ae2051800 100644 --- a/src/test/java/org/apache/commons/jexl3/LambdaTest.java +++ b/src/test/java/org/apache/commons/jexl3/LambdaTest.java @@ -555,4 +555,66 @@ public void testScriptContext() { assertEquals(42, result); } + /** + * see JEXL-426 + */ + @Test void testRefCapture1() { + String src = "let x = 10;\n" + + "let foo = () -> {\n" + + "x += 2;\n" + + "}\n" + + "x = 40;\n" + + "foo();\n" + + "x"; + final JexlEngine jexl = new JexlBuilder() + .features(new JexlFeatures().referenceCapture(true)) + .create(); + JexlScript script; + Object result; + script = jexl.createScript(src); + result = script.execute(null); + assertEquals(42, result); + } + + @Test void testRefCapture2() { + String src = "let x = 10; let f = () -> { x + 2 }; x = 40; f()"; + final JexlEngine jexl = new JexlBuilder() + .features(new JexlFeatures().constCapture(true).referenceCapture(true)) + .create(); + JexlScript script; + Object result; + script = jexl.createScript(src); + result = script.execute(null); + assertEquals(42, result); + } + + @Test void testRefCapture3() { + String src = "let x = 10; let f = () -> { x + 2 }; x = 40; f"; + final JexlEngine jexl = new JexlBuilder() + .features(new JexlFeatures().constCapture(true).referenceCapture(true)) + .create(); + JexlScript script; + Object result; + script = jexl.createScript(src); + result = script.execute(null); + assertTrue(result instanceof JexlScript); + script = jexl.createScript("f()", "f"); + result = script.execute(null, result); + assertEquals(42, result); + } + + @Test void testRefCapture4() { + String src = "let x = 10; let f = () -> { let x = 142; x }; x = 40; f"; + final JexlEngine jexl = new JexlBuilder() + .features(new JexlFeatures().referenceCapture(true)) + .create(); + JexlScript script; + Object result; + script = jexl.createScript(src); + result = script.execute(null); + assertTrue(result instanceof JexlScript); + script = jexl.createScript("f()", "f"); + result = script.execute(null, result); + assertEquals(142, result); + } }