You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by jo...@apache.org on 2015/03/16 15:12:40 UTC
cayenne git commit: CAY-1987 Widen types before performing in-memory
evaluation of qualifiers using j.l.Number subclasses
Repository: cayenne
Updated Branches:
refs/heads/master 5bbbd9217 -> 41d362b0c
CAY-1987 Widen types before performing in-memory evaluation of qualifiers using j.l.Number subclasses
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/41d362b0
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/41d362b0
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/41d362b0
Branch: refs/heads/master
Commit: 41d362b0c40fcdf31e4a3ef1d6df9e96b9c61f8d
Parents: 5bbbd92
Author: John Huss <jo...@apache.org>
Authored: Tue Feb 10 12:11:56 2015 -0600
Committer: John Huss <jo...@apache.org>
Committed: Mon Mar 16 09:11:11 2015 -0500
----------------------------------------------------------------------
.../apache/cayenne/exp/parser/Evaluator.java | 143 +++++++++++++++++++
.../cayenne/exp/parser/EvaluatorTest.java | 133 +++++++++++++++++
docs/doc/src/main/resources/RELEASE-NOTES.txt | 1 +
3 files changed, 277 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/41d362b0/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
index 866322b..df6785b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
@@ -19,9 +19,14 @@
package org.apache.cayenne.exp.parser;
import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import org.apache.cayenne.Cayenne;
import org.apache.cayenne.ObjectId;
@@ -41,7 +46,10 @@ abstract class Evaluator {
private static final Evaluator DEFAULT_EVALUATOR;
private static final Evaluator PERSISTENT_EVALUATOR;
private static final Evaluator BIG_DECIMAL_EVALUATOR;
+ private static final Evaluator NUMBER_EVALUATOR;
private static final Evaluator COMPAREABLE_EVALUATOR;
+
+ static double EPSILON = 0.0000001;
/**
* A decorator of an evaluator that presumes non-null 'lhs' argument and
@@ -160,6 +168,137 @@ abstract class Evaluator {
}
});
+ NUMBER_EVALUATOR = new NonNullLhsEvaluator(new Evaluator() {
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ Integer compare(Object lhs, Object rhs) {
+ return compareNumbers((Number)lhs, rhs);
+ }
+
+ @Override
+ boolean eq(Object lhs, Object rhs) {
+ return equalNumbers((Number)lhs, (Number)rhs);
+ }
+
+ private final List WHOLE_NUMBER_CLASSES = Arrays.asList(Byte.class, Short.class, Integer.class, AtomicInteger.class, Long.class, AtomicLong.class, BigInteger.class);
+ private final List FLOATING_POINT_CLASSES = Arrays.asList(Float.class, Double.class);
+
+ /**
+ * Returns the widest primitive wrapper class given the two operands, used in preparation for
+ * comparing two boxed numbers of different types, like java.lang.Short and java.lang.Integer.
+ *
+ * @param lhs
+ * @param rhs
+ * @return
+ */
+ Class<?> widestNumberType(Number lhs, Number rhs) {
+ if (lhs.getClass().equals(rhs.getClass())) return lhs.getClass();
+
+ int lhsIndex = WHOLE_NUMBER_CLASSES.indexOf(lhs.getClass());
+ int rhsIndex = WHOLE_NUMBER_CLASSES.indexOf(rhs.getClass());
+
+ Class<?> widestClass;
+ if (lhsIndex != -1 && rhsIndex != -1) {
+ widestClass = (Class<?>) WHOLE_NUMBER_CLASSES.get(Math.max(lhsIndex, rhsIndex));
+
+ } else if (lhsIndex == -1 && rhsIndex == -1) {
+ widestClass = Double.class; // must be one float and one double;
+
+ } else if (lhsIndex != -1 || rhsIndex != -1){ // must be whole number and a float or double
+ widestClass = Double.class;
+
+ } else {
+ widestClass = null;
+ }
+
+ return widestClass;
+ }
+
+ /**
+ * Enables equality tests for two boxed numbers of different types, like java.lang.Short and java.lang.Integer.
+ * @param lhs
+ * @param _rhs
+ * @return
+ */
+ boolean equalNumbers(Number lhs, Object _rhs) {
+ if (!Number.class.isAssignableFrom(_rhs.getClass())) {
+ return lhs.equals(_rhs);
+ }
+
+ Number rhs = (Number) _rhs;
+
+ Class<?> widestClass = widestNumberType(lhs, rhs);
+
+ if (Integer.class.equals(widestClass) || AtomicInteger.class.equals(widestClass)) {
+ return lhs.intValue() == rhs.intValue();
+
+ } else if (Long.class.equals(widestClass) || AtomicLong.class.equals(widestClass)) {
+ return lhs.longValue() == rhs.longValue();
+
+ } else if (Double.class.equals(widestClass)) {
+ return Math.abs(lhs.doubleValue() - rhs.doubleValue()) < EPSILON;
+
+ } else if (Short.class.equals(widestClass)) {
+ return lhs.shortValue() == rhs.shortValue();
+
+ } else if (BigInteger.class.equals(widestClass)) {
+ return lhs.toString().equals(rhs.toString());
+
+ } else {
+ return lhs.equals(rhs);
+ }
+ }
+
+ /**
+ * Enables comparison of two boxed numbers of different types, like java.lang.Short and java.lang.Integer.
+ * @param lhs
+ * @param _rhs
+ * @return
+ */
+ Integer compareNumbers(Number lhs, Object _rhs) {
+ if (!Number.class.isAssignableFrom(_rhs.getClass())) {
+ return null;
+ }
+
+ Number rhs = (Number) _rhs;
+
+ Class widestClass = widestNumberType(lhs, rhs);
+
+ if (Integer.class.equals(widestClass) || AtomicInteger.class.equals(widestClass)) {
+ return Integer.valueOf(lhs.intValue()).compareTo(rhs.intValue());
+
+ } else if (Long.class.equals(widestClass) || AtomicLong.class.equals(widestClass)) {
+ return Long.valueOf(lhs.longValue()).compareTo(rhs.longValue());
+
+ } else if (Double.class.equals(widestClass)) {
+ boolean areEqual = Math.abs(lhs.doubleValue() - rhs.doubleValue()) < EPSILON;
+ return areEqual ? 0 : Double.compare(lhs.doubleValue(), rhs.doubleValue());
+
+ } else if (Float.class.equals(widestClass)) {
+ boolean areEqual = Math.abs(lhs.floatValue() - rhs.floatValue()) < EPSILON;
+ return areEqual ? 0 : Float.compare(lhs.floatValue(), rhs.floatValue());
+
+ } else if (Short.class.equals(widestClass)) {
+ return Short.valueOf(lhs.shortValue()).compareTo(rhs.shortValue());
+
+ } else if (Byte.class.equals(widestClass)) {
+ return Byte.valueOf(lhs.byteValue()).compareTo(rhs.byteValue());
+
+ } else if (BigInteger.class.equals(widestClass)) {
+ BigInteger left = lhs instanceof BigInteger ? (BigInteger)lhs : new BigInteger(lhs.toString());
+ BigInteger right = rhs instanceof BigInteger ? (BigInteger)rhs : new BigInteger(rhs.toString());
+ return left.compareTo(right);
+
+ } else if (Comparable.class.isAssignableFrom(lhs.getClass())) {
+ return ((Comparable)lhs).compareTo(rhs);
+
+ } else {
+ return null;
+ }
+ }
+ });
+
COMPAREABLE_EVALUATOR = new NonNullLhsEvaluator(new Evaluator() {
@SuppressWarnings({ "unchecked", "rawtypes" })
@@ -210,6 +349,10 @@ abstract class Evaluator {
return BIG_DECIMAL_EVALUATOR;
}
+ if (Number.class.isAssignableFrom(lhsType)) {
+ return NUMBER_EVALUATOR;
+ }
+
if (Comparable.class.isAssignableFrom(lhsType)) {
return COMPAREABLE_EVALUATOR;
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/41d362b0/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/EvaluatorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/EvaluatorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/EvaluatorTest.java
index 2ae24dc..33d1a9e 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/EvaluatorTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/EvaluatorTest.java
@@ -23,8 +23,11 @@ import org.apache.cayenne.Persistent;
import org.junit.Test;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -63,6 +66,136 @@ public class EvaluatorTest {
assertFalse(e.eq(1, 1.1));
}
+ @Test
+ public void testEvaluator_NumberWideningEquals() {
+ Evaluator e = Evaluator.evaluator(1);
+
+ assertTrue(e.eq((byte)1, (byte)1));
+ assertTrue(e.eq((byte)1, (short)1));
+ assertTrue(e.eq((byte)1, (int)1));
+ assertTrue(e.eq((byte)1, (long)1));
+ assertTrue(e.eq((byte)1, (float)1));
+ assertTrue(e.eq((byte)1, (double)1));
+
+ assertTrue(e.eq((short)1, (byte)1));
+ assertTrue(e.eq((short)1, (short)1));
+ assertTrue(e.eq((short)1, (int)1));
+ assertTrue(e.eq((short)1, (long)1));
+ assertTrue(e.eq((short)1, (float)1));
+ assertTrue(e.eq((short)1, (double)1));
+
+ assertTrue(e.eq((int)1, (byte)1));
+ assertTrue(e.eq((int)1, (short)1));
+ assertTrue(e.eq((int)1, (int)1));
+ assertTrue(e.eq((int)1, (long)1));
+ assertTrue(e.eq((int)1, (float)1));
+ assertTrue(e.eq((int)1, (double)1));
+
+ assertTrue(e.eq((long)1, (byte)1));
+ assertTrue(e.eq((long)1, (short)1));
+ assertTrue(e.eq((long)1, (int)1));
+ assertTrue(e.eq((long)1, (long)1));
+ assertTrue(e.eq((long)1, (float)1));
+ assertTrue(e.eq((long)1, (double)1));
+
+ assertTrue(e.eq((float)1, (byte)1));
+ assertTrue(e.eq((float)1, (short)1));
+ assertTrue(e.eq((float)1, (int)1));
+ assertTrue(e.eq((float)1, (long)1));
+ assertTrue(e.eq((float)1, (float)1));
+ assertTrue(e.eq((float)1, (double)1));
+
+ assertTrue(e.eq((double)1, (byte)1));
+ assertTrue(e.eq((double)1, (short)1));
+ assertTrue(e.eq((double)1, (int)1));
+ assertTrue(e.eq((double)1, (long)1));
+ assertTrue(e.eq((double)1, (float)1));
+ assertTrue(e.eq((double)1, (double)1));
+
+ assertTrue(e.eq((float)1.1, (float)1.1));
+ assertTrue(e.eq((float)1.1, (double)1.1));
+
+ assertTrue(e.eq(Long.MAX_VALUE, Long.MAX_VALUE));
+ assertTrue(e.eq(Double.MAX_VALUE, Double.MAX_VALUE));
+
+ assertTrue(e.eq((int)1, new AtomicInteger(1)));
+ assertTrue(e.eq(new AtomicInteger(1), (int)1));
+
+ assertTrue(e.eq((int)1, new AtomicLong(1)));
+ assertTrue(e.eq(new AtomicLong(1), (int)1));
+
+ assertTrue(e.eq((int)1, BigInteger.ONE));
+ assertTrue(e.eq(BigInteger.ONE, (int)1));
+
+ BigInteger bigInt = new BigInteger(Long.valueOf(Long.MAX_VALUE).toString() + "0");
+ assertTrue(e.eq(bigInt, bigInt));
+ }
+
+ @Test
+ public void testEvaluator_NumberWideningCompare() {
+ Evaluator e = Evaluator.evaluator(1);
+
+ assertTrue(e.compare((byte)1, (byte)1) == 0);
+ assertTrue(e.compare((byte)1, (short)1) == 0);
+ assertTrue(e.compare((byte)1, (int)1) == 0);
+ assertTrue(e.compare((byte)1, (long)1) == 0);
+ assertTrue(e.compare((byte)1, (float)1) == 0);
+ assertTrue(e.compare((byte)1, (double)1) == 0);
+
+ assertTrue(e.compare((short)1, (byte)1) == 0);
+ assertTrue(e.compare((short)1, (short)1) == 0);
+ assertTrue(e.compare((short)1, (int)1) == 0);
+ assertTrue(e.compare((short)1, (long)1) == 0);
+ assertTrue(e.compare((short)1, (float)1) == 0);
+ assertTrue(e.compare((short)1, (double)1) == 0);
+
+ assertTrue(e.compare((int)1, (byte)1) == 0);
+ assertTrue(e.compare((int)1, (short)1) == 0);
+ assertTrue(e.compare((int)1, (int)1) == 0);
+ assertTrue(e.compare((int)1, (long)1) == 0);
+ assertTrue(e.compare((int)1, (float)1) == 0);
+ assertTrue(e.compare((int)1, (double)1) == 0);
+
+ assertTrue(e.compare((long)1, (byte)1) == 0);
+ assertTrue(e.compare((long)1, (short)1) == 0);
+ assertTrue(e.compare((long)1, (int)1) == 0);
+ assertTrue(e.compare((long)1, (long)1) == 0);
+ assertTrue(e.compare((long)1, (float)1) == 0);
+ assertTrue(e.compare((long)1, (double)1) == 0);
+
+ assertTrue(e.compare((float)1, (byte)1) == 0);
+ assertTrue(e.compare((float)1, (short)1) == 0);
+ assertTrue(e.compare((float)1, (int)1) == 0);
+ assertTrue(e.compare((float)1, (long)1) == 0);
+ assertTrue(e.compare((float)1, (float)1) == 0);
+ assertTrue(e.compare((float)1, (double)1) == 0);
+
+ assertTrue(e.compare((double)1, (byte)1) == 0);
+ assertTrue(e.compare((double)1, (short)1) == 0);
+ assertTrue(e.compare((double)1, (int)1) == 0);
+ assertTrue(e.compare((double)1, (long)1) == 0);
+ assertTrue(e.compare((double)1, (float)1) == 0);
+ assertTrue(e.compare((double)1, (double)1) == 0);
+
+ assertTrue(e.compare((float)1.1, (float)1.1) == 0);
+ assertTrue(e.compare((float)1.1, (double)1.1) == 0);
+
+ assertTrue(e.compare(Long.MAX_VALUE, Long.MAX_VALUE) == 0);
+ assertTrue(e.compare(Double.MAX_VALUE, Double.MAX_VALUE) == 0);
+
+ assertTrue(e.compare((int)1, new AtomicInteger(1)) == 0);
+ assertTrue(e.compare(new AtomicInteger(1), (int)1) == 0);
+
+ assertTrue(e.compare((int)1, new AtomicLong(1)) == 0);
+ assertTrue(e.compare(new AtomicLong(1), (int)1) == 0);
+
+ assertTrue(e.compare((int)1, BigInteger.ONE) == 0);
+ assertTrue(e.compare(BigInteger.ONE, (int)1) == 0);
+
+ BigInteger bigInt = new BigInteger(Long.valueOf(Long.MAX_VALUE).toString() + "0");
+ assertTrue(e.compare(bigInt, bigInt) == 0);
+ }
+
@Test
public void testEvaluator_BigDecimal() {
Object lhs = new BigDecimal("1.10");
http://git-wip-us.apache.org/repos/asf/cayenne/blob/41d362b0/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index 6fbefd9..6ad161d 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -18,6 +18,7 @@ CAY-1991 More control over generated String property names
Bug Fixes:
+CAY-1987 Widen types before performing in-memory evaluation of qualifiers using j.l.Number subclasses
CAY-1990 Incorrect display of the raw SQL query in Modeler
----------------------------------