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
 
 ----------------------------------