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 2012/10/17 22:03:03 UTC

svn commit: r1399410 - in /cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src: main/java/org/apache/cayenne/reflect/ test/java/org/apache/cayenne/reflect/ test/java/org/apache/cayenne/reflect/generic/

Author: aadamchik
Date: Wed Oct 17 20:03:02 2012
New Revision: 1399410

URL: http://svn.apache.org/viewvc?rev=1399410&view=rev
Log:
CAY-1729 PersistentDescriptor must have predictable property iteration order

Added:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PropertyComparator.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/generic/DataObjectDescriptorFactoryTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/generic/DataObjectDescriptorFactory_InheritanceMapsTest.java
Modified:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/PersistentDescriptorTest.java

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java?rev=1399410&r1=1399409&r2=1399410&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java Wed Oct 17 20:03:02 2012
@@ -23,7 +23,10 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.PersistenceState;
@@ -50,7 +53,8 @@ public class PersistentDescriptor implem
     // compiled properties ...
     protected Class<?> objectClass;
     protected Map<String, PropertyDescriptor> declaredProperties;
-    protected Map<String, PropertyDescriptor> superProperties;
+    protected Map<String, PropertyDescriptor> properties;
+   
     protected Map<String, ClassDescriptor> subclassDescriptors;
     protected Accessor persistenceStateAccessor;
 
@@ -73,7 +77,7 @@ public class PersistentDescriptor implem
      */
     public PersistentDescriptor() {
         this.declaredProperties = new HashMap<String, PropertyDescriptor>();
-        this.superProperties = new HashMap<String, PropertyDescriptor>();
+        this.properties = new HashMap<String, PropertyDescriptor>();
         this.subclassDescriptors = new HashMap<String, ClassDescriptor>();
 
         // must be a set as duplicate addition attempts are expected...
@@ -93,7 +97,7 @@ public class PersistentDescriptor implem
      * Registers a superclass property.
      */
     public void addSuperProperty(PropertyDescriptor property) {
-        superProperties.put(property.getName(), property);
+        properties.put(property.getName(), property);
         indexAddedProperty(property);
     }
 
@@ -103,6 +107,7 @@ public class PersistentDescriptor implem
      */
     public void addDeclaredProperty(PropertyDescriptor property) {
         declaredProperties.put(property.getName(), property);
+        properties.put(property.getName(), property);
         indexAddedProperty(property);
     }
 
@@ -112,7 +117,39 @@ public class PersistentDescriptor implem
     public void addRootDbEntity(DbEntity dbEntity) {
         this.rootDbEntities.add(dbEntity);
     }
+    
+    void sortProperties() {
 
+        // ensure properties are stored in predictable order per CAY-1729
+
+        // 'properties' is a superset of 'declaredProperties', so let's sort all
+        // properties, and populated both ordered collections at once
+        if (properties.size() > 1) {
+
+            List<Entry<String, PropertyDescriptor>> entries = new ArrayList<Entry<String, PropertyDescriptor>>(
+                    properties.entrySet());
+
+            Collections.sort(entries, PropertyComparator.comparator);
+
+            Map<String, PropertyDescriptor> orderedProperties = new LinkedHashMap<String, PropertyDescriptor>(
+                    (int) (entries.size() / 0.75));
+
+            Map<String, PropertyDescriptor> orderedDeclared = new LinkedHashMap<String, PropertyDescriptor>(
+                    (int) (declaredProperties.size() / 0.75));
+
+            for (Entry<String, PropertyDescriptor> e : entries) {
+                orderedProperties.put(e.getKey(), e.getValue());
+                
+                if (declaredProperties.containsKey(e.getKey())) {
+                    orderedDeclared.put(e.getKey(), e.getValue());
+                }
+            }
+
+            this.properties = orderedProperties;
+            this.declaredProperties = orderedDeclared;
+        }
+    }
+    
     void indexAddedProperty(PropertyDescriptor property) {
         if (property instanceof AttributeProperty) {
 
@@ -157,6 +194,8 @@ public class PersistentDescriptor implem
             if (mapArcProperties != null) {
                 mapArcProperties.remove(removed);
             }
+            
+            properties.remove(propertyName);
         }
     }
 
@@ -337,19 +376,6 @@ public class PersistentDescriptor implem
     /**
      * @since 3.0
      */
-    boolean visitSuperProperties(PropertyVisitor visitor) {
-        for (PropertyDescriptor next : superProperties.values()) {
-            if (!next.visit(visitor)) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * @since 3.0
-     */
     public boolean visitDeclaredProperties(PropertyVisitor visitor) {
 
         for (PropertyDescriptor next : declaredProperties.values()) {
@@ -381,11 +407,14 @@ public class PersistentDescriptor implem
     }
 
     public boolean visitProperties(PropertyVisitor visitor) {
-        if (!visitSuperProperties(visitor)) {
-            return false;
+        
+        for (PropertyDescriptor next : properties.values()) {
+            if (!next.visit(visitor)) {
+                return false;
+            }
         }
 
-        return visitDeclaredProperties(visitor);
+        return true;
     }
 
     public void setPersistenceStateAccessor(Accessor persistenceStateAccessor) {

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java?rev=1399410&r1=1399409&r2=1399410&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java Wed Oct 17 20:03:02 2012
@@ -132,6 +132,8 @@ public abstract class PersistentDescript
         indexRootDbEntities(descriptor, inheritanceTree);
 
         indexSuperclassProperties(descriptor);
+        
+        descriptor.sortProperties();
 
         return descriptor;
     }

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PropertyComparator.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PropertyComparator.java?rev=1399410&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PropertyComparator.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PropertyComparator.java Wed Oct 17 20:03:02 2012
@@ -0,0 +1,48 @@
+/*****************************************************************
+ *   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.reflect;
+
+import java.util.Comparator;
+import java.util.Map.Entry;
+
+/**
+ * @since 3.1
+ */
+final class PropertyComparator implements
+        Comparator<Entry<String, PropertyDescriptor>> {
+
+    static final Comparator<Entry<String, PropertyDescriptor>> comparator = new PropertyComparator();
+
+    public int compare(Entry<String, PropertyDescriptor> o1,
+            Entry<String, PropertyDescriptor> o2) {
+        if (o1.getValue() instanceof ArcProperty) {
+            if (o2.getValue() instanceof ArcProperty) {
+                return o1.getKey().compareTo(o2.getKey());
+            } else {
+                return 1;
+            }
+        } else {
+            if (o2.getValue() instanceof ArcProperty) {
+                return -1;
+            } else {
+                return o1.getKey().compareTo(o2.getKey());
+            }
+        }
+    }
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/PersistentDescriptorTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/PersistentDescriptorTest.java?rev=1399410&r1=1399409&r2=1399410&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/PersistentDescriptorTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/PersistentDescriptorTest.java Wed Oct 17 20:03:02 2012
@@ -19,12 +19,10 @@
 
 package org.apache.cayenne.reflect;
 
+import static org.mockito.Mockito.mock;
 import junit.framework.TestCase;
 
-import org.apache.cayenne.reflect.FieldAccessor;
-import org.apache.cayenne.reflect.PersistentDescriptor;
-import org.apache.cayenne.reflect.PropertyDescriptor;
-import org.apache.cayenne.reflect.SimpleAttributeProperty;
+import org.apache.cayenne.map.ObjAttribute;
 import org.apache.cayenne.unit.util.TestBean;
 
 public class PersistentDescriptorTest extends TestCase {
@@ -42,10 +40,13 @@ public class PersistentDescriptorTest ex
     public void testCopyObjectProperties() {
         PersistentDescriptor d1 = new PersistentDescriptor();
 
-        FieldAccessor accessor = new FieldAccessor(TestBean.class, "string", String.class);
-        PropertyDescriptor property = new SimpleAttributeProperty(d1, accessor, null);
+        ObjAttribute attribute = mock(ObjAttribute.class);
+        FieldAccessor accessor = new FieldAccessor(TestBean.class, "string",
+                String.class);
+        PropertyDescriptor property = new SimpleAttributeProperty(d1, accessor,
+                attribute);
 
-        d1.declaredProperties.put(property.getName(), property);
+        d1.addDeclaredProperty(property);
 
         TestBean from = new TestBean();
         from.setString("123");
@@ -55,4 +56,5 @@ public class PersistentDescriptorTest ex
         d1.shallowMerge(from, to);
         assertEquals("123", to.getString());
     }
+
 }

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/generic/DataObjectDescriptorFactoryTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/generic/DataObjectDescriptorFactoryTest.java?rev=1399410&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/generic/DataObjectDescriptorFactoryTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/generic/DataObjectDescriptorFactoryTest.java Wed Oct 17 20:03:02 2012
@@ -0,0 +1,138 @@
+/*****************************************************************
+ *   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.reflect.generic;
+
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.reflect.ArcProperty;
+import org.apache.cayenne.reflect.AttributeProperty;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.reflect.PropertyDescriptor;
+import org.apache.cayenne.reflect.PropertyVisitor;
+import org.apache.cayenne.reflect.SingletonFaultFactory;
+import org.apache.cayenne.reflect.ToManyProperty;
+import org.apache.cayenne.reflect.ToOneProperty;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+
+@UseServerRuntime(ServerCase.TESTMAP_PROJECT)
+public class DataObjectDescriptorFactoryTest extends ServerCase {
+
+    @Inject
+    private EntityResolver resolver;
+
+    public void testVisitDeclaredProperties_IterationOrder() {
+
+        DataObjectDescriptorFactory factory = new DataObjectDescriptorFactory(
+                resolver.getClassDescriptorMap(), new SingletonFaultFactory());
+
+        for (ObjEntity e : resolver.getObjEntities()) {
+            ClassDescriptor descriptor = factory.getDescriptor(e.getName());
+
+            final PropertyDescriptor[] lastProcessed = new PropertyDescriptor[1];
+
+            PropertyVisitor visitor = new PropertyVisitor() {
+
+                public boolean visitToOne(ToOneProperty property) {
+                    assertPropertiesAreInOrder(lastProcessed[0], property);
+                    lastProcessed[0] = property;
+                    return true;
+                }
+
+                public boolean visitToMany(ToManyProperty property) {
+                    assertPropertiesAreInOrder(lastProcessed[0], property);
+                    lastProcessed[0] = property;
+                    return true;
+                }
+
+                public boolean visitAttribute(AttributeProperty property) {
+                    assertPropertiesAreInOrder(lastProcessed[0], property);
+                    lastProcessed[0] = property;
+                    return true;
+                }
+            };
+
+            descriptor.visitDeclaredProperties(visitor);
+        }
+
+    }
+
+    public void testVisitProperties_IterationOrder() {
+
+        DataObjectDescriptorFactory factory = new DataObjectDescriptorFactory(
+                resolver.getClassDescriptorMap(), new SingletonFaultFactory());
+
+        for (ObjEntity e : resolver.getObjEntities()) {
+            ClassDescriptor descriptor = factory.getDescriptor(e.getName());
+
+            final PropertyDescriptor[] lastProcessed = new PropertyDescriptor[1];
+
+            PropertyVisitor visitor = new PropertyVisitor() {
+
+                public boolean visitToOne(ToOneProperty property) {
+                    assertPropertiesAreInOrder(lastProcessed[0], property);
+                    lastProcessed[0] = property;
+                    return true;
+                }
+
+                public boolean visitToMany(ToManyProperty property) {
+                    assertPropertiesAreInOrder(lastProcessed[0], property);
+                    lastProcessed[0] = property;
+                    return true;
+                }
+
+                public boolean visitAttribute(AttributeProperty property) {
+                    assertPropertiesAreInOrder(lastProcessed[0], property);
+                    lastProcessed[0] = property;
+                    return true;
+                }
+            };
+
+            descriptor.visitProperties(visitor);
+        }
+
+    }
+
+    static void assertPropertiesAreInOrder(PropertyDescriptor o1,
+            PropertyDescriptor o2) {
+
+        if (o1 == null) {
+            return;
+        }
+
+        if (o1 instanceof ArcProperty) {
+            if (o2 instanceof ArcProperty) {
+                assertTrue("Names are not ordered: " + o1.getName()
+                        + " before " + o2.getName(),
+                        o1.getName().compareTo(o2.getName()) < 0);
+            } else {
+                fail("Arc preceded regular property");
+            }
+        } else {
+            if (o2 instanceof ArcProperty) {
+                // this is correct
+            } else {
+                assertTrue("Names are not ordered: " + o1.getName()
+                        + " before " + o2.getName(),
+                        o1.getName().compareTo(o2.getName()) < 0);
+            }
+        }
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/generic/DataObjectDescriptorFactory_InheritanceMapsTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/generic/DataObjectDescriptorFactory_InheritanceMapsTest.java?rev=1399410&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/generic/DataObjectDescriptorFactory_InheritanceMapsTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/generic/DataObjectDescriptorFactory_InheritanceMapsTest.java Wed Oct 17 20:03:02 2012
@@ -0,0 +1,78 @@
+/*****************************************************************
+ *   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.reflect.generic;
+
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.reflect.AttributeProperty;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.reflect.PropertyDescriptor;
+import org.apache.cayenne.reflect.PropertyVisitor;
+import org.apache.cayenne.reflect.SingletonFaultFactory;
+import org.apache.cayenne.reflect.ToManyProperty;
+import org.apache.cayenne.reflect.ToOneProperty;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+
+@UseServerRuntime(ServerCase.INHERTITANCE_SINGLE_TABLE1_PROJECT)
+public class DataObjectDescriptorFactory_InheritanceMapsTest extends ServerCase {
+
+    @Inject
+    private EntityResolver resolver;
+
+    public void testVisitProperties_IterationOrder() {
+
+        DataObjectDescriptorFactory factory = new DataObjectDescriptorFactory(
+                resolver.getClassDescriptorMap(), new SingletonFaultFactory());
+
+        for (ObjEntity e : resolver.getObjEntities()) {
+            ClassDescriptor descriptor = factory.getDescriptor(e.getName());
+
+            final PropertyDescriptor[] lastProcessed = new PropertyDescriptor[1];
+
+            PropertyVisitor visitor = new PropertyVisitor() {
+
+                public boolean visitToOne(ToOneProperty property) {
+                    DataObjectDescriptorFactoryTest.assertPropertiesAreInOrder(
+                            lastProcessed[0], property);
+                    lastProcessed[0] = property;
+                    return true;
+                }
+
+                public boolean visitToMany(ToManyProperty property) {
+                    DataObjectDescriptorFactoryTest.assertPropertiesAreInOrder(
+                            lastProcessed[0], property);
+                    lastProcessed[0] = property;
+                    return true;
+                }
+
+                public boolean visitAttribute(AttributeProperty property) {
+                    DataObjectDescriptorFactoryTest.assertPropertiesAreInOrder(
+                            lastProcessed[0], property);
+                    lastProcessed[0] = property;
+                    return true;
+                }
+            };
+
+            descriptor.visitProperties(visitor);
+        }
+
+    }
+}