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). *

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 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 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 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 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 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 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)); - } + public static List 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 map = Collections.singletonMap("42", S42); + + script = jexl.createScript("let x = 42; map.`${x}`", "map"); + result = script.execute(null, map); + assertEquals(S42, result); + List 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 map = Collections.singletonMap(0, S42); + script = jexl.createScript("let x = 0; map.`${x}`", "map"); + result = script.execute(null, map); + assertEquals(S42, result); + List 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 = "