This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch JEXL-360 in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
commit 43fdeed59b161f9846b6746e63000be0e7b767a3 Author: henrib <hen...@apache.org> AuthorDate: Thu Feb 17 18:03:25 2022 +0100 JEXL-360: try a more refined arithmetic; - refactored tests; --- .../org/apache/commons/jexl3/Arithmetic360.java | 221 +++++++++++++++++++++ .../apache/commons/jexl3/ShiftOperatorsTest.java | 185 +++++++++++++++++ .../org/apache/commons/jexl3/junit/Asserter.java | 4 +- 3 files changed, 408 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/apache/commons/jexl3/Arithmetic360.java b/src/test/java/org/apache/commons/jexl3/Arithmetic360.java new file mode 100644 index 0000000..aacde4b --- /dev/null +++ b/src/test/java/org/apache/commons/jexl3/Arithmetic360.java @@ -0,0 +1,221 @@ +/* + * 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.math.BigInteger; +import java.math.MathContext; + +/** + * An arithmetic that tries to keep argument types for bit-twiddling operators. + */ +public class Arithmetic360 extends JexlArithmetic { + public Arithmetic360(boolean astrict) { + super(astrict); + } + + public Arithmetic360(boolean astrict, MathContext bigdContext, int bigdScale) { + super(astrict, bigdContext, bigdScale); + } + + /** + * Given a long, attempt to narrow it to an int. + * <p>Narrowing will only occur if no operand is a Long. + * @param lhs the left-hand side operand that lead to the long result + * @param rhs the right-hand side operand that lead to the long result + * @param result the long to narrow + * @return an Integer if narrowing is possible, the original Long otherwise + */ + protected Number narrowLong(final Object lhs, final Object rhs, final long result) { + if (!(lhs instanceof Long || rhs instanceof Long)) { + final int ir = (int) result; + if (result == (long) ir) { + return ir; + } + } + return result; + } + + /** + * Given a long, attempt to narrow it to an int. + * <p>Narrowing will only occur if the initial operand is not a Long. + * @param operand the operand that lead to the long result + * @param result the long result to narrow + * @return an Integer if narrowing is possible, the original Long otherwise + */ + protected Number narrowLong(final Object operand, final long result) { + if (!(operand instanceof Long)) { + final int ir = (int) result; + if (result == (long) ir) { + return ir; + } + } + return result; + } + /** + * Checks if value class is a number that can be represented exactly in a long. + * + * @param value argument + * @return true if argument can be represented by a long + */ + protected Number asIntNumber(final Object value) { + return value instanceof Integer + || value instanceof Short + || value instanceof Byte + ? (Number) value + : null; + } + + /** + * Casts to Long if possible. + * @param value the Long or else + * @return the Long or null + */ + protected Long castLongNumber(final Object value) { + return value instanceof Long ? (Long) value : null; + } + + /** + * Performs a bitwise and. + * + * @param left the left operand + * @param right the right operator + * @return left & right + */ + public Object and(final Object left, final Object right) { + final Number l = asLongNumber(left); + final Number r = asLongNumber(right); + if (l != null && r != null) { + return narrowLong(left, right, l.longValue() & r.longValue()); + } + return toBigInteger(left).and(toBigInteger(right)); + } + + /** + * Performs a bitwise or. + * + * @param left the left operand + * @param right the right operator + * @return left | right + */ + public Object or(final Object left, final Object right) { + final Number l = asLongNumber(left); + final Number r = asLongNumber(right); + if (l != null && r != null) { + return narrowLong(left, right, l.longValue() | r.longValue()); + } + return toBigInteger(left).or(toBigInteger(right)); + } + + /** + * Performs a bitwise xor. + * + * @param left the left operand + * @param right the right operator + * @return left ^ right + */ + public Object xor(final Object left, final Object right) { + final Number l = asLongNumber(left); + final Number r = asLongNumber(right); + if (l != null && r != null) { + return narrowLong(left, right, l.longValue() ^ r.longValue()); + } + return toBigInteger(left).xor(toBigInteger(right)); + } + + /** + * Performs a bitwise complement. + * + * @param val the operand + * @return ~val + */ + public Object complement(final Object val) { + final long l = toLong(val); + return narrowLong(val, ~l); + } + + /** + * Shifts a bit pattern to the right. + * + * @param left left argument + * @param right right argument + * @return left << right. + */ + public Object shiftLeft(Object left, Object right) { + if (left == null && right == null) { + return controlNullNullOperands(); + } + final int r = toInteger(right); + Number l = asIntNumber(left); + if (l != null) { + return l.intValue() << r; + } + l = castLongNumber(left); + if (l != null) { + return l.longValue() << r; + } + return toBigInteger(left).shiftLeft(r); + } + + /** + * Shifts a bit pattern to the right. + * + * @param left left argument + * @param right right argument + * @return left >> right. + */ + public Object shiftRight(Object left, Object right) { + if (left == null && right == null) { + return controlNullNullOperands(); + } + final int r = toInteger(right); + Number l = asIntNumber(left); + if (l != null) { + return l.intValue() >> r; + } + l = castLongNumber(left); + if (l != null) { + return l.longValue() >> r; + } + return toBigInteger(left).shiftRight(r); + } + + /** + * Shifts a bit pattern to the right unsigned. + * + * @param left left argument + * @param right right argument + * @return left >>> right. + */ + public Object shiftRightUnsigned(Object left, Object right) { + if (left == null && right == null) { + return controlNullNullOperands(); + } + final int r = toInteger(right); + Number l = asIntNumber(left); + if (l != null) { + return l.intValue() >>> r; + } + l = castLongNumber(left); + if (l != null) { + return l.longValue() >>> r; + } + BigInteger bl = toBigInteger(left); + return bl.signum() < 0? bl.negate().shiftRight(r) : bl.shiftRight(r); + } + +} diff --git a/src/test/java/org/apache/commons/jexl3/ShiftOperatorsTest.java b/src/test/java/org/apache/commons/jexl3/ShiftOperatorsTest.java new file mode 100644 index 0000000..2ee14b9 --- /dev/null +++ b/src/test/java/org/apache/commons/jexl3/ShiftOperatorsTest.java @@ -0,0 +1,185 @@ +/* + * 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.math.BigInteger; + +import org.apache.commons.jexl3.junit.Asserter; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests shift operators. + */ +@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"}) +public class ShiftOperatorsTest extends JexlTestCase { + private Asserter asserter; + + private Asserter a360; + + public ShiftOperatorsTest() { + super("ShiftOperatorsTest"); + asserter = new Asserter(JEXL); + asserter.setStrict(false, false); + + JexlEngine j360 = new JexlBuilder().arithmetic(new Arithmetic360(true)).strict(true).create(); + a360 = new Asserter(j360); + a360.setStrict(false, false); + } + + @Test + public void testLeftShiftIntValue() throws Exception { + final String expr = "(x, y)->{ x << y }"; + asserter.assertExpression(expr, 1L << 2, 1L, 2); + asserter.assertExpression(expr, 1L << -2, 1L, -2); + asserter.assertExpression(expr, -1L << 2, -1L, 2); + asserter.assertExpression(expr, -1L << -2, -1L, -2); + + a360.assertExpression(expr, 1L << 2, 1L, 2); + a360.assertExpression(expr, 1L << -2, 1L, -2); + a360.assertExpression(expr, -1L << 2, -1L, 2); + a360.assertExpression(expr, -1L << -2, -1L, -2); + + a360.assertExpression(expr, 1 << 2, 1, 2); + a360.assertExpression(expr, 1 << -2, 1, -2); + a360.assertExpression(expr, -1 << 2, -1, 2); + a360.assertExpression(expr, -1 << -2, -1, -2); + } + + @Test + public void testRightShiftIntValue() throws Exception { + final String expr = "(x, y)->{ x >> y }"; + asserter.assertExpression(expr, 42L >> 2, 42L, 2); + asserter.assertExpression(expr, 42L >> -2, 42L, -2); + asserter.assertExpression(expr, -42L >> 2, -42L, 2); + asserter.assertExpression(expr, -42L >> -2, -42L, -2); + + a360.assertExpression(expr, 42L >> 2, 42L, 2); + a360.assertExpression(expr, 42L >> -2, 42L, -2); + a360.assertExpression(expr, -42L >> 2, -42L, 2); + a360.assertExpression(expr, -42L >> -2, -42L, -2); + + a360.assertExpression(expr, 42 >> 2, 42, 2); + a360.assertExpression(expr, 42 >> -2, 42, -2); + a360.assertExpression(expr, -42 >> 2, -42, 2); + a360.assertExpression(expr, -42 >> -2, -42, -2); + } + + @Test + public void testRightShiftUnsignedIntValue() throws Exception { + final String expr = "(x, y)->{ x >>> y }"; + asserter.assertExpression(expr, 42L >>> 2, 42L, 2); + asserter.assertExpression(expr, 42L >>> -2, 42L, -2); + asserter.assertExpression(expr, -42L >>> 2, -42L, 2); + asserter.assertExpression(expr, -42L >>> -2, -42L, -2); + } + + @Test + public void testLeftShiftLongValue() throws Exception { + a360.assertExpression("2147483648 << 2", 2147483648L << 2); + a360.assertExpression("2147483648 << -2", 2147483648L << -2); + a360.assertExpression("-2147483649 << 2", -2147483649L << 2); + a360.assertExpression("-2147483649 << -2", -2147483649L << -2); + } + + @Test + public void testRightShiftLongValue() throws Exception { + a360.assertExpression("8589934592 >> 2", 8589934592L >> 2); + a360.assertExpression("8589934592 >> -2", 8589934592L >> -2); + a360.assertExpression("-8589934592 >> 2", -8589934592L >> 2); + a360.assertExpression("-8589934592 >> -2", -8589934592L >> -2); + } + + @Test + public void testRightShiftBigValue() throws Exception { + a360.assertExpression( "9223372036854775808 >> 2", new BigInteger("9223372036854775808").shiftRight(2)); + a360.assertExpression("9223372036854775808 >> -2", new BigInteger("9223372036854775808").shiftRight(-2)); + a360.assertExpression("-9223372036854775809 >> 2", new BigInteger("-9223372036854775809").shiftRight(2)); + a360.assertExpression("-9223372036854775809 >> -2", new BigInteger("-9223372036854775809").shiftRight(-2)); + } + + static BigInteger shiftRightUnsigned(String bl, int r) { + return shiftRightUnsigned(new BigInteger(bl), r); + } + static BigInteger shiftRightUnsigned(BigInteger bl, int r) { + return bl.signum() < 0 ? bl.negate().shiftRight(r) : bl.shiftRight(r); + } + + @Test + public void testRightShiftUnsignedBigValue() throws Exception { + a360.assertExpression( "9223372036854775808 >>> 2", shiftRightUnsigned("9223372036854775808", 2)); + a360.assertExpression("9223372036854775808 >>> -2", shiftRightUnsigned("9223372036854775808",-2)); + a360.assertExpression("-9223372036854775809 >>> 2", shiftRightUnsigned("-9223372036854775809", 2)); + a360.assertExpression("-9223372036854775809 >>> -2", shiftRightUnsigned("-9223372036854775809",-2)); + } + + public static class ShiftArithmetic extends JexlArithmetic { + ShiftArithmetic(boolean flag) { + super(flag); + } + + public Object shiftLeft(StringBuilder c, String value) { + c.append(value); + return c; + } + + public Object shiftRight(String value, StringBuilder c) { + c.append(value); + return c; + } + + public Object shiftRightUnsigned(String value, StringBuilder c) { + c.append(value.toLowerCase()); + return c; + } + } + + @Test + public void testOverloadedShift() throws Exception { + JexlEngine jexl = new JexlBuilder().arithmetic(new ShiftArithmetic(true)).create(); + JexlScript e = jexl.createScript("x << 'Left'", "x"); + StringBuilder x = new StringBuilder("1"); + Object o = e.execute(null, x); + Assert.assertEquals(e.getSourceText(), "1Left", x.toString()); + + e = jexl.createScript("'Right' >> x", "x"); + x = new StringBuilder("1"); + o = e.execute(null, x); + Assert.assertEquals(e.getSourceText(), "1Right", x.toString()); + + e = jexl.createScript("'Right' >>> x", "x"); + x = new StringBuilder("1"); + o = e.execute(null, x); + Assert.assertEquals(e.getSourceText(), "1right", x.toString()); + } + + @Test + public void testPrecedence() throws Exception { + a360.assertExpression("40 + 2 << 1 + 1", 40 + 2 << 1 + 1); + a360.assertExpression("40 + (2 << 1) + 1", 40 + (2 << 1) + 1); + a360.assertExpression("(40 + 2) << (1 + 1)", (40 + 2) << (1 + 1)); + + a360.assertExpression("40 + 2L << 1 + 1", 40 + 2L << 1 + 1); + a360.assertExpression("40 + (2L << 1) + 1", 40 + (2L << 1) + 1); + a360.assertExpression("(40 + 2L) << (1 + 1)", (40 + 2L) << (1 + 1)); + + a360.assertExpression("40L + 2 << 1 + 1", 40L + 2L << 1 + 1); + a360.assertExpression("40L + (2 << 1) + 1", 40L + (2L << 1) + 1); + a360.assertExpression("(40L + 2) << (1 + 1)", (40L + 2L) << (1 + 1)); + } + +} diff --git a/src/test/java/org/apache/commons/jexl3/junit/Asserter.java b/src/test/java/org/apache/commons/jexl3/junit/Asserter.java index 7530ff4..2aaf29a 100644 --- a/src/test/java/org/apache/commons/jexl3/junit/Asserter.java +++ b/src/test/java/org/apache/commons/jexl3/junit/Asserter.java @@ -92,9 +92,9 @@ public class Asserter extends Assert { * @throws Exception if the expression could not be evaluationed or an assertion * fails */ - public void assertExpression(final String expression, final Object expected) throws Exception { + public void assertExpression(final String expression, final Object expected, Object... args) throws Exception { final JexlScript exp = engine.createScript(expression); - final Object value = exp.execute(context); + final Object value = exp.execute(context, args); if (expected instanceof BigDecimal) { final JexlArithmetic jexla = engine.getArithmetic(); Assert.assertEquals("expression: " + expression, 0,