You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2013/08/02 15:56:48 UTC

svn commit: r1509717 - in /cayenne/main/trunk/framework/cayenne-core-unpublished/src: main/java/org/apache/cayenne/exp/parser/ test/java/org/apache/cayenne/exp/

Author: aadamchik
Date: Fri Aug  2 13:56:48 2013
New Revision: 1509717

URL: http://svn.apache.org/r1509717
Log:
CAY-1860  In-memory matching of DataObjects against ObjectId or int

experimenting with static evaluator for LHS expression

Added:
    cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
Modified:
    cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java
    cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
    cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
    cayenne/main/trunk/framework/cayenne-core-unpublished/src/test/java/org/apache/cayenne/exp/ExpressionTest.java

Modified: cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java?rev=1509717&r1=1509716&r2=1509717&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java (original)
+++ cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java Fri Aug  2 13:56:48 2013
@@ -19,11 +19,8 @@
 
 package org.apache.cayenne.exp.parser;
 
-import java.math.BigDecimal;
 import java.util.List;
 
-import org.apache.cayenne.DataObject;
-import org.apache.cayenne.PersistenceState;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ValueInjector;
 import org.apache.commons.logging.Log;
@@ -35,8 +32,9 @@ import org.apache.commons.logging.LogFac
  * @since 1.1
  */
 public class ASTEqual extends ConditionNode implements ValueInjector {
-    private static final Log logObj = LogFactory.getLog(ASTEqual.class);
     
+    private static final Log logObj = LogFactory.getLog(ASTEqual.class);
+
     /**
      * Constructor used by expression parser. Do not invoke directly.
      */
@@ -70,26 +68,26 @@ public class ASTEqual extends ConditionN
 
         return evaluateImpl(o1, o2);
     }
-    
+
     /**
-     * Compares two objects, if one of them is array, 'in' operation is performed
+     * Compares two objects, if one of them is array, 'in' operation is
+     * performed
      */
     static boolean evaluateImpl(Object o1, Object o2) {
         // TODO: maybe we need a comparison "strategy" here, instead of
         // a switch of all possible cases? ... there were other requests for
         // more relaxed type-unsafe comparison (e.g. numbers to strings)
-        
+
         if (o1 == null && o2 == null) {
             return true;
-        }
-        else if (o1 != null) {
-            /**
-             * Per CAY-419 we perform 'in' comparison if one object is a list,
-             * and other is not
-             */
+        } else if (o1 != null) {
+
+            // Per CAY-419 we perform 'in' comparison if one object is a list,
+            // and other is not
+
             if (o1 instanceof List && !(o2 instanceof List)) {
                 for (Object element : ((List<?>) o1)) {
-                    if (element != null && evaluateAtomic(element, o2)) {
+                    if (element != null && Evaluator.evaluator(element).eq(element, o2)) {
                         return true;
                     }
                 }
@@ -97,45 +95,16 @@ public class ASTEqual extends ConditionN
             }
             if (o2 instanceof List && !(o1 instanceof List)) {
                 for (Object element : ((List<?>) o2)) {
-                    if (element != null && evaluateAtomic(element, o1)) {
+                    if (element != null && Evaluator.evaluator(element).eq(element, o1)) {
                         return true;
                     }
                 }
                 return false;
             }
-            
-            return evaluateAtomic(o1, o2);
-        }
-        return false;
-    }
-    
-    /**
-     * Compares two objects. They must not be null
-     */
-    static boolean evaluateAtomic(Object o1, Object o2) {
-        // BigDecimals must be compared using compareTo (
-        // see CAY-280 and BigDecimal.equals JavaDoc)
-        if (o1 instanceof BigDecimal) {
-            if (o2 instanceof BigDecimal) {
-                return ((BigDecimal) o1).compareTo((BigDecimal) o2) == 0;
-            }
 
-            return false;
+            return Evaluator.evaluator(o1).eq(o1, o2);
         }
-
-        if (o1 instanceof DataObject) {
-            if (o2 instanceof DataObject && ((DataObject)o1).getObjectId().equals(((DataObject)o2).getObjectId())) {
-                if (((DataObject)o1).getObjectContext().equals(((DataObject)o2).getObjectContext())) {
-                    return o1.equals(o2);
-                } else if (((DataObject)o1).getPersistenceState() == ((DataObject)o2).getPersistenceState()) {
-                    DataObject o1_context = (((DataObject)o2).getObjectContext()).localObject(((DataObject)o1));
-                    return ((DataObject)o2).equals(o1_context);
-                }
-            }
-            return false;
-        }
-        
-        return o1.equals(o2);
+        return false;
     }
 
     /**
@@ -150,11 +119,11 @@ public class ASTEqual extends ConditionN
     protected String getExpressionOperator(int index) {
         return "=";
     }
-    
+
     @Override
     protected String getEJBQLExpressionOperator(int index) {
         if (jjtGetChild(1) instanceof ASTScalar && ((ASTScalar) jjtGetChild(1)).getValue() == null) {
-            //for ejbql, we need "is null" instead of "= null"
+            // for ejbql, we need "is null" instead of "= null"
             return "is";
         }
         return getExpressionOperator(index);
@@ -166,27 +135,25 @@ public class ASTEqual extends ConditionN
     }
 
     public void injectValue(Object o) {
-        //try to inject value, if one of the operands is scalar, and other is a path 
-        
+        // try to inject value, if one of the operands is scalar, and other is a
+        // path
+
         Node[] args = new Node[] { jjtGetChild(0), jjtGetChild(1) };
-        
+
         int scalarIndex = -1;
         if (args[0] instanceof ASTScalar) {
             scalarIndex = 0;
-        }
-        else if (args[1] instanceof ASTScalar) {
+        } else if (args[1] instanceof ASTScalar) {
             scalarIndex = 1;
         }
-        
+
         if (scalarIndex != -1 && args[1 - scalarIndex] instanceof ASTObjPath) {
-            //inject
+            // inject
             ASTObjPath path = (ASTObjPath) args[1 - scalarIndex];
             try {
                 path.injectValue(o, evaluateChild(scalarIndex, o));
-            }
-            catch (Exception ex) {
-                logObj.warn("Failed to inject value " + 
-                        " on path " + path.getPath() + " to " + o, ex);
+            } catch (Exception ex) {
+                logObj.warn("Failed to inject value " + " on path " + path.getPath() + " to " + o, ex);
             }
         }
     }

Modified: cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java?rev=1509717&r1=1509716&r2=1509717&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java (original)
+++ cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java Fri Aug  2 13:56:48 2013
@@ -64,22 +64,21 @@ public class ASTIn extends ConditionNode
         if (objects == null) {
             return Boolean.FALSE;
         }
-    
+
         int size = objects.length;
         for (int i = 0; i < size; i++) {
             if (objects[i] != null) {
                 if (o1 instanceof Collection) {
-                	/* handle the case where we have a collection of objects */
-                	for (Object obj : (Collection) o1) {
-                    	if (ASTEqual.evaluateAtomic(obj, objects[i])) {
+                    // handle the case where we have a collection of objects
+                    for (Object obj : (Collection) o1) {
+                        if (Evaluator.evaluator(obj).eq(obj, objects[i])) {
                             return Boolean.TRUE;
-                    	}
-                	}
-                }
-                else {
-                	if (ASTEqual.evaluateAtomic(o1, objects[i])) {
+                        }
+                    }
+                } else {
+                    if (Evaluator.evaluator(o1).eq(o1, objects[i])) {
                         return Boolean.TRUE;
-                	}
+                    }
                 }
             }
         }
@@ -108,7 +107,7 @@ public class ASTIn extends ConditionNode
     @Override
     protected Object transformExpression(Transformer transformer) {
         Object transformed = super.transformExpression(transformer);
-        
+
         // transform empty ASTIn to ASTFalse
         if (transformed instanceof ASTIn) {
             ASTIn exp = (ASTIn) transformed;

Modified: cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java?rev=1509717&r1=1509716&r2=1509717&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java (original)
+++ cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java Fri Aug  2 13:56:48 2013
@@ -62,7 +62,7 @@ public class ASTNotIn extends ConditionN
 
         int size = objects.length;
         for (int i = 0; i < size; i++) {
-            if (objects[i] != null && ASTEqual.evaluateAtomic(o1, objects[i])) {
+            if (objects[i] != null && Evaluator.evaluator(o1).eq(o1, objects[i])) {
                 return Boolean.FALSE;
             }
         }

Added: cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java?rev=1509717&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java (added)
+++ cayenne/main/trunk/framework/cayenne-core-unpublished/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java Fri Aug  2 13:56:48 2013
@@ -0,0 +1,209 @@
+/*****************************************************************
+ *   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.cayenne.exp.parser;
+
+import java.math.BigDecimal;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.cayenne.Cayenne;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.util.ConversionUtil;
+
+/**
+ * Performs argument conversions for a calling binary expression, so that the
+ * expression could eval the arguments of the same type.
+ * 
+ * @since 3.2
+ */
+abstract class Evaluator {
+
+    private static final ConcurrentMap<Class<?>, Evaluator> evaluators;
+    private static final Evaluator DEFAULT_EVALUATOR;
+    private static final Evaluator PERSISTENT_EVALUATOR;
+    private static final Evaluator BIG_DECIMAL_EVALUATOR;
+    private static final Evaluator COMPAREABLE_EVALUATOR;
+
+    static class NullEvaluator extends Evaluator {
+        Evaluator delegate;
+
+        NullEvaluator(Evaluator delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        int compare(Object lhs, Object rhs) {
+            if (lhs == null && rhs == null) {
+                return 0;
+            } else if (lhs == null) {
+                return -1;
+            } else if (rhs == null) {
+                return 1;
+            } else {
+                return delegate.compare(lhs, rhs);
+            }
+        }
+
+        @Override
+        boolean eq(Object lhs, Object rhs) {
+            if (lhs == null) {
+                return rhs == null;
+            }
+
+            return (rhs != null) ? delegate.eq(lhs, rhs) : false;
+        }
+    }
+
+    static {
+        evaluators = new ConcurrentHashMap<Class<?>, Evaluator>();
+        DEFAULT_EVALUATOR = new NullEvaluator(new Evaluator() {
+            @Override
+            boolean eq(Object lhs, Object rhs) {
+                return lhs.equals(rhs);
+            }
+
+            @Override
+            int compare(Object lhs, Object rhs) {
+                throw new UnsupportedOperationException("Unsupported");
+            }
+        });
+
+        PERSISTENT_EVALUATOR = new NullEvaluator(new Evaluator() {
+
+            @Override
+            int compare(Object lhs, Object rhs) {
+                throw new UnsupportedOperationException("Unsupported");
+            }
+
+            @Override
+            boolean eq(Object lhs, Object rhs) {
+
+                Persistent lhsPersistent = (Persistent) lhs;
+
+                if (rhs instanceof Persistent) {
+                    return lhsPersistent.getObjectId().equals(((Persistent) rhs).getObjectId());
+                }
+
+                if (rhs instanceof ObjectId) {
+                    return lhsPersistent.getObjectId().equals((ObjectId) rhs);
+                }
+
+                // comparing ObjectId with a single value ...
+                if (lhsPersistent.getObjectId().getIdSnapshot().size() != 1) {
+                    return false;
+                }
+
+                if (rhs instanceof Number) {
+                    return Cayenne.longPKForObject(lhsPersistent) == ((Number) rhs).longValue();
+                }
+
+                return Cayenne.pkForObject(lhsPersistent).equals(rhs);
+            }
+        });
+
+        BIG_DECIMAL_EVALUATOR = new NullEvaluator(new Evaluator() {
+
+            @Override
+            int compare(Object lhs, Object rhs) {
+                return ((BigDecimal) lhs).compareTo(ConversionUtil.toBigDecimal(rhs));
+            }
+
+            @Override
+            boolean eq(Object lhs, Object rhs) {
+
+                // BigDecimals must be compared using compareTo (
+                // see CAY-280 and BigDecimal.equals JavaDoc)
+
+                return compare(lhs, rhs) == 0;
+            }
+        });
+
+        COMPAREABLE_EVALUATOR = new NullEvaluator(new Evaluator() {
+
+            @SuppressWarnings({ "unchecked", "rawtypes" })
+            @Override
+            int compare(Object lhs, Object rhs) {
+                return ((Comparable) lhs).compareTo(ConversionUtil.toComparable(rhs));
+            }
+
+            @Override
+            boolean eq(Object lhs, Object rhs) {
+                return lhs.equals(rhs);
+            }
+        });
+    }
+
+    static <T> Evaluator evaluator(Object lhs) {
+        
+        if(lhs == null) {
+            return DEFAULT_EVALUATOR;
+        }
+        
+        Class<?> lhsType = lhs.getClass();
+
+        Evaluator e = evaluators.get(lhsType);
+
+        if (e == null) {
+            Evaluator created = compileEvaluator(lhsType);
+            Evaluator existing = evaluators.putIfAbsent(lhsType, created);
+            e = existing != null ? existing : created;
+        }
+
+        return e;
+    }
+
+    private static Evaluator compileEvaluator(Class<?> lhsType) {
+
+        Evaluator ev = findInHierarchy(lhsType);
+        if (ev != null) {
+            return ev;
+        }
+
+        // check known interfaces
+        if (Persistent.class.isAssignableFrom(lhsType)) {
+            return PERSISTENT_EVALUATOR;
+        }
+
+        if (BigDecimal.class.isAssignableFrom(lhsType)) {
+            return BIG_DECIMAL_EVALUATOR;
+        }
+
+        if (Comparable.class.isAssignableFrom(lhsType)) {
+            return COMPAREABLE_EVALUATOR;
+        }
+
+        // nothing we recognize... return default
+        return DEFAULT_EVALUATOR;
+    }
+
+    private static Evaluator findInHierarchy(Class<?> lhsType) {
+
+        if (Object.class.equals(lhsType)) {
+            return null;
+        }
+
+        Evaluator ev = evaluators.get(lhsType);
+        return (ev != null) ? ev : findInHierarchy(lhsType.getSuperclass());
+    }
+
+    abstract boolean eq(Object lhs, Object rhs);
+
+    abstract int compare(Object lhs, Object rhs);
+}

Modified: cayenne/main/trunk/framework/cayenne-core-unpublished/src/test/java/org/apache/cayenne/exp/ExpressionTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-core-unpublished/src/test/java/org/apache/cayenne/exp/ExpressionTest.java?rev=1509717&r1=1509716&r2=1509717&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-core-unpublished/src/test/java/org/apache/cayenne/exp/ExpressionTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-core-unpublished/src/test/java/org/apache/cayenne/exp/ExpressionTest.java Fri Aug  2 13:56:48 2013
@@ -253,15 +253,14 @@ public class ExpressionTest extends Serv
         assertTrue(e.match(objects.get(0)));
 
         // we change one object - so the objects are different now
-        // (PersistenceState
-        // different)
+        // (PersistenceState different)
         a1.setArtistName("newName");
 
         SelectQuery q2 = new SelectQuery(Painting.class);
         Expression ex2 = ExpressionFactory.matchExp(Painting.TO_ARTIST_PROPERTY, a1);
         q2.setQualifier(ex2);
 
-        assertFalse(ex2.match(objects.get(0)));
+        assertTrue(ex2.match(objects.get(0)));
 
         Artist a2 = context.newObject(Artist.class);
         a1.setArtistName("Equals");