blob: adbb053771e1b435b7d92f447325564bf1b350e5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.jexl3;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Collections;
import org.apache.commons.jexl3.junit.Asserter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* Tests for array access operator []
*
* @since 2.0
*/
@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
public class SideEffectTest extends JexlTestCase {
private Asserter asserter;
public SideEffectTest() {
super("SideEffectTest");
}
@Override
@Before
public void setUp() {
asserter = new Asserter(JEXL);
}
@Test
public void testSideEffectVar() throws Exception {
final Map<String,Object> context = asserter.getVariables();
final Integer i41 = Integer.valueOf(4141);
final Object foo = i41;
context.put("foo", foo);
asserter.assertExpression("foo += 2", i41 + 2);
Assert.assertEquals(context.get("foo"), i41 + 2);
context.put("foo", foo);
asserter.assertExpression("foo -= 2", i41 - 2);
Assert.assertEquals(context.get("foo"), i41 - 2);
context.put("foo", foo);
asserter.assertExpression("foo *= 2", i41 * 2);
Assert.assertEquals(context.get("foo"), i41 * 2);
context.put("foo", foo);
asserter.assertExpression("foo /= 2", i41 / 2);
Assert.assertEquals(context.get("foo"), i41 / 2);
context.put("foo", foo);
asserter.assertExpression("foo %= 2", i41 % 2);
Assert.assertEquals(context.get("foo"), i41 % 2);
context.put("foo", foo);
asserter.assertExpression("foo &= 3", (long) (i41 & 3));
Assert.assertEquals(context.get("foo"), (long)(i41 & 3));
context.put("foo", foo);
asserter.assertExpression("foo |= 2", (long)(i41 | 2));
Assert.assertEquals(context.get("foo"), (long)(i41 | 2));
context.put("foo", foo);
asserter.assertExpression("foo ^= 2", (long)(i41 ^ 2));
Assert.assertEquals(context.get("foo"), (long)(i41 ^ 2));
}
@Test
public void testSideEffectVarDots() throws Exception {
final Map<String,Object> context = asserter.getVariables();
final Integer i41 = Integer.valueOf(4141);
final Object foo = i41;
context.put("foo.bar.quux", foo);
asserter.assertExpression("foo.bar.quux += 2", i41 + 2);
Assert.assertEquals(context.get("foo.bar.quux"), i41 + 2);
context.put("foo.bar.quux", foo);
asserter.assertExpression("foo.bar.quux -= 2", i41 - 2);
Assert.assertEquals(context.get("foo.bar.quux"), i41 - 2);
context.put("foo.bar.quux", foo);
asserter.assertExpression("foo.bar.quux *= 2", i41 * 2);
Assert.assertEquals(context.get("foo.bar.quux"), i41 * 2);
context.put("foo.bar.quux", foo);
asserter.assertExpression("foo.bar.quux /= 2", i41 / 2);
Assert.assertEquals(context.get("foo.bar.quux"), i41 / 2);
context.put("foo.bar.quux", foo);
asserter.assertExpression("foo.bar.quux %= 2", i41 % 2);
Assert.assertEquals(context.get("foo.bar.quux"), i41 % 2);
context.put("foo.bar.quux", foo);
asserter.assertExpression("foo.bar.quux &= 3", (long) (i41 & 3));
Assert.assertEquals(context.get("foo.bar.quux"), (long)(i41 & 3));
context.put("foo.bar.quux", foo);
asserter.assertExpression("foo.bar.quux |= 2", (long)(i41 | 2));
Assert.assertEquals(context.get("foo.bar.quux"), (long)(i41 | 2));
context.put("foo.bar.quux", foo);
asserter.assertExpression("foo.bar.quux ^= 2", (long)(i41 ^ 2));
Assert.assertEquals(context.get("foo.bar.quux"), (long)(i41 ^ 2));
}
@Test
public void testSideEffectArray() throws Exception {
final Integer i41 = Integer.valueOf(4141);
final Integer i42 = Integer.valueOf(42);
final Integer i43 = Integer.valueOf(43);
final String s42 = "fourty-two";
final String s43 = "fourty-three";
final Object[] foo = new Object[3];
foo[1] = i42;
foo[2] = i43;
asserter.setVariable("foo", foo);
foo[0] = i41;
asserter.assertExpression("foo[0] += 2", i41 + 2);
Assert.assertEquals(foo[0], i41 + 2);
foo[0] = i41;
asserter.assertExpression("foo[0] -= 2", i41 - 2);
Assert.assertEquals(foo[0], i41 - 2);
foo[0] = i41;
asserter.assertExpression("foo[0] *= 2", i41 * 2);
Assert.assertEquals(foo[0], i41 * 2);
foo[0] = i41;
asserter.assertExpression("foo[0] /= 2", i41 / 2);
Assert.assertEquals(foo[0], i41 / 2);
foo[0] = i41;
asserter.assertExpression("foo[0] %= 2", i41 % 2);
Assert.assertEquals(foo[0], i41 % 2);
foo[0] = i41;
asserter.assertExpression("foo[0] &= 3", (long) (i41 & 3));
Assert.assertEquals(foo[0], (long)(i41 & 3));
foo[0] = i41;
asserter.assertExpression("foo[0] |= 2", (long)(i41 | 2));
Assert.assertEquals(foo[0], (long)(i41 | 2));
foo[0] = i41;
asserter.assertExpression("foo[0] ^= 2", (long)(i41 ^ 2));
Assert.assertEquals(foo[0], (long)(i41 ^ 2));
}
@Test
public void testSideEffectDotArray() throws Exception {
final Integer i41 = Integer.valueOf(4141);
final Integer i42 = Integer.valueOf(42);
final Integer i43 = Integer.valueOf(43);
final String s42 = "fourty-two";
final String s43 = "fourty-three";
final Object[] foo = new Object[3];
foo[1] = i42;
foo[2] = i43;
asserter.setVariable("foo", foo);
foo[0] = i41;
asserter.assertExpression("foo.0 += 2", i41 + 2);
Assert.assertEquals(foo[0], i41 + 2);
foo[0] = i41;
asserter.assertExpression("foo.0 -= 2", i41 - 2);
Assert.assertEquals(foo[0], i41 - 2);
foo[0] = i41;
asserter.assertExpression("foo.0 *= 2", i41 * 2);
Assert.assertEquals(foo[0], i41 * 2);
foo[0] = i41;
asserter.assertExpression("foo.0 /= 2", i41 / 2);
Assert.assertEquals(foo[0], i41 / 2);
foo[0] = i41;
asserter.assertExpression("foo.0 %= 2", i41 % 2);
Assert.assertEquals(foo[0], i41 % 2);
foo[0] = i41;
asserter.assertExpression("foo.0 &= 3", (long) (i41 & 3));
Assert.assertEquals(foo[0], (long)(i41 & 3));
foo[0] = i41;
asserter.assertExpression("foo.0 |= 2", (long)(i41 | 2));
Assert.assertEquals(foo[0], (long)(i41 | 2));
foo[0] = i41;
asserter.assertExpression("foo.0 ^= 2", (long)(i41 ^ 2));
Assert.assertEquals(foo[0], (long)(i41 ^ 2));
}
@Test
public void testSideEffectAntishArray() throws Exception {
final Integer i41 = Integer.valueOf(4141);
final Integer i42 = Integer.valueOf(42);
final Integer i43 = Integer.valueOf(43);
final Object[] foo = new Object[3];
foo[1] = i42;
foo[2] = i43;
asserter.setVariable("foo.bar", foo);
foo[0] = i41;
asserter.assertExpression("foo.bar[0] += 2", i41 + 2);
Assert.assertEquals(foo[0], i41 + 2);
foo[0] = i41;
asserter.assertExpression("foo.bar[0] -= 2", i41 - 2);
Assert.assertEquals(foo[0], i41 - 2);
foo[0] = i41;
asserter.assertExpression("foo.bar[0] *= 2", i41 * 2);
Assert.assertEquals(foo[0], i41 * 2);
foo[0] = i41;
asserter.assertExpression("foo.bar[0] /= 2", i41 / 2);
Assert.assertEquals(foo[0], i41 / 2);
foo[0] = i41;
asserter.assertExpression("foo.bar[0] %= 2", i41 % 2);
Assert.assertEquals(foo[0], i41 % 2);
foo[0] = i41;
asserter.assertExpression("foo.bar[0] &= 3", (long) (i41 & 3));
Assert.assertEquals(foo[0], (long)(i41 & 3));
foo[0] = i41;
asserter.assertExpression("foo.bar[0] |= 2", (long)(i41 | 2));
Assert.assertEquals(foo[0], (long)(i41 | 2));
foo[0] = i41;
asserter.assertExpression("foo.bar[0] ^= 2", (long)(i41 ^ 2));
Assert.assertEquals(foo[0], (long)(i41 ^ 2));
}
public static class Foo {
int value;
Foo(final int v) {
value = v;
}
@Override
public String toString() {
return Integer.toString(value);
}
public void setValue(final long v) {
value = (int) v;
}
public int getValue() {
return value;
}
public void setBar(final int x, final long v) {
value = (int) v + x;
}
public int getBar(final int x) {
return value + x;
}
}
@Test
public void testSideEffectBean() throws Exception {
final Integer i41 = Integer.valueOf(4141);
final Foo foo = new Foo(0);
asserter.setVariable("foo", foo);
foo.value = i41;
asserter.assertExpression("foo.value += 2", i41 + 2);
Assert.assertEquals(foo.value, i41 + 2);
foo.value = i41;
asserter.assertExpression("foo.value -= 2", i41 - 2);
Assert.assertEquals(foo.value, i41 - 2);
foo.value = i41;
asserter.assertExpression("foo.value *= 2", i41 * 2);
Assert.assertEquals(foo.value, i41 * 2);
foo.value = i41;
asserter.assertExpression("foo.value /= 2", i41 / 2);
Assert.assertEquals(foo.value, i41 / 2);
foo.value = i41;
asserter.assertExpression("foo.value %= 2", i41 % 2);
Assert.assertEquals(foo.value, i41 % 2);
foo.value = i41;
asserter.assertExpression("foo.value &= 3", (long) (i41 & 3));
Assert.assertEquals(foo.value, i41 & 3);
foo.value = i41;
asserter.assertExpression("foo.value |= 2", (long)(i41 | 2));
Assert.assertEquals(foo.value, i41 | 2);
foo.value = i41;
asserter.assertExpression("foo.value ^= 2", (long)(i41 ^ 2));
Assert.assertEquals(foo.value, i41 ^ 2);
}
@Test
public void testSideEffectBeanContainer() throws Exception {
final Integer i41 = Integer.valueOf(4141);
final Foo foo = new Foo(0);
asserter.setVariable("foo", foo);
foo.value = i41;
asserter.assertExpression("foo.bar[0] += 2", i41 + 2);
Assert.assertEquals(foo.value, i41 + 2);
foo.value = i41;
asserter.assertExpression("foo.bar[1] += 2", i41 + 3);
Assert.assertEquals(foo.value, i41 + 4);
foo.value = i41;
asserter.assertExpression("foo.bar[0] -= 2", i41 - 2);
Assert.assertEquals(foo.value, i41 - 2);
foo.value = i41;
asserter.assertExpression("foo.bar[0] *= 2", i41 * 2);
Assert.assertEquals(foo.value, i41 * 2);
foo.value = i41;
asserter.assertExpression("foo.bar[0] /= 2", i41 / 2);
Assert.assertEquals(foo.value, i41 / 2);
foo.value = i41;
asserter.assertExpression("foo.bar[0] %= 2", i41 % 2);
Assert.assertEquals(foo.value, i41 % 2);
foo.value = i41;
asserter.assertExpression("foo.bar[0] &= 3", (long) (i41 & 3));
Assert.assertEquals(foo.value, i41 & 3);
foo.value = i41;
asserter.assertExpression("foo.bar[0] |= 2", (long)(i41 | 2));
Assert.assertEquals(foo.value, i41 | 2);
foo.value = i41;
asserter.assertExpression("foo.bar[0] ^= 2", (long)(i41 ^ 2));
Assert.assertEquals(foo.value, i41 ^ 2);
}
@Test
public void testArithmeticSelf() throws Exception {
final JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new SelfArithmetic(false)).create();
final JexlContext jc = null;
runSelfOverload(jexl, jc);
runSelfOverload(jexl, jc);
}
@Test
public void testArithmeticSelfNoCache() throws Exception {
final JexlEngine jexl = new JexlBuilder().cache(0).arithmetic(new SelfArithmetic(false)).create();
final JexlContext jc = null;
runSelfOverload(jexl, jc);
}
protected void runSelfOverload(final JexlEngine jexl, final JexlContext jc) {
JexlScript script;
Object result;
script = jexl.createScript("(x, y)->{ x += y }");
result = script.execute(jc, 3115, 15);
Assert.assertEquals(3115 + 15, result);
final Var v0 = new Var(3115);
result = script.execute(jc, v0, new Var(15));
Assert.assertEquals(result, v0);
Assert.assertEquals(3115 + 15, v0.value);
script = jexl.createScript("(x, y)->{ x -= y}");
result = script.execute(jc, 3115, 15);
Assert.assertEquals(3115 - 15, result);
final Var v1 = new Var(3115);
result = script.execute(jc, v1, new Var(15));
Assert.assertNotEquals(result, v1); // not a real side effect
Assert.assertEquals(3115 - 15, ((Var) result).value);
script = jexl.createScript("(x, y)->{ x *= y }");
result = script.execute(jc, 3115, 15);
Assert.assertEquals(3115 * 15, result);
final Var v2 = new Var(3115);
result = script.execute(jc, v2, new Var(15));
Assert.assertEquals(result, v2);
Assert.assertEquals(3115 * 15, v2.value);
script = jexl.createScript("(x, y)->{ x /= y }");
result = script.execute(jc, 3115, 15);
Assert.assertEquals(3115 / 15, result);
final Var v3 = new Var(3115);
result = script.execute(jc, v3, new Var(15));
Assert.assertEquals(result, v3);
Assert.assertEquals(3115 / 15, v3.value);
script = jexl.createScript("(x, y)->{ x %= y }");
result = script.execute(jc, 3115, 15);
Assert.assertEquals(3115 % 15, result);
final Var v4 = new Var(3115);
result = script.execute(jc, v4, new Var(15));
Assert.assertEquals(result, v4);
Assert.assertEquals(3115 % 15, v4.value);
script = jexl.createScript("(x, y)->{ x &= y }");
result = script.execute(jc, 3115, 15);
Assert.assertEquals(3115L & 15, result);
final Var v5 = new Var(3115);
result = script.execute(jc, v5, new Var(15));
Assert.assertEquals(result, v5);
Assert.assertEquals(3115 & 15, v5.value);
script = jexl.createScript("(x, y)->{ x |= y }");
result = script.execute(jc, 3115, 15);
Assert.assertEquals(3115L | 15, result);
final Var v6 = new Var(3115);
result = script.execute(jc, v6, new Var(15));
Assert.assertEquals(result, v6);
Assert.assertEquals(3115L | 15, v6.value);
script = jexl.createScript("(x, y)->{ x ^= y }");
result = script.execute(jc, 3115, 15);
Assert.assertEquals(3115L ^ 15, result);
final Var v7 = new Var(3115);
result = script.execute(jc, v7, new Var(15));
Assert.assertEquals(result, v7);
Assert.assertEquals(3115L ^ 15, v7.value);
}
@Test
public void testOverrideGetSet() throws Exception {
final JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new SelfArithmetic(false)).create();
final JexlContext jc = null;
JexlScript script;
Object result;
final Var v0 = new Var(3115);
script = jexl.createScript("(x)->{ x.value}");
result = script.execute(jc, v0);
Assert.assertEquals(3115, result);
script = jexl.createScript("(x)->{ x['VALUE']}");
result = script.execute(jc, v0);
Assert.assertEquals(3115, result);
script = jexl.createScript("(x,y)->{ x.value = y}");
result = script.execute(jc, v0, 42);
Assert.assertEquals(42, result);
script = jexl.createScript("(x,y)->{ x['VALUE'] = y}");
result = script.execute(jc, v0, 169);
Assert.assertEquals(169, result);
}
public static class Var {
int value;
Var(final int v) {
value = v;
}
@Override
public String toString() {
return Integer.toString(value);
}
}
// an arithmetic that performs side effects
public static class SelfArithmetic extends JexlArithmetic {
public SelfArithmetic(final boolean strict) {
super(strict);
}
public Object propertyGet(final Var var, final String property) {
return "value".equals(property)? var.value : JexlEngine.TRY_FAILED;
}
public Object propertySet(final Var var, final String property, final int v) {
return "value".equals(property)? var.value = v : JexlEngine.TRY_FAILED;
}
public Object arrayGet(final Var var, final String property) {
return "VALUE".equals(property)? var.value : JexlEngine.TRY_FAILED;
}
public Object arraySet(final Var var, final String property, final int v) {
return "VALUE".equals(property)? var.value = v : JexlEngine.TRY_FAILED;
}
public JexlOperator selfAdd(final Var lhs, final Var rhs) {
lhs.value += rhs.value;
return JexlOperator.ASSIGN;
}
// for kicks, this one does not side effect but overloads nevertheless
public Var selfSubtract(final Var lhs, final Var rhs) {
return new Var(lhs.value - rhs.value);
}
public JexlOperator selfDivide(final Var lhs, final Var rhs) {
lhs.value /= rhs.value;
return JexlOperator.ASSIGN;
}
public JexlOperator selfMultiply(final Var lhs, final Var rhs) {
lhs.value *= rhs.value;
return JexlOperator.ASSIGN;
}
public JexlOperator selfMod(final Var lhs, final Var rhs) {
lhs.value %= rhs.value;
return JexlOperator.ASSIGN;
}
public Var and(final Var lhs, final Var rhs) {
return new Var(lhs.value & rhs.value);
}
public JexlOperator selfAnd(final Var lhs, final Var rhs) {
lhs.value &= rhs.value;
return JexlOperator.ASSIGN;
}
public Var or(final Var lhs, final Var rhs) {
return new Var(lhs.value | rhs.value);
}
public JexlOperator selfOr(final Var lhs, final Var rhs) {
lhs.value |= rhs.value;
return JexlOperator.ASSIGN;
}
public Var xor(final Var lhs, final Var rhs) {
return new Var(lhs.value ^ rhs.value);
}
public JexlOperator selfXor(final Var lhs, final Var rhs) {
lhs.value ^= rhs.value;
return JexlOperator.ASSIGN;
}
}
/**
* An arithmetic that implements 2 selfAdd methods.
*/
public static class Arithmetic246 extends JexlArithmetic {
public Arithmetic246(final boolean astrict) {
super(astrict);
}
public JexlOperator selfAdd(final Collection<String> c, final String item) throws IOException {
c.add(item);
return JexlOperator.ASSIGN;
}
public JexlOperator selfAdd(final Appendable c, final String item) throws IOException {
c.append(item);
return JexlOperator.ASSIGN;
}
@Override
public Object add(final Object right, final Object left) {
return super.add(left, right);
}
}
public static class Arithmetic246b extends Arithmetic246 {
public Arithmetic246b(final boolean astrict) {
super(astrict);
}
public Object selfAdd(final Object c, final String item) throws IOException {
if (c == null) {
return new ArrayList<String>(Collections.singletonList(item));
}
if (c instanceof Appendable) {
((Appendable) c).append(item);
return JexlOperator.ASSIGN;
}
return JexlEngine.TRY_FAILED;
}
}
@Test
public void test246() throws Exception {
run246(new Arithmetic246(true));
}
@Test
public void test246b() throws Exception {
run246(new Arithmetic246b(true));
}
private void run246(final JexlArithmetic j246) throws Exception {
final Log log246 = LogFactory.getLog(SideEffectTest.class);
// quiesce the logger
final java.util.logging.Logger ll246 = java.util.logging.LogManager.getLogManager().getLogger(SideEffectTest.class.getName());
// ll246.setLevel(Level.WARNING);
final JexlEngine jexl = new JexlBuilder().arithmetic(j246).cache(32).debug(true).logger(log246).create();
final JexlScript script = jexl.createScript("z += x", "x");
final MapContext ctx = new MapContext();
List<String> z = new ArrayList<String>(1);
// no ambiguous, std case
ctx.set("z", z);
Object zz = script.execute(ctx, "42");
Assert.assertSame(zz, z);
Assert.assertEquals(1, z.size());
z.clear();
ctx.clear();
boolean t246 = false;
// call with null
try {
script.execute(ctx, "42");
zz = ctx.get("z");
Assert.assertTrue(zz instanceof List<?>);
z = (List<String>) zz;
Assert.assertEquals(1, z.size());
} catch(JexlException | ArithmeticException xjexl) {
t246 = true;
Assert.assertEquals(j246.getClass(), Arithmetic246.class);
}
ctx.clear();
// a non ambiguous call still succeeds
ctx.set("z", z);
zz = script.execute(ctx, "-42");
Assert.assertSame(zz, z);
Assert.assertEquals(t246? 1 : 2, z.size());
}
// an arithmetic that performs side effects
public static class Arithmetic248 extends JexlArithmetic {
public Arithmetic248(final boolean strict) {
super(strict);
}
public Object arrayGet(final List<?> list, final Collection<Integer> range) {
final List<Object> rl = new ArrayList<Object>(range.size());
for(final int i : range) {
rl.add(list.get(i));
}
return rl;
}
public Object arraySet(final List<Object> list, final Collection<Integer> range, final Object value) {
for(final int i : range) {
list.set(i, value);
}
return list;
}
}
@Test
public void test248() throws Exception {
final MapContext ctx = new MapContext();
final List<Object> foo = new ArrayList<Object>(Arrays.asList(10, 20, 30, 40));
ctx.set("foo", foo);
final JexlEngine engine = new JexlBuilder().arithmetic(new Arithmetic248(true)).create();
final JexlScript foo12 = engine.createScript("foo[1..2]");
try {
final Object r = foo12.execute(ctx);
Assert.assertEquals(Arrays.asList(20, 30), r);
} catch (final JexlException xp) {
Assert.assertTrue(xp instanceof JexlException.Property);
}
final JexlScript foo12assign = engine.createScript("foo[1..2] = x", "x");
try {
final Object r = foo12assign.execute(ctx, 25);
Assert.assertEquals(25, r);
Assert.assertEquals(Arrays.asList(10, 25, 25, 40), foo);
} catch (final JexlException xp) {
Assert.assertTrue(xp instanceof JexlException.Property);
}
}
}