You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polygene.apache.org by ni...@apache.org on 2015/04/17 18:05:52 UTC

[32/50] [abbrv] zest-qi4j git commit: Core Runtime: Fix Property and Value equality

Core Runtime: Fix Property and Value equality

Fix the spotted bug and make equals/hashcode behaviour uniform accross
PropertyInstance, AssociationInstance, ManyAssociationInstance and
ValueInstance. Transients, Entities and Services remain untouched.

Assertions are in PropertyEqualityTest, AssociationEqualityTest and
ValueEqualityTest.


Project: http://git-wip-us.apache.org/repos/asf/zest-qi4j/repo
Commit: http://git-wip-us.apache.org/repos/asf/zest-qi4j/commit/ff99cbd0
Tree: http://git-wip-us.apache.org/repos/asf/zest-qi4j/tree/ff99cbd0
Diff: http://git-wip-us.apache.org/repos/asf/zest-qi4j/diff/ff99cbd0

Branch: refs/heads/master
Commit: ff99cbd05668eab6123455b133af92f584cf4410
Parents: 85c1ca2
Author: Paul Merlin <pa...@nosphere.org>
Authored: Tue Feb 19 15:26:17 2013 +0100
Committer: Paul Merlin <pa...@nosphere.org>
Committed: Tue Feb 19 17:31:48 2013 +0100

----------------------------------------------------------------------
 core/api/src/docs/valuecomposite.txt            |   3 +-
 .../org/qi4j/api/entity/EntityReference.java    |   4 +-
 .../association/AssociationInstance.java        |  28 +-
 .../runtime/association/AssociationModel.java   |   4 +-
 .../association/ManyAssociationInstance.java    |  19 +-
 .../association/ManyAssociationModel.java       |   4 +-
 .../qi4j/runtime/property/PropertyInstance.java |  35 +-
 .../qi4j/runtime/property/PropertyModel.java    |   4 +-
 .../org/qi4j/runtime/value/ValueInstance.java   |  24 +-
 .../association/AssociationEqualityTest.java    | 388 +++++++++++++++++
 .../runtime/property/PropertyEqualityTest.java  | 430 +++++++++++++++++++
 .../qi4j/runtime/value/ValueEqualityTest.java   | 238 ++++++++++
 12 files changed, 1145 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/api/src/docs/valuecomposite.txt
----------------------------------------------------------------------
diff --git a/core/api/src/docs/valuecomposite.txt b/core/api/src/docs/valuecomposite.txt
index d5a0e05..dbd2f3c 100644
--- a/core/api/src/docs/valuecomposite.txt
+++ b/core/api/src/docs/valuecomposite.txt
@@ -28,8 +28,9 @@ of an EntityComposite via a Property.
 The characteristics of a ValueComposite compared to other Composite meta types are;
 
     * It is Immutable.
-    * Its equals/hashCode works on the values of the ValueComposite.
+    * Its equals/hashCode works on both the descriptor and the values of the ValueComposite.
     * Can be used as Property types.
+    * Can be serialized and deserialized.
 
 == Value Serialization ==
 Value objects can be serialized and deserialized using the ValueSerialization API which is a Service API implemented

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/api/src/main/java/org/qi4j/api/entity/EntityReference.java
----------------------------------------------------------------------
diff --git a/core/api/src/main/java/org/qi4j/api/entity/EntityReference.java b/core/api/src/main/java/org/qi4j/api/entity/EntityReference.java
index 5cdece2..60c4d1b 100644
--- a/core/api/src/main/java/org/qi4j/api/entity/EntityReference.java
+++ b/core/api/src/main/java/org/qi4j/api/entity/EntityReference.java
@@ -112,9 +112,7 @@ public final class EntityReference
     @Override
     public int hashCode()
     {
-        int result;
-        result = identity.hashCode();
-        return result;
+        return identity.hashCode();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/runtime/src/main/java/org/qi4j/runtime/association/AssociationInstance.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/qi4j/runtime/association/AssociationInstance.java b/core/runtime/src/main/java/org/qi4j/runtime/association/AssociationInstance.java
index 6672536..d94aae2 100644
--- a/core/runtime/src/main/java/org/qi4j/runtime/association/AssociationInstance.java
+++ b/core/runtime/src/main/java/org/qi4j/runtime/association/AssociationInstance.java
@@ -16,6 +16,7 @@ package org.qi4j.runtime.association;
 
 import java.lang.reflect.Type;
 import org.qi4j.api.association.Association;
+import org.qi4j.api.association.AssociationDescriptor;
 import org.qi4j.api.entity.EntityReference;
 import org.qi4j.api.property.Property;
 import org.qi4j.functional.Function2;
@@ -79,14 +80,12 @@ public final class AssociationInstance<T>
     @Override
     public int hashCode()
     {
-        if( associationState.get() == null )
+        int hash = associationInfo.hashCode() * 61; // Descriptor
+        if( associationState.get() != null )
         {
-            return 0;
-        }
-        else
-        {
-            return associationState.get().hashCode();
+            hash += associationState.get().hashCode() * 3; // State
         }
+        return hash;
     }
 
     @Override
@@ -100,15 +99,20 @@ public final class AssociationInstance<T>
         {
             return false;
         }
-
-        AssociationInstance that = (AssociationInstance) o;
-
-        if( associationState.get() != null ? !associationState.get()
-            .equals( that.associationState.get() ) : that.associationState.get() != null )
+        AssociationInstance<?> that = (AssociationInstance) o;
+        AssociationDescriptor thatDescriptor = (AssociationDescriptor) that.associationInfo();
+        // Descriptor equality
+        if( !associationInfo.equals( thatDescriptor ) )
+        {
+            return false;
+        }
+        // State equality
+        if( associationState.get() != null
+            ? !associationState.get().equals( that.associationState.get() )
+            : that.associationState.get() != null )
         {
             return false;
         }
-
         return true;
     }
 }

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/runtime/src/main/java/org/qi4j/runtime/association/AssociationModel.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/qi4j/runtime/association/AssociationModel.java b/core/runtime/src/main/java/org/qi4j/runtime/association/AssociationModel.java
index bb128a4..d1d5f36 100644
--- a/core/runtime/src/main/java/org/qi4j/runtime/association/AssociationModel.java
+++ b/core/runtime/src/main/java/org/qi4j/runtime/association/AssociationModel.java
@@ -44,7 +44,9 @@ import static org.qi4j.functional.Iterables.empty;
 import static org.qi4j.functional.Iterables.first;
 
 /**
- * JAVADOC
+ * Model for an Association.
+ *
+ * <p>Equality is based on the Association accessor object (associated type and name), not on the QualifiedName.</p>
  */
 public final class AssociationModel
     implements AssociationDescriptor, AssociationInfo, Binder, Visitable<AssociationModel>

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/runtime/src/main/java/org/qi4j/runtime/association/ManyAssociationInstance.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/qi4j/runtime/association/ManyAssociationInstance.java b/core/runtime/src/main/java/org/qi4j/runtime/association/ManyAssociationInstance.java
index 3bbed32..ad5a734 100644
--- a/core/runtime/src/main/java/org/qi4j/runtime/association/ManyAssociationInstance.java
+++ b/core/runtime/src/main/java/org/qi4j/runtime/association/ManyAssociationInstance.java
@@ -6,6 +6,7 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import org.qi4j.api.association.AssociationDescriptor;
 import org.qi4j.api.association.ManyAssociation;
 import org.qi4j.api.entity.EntityReference;
 import org.qi4j.functional.Function2;
@@ -120,13 +121,20 @@ public class ManyAssociationInstance<T>
             return false;
         }
         ManyAssociationInstance<?> that = (ManyAssociationInstance) o;
+        AssociationDescriptor thatDescriptor = (AssociationDescriptor) that.associationInfo();
+        // Descriptor equality
+        if( !associationInfo.equals( thatDescriptor ) )
+        {
+            return false;
+        }
+        // State equality
         if( manyAssociationState.count() != that.manyAssociationState.count() )
         {
             return false;
         }
         for( EntityReference ref : manyAssociationState )
         {
-            if(!that.manyAssociationState.contains( ref ) )
+            if( !that.manyAssociationState.contains( ref ) )
             {
                 return false;
             }
@@ -137,9 +145,12 @@ public class ManyAssociationInstance<T>
     @Override
     public int hashCode()
     {
-        int result = super.hashCode();
-        result = 31 * result + manyAssociationState.hashCode();
-        return result;
+        int hash = associationInfo.hashCode() * 31; // Descriptor
+        for( EntityReference ref : manyAssociationState )
+        {
+            hash += ref.hashCode() * 7; // State
+        }
+        return hash;
     }
 
     public ManyAssociationState getManyAssociationState()

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/runtime/src/main/java/org/qi4j/runtime/association/ManyAssociationModel.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/qi4j/runtime/association/ManyAssociationModel.java b/core/runtime/src/main/java/org/qi4j/runtime/association/ManyAssociationModel.java
index 1eef31b..69f0f82 100644
--- a/core/runtime/src/main/java/org/qi4j/runtime/association/ManyAssociationModel.java
+++ b/core/runtime/src/main/java/org/qi4j/runtime/association/ManyAssociationModel.java
@@ -48,7 +48,9 @@ import static org.qi4j.functional.Iterables.empty;
 import static org.qi4j.functional.Iterables.first;
 
 /**
- * JAVADOC
+ * Model for a ManyAssociation.
+ *
+ * <p>Equality is based on the ManyAssociation accessor object (associated type and name), not on the QualifiedName.</p>
  */
 public final class ManyAssociationModel
     implements AssociationDescriptor, AssociationInfo, Binder, Visitable<ManyAssociationModel>

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/runtime/src/main/java/org/qi4j/runtime/property/PropertyInstance.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/qi4j/runtime/property/PropertyInstance.java b/core/runtime/src/main/java/org/qi4j/runtime/property/PropertyInstance.java
index b54330e..75bda37 100644
--- a/core/runtime/src/main/java/org/qi4j/runtime/property/PropertyInstance.java
+++ b/core/runtime/src/main/java/org/qi4j/runtime/property/PropertyInstance.java
@@ -23,6 +23,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.qi4j.api.property.Property;
+import org.qi4j.api.property.PropertyDescriptor;
+import org.qi4j.api.property.PropertyWrapper;
 import org.qi4j.api.type.CollectionType;
 import org.qi4j.api.type.MapType;
 import org.qi4j.api.type.ValueCompositeType;
@@ -55,6 +57,9 @@ public class PropertyInstance<T>
         return model;
     }
 
+    /**
+     * @param model The property model. This argument must not be {@code null}.
+     */
     public void setPropertyInfo( PropertyInfo model )
     {
         this.model = model;
@@ -91,12 +96,12 @@ public class PropertyInstance<T>
 
     /**
      * Perform equals with {@code o} argument.
-     * <p/>
-     * The definition of equals() for the property is that if the value and subclass are
-     * equal, then the properties are equal
+     * <p>
+     *     The definition of equals() for the Property is that if both the state and descriptor are equal,
+     *     then the properties are equal.
+     * </p>
      *
      * @param o The other object to compare.
-     *
      * @return Returns a {@code boolean} indicator whether this object is equals the other.
      */
     @Override
@@ -112,7 +117,18 @@ public class PropertyInstance<T>
         }
 
         Property<?> that = (Property<?>) o;
-
+        // Unwrap if needed
+        while( that instanceof PropertyWrapper )
+        {
+            that = ( (PropertyWrapper) that ).next();
+        }
+        PropertyDescriptor thatDescriptor = (PropertyDescriptor) ( (PropertyInstance) that ).propertyInfo();
+        // Descriptor equality
+        if( !model.equals( thatDescriptor ) )
+        {
+            return false;
+        }
+        // State equality
         T value = get();
         if( value == null )
         {
@@ -129,16 +145,11 @@ public class PropertyInstance<T>
     @Override
     public int hashCode()
     {
-        int hash = getClass().hashCode();
-        if( model != null )
-        {
-            hash = model.type().hashCode();
-        }
-        hash = hash * 19;
+        int hash = model.hashCode() * 19; // Descriptor
         T value = get();
         if( value != null )
         {
-            hash = hash + value.hashCode() * 13;
+            hash += value.hashCode() * 13; // State
         }
         return hash;
     }

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/runtime/src/main/java/org/qi4j/runtime/property/PropertyModel.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/qi4j/runtime/property/PropertyModel.java b/core/runtime/src/main/java/org/qi4j/runtime/property/PropertyModel.java
index 05eb2f9..e547d8a 100644
--- a/core/runtime/src/main/java/org/qi4j/runtime/property/PropertyModel.java
+++ b/core/runtime/src/main/java/org/qi4j/runtime/property/PropertyModel.java
@@ -47,7 +47,9 @@ import static org.qi4j.functional.Iterables.empty;
 import static org.qi4j.functional.Iterables.first;
 
 /**
- * JAVADOC
+ * Model for a Property.
+ *
+ * <p>Equality is based on the Property accessor object (property type and name), not on the QualifiedName.</p>
  */
 public class PropertyModel
     implements PropertyDescriptor, PropertyInfo, Binder, Visitable<PropertyModel>

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/runtime/src/main/java/org/qi4j/runtime/value/ValueInstance.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/qi4j/runtime/value/ValueInstance.java b/core/runtime/src/main/java/org/qi4j/runtime/value/ValueInstance.java
index a68d108..ae8a4ab 100644
--- a/core/runtime/src/main/java/org/qi4j/runtime/value/ValueInstance.java
+++ b/core/runtime/src/main/java/org/qi4j/runtime/value/ValueInstance.java
@@ -47,6 +47,16 @@ public final class ValueInstance
         super( compositeModel, moduleInstance, mixins, state );
     }
 
+    /**
+     * Perform equals with {@code o} argument.
+     * <p>
+     *     The definition of equals() for the Value is that if both the state and descriptor are equal,
+     *     then the values are equal.
+     * </p>
+     *
+     * @param o The other object to compare.
+     * @return Returns a {@code boolean} indicator whether this object is equals the other.
+     */
     @Override
     public boolean equals( Object o )
     {
@@ -62,6 +72,12 @@ public final class ValueInstance
         try
         {
             ValueInstance that = (ValueInstance) Proxy.getInvocationHandler( o );
+            // Descriptor equality
+            if( !descriptor().equals( that.descriptor() ) )
+            {
+                return false;
+            }
+            // State equality
             return state.equals( that.state );
         }
         catch( ClassCastException e )
@@ -132,10 +148,16 @@ public final class ValueInstance
         }
     }
 
+    /**
+     * Calculate hash code.
+     *
+     * @return the hashcode of this instance.
+     */
     @Override
     public int hashCode()
     {
-        return state.hashCode();
+        int hash = compositeModel.hashCode() * 23; // Descriptor
+        return hash + state.hashCode() * 5; // State
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/runtime/src/test/java/org/qi4j/runtime/association/AssociationEqualityTest.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/test/java/org/qi4j/runtime/association/AssociationEqualityTest.java b/core/runtime/src/test/java/org/qi4j/runtime/association/AssociationEqualityTest.java
new file mode 100644
index 0000000..1c3852e
--- /dev/null
+++ b/core/runtime/src/test/java/org/qi4j/runtime/association/AssociationEqualityTest.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (c) 2013, Paul Merlin. All Rights Reserved.
+ *
+ * Licensed 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.qi4j.runtime.association;
+
+import org.junit.Test;
+import org.qi4j.api.association.Association;
+import org.qi4j.api.association.AssociationDescriptor;
+import org.qi4j.api.association.ManyAssociation;
+import org.qi4j.api.common.Optional;
+import org.qi4j.api.unitofwork.UnitOfWork;
+import org.qi4j.api.value.ValueBuilder;
+import org.qi4j.bootstrap.AssemblyException;
+import org.qi4j.bootstrap.ModuleAssembly;
+import org.qi4j.test.AbstractQi4jTest;
+import org.qi4j.test.EntityTestAssembler;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Assert that Association and ManyAssociation equals/hashcode methods combine AssociationDescriptor and State.
+ */
+public class AssociationEqualityTest
+    extends AbstractQi4jTest
+{
+
+    //
+    // --------------------------------------:: Types under test ::-----------------------------------------------------
+    //
+    @Override
+    public void assemble( ModuleAssembly module )
+        throws AssemblyException
+    {
+        new EntityTestAssembler().assemble( module );
+        module.entities( AnEntity.class );
+        module.values( SomeWithAssociations.class, OtherWithAssociations.class );
+    }
+
+    public interface AnEntity
+    {
+    }
+
+    public interface SomeWithAssociations
+    {
+
+        @Optional
+        Association<AnEntity> anEntity();
+
+        ManyAssociation<AnEntity> manyEntities();
+    }
+
+    public interface OtherWithAssociations
+    {
+
+        @Optional
+        Association<AnEntity> anEntity();
+
+        ManyAssociation<AnEntity> manyEntities();
+    }
+
+    //
+    // ----------------------------:: AssociationDescriptor equality tests ::-------------------------------------------
+    //
+    @Test
+    public void givenValuesOfTheSameTypeAndSameStateWhenTestingAssociationDescriptorEqualityExpectEquals()
+    {
+        UnitOfWork uow = module.newUnitOfWork();
+        try
+        {
+            AnEntity anEntity = uow.newEntity( AnEntity.class );
+
+            SomeWithAssociations some = buildSomeWithAssociation( anEntity );
+            AssociationDescriptor someAssocDesc = qi4j.api().associationDescriptorFor( some.anEntity() );
+            AssociationDescriptor someManyAssocDesc = qi4j.api().associationDescriptorFor( some.manyEntities() );
+
+            SomeWithAssociations some2 = buildSomeWithAssociation( anEntity );
+            AssociationDescriptor some2AssocDesc = qi4j.api().associationDescriptorFor( some2.anEntity() );
+            AssociationDescriptor some2ManyAssocDesc = qi4j.api().associationDescriptorFor( some2.manyEntities() );
+
+            assertThat( "AssociationDescriptor equal",
+                        someAssocDesc,
+                        equalTo( some2AssocDesc ) );
+            assertThat( "AssociationDescriptor hashcode equal",
+                        someAssocDesc.hashCode(),
+                        equalTo( some2AssocDesc.hashCode() ) );
+            assertThat( "ManyAssociationDescriptor equal",
+                        someManyAssocDesc,
+                        equalTo( some2ManyAssocDesc ) );
+            assertThat( "ManyAssociationDescriptor hashcode equal",
+                        someManyAssocDesc.hashCode(),
+                        equalTo( some2ManyAssocDesc.hashCode() ) );
+        }
+        finally
+        {
+            uow.discard();
+        }
+    }
+
+    @Test
+    public void givenValuesOfTheSameTypeAndDifferentStateWhenTestingAssociationDescriptorEqualityExpectEquals()
+    {
+        UnitOfWork uow = module.newUnitOfWork();
+        try
+        {
+            SomeWithAssociations some = buildSomeWithAssociation( uow.newEntity( AnEntity.class ) );
+            AssociationDescriptor someAssocDesc = qi4j.api().associationDescriptorFor( some.anEntity() );
+            AssociationDescriptor someManyAssocDesc = qi4j.api().associationDescriptorFor( some.manyEntities() );
+
+            SomeWithAssociations some2 = buildSomeWithAssociation( uow.newEntity( AnEntity.class ) );
+            AssociationDescriptor some2AssocDesc = qi4j.api().associationDescriptorFor( some2.anEntity() );
+            AssociationDescriptor some2ManyAssocDesc = qi4j.api().associationDescriptorFor( some2.manyEntities() );
+
+            assertThat( "AssociationDescriptor equal",
+                        someAssocDesc,
+                        equalTo( some2AssocDesc ) );
+            assertThat( "AssociationDescriptor hashcode equal",
+                        someAssocDesc.hashCode(),
+                        equalTo( some2AssocDesc.hashCode() ) );
+            assertThat( "ManyAssociationDescriptor equal",
+                        someManyAssocDesc,
+                        equalTo( some2ManyAssocDesc ) );
+            assertThat( "ManyAssociationDescriptor hashcode equal",
+                        someManyAssocDesc.hashCode(),
+                        equalTo( some2ManyAssocDesc.hashCode() ) );
+        }
+        finally
+        {
+            uow.discard();
+        }
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypeAndSameStateWhenTestingAssociationDescriptorEqualityExpectNotEquals()
+    {
+        UnitOfWork uow = module.newUnitOfWork();
+        try
+        {
+            AnEntity anEntity = uow.newEntity( AnEntity.class );
+
+            SomeWithAssociations some = buildSomeWithAssociation( anEntity );
+            AssociationDescriptor someAssocDesc = qi4j.api().associationDescriptorFor( some.anEntity() );
+            AssociationDescriptor someManyAssocDesc = qi4j.api().associationDescriptorFor( some.manyEntities() );
+
+            OtherWithAssociations other = buildOtherWithAssociation( anEntity );
+            AssociationDescriptor otherAssocDesc = qi4j.api().associationDescriptorFor( other.anEntity() );
+            AssociationDescriptor some2ManyAssocDesc = qi4j.api().associationDescriptorFor( other.manyEntities() );
+
+            assertThat( "AssociationDescriptor not equal",
+                        someAssocDesc,
+                        not( equalTo( otherAssocDesc ) ) );
+            assertThat( "AssociationDescriptor hashcode not equal",
+                        someAssocDesc.hashCode(),
+                        not( equalTo( otherAssocDesc.hashCode() ) ) );
+            assertThat( "ManyAssociationDescriptor not equal",
+                        someManyAssocDesc,
+                        not( equalTo( some2ManyAssocDesc ) ) );
+            assertThat( "ManyAssociationDescriptor hashcode not equal",
+                        someManyAssocDesc.hashCode(),
+                        not( equalTo( some2ManyAssocDesc.hashCode() ) ) );
+        }
+        finally
+        {
+            uow.discard();
+        }
+    }
+
+    //
+    // --------------------------------:: Association State equality tests ::----------------------------------------------
+    //
+    @Test
+    public void givenValuesOfSameTypeAndDifferentStateWhenTestingAssociationStateEqualityExpectNotEquals()
+    {
+        UnitOfWork uow = module.newUnitOfWork();
+        try
+        {
+            SomeWithAssociations some = buildSomeWithAssociation( uow.newEntity( AnEntity.class ) );
+            SomeWithAssociations some2 = buildSomeWithAssociation( uow.newEntity( AnEntity.class ) );
+
+            assertThat( "Association State not equal",
+                        some.anEntity().get(),
+                        not( equalTo( some2.anEntity().get() ) ) );
+            assertThat( "Association State hashcode not equal",
+                        some.anEntity().get().hashCode(),
+                        not( equalTo( some2.anEntity().get().hashCode() ) ) );
+            assertThat( "ManyAssociation State not equal",
+                        some.manyEntities().toList(),
+                        not( equalTo( some2.manyEntities().toList() ) ) );
+            assertThat( "ManyAssociation State hashcode not equal",
+                        some.manyEntities().toList().hashCode(),
+                        not( equalTo( some2.manyEntities().toList().hashCode() ) ) );
+        }
+        finally
+        {
+            uow.discard();
+        }
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesAndSameStateWhenTestingAssociationStateEqualityExpectEquals()
+    {
+        UnitOfWork uow = module.newUnitOfWork();
+        try
+        {
+            AnEntity anEntity = uow.newEntity( AnEntity.class );
+
+            SomeWithAssociations some = buildSomeWithAssociation( anEntity );
+            OtherWithAssociations other = buildOtherWithAssociation( anEntity );
+
+            assertThat( "Association State equal",
+                        some.anEntity().get(),
+                        equalTo( other.anEntity().get() ) );
+            assertThat( "Association State hashcode equal",
+                        some.anEntity().get().hashCode(),
+                        equalTo( other.anEntity().get().hashCode() ) );
+            assertThat( "ManyAssociation State equal",
+                        some.manyEntities().toList(),
+                        equalTo( other.manyEntities().toList() ) );
+            assertThat( "ManyAssociation State hashcode equal",
+                        some.manyEntities().toList().hashCode(),
+                        equalTo( other.manyEntities().toList().hashCode() ) );
+        }
+        finally
+        {
+            uow.discard();
+        }
+    }
+
+    //
+    // ----------------------------------:: Association equality tests ::-----------------------------------------------
+    //
+    @Test
+    public void givenValuesOfTheSameTypeAndSameStateWhenTestingAssociationEqualityExpectEquals()
+    {
+        UnitOfWork uow = module.newUnitOfWork();
+        try
+        {
+            AnEntity anEntity = uow.newEntity( AnEntity.class );
+
+            SomeWithAssociations some = buildSomeWithAssociation( anEntity );
+            SomeWithAssociations some2 = buildSomeWithAssociation( anEntity );
+
+            assertThat( "Association equal",
+                        some.anEntity(),
+                        equalTo( some2.anEntity() ) );
+            assertThat( "Association hashcode equal",
+                        some.anEntity().hashCode(),
+                        equalTo( some2.anEntity().hashCode() ) );
+            assertThat( "ManyAssociation equal",
+                        some.manyEntities(),
+                        equalTo( some2.manyEntities() ) );
+            assertThat( "ManyAssociation hashcode equal",
+                        some.manyEntities().hashCode(),
+                        equalTo( some2.manyEntities().hashCode() ) );
+        }
+        finally
+        {
+            uow.discard();
+        }
+    }
+
+    @Test
+    public void givenValuesOfTheSameTypeAndDifferentStateWhenTestingAssociationEqualityExpectNotEquals()
+    {
+        UnitOfWork uow = module.newUnitOfWork();
+        try
+        {
+            SomeWithAssociations some = buildSomeWithAssociation( uow.newEntity( AnEntity.class ) );
+            SomeWithAssociations some2 = buildSomeWithAssociation( uow.newEntity( AnEntity.class ) );
+
+            assertThat( "Association not equal",
+                        some.anEntity(),
+                        not( equalTo( some2.anEntity() ) ) );
+            assertThat( "Association hashcode not equal",
+                        some.anEntity().hashCode(),
+                        not( equalTo( some2.anEntity().hashCode() ) ) );
+            assertThat( "ManyAssociation not equal",
+                        some.manyEntities(),
+                        not( equalTo( some2.manyEntities() ) ) );
+            assertThat( "ManyAssociation hashcode not equal",
+                        some.manyEntities().hashCode(),
+                        not( equalTo( some2.manyEntities().hashCode() ) ) );
+        }
+        finally
+        {
+            uow.discard();
+        }
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesAndSameStateWhenTestingAssociationEqualityExpectNotEquals()
+    {
+        UnitOfWork uow = module.newUnitOfWork();
+        try
+        {
+            AnEntity anEntity = uow.newEntity( AnEntity.class );
+
+            SomeWithAssociations some = buildSomeWithAssociation( anEntity );
+            OtherWithAssociations other = buildOtherWithAssociation( anEntity );
+
+            assertThat( "Association not equal",
+                        some.anEntity(),
+                        not( equalTo( other.anEntity() ) ) );
+            assertThat( "Association hashcode not equal",
+                        some.anEntity().hashCode(),
+                        not( equalTo( other.anEntity().hashCode() ) ) );
+            assertThat( "ManyAssociation not equal",
+                        some.manyEntities(),
+                        not( equalTo( other.manyEntities() ) ) );
+            assertThat( "ManyAssociation hashcode not equal",
+                        some.manyEntities().hashCode(),
+                        not( equalTo( other.manyEntities().hashCode() ) ) );
+        }
+        finally
+        {
+            uow.discard();
+        }
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesAndDifferentStateWhenTestingAssociationEqualityExpectNotEquals()
+    {
+        UnitOfWork uow = module.newUnitOfWork();
+        try
+        {
+            SomeWithAssociations some = buildSomeWithAssociation( uow.newEntity( AnEntity.class ) );
+            OtherWithAssociations other = buildOtherWithAssociation( uow.newEntity( AnEntity.class ) );
+
+            assertThat( "Association not equal",
+                        some.anEntity(),
+                        not( equalTo( other.anEntity() ) ) );
+            assertThat( "Association hashcode not equal",
+                        some.anEntity().hashCode(),
+                        not( equalTo( other.anEntity().hashCode() ) ) );
+            assertThat( "ManyAssociation not equal",
+                        some.manyEntities(),
+                        not( equalTo( other.manyEntities() ) ) );
+            assertThat( "ManyAssociation hashcode not equal",
+                        some.manyEntities().hashCode(),
+                        not( equalTo( other.manyEntities().hashCode() ) ) );
+        }
+        finally
+        {
+            uow.discard();
+        }
+    }
+
+    //
+    // -----------------------------------:: Values factory methods ::--------------------------------------------------
+    //
+    private SomeWithAssociations buildSomeWithAssociation( AnEntity associated )
+    {
+        SomeWithAssociations some;
+        {
+            ValueBuilder<SomeWithAssociations> builder = module.newValueBuilder( SomeWithAssociations.class );
+            builder.prototype().anEntity().set( associated );
+            builder.prototype().manyEntities().add( associated );
+            some = builder.newInstance();
+        }
+        return some;
+    }
+
+    private OtherWithAssociations buildOtherWithAssociation( AnEntity associated )
+    {
+        OtherWithAssociations some;
+        {
+            ValueBuilder<OtherWithAssociations> builder = module.newValueBuilder( OtherWithAssociations.class );
+            builder.prototype().anEntity().set( associated );
+            builder.prototype().manyEntities().add( associated );
+            some = builder.newInstance();
+        }
+        return some;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/runtime/src/test/java/org/qi4j/runtime/property/PropertyEqualityTest.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/test/java/org/qi4j/runtime/property/PropertyEqualityTest.java b/core/runtime/src/test/java/org/qi4j/runtime/property/PropertyEqualityTest.java
new file mode 100644
index 0000000..22de796
--- /dev/null
+++ b/core/runtime/src/test/java/org/qi4j/runtime/property/PropertyEqualityTest.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2013, Paul Merlin. All Rights Reserved.
+ *
+ * Licensed 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.qi4j.runtime.property;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.junit.Test;
+import org.qi4j.api.common.Optional;
+import org.qi4j.api.property.Property;
+import org.qi4j.api.property.PropertyDescriptor;
+import org.qi4j.api.structure.Module;
+import org.qi4j.api.value.ValueBuilder;
+import org.qi4j.api.value.ValueDescriptor;
+import org.qi4j.bootstrap.AssemblyException;
+import org.qi4j.bootstrap.ModuleAssembly;
+import org.qi4j.test.AbstractQi4jTest;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.joda.time.DateTimeZone.UTC;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Assert that Property equals/hashcode methods combine PropertyDescriptor and State.
+ */
+public class PropertyEqualityTest
+    extends AbstractQi4jTest
+{
+
+    //
+    // --------------------------------------:: Types under test ::-----------------------------------------------------
+    //
+    @Override
+    public void assemble( ModuleAssembly module )
+        throws AssemblyException
+    {
+        module.values( PrimitivesValue.class, Some.class, AnotherSome.class, Other.class );
+    }
+
+    public enum AnEnum
+    {
+
+        BAZAR, CATHEDRAL
+    }
+
+    public interface PrimitivesValue
+    {
+
+        Property<Character> characterProperty();
+
+        Property<String> stringProperty();
+
+        Property<Boolean> booleanProperty();
+
+        Property<Integer> integerProperty();
+
+        Property<Long> longProperty();
+
+        Property<Float> floatProperty();
+
+        Property<Double> doubleProperty();
+
+        Property<Short> shortProperty();
+
+        Property<Byte> byteProperty();
+
+        Property<AnEnum> enumProperty();
+    }
+
+    public interface Some
+        extends PrimitivesValue
+    {
+
+        @Optional
+        Property<Some> selfProperty();
+
+        Property<BigInteger> bigIntegerProperty();
+
+        Property<BigDecimal> bigDecimalProperty();
+
+        Property<Date> dateProperty();
+
+        Property<DateTime> dateTimeProperty();
+
+        Property<LocalDate> localDateProperty();
+
+        Property<LocalDateTime> localDateTimeProperty();
+    }
+
+    public interface AnotherSome
+        extends Some
+    {
+    }
+
+    public interface Other
+    {
+
+        Property<Character> characterProperty();
+    }
+
+    //
+    // ------------------------------:: PropertyDescriptor equality tests ::--------------------------------------------
+    //
+    @Test
+    public void givenValuesOfTheSameTypeWhenTestingPropertyDescriptorEqualityExpectEquals()
+    {
+        Some some = buildSomeValue( module );
+        ValueDescriptor someDescriptor = qi4j.api().valueDescriptorFor( some );
+        PropertyDescriptor someCharPropDesc = someDescriptor.state().findPropertyModelByName( "characterProperty" );
+
+        Some other = buildSomeValue( module );
+        ValueDescriptor otherDescriptor = qi4j.api().valueDescriptorFor( other );
+        PropertyDescriptor otherCharPropDesc = otherDescriptor.state().findPropertyModelByName( "characterProperty" );
+
+        assertThat( "PropertyDescriptors equal",
+                    someCharPropDesc,
+                    equalTo( otherCharPropDesc ) );
+        assertThat( "PropertyDescriptors hashcode equal",
+                    someCharPropDesc.hashCode(),
+                    equalTo( otherCharPropDesc.hashCode() ) );
+    }
+
+    @Test
+    public void givenValuesOfCommonTypesWhenTestingPropertyDescriptorEqualityExpectEquals()
+    {
+        Some some = buildSomeValue( module );
+        ValueDescriptor someDescriptor = qi4j.api().valueDescriptorFor( some );
+        PropertyDescriptor someCharPropDesc = someDescriptor.state().findPropertyModelByName( "characterProperty" );
+
+        PrimitivesValue primitive = buildPrimitivesValue( module );
+        ValueDescriptor primitiveDescriptor = qi4j.api().valueDescriptorFor( primitive );
+        PropertyDescriptor primitiveCharPropDesc = primitiveDescriptor.state().findPropertyModelByName( "characterProperty" );
+
+        assertThat( "PropertyDescriptors equal",
+                    someCharPropDesc,
+                    equalTo( primitiveCharPropDesc ) );
+        assertThat( "PropertyDescriptors hashcode equal",
+                    someCharPropDesc.hashCode(),
+                    equalTo( primitiveCharPropDesc.hashCode() ) );
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesWhenTestingPropertyDescriptorEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        ValueDescriptor someDescriptor = qi4j.api().valueDescriptorFor( some );
+        PropertyDescriptor someCharPropDesc = someDescriptor.state().findPropertyModelByName( "characterProperty" );
+
+        Other other = buildOtherValue( module );
+        ValueDescriptor otherDescriptor = qi4j.api().valueDescriptorFor( other );
+        PropertyDescriptor otherCharPropDesc = otherDescriptor.state().findPropertyModelByName( "characterProperty" );
+
+        assertThat( "PropertyDescriptors not equal",
+                    someCharPropDesc,
+                    not( equalTo( otherCharPropDesc ) ) );
+        assertThat( "PropertyDescriptors hashcode not equal",
+                    someCharPropDesc.hashCode(),
+                    not( equalTo( otherCharPropDesc.hashCode() ) ) );
+    }
+
+    //
+    // --------------------------------:: Property State equality tests ::----------------------------------------------
+    //
+    @Test
+    public void givenValuesOfDifferentTypesAndSameStateWhenTestingPropertyStateEqualityExpectEquals()
+    {
+        PrimitivesValue primitives = buildPrimitivesValue( module );
+        Some some = buildSomeValue( module );
+        Some some2 = buildSomeValue( module );
+        Other other = buildOtherValue( module );
+        assertThat( "Property state equal",
+                    'q',
+                    allOf( equalTo( primitives.characterProperty().get() ),
+                           equalTo( some.characterProperty().get() ),
+                           equalTo( some2.characterProperty().get() ),
+                           equalTo( other.characterProperty().get() ) ) );
+        assertThat( "Property state hashcode equal",
+                    new Character( 'q' ).hashCode(),
+                    allOf( equalTo( primitives.characterProperty().get().hashCode() ),
+                           equalTo( some.characterProperty().get().hashCode() ),
+                           equalTo( some2.characterProperty().get().hashCode() ),
+                           equalTo( other.characterProperty().get().hashCode() ) ) );
+    }
+
+    //
+    // -----------------------------------:: Property equality tests ::-------------------------------------------------
+    //
+    @Test
+    public void givenValuesOfTheSameTypeAndSameStateWhenTestingPropertyEqualityExpectEquals()
+    {
+        Some some = buildSomeValue( module );
+        Some some2 = buildSomeValue( module );
+        assertThat( "Property equals",
+                    some.characterProperty(),
+                    equalTo( some2.characterProperty() ) );
+        assertThat( "Property hashcode equals",
+                    some.characterProperty().hashCode(),
+                    equalTo( some2.characterProperty().hashCode() ) );
+    }
+
+    @Test
+    public void givenValuesOfTheSameTypeWithDifferentStateWhenTestingPropertyEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        Some some2 = buildSomeValueWithDifferentState( module );
+        assertThat( "Property not equals",
+                    some.characterProperty(),
+                    not( equalTo( some2.characterProperty() ) ) );
+        assertThat( "Property hashcode not equals",
+                    some.characterProperty().hashCode(),
+                    not( equalTo( some2.characterProperty().hashCode() ) ) );
+    }
+
+    @Test
+    public void givenValuesOfCommonTypesAndSameStateWhenTestingPropertyEqualityExpectEquals()
+    {
+        Some some = buildSomeValue( module );
+        PrimitivesValue primitive = buildPrimitivesValue( module );
+        assertThat( "Property equal",
+                    some.characterProperty(),
+                    equalTo( primitive.characterProperty() ) );
+    }
+
+    @Test
+    public void givenValuesOfCommonTypesWithDifferentStateWhenTestingPropertyEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        PrimitivesValue primitive = buildPrimitivesValueWithDifferentState( module );
+        assertThat( "Property not equal",
+                    some.characterProperty(),
+                    not( equalTo( primitive.characterProperty() ) ) );
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesAndSameStateWhenTestingPropertyEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        Other other = buildOtherValue( module );
+        assertThat( "Property not equal",
+                    some.characterProperty(),
+                    not( equalTo( other.characterProperty() ) ) );
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesWithDifferentStateWhenTestingPropertyEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        Other other = buildOtherValue( module );
+        assertThat( "Property not equal",
+                    some.characterProperty(),
+                    not( equalTo( other.characterProperty() ) ) );
+    }
+
+    //
+    // -----------------------------------:: Values factory methods ::--------------------------------------------------
+    //
+    public static PrimitivesValue buildPrimitivesValue( Module module )
+    {
+        PrimitivesValue primitive;
+        {
+            ValueBuilder<PrimitivesValue> builder = module.newValueBuilder( PrimitivesValue.class );
+            builder.prototype().characterProperty().set( 'q' );
+            builder.prototype().stringProperty().set( "foo" );
+            builder.prototype().booleanProperty().set( true );
+            builder.prototype().integerProperty().set( 42 );
+            builder.prototype().longProperty().set( 42L );
+            builder.prototype().floatProperty().set( 42.23F );
+            builder.prototype().doubleProperty().set( 42.23D );
+            builder.prototype().shortProperty().set( (short) 42 );
+            builder.prototype().byteProperty().set( (byte) 42 );
+            builder.prototype().enumProperty().set( AnEnum.BAZAR );
+            primitive = builder.newInstance();
+        }
+        return primitive;
+    }
+
+    public static PrimitivesValue buildPrimitivesValueWithDifferentState( Module module )
+    {
+        PrimitivesValue primitive;
+        {
+            ValueBuilder<PrimitivesValue> builder = module.newValueBuilder( PrimitivesValue.class );
+            builder.prototype().characterProperty().set( 'i' );
+            builder.prototype().stringProperty().set( "bar" );
+            builder.prototype().booleanProperty().set( false );
+            builder.prototype().integerProperty().set( 23 );
+            builder.prototype().longProperty().set( 23L );
+            builder.prototype().floatProperty().set( 23.42F );
+            builder.prototype().doubleProperty().set( 23.42D );
+            builder.prototype().shortProperty().set( (short) 23 );
+            builder.prototype().byteProperty().set( (byte) 23 );
+            builder.prototype().enumProperty().set( AnEnum.CATHEDRAL );
+            primitive = builder.newInstance();
+        }
+        return primitive;
+    }
+
+    public static Some buildSomeValue( Module module )
+    {
+        Some some;
+        {
+            ValueBuilder<Some> builder = module.newValueBuilder( Some.class );
+            builder.prototype().characterProperty().set( 'q' );
+            builder.prototype().stringProperty().set( "foo" );
+            builder.prototype().booleanProperty().set( true );
+            builder.prototype().integerProperty().set( 42 );
+            builder.prototype().longProperty().set( 42L );
+            builder.prototype().floatProperty().set( 42.23F );
+            builder.prototype().doubleProperty().set( 42.23D );
+            builder.prototype().shortProperty().set( (short) 42 );
+            builder.prototype().byteProperty().set( (byte) 42 );
+            builder.prototype().enumProperty().set( AnEnum.BAZAR );
+            builder.prototype().bigIntegerProperty().set( new BigInteger( "42" ) );
+            builder.prototype().bigDecimalProperty().set( new BigDecimal( "42.23" ) );
+            builder.prototype().dateProperty().set( new DateTime( "2020-03-04T13:24:35", UTC ).toDate() );
+            builder.prototype().dateTimeProperty().set( new DateTime( "2020-03-04T13:24:35", UTC ) );
+            builder.prototype().localDateProperty().set( new LocalDate( "2020-03-04" ) );
+            builder.prototype().localDateTimeProperty().set( new LocalDateTime( "2020-03-04T13:23:00", UTC ) );
+            some = builder.newInstance();
+        }
+        return some;
+    }
+
+    public static Some buildSomeValueWithDifferentState( Module module )
+    {
+        Some some;
+        {
+            ValueBuilder<Some> builder = module.newValueBuilder( Some.class );
+            builder.prototype().characterProperty().set( 'i' );
+            builder.prototype().stringProperty().set( "bar" );
+            builder.prototype().booleanProperty().set( false );
+            builder.prototype().integerProperty().set( 23 );
+            builder.prototype().longProperty().set( 23L );
+            builder.prototype().floatProperty().set( 23.42F );
+            builder.prototype().doubleProperty().set( 23.42D );
+            builder.prototype().shortProperty().set( (short) 23 );
+            builder.prototype().byteProperty().set( (byte) 23 );
+            builder.prototype().enumProperty().set( AnEnum.CATHEDRAL );
+            builder.prototype().bigIntegerProperty().set( new BigInteger( "23" ) );
+            builder.prototype().bigDecimalProperty().set( new BigDecimal( "23.42" ) );
+            builder.prototype().dateProperty().set( new DateTime( "2030-02-08T09:09:09", UTC ).toDate() );
+            builder.prototype().dateTimeProperty().set( new DateTime( "2030-02-08T09:09:09", UTC ) );
+            builder.prototype().localDateProperty().set( new LocalDate( "2030-02-08" ) );
+            builder.prototype().localDateTimeProperty().set( new LocalDateTime( "2030-02-08T09:09:09", UTC ) );
+            some = builder.newInstance();
+        }
+        return some;
+    }
+
+    public static AnotherSome buildAnotherSomeValue( Module module )
+    {
+        AnotherSome anotherSome;
+        {
+            ValueBuilder<AnotherSome> builder = module.newValueBuilder( AnotherSome.class );
+            builder.prototype().characterProperty().set( 'q' );
+            builder.prototype().stringProperty().set( "foo" );
+            builder.prototype().booleanProperty().set( true );
+            builder.prototype().integerProperty().set( 42 );
+            builder.prototype().longProperty().set( 42L );
+            builder.prototype().floatProperty().set( 42.23F );
+            builder.prototype().doubleProperty().set( 42.23D );
+            builder.prototype().shortProperty().set( (short) 42 );
+            builder.prototype().byteProperty().set( (byte) 42 );
+            builder.prototype().enumProperty().set( AnEnum.BAZAR );
+            builder.prototype().bigIntegerProperty().set( new BigInteger( "42" ) );
+            builder.prototype().bigDecimalProperty().set( new BigDecimal( "42.23" ) );
+            builder.prototype().dateProperty().set( new DateTime( "2020-03-04T13:24:35", UTC ).toDate() );
+            builder.prototype().dateTimeProperty().set( new DateTime( "2020-03-04T13:24:35", UTC ) );
+            builder.prototype().localDateProperty().set( new LocalDate( "2020-03-04" ) );
+            builder.prototype().localDateTimeProperty().set( new LocalDateTime( "2020-03-04T13:23:00", UTC ) );
+            anotherSome = builder.newInstance();
+        }
+        return anotherSome;
+    }
+
+    public static AnotherSome buildAnotherSomeValueWithDifferentState( Module module )
+    {
+        AnotherSome anotherSome;
+        {
+            ValueBuilder<AnotherSome> builder = module.newValueBuilder( AnotherSome.class );
+            builder.prototype().characterProperty().set( 'i' );
+            builder.prototype().stringProperty().set( "bar" );
+            builder.prototype().booleanProperty().set( false );
+            builder.prototype().integerProperty().set( 23 );
+            builder.prototype().longProperty().set( 23L );
+            builder.prototype().floatProperty().set( 23.42F );
+            builder.prototype().doubleProperty().set( 23.42D );
+            builder.prototype().shortProperty().set( (short) 23 );
+            builder.prototype().byteProperty().set( (byte) 23 );
+            builder.prototype().enumProperty().set( AnEnum.CATHEDRAL );
+            builder.prototype().bigIntegerProperty().set( new BigInteger( "23" ) );
+            builder.prototype().bigDecimalProperty().set( new BigDecimal( "23.42" ) );
+            builder.prototype().dateProperty().set( new DateTime( "2030-02-08T09:09:09", UTC ).toDate() );
+            builder.prototype().dateTimeProperty().set( new DateTime( "2030-02-08T09:09:09", UTC ) );
+            builder.prototype().localDateProperty().set( new LocalDate( "2030-02-08" ) );
+            builder.prototype().localDateTimeProperty().set( new LocalDateTime( "2030-02-08T09:09:09", UTC ) );
+            anotherSome = builder.newInstance();
+        }
+        return anotherSome;
+    }
+
+    public static Other buildOtherValue( Module module )
+    {
+        Other other;
+        {
+            ValueBuilder<Other> builder = module.newValueBuilder( Other.class );
+            builder.prototype().characterProperty().set( 'q' );
+            other = builder.newInstance();
+        }
+        return other;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/ff99cbd0/core/runtime/src/test/java/org/qi4j/runtime/value/ValueEqualityTest.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/test/java/org/qi4j/runtime/value/ValueEqualityTest.java b/core/runtime/src/test/java/org/qi4j/runtime/value/ValueEqualityTest.java
new file mode 100644
index 0000000..e0df77a
--- /dev/null
+++ b/core/runtime/src/test/java/org/qi4j/runtime/value/ValueEqualityTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2013, Paul Merlin. All Rights Reserved.
+ *
+ * Licensed 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.qi4j.runtime.value;
+
+import org.junit.Test;
+import org.qi4j.api.association.AssociationStateHolder;
+import org.qi4j.api.value.ValueComposite;
+import org.qi4j.api.value.ValueDescriptor;
+import org.qi4j.bootstrap.AssemblyException;
+import org.qi4j.bootstrap.ModuleAssembly;
+import org.qi4j.runtime.property.PropertyEqualityTest.AnotherSome;
+import org.qi4j.runtime.property.PropertyEqualityTest.Other;
+import org.qi4j.runtime.property.PropertyEqualityTest.PrimitivesValue;
+import org.qi4j.runtime.property.PropertyEqualityTest.Some;
+import org.qi4j.test.AbstractQi4jTest;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.qi4j.runtime.property.PropertyEqualityTest.buildAnotherSomeValue;
+import static org.qi4j.runtime.property.PropertyEqualityTest.buildAnotherSomeValueWithDifferentState;
+import static org.qi4j.runtime.property.PropertyEqualityTest.buildOtherValue;
+import static org.qi4j.runtime.property.PropertyEqualityTest.buildPrimitivesValue;
+import static org.qi4j.runtime.property.PropertyEqualityTest.buildSomeValue;
+import static org.qi4j.runtime.property.PropertyEqualityTest.buildSomeValueWithDifferentState;
+
+/**
+ * Assert that Value equals/hashcode methods combine ValueDescriptor and ValueState.
+ */
+public class ValueEqualityTest
+    extends AbstractQi4jTest
+{
+
+    //
+    // --------------------------------------:: Types under test ::-----------------------------------------------------
+    //
+    @Override
+    public void assemble( ModuleAssembly module )
+        throws AssemblyException
+    {
+        module.values( PrimitivesValue.class, Some.class, AnotherSome.class, Other.class );
+    }
+
+    //
+    // -------------------------------:: ValueDescriptor equality tests ::----------------------------------------------
+    //
+    @Test
+    public void givenValuesOfTheSameTypeWhenTestingValueDescriptorEqualityExpectEquals()
+    {
+        Some some = buildSomeValue( module );
+        ValueDescriptor someDescriptor = qi4j.api().valueDescriptorFor( some );
+
+        Some other = buildSomeValue( module );
+        ValueDescriptor otherDescriptor = qi4j.api().valueDescriptorFor( other );
+
+        assertThat( "ValueDescriptors equal",
+                    someDescriptor,
+                    equalTo( otherDescriptor ) );
+        assertThat( "ValueDescriptors hashcode equal",
+                    someDescriptor.hashCode(),
+                    equalTo( otherDescriptor.hashCode() ) );
+    }
+
+    @Test
+    public void givenValuesOfCommonTypesWhenTestingValueDescriptorEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        ValueDescriptor someDescriptor = qi4j.api().valueDescriptorFor( some );
+
+        PrimitivesValue primitive = buildPrimitivesValue( module );
+        ValueDescriptor primitiveDescriptor = qi4j.api().valueDescriptorFor( primitive );
+
+        assertThat( "ValueDescriptors not equal",
+                    someDescriptor,
+                    not( equalTo( primitiveDescriptor ) ) );
+        assertThat( "ValueDescriptors hashcode not equal",
+                    someDescriptor.hashCode(),
+                    not( equalTo( primitiveDescriptor.hashCode() ) ) );
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesWhenTestingValueDescriptorEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        ValueDescriptor someDescriptor = qi4j.api().valueDescriptorFor( some );
+
+        Other other = buildOtherValue( module );
+        ValueDescriptor otherDescriptor = qi4j.api().valueDescriptorFor( other );
+
+        assertThat( "ValueDescriptors not equal",
+                    someDescriptor,
+                    not( equalTo( otherDescriptor ) ) );
+        assertThat( "ValueDescriptors hashcode not equal",
+                    someDescriptor.hashCode(),
+                    not( equalTo( otherDescriptor.hashCode() ) ) );
+    }
+
+    //
+    // ---------------------------------:: Value State equality tests ::------------------------------------------------
+    //
+    @Test
+    public void givenValuesOfSameTypesAndSameStateWhenTestingValueStateEqualityExpectEquals()
+    {
+        Some some = buildSomeValue( module );
+        AssociationStateHolder someState = qi4j.spi().stateOf( (ValueComposite) some );
+
+        Some some2 = buildSomeValue( module );
+        AssociationStateHolder some2State = qi4j.spi().stateOf( (ValueComposite) some2 );
+
+        assertThat( "ValueStates equal",
+                    someState,
+                    equalTo( some2State ) );
+        assertThat( "ValueStates hashcode equal",
+                    someState.hashCode(),
+                    equalTo( some2State.hashCode() ) );
+    }
+
+    @Test
+    public void givenValuesOfSameTypesAndDifferentStateWhenTestingValueStateEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        AssociationStateHolder someState = qi4j.spi().stateOf( (ValueComposite) some );
+
+        Some some2 = buildSomeValueWithDifferentState( module );
+        AssociationStateHolder some2State = qi4j.spi().stateOf( (ValueComposite) some2 );
+
+        assertThat( "ValueStates not equal",
+                    someState,
+                    not( equalTo( some2State ) ) );
+        assertThat( "ValueStates hashcode not equal",
+                    someState.hashCode(),
+                    not( equalTo( some2State.hashCode() ) ) );
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesAndSameStateWhenTestingValueStateEqualityExpectEquals()
+    {
+        Some some = buildSomeValue( module );
+        AssociationStateHolder someState = qi4j.spi().stateOf( (ValueComposite) some );
+
+        AnotherSome anotherSome = buildAnotherSomeValue( module );
+        AssociationStateHolder anotherSomeState = qi4j.spi().stateOf( (ValueComposite) anotherSome );
+
+        assertThat( "ValueStates equal",
+                    someState,
+                    equalTo( anotherSomeState ) );
+        assertThat( "ValueStates hashcode equal",
+                    someState.hashCode(),
+                    equalTo( anotherSomeState.hashCode() ) );
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesAndDifferentStateWhenTestingValueStateEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        AssociationStateHolder someState = qi4j.spi().stateOf( (ValueComposite) some );
+
+        AnotherSome anotherSome = buildAnotherSomeValueWithDifferentState( module );
+        AssociationStateHolder anotherSomeState = qi4j.spi().stateOf( (ValueComposite) anotherSome );
+
+        assertThat( "ValueStates not equal",
+                    someState,
+                    not( equalTo( anotherSomeState ) ) );
+        assertThat( "ValueStates hashcode not equal",
+                    someState.hashCode(),
+                    not( equalTo( anotherSomeState.hashCode() ) ) );
+    }
+
+    //
+    // ------------------------------------:: Value equality tests ::---------------------------------------------------
+    //
+    @Test
+    public void givenValuesOfSameTypesAndSameStateWhenTestingValueEqualityExpectEquals()
+    {
+        Some some = buildSomeValue( module );
+        Some some2 = buildSomeValue( module );
+        assertThat( "Values equal",
+                    some,
+                    equalTo( some2 ) );
+        assertThat( "Values hashcode equal",
+                    some.hashCode(),
+                    equalTo( some2.hashCode() ) );
+    }
+
+    @Test
+    public void givenValuesOfTheSameTypeWithDifferentStateWhenTestingValueEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        Some some2 = buildSomeValueWithDifferentState( module );
+        assertThat( "Values not equals",
+                    some,
+                    not( equalTo( some2 ) ) );
+        assertThat( "Values hashcode not equals",
+                    some.hashCode(),
+                    not( equalTo( some2.hashCode() ) ) );
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesAndSameStateWhenTestingValueEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        Some anotherSome = buildAnotherSomeValue( module );
+
+        assertThat( "Values not equal",
+                    some,
+                    not( equalTo( anotherSome ) ) );
+        assertThat( "Values hashcode not equal",
+                    some.hashCode(),
+                    not( equalTo( anotherSome.hashCode() ) ) );
+    }
+
+    @Test
+    public void givenValuesOfDifferentTypesAndDifferentStateWhenTestingValueEqualityExpectNotEquals()
+    {
+        Some some = buildSomeValue( module );
+        Some anotherSome = buildAnotherSomeValueWithDifferentState( module );
+        assertThat( "Values not equal",
+                    some,
+                    not( equalTo( anotherSome ) ) );
+        assertThat( "Values hashcode not equal",
+                    some.hashCode(),
+                    not( equalTo( anotherSome.hashCode() ) ) );
+    }
+}