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
The following commit(s) were added to refs/heads/JEXL-360 by this push:
new 29422ce JEXL-360: try a more refined arithmetic; - refactored tests;
29422ce is described below
commit 29422ced61b33022af74359a6f0aec95d30924a3
Author: henrib <[email protected]>
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,