]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.databoard/src/org/simantics/databoard/binding/Binding.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / binding / Binding.java
diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/Binding.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/Binding.java
new file mode 100644 (file)
index 0000000..6bc33f7
--- /dev/null
@@ -0,0 +1,759 @@
+/*******************************************************************************\r
+ *  Copyright (c) 2010 Association for Decentralized Information Management in\r
+ *  Industry THTH ry.\r
+ *  All rights reserved. This program and the accompanying materials\r
+ *  are made available under the terms of the Eclipse Public License v1.0\r
+ *  which accompanies this distribution, and is available at\r
+ *  http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ *  Contributors:\r
+ *      VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.databoard.binding;
+
+import java.io.IOException;\r
+import java.io.Reader;\r
+import java.util.Comparator;\r
+import java.util.HashSet;\r
+import java.util.IdentityHashMap;\r
+import java.util.Random;\r
+import java.util.Set;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.accessor.error.AccessorConstructionException;\r
+import org.simantics.databoard.accessor.error.AccessorException;\r
+import org.simantics.databoard.accessor.reference.ChildReference;\r
+import org.simantics.databoard.adapter.AdaptException;\r
+import org.simantics.databoard.adapter.AdapterConstructionException;\r
+import org.simantics.databoard.adapter.RuntimeAdaptException;\r
+import org.simantics.databoard.binding.error.BindingException;\r
+import org.simantics.databoard.binding.error.RuntimeBindingException;\r
+import org.simantics.databoard.binding.impl.BindingPrintContext;\r
+import org.simantics.databoard.binding.mutable.MutableVariant;\r
+import org.simantics.databoard.binding.mutable.Variant;\r
+import org.simantics.databoard.binding.util.DefaultValue;\r
+import org.simantics.databoard.binding.util.RandomValue;\r
+import org.simantics.databoard.parser.DataParser;\r
+import org.simantics.databoard.parser.DataValuePrinter;\r
+import org.simantics.databoard.parser.ParseException;\r
+import org.simantics.databoard.parser.PrintFormat;\r
+import org.simantics.databoard.parser.repository.DataTypeSyntaxError;\r
+import org.simantics.databoard.parser.repository.DataValueRepository;\r
+import org.simantics.databoard.serialization.RuntimeSerializerConstructionException;\r
+import org.simantics.databoard.serialization.Serializer;\r
+import org.simantics.databoard.serialization.SerializerFactory;\r
+import org.simantics.databoard.serialization.SerializerScheme;\r
+import org.simantics.databoard.type.Datatype;\r
+import org.simantics.databoard.util.IdentityPair;\r
+
+/**\r
+ * This class represents connection between abstract datatype and java class.\r
+ * A binding allows an access to an Object in scope of a datatype. <p>\r
+ * \r
+ * For example, IntegerBinding gives unified access to any integer class\r
+ * (Integer, int, MutableInteger, UnsignedInteger). There is same unification\r
+ * for primitive types and constructed types (record, map, array, union, variant). <p>\r
+ * \r
+ * You can get a hold of binding several ways:\r
+ *   1) Use one of the default bindings e.g. {@link Bindings#BYTE_ARRAY}\r
+ *   2) Create one using Datatype {@link Bindings#getMutableBinding(Datatype)}\r
+ *   3) Create one using Reflectiong {@link Bindings#getBinding(Class)}\r
+ *   4) Instantiate binding your self. e.g. new TreeMapBinding( Bindings.STRING, Bindings.STRING );\r
+ *   5) Sub-class one of the abstract binding classes \r
+ *             @see BooleanBinding\r
+ *             @see ByteBinding\r
+ *             @see IntegerBinding\r
+ *             @see LongBinding\r
+ *             @see FloatBinding\r
+ *             @see DoubleBinding\r
+ *             @see StringBinding\r
+ *             @see RecordBinding\r
+ *             @see ArrayBinding\r
+ *             @see MapBinding\r
+ *             @see OptionalBinding\r
+ *             @see UnionBinding\r
+ *             @see VariantBinding\r
+ * \r
+ * See examples/BindingExample.java \r
+ * @see Bindings Facade class Bindings provices extra functionality. \r
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
+ * @author Hannu Niemisto
+ */
+public abstract class Binding implements Comparator<Object> {
+
+       protected Datatype type;
+       protected transient Serializer binarySerializer;\r
+       
+       /**
+        * Get Value Type
+        * 
+        * @return value type
+        */
+       public Datatype type() {
+               return type;
+       }
+       
+       protected void setType(Datatype type) {
+               this.type = type;
+       }               
+       
+    public interface Visitor1 {
+        void visit(ArrayBinding b, Object obj);
+        void visit(BooleanBinding b, Object obj);
+        void visit(DoubleBinding b, Object obj);
+        void visit(FloatBinding b, Object obj);
+        void visit(IntegerBinding b, Object obj);
+        void visit(ByteBinding b, Object obj);
+        void visit(LongBinding b, Object obj);
+        void visit(OptionalBinding b, Object obj);
+        void visit(RecordBinding b, Object obj);
+        void visit(StringBinding b, Object obj);
+        void visit(UnionBinding b, Object obj);
+        void visit(VariantBinding b, Object obj);
+        void visit(MapBinding b, Object obj);
+    }
+       
+    public abstract void accept(Visitor1 v, Object obj);
+    
+    public interface Visitor<T> {
+        T visit(ArrayBinding b);
+        T visit(BooleanBinding b);
+        T visit(DoubleBinding b);
+        T visit(FloatBinding b);
+        T visit(IntegerBinding b);
+        T visit(ByteBinding b);
+        T visit(LongBinding b);
+        T visit(OptionalBinding b);
+        T visit(RecordBinding b);
+        T visit(StringBinding b);
+        T visit(UnionBinding b);
+        T visit(VariantBinding b);
+        T visit(MapBinding b);
+    }
+    
+    public abstract <T> T accept(Visitor<T> v);
+    \r
+    /**\r
+     * Absolutely for databoard-internal use only. Used in caching serializers\r
+     * constructed by {@link SerializerFactory}.\r
+     * \r
+     * @return the serializer that has been cached in this Binding instance or\r
+     *         <code>null</code> if nothing is cached yet\r
+     * @since Simantics 1.15.1\r
+     */\r
+    public Serializer cachedSerializer() {\r
+        return binarySerializer;\r
+    }\r
+\r
+    /**\r
+     * Absolutely for databoard-internal use only. Used in caching serializers\r
+     * constructed by {@link SerializerFactory}.\r
+     * \r
+     * @param serializer the cached serializer to set for this binding\r
+     * @since Simantics 1.15.1\r
+     */\r
+    public void cacheSerializer(Serializer serializer) {\r
+        this.binarySerializer = serializer;\r
+    }\r
+
+    /**
+     * Get or create default serializer.
+     * 
+     * <a href="http://dev.simantics.org/index.php/Org.simantics.databoard_Manual#Binary_Serialization">Binary Serialization format</a>
+     *  
+     * @return serializer for this binding\r
+     * @deprecated Instead use {@link Bindings#getSerializerUnchecked(Binding)} or {@link SerializerScheme#getSerializerUnchecked(Binding)} 
+     */\r
+    @Deprecated 
+    public Serializer serializer()
+    throws RuntimeSerializerConstructionException
+    {          \r
+       //return Bindings.serializationFactory.getSerializerUnchecked(this);\r
+       if (binarySerializer==null) {  
+               synchronized (this) {
+                       if (binarySerializer==null) binarySerializer = Bindings.serializationFactory.getSerializerUnchecked(this);\r
+               }\r
+       }
+       return binarySerializer;\r
+    }
+    
+    public abstract boolean isInstance(Object obj);    
+
+    /**
+     * Return true if the value is immutable. 
+     * This question excludes the immutability of the component types. 
+     * 
+     * @return <code>true</code> value if immutable 
+     */
+    public boolean isImmutable() {
+       return false;
+    }\r
+    \r
+    /**\r
+     * Read values from one object to another.\r
+     * \r
+     * @param srcBinding\r
+     * @param src\r
+     * @param dst valid object of this binding\r
+     * @throws BindingException\r
+     */\r
+    public abstract void readFrom(Binding srcBinding, Object src, Object dst) throws BindingException;
+    \r
+    /**\r
+     * Read values from another object.\r
+     * \r
+     * @param srcBinding\r
+     * @param src\r
+     * @param dst valid object of this binding\r
+     * @throws RuntimeBindingException\r
+     */\r
+    public void readFromUnchecked(Binding srcBinding, Object src, Object dst) throws RuntimeBindingException\r
+    {\r
+       try {\r
+                       readFrom(srcBinding, src, dst);\r
+               } catch (BindingException e) {\r
+                       throw new RuntimeBindingException( e );\r
+               }\r
+    }\r
+    \r
+    /**\r
+     * Read values from one object to another.\r
+     * \r
+     * @param srcBinding\r
+     * @param src\r
+     * @param dst valid object of this binding\r
+     * @return dst or new instance if could not be read to dst\r
+     * @throws BindingException\r
+     */\r
+    public Object readFromTry(Binding srcBinding, Object src, Object dst) throws BindingException\r
+    {\r
+       readFrom(srcBinding, src, dst);\r
+       return dst;\r
+    }\r
+    public Object readFromTryUnchecked(Binding srcBinding, Object src, Object dst) throws BindingException\r
+    {\r
+       try {\r
+               return readFromTry(srcBinding, src, dst);\r
+               } catch (BindingException e) {\r
+                       throw new RuntimeBindingException( e );\r
+               }\r
+    }\r
+    
+    /**
+     * Assert the obj is valid data type
+     * 
+     * @param obj the instance
+     * @throws BindingException on invalid instance
+     */
+    public void assertInstaceIsValid(Object obj)
+    throws BindingException
+    {
+       assertInstaceIsValid(obj, null);
+    }
+    
+    /**
+     * Assert the obj is valid data type
+     * 
+     * @param obj the instance
+     * @param validInstances optional set of already validated instances
+     * @throws BindingException on invalid instance
+     */
+    public abstract void assertInstaceIsValid(Object obj, Set<Object> validInstances)
+    throws BindingException;
+    
+    /**
+     * Parse data value from a text to a value instance.
+     * 
+     * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue notation</a>
+     * 
+     * @param stream
+     * @return the value
+     * @throws BindingException 
+     * @throws ParseException 
+     */
+    public Object parseValue(Reader stream, DataValueRepository repository) throws DataTypeSyntaxError, BindingException
+    {
+       DataParser parser = new DataParser(stream);
+       try {
+               return new DataValueRepository().translate(parser.value(), this);
+       } catch(ParseException e) {
+               throw new DataTypeSyntaxError(e);
+       }
+    }
+
+    /**
+     * Parse data value from a text to a value instance.
+     * 
+     * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue Notation</a>
+     * 
+     * @param text
+     * @return the value
+     * @throws BindingException 
+     * @throws ParseException 
+     */    
+    public Object parseValue(String text, DataValueRepository repository) throws DataTypeSyntaxError, BindingException
+    {          
+       return repository.translate(text, this);
+    }
+    
+    /**
+     * Parse data value from a text to a value instance.
+     * 
+     * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue Notation</a>
+     * 
+     * @param text
+     * @return the value
+     * @throws BindingException 
+     * @throws ParseException 
+     */    
+    public Object parseValueDefinition(String text) throws DataTypeSyntaxError, BindingException
+    {          
+       try {
+               DataValueRepository repo = new DataValueRepository();
+               String name = repo.addValueDefinition("value : Variant = " + text);
+               MutableVariant value = repo.get(name);
+                       return value.getValue(this);
+               } catch (AdaptException e) {
+                       throw new BindingException(e);
+               }
+    }
+    
+    /**
+     * Print a value as a data value repository.
+     * 
+     * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue notation</a>
+     * 
+     * @param value
+     * @param singleLine
+     * @return the value in ascii format
+     * @throws IOException
+     * @throws BindingException
+     */
+    public String printValueDefinition(Object value, boolean singleLine) throws IOException, BindingException
+    {
+               DataValueRepository valueRepository = new DataValueRepository();
+               valueRepository.put("value", this, value);\r
+               StringBuilder sb = new StringBuilder();\r
+               DataValuePrinter vp = new DataValuePrinter(sb, valueRepository);\r
+               vp.setFormat( PrintFormat.MULTI_LINE );\r
+               vp.print(this, value);\r
+               return sb.toString();\r
+    }
+    
+    /**
+     * Print a value to an appendable using data value notation.
+     * 
+     * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue Notation</a>
+     * 
+     * @param value
+     * @param out
+     * @param singleLine
+     * @throws IOException
+     * @throws BindingException
+     */
+    public void printValue(Object value, Appendable out, DataValueRepository valueRepository, boolean singleLine) throws IOException, BindingException
+    {
+               DataValuePrinter writable = new DataValuePrinter( out, valueRepository );
+               writable.setFormat(singleLine ? PrintFormat.SINGLE_LINE : PrintFormat.MULTI_LINE);
+               writable.print(this, value);
+    }
+    
+    /**
+     * Calculate Hash code for a Data Value.
+     * 
+     * Type         Hash Function
+     * ------------------------------------------------
+     * Boolean             true=1231, false=1237
+        * Integer          value
+        * Long             lower 32-bits ^ higher 32-bits
+        * Float            IEEE 754 floating-point "single format" bit layout as is.
+        * Double           lower 32-bits ^ higher 32-bits of IEEE 754 floating-point "double format" bit layout.
+        * Optional     no value = 0, else hash(value)   
+        * Array            int result = 1; for (int element : array) result = 31 * result + hash(element);
+        * Record           int result = 3; for (field : record) result = 31 * result + hash(field) (See *);
+        * Variant          hash(type) + hash(value)
+        * Union            tag + hash(value)
+        * Map          int result = 0; for (entry : map) result += hash(key) ^ hash(value);
+        * Byte             value
+        * 
+        * *) In case of recursion, the function (hash or compareTo) will not enter same value twice. 0 is returned in such a case.
+     * 
+     * @param value
+     * @return hash value
+     * @throws BindingException
+     */
+    public int hashValue(Object value) throws BindingException
+    {
+       return deepHashValue(value, null);
+    }
+    
+    /**
+     * Calculate hash value
+     * 
+     * @param value
+     * @param hashedObjects collection of already hashed object or optionally <code>null</code>
+     * @return hash value
+     */
+    public abstract int deepHashValue(Object value, IdentityHashMap<Object, Object> hashedObjects) throws BindingException;
+    
+    /**
+     * Compares its two data values for order.  Returns a negative integer,
+     * zero, or a positive integer as the first argument is less than, equal
+     * to, or greater than the second.<p>
+     * 
+     * The implementor must also ensure that the relation is transitive:
+     * <code>((compare(x, y)&gt;0) &amp;&amp; (compare(y, z)&gt;0))</code> implies
+     * <code>compare(x, z)&gt;0</code>.<p>
+     *
+     * Finally, the implementor must ensure that <code>compare(x, y)==0</code>
+     * implies that <code>sgn(compare(x, z))==sgn(compare(y, z))</code> for all
+     * <code>z</code>.<p>
+     * 
+     * The comparison function is defined at 
+     * http://dev.simantics.org/index.php/Org.simantics.databoard_Manual#CompareTo_and_Equals <p>
+     * 
+     * Note, comparing 2 different number types will not result a value comparison.
+     * Instead values have the following type precedence ByteType, IntegerType, LongType,
+     * FloatType, and the highest DoubleType. <p>
+     *
+     * @param o1 the first object to be compared.
+     * @param o2 the second object to be compared.
+     * @return a negative integer, zero, or a positive integer as the
+     *                first argument is less than, equal to, or greater than the
+     *        second.
+     * @throws BindingException if object cannot be handled by a binding
+     */    \r
+    @Override
+    public int compare(Object o1, Object o2)
+    throws RuntimeBindingException
+    {
+               if (o1==o2) return 0;
+               if (!isInstance(o1)) throw new IllegalArgumentException(o1+" is not of expected class");
+               if (!isInstance(o2)) throw new IllegalArgumentException(o2+" is not of expected class");
+       
+       try {
+                       return deepCompare(o1, o2, null);
+               } catch (BindingException e) {
+                       throw new RuntimeBindingException(e);
+               }
+    }   
+    
+     /**
+      * Compare two Java Objects of this binding for equality.
+      * 
+      * @param o1
+      * @param o2
+      * @return true if equal
+      * @throws RuntimeBindingException
+      */
+    public boolean equals(Object o1, Object o2)
+    throws RuntimeBindingException 
+    {
+       int dif = compare(o1, o2);
+       return dif == 0;
+    }
+    
+    /**
+     * Make a complete copy of a the java object. Bindings that handle immutable values
+     * may return the same instance, others will guarantee a complete copy.<p>
+     * 
+     * Note, this is a generic implementation, override for better performance.
+     * 
+     * @param o to be cloned
+     * @return a complete copy
+     * @throws AdapterConstructionException 
+     * @throws AdaptException 
+     */
+    public Object clone(Object o) throws AdaptException {
+       try {
+                       return Bindings.adapterFactory.getAdapter(this, this, false, true).adapt(o);
+               } catch (AdapterConstructionException e) {
+                       // Should not occur
+                       throw new AdaptException(e);
+               }
+    }
+    
+    public Object cloneUnchecked(Object o) throws RuntimeAdaptException {
+               try {
+                       return Bindings.adapterFactory.getAdapter(this, this, false, true).adapt(o);
+               } catch (AdaptException e) {
+                       throw new RuntimeAdaptException(e);
+               } catch (AdapterConstructionException e) {
+                       throw new RuntimeAdaptException(new AdaptException(e));
+               }
+    }
+    
+    
+    public abstract int deepCompare(Object o1, Object o2, Set<IdentityPair<Object, Object>> compareHistory) 
+    throws BindingException; 
+    
+    /**
+     * Create a value with valid default values.  
+     * 
+     * Boolean                  false
+     * Byte, Integer, Long      0
+     * Float, Double            0.0
+     * String                   "" (may not follow pattern)
+     * Optional                 *novalue*
+     * Union                    tag 0
+     * Record                   each field with default value
+     * Array                    min range number of elements
+     * Map                      no entries
+     * Variant                  Optional with no value
+     * 
+     * @return default value
+     */
+    public Object createDefault()
+    throws BindingException
+    {
+       try {
+               return accept(new DefaultValue());
+       } catch (RuntimeBindingException e) {
+               throw e.getCause();
+       }
+    }
+    public Object createDefaultUnchecked()
+    throws RuntimeBindingException
+    {
+               return accept(new DefaultValue());
+    }
+    
+    /**\r
+     * Create random valid value.\r
+     * \r
+     * @param seed random seed\r
+     * @return random value\r
+     * @throws BindingException\r
+     */\r
+    public Object createRandom(int seed) \r
+    throws BindingException\r
+    {\r
+       try {\r
+               return accept(new RandomValue( seed ));\r
+       } catch (RuntimeBindingException e) {\r
+               throw e.getCause();\r
+       }\r
+    }\r
+    \r
+    /**\r
+     * Create random valid value.\r
+     * \r
+     * @param rv random seed\r
+     * @return random value\r
+     * @throws BindingException\r
+     */\r
+    public Object createRandom(RandomValue rv) \r
+    throws BindingException\r
+    {\r
+       try {\r
+               return accept(rv);\r
+       } catch (RuntimeBindingException e) {\r
+               throw e.getCause();\r
+       }\r
+    }\r
+    \r
+    /**\r
+     * Create random valid value.\r
+     * \r
+     * @param random random seed\r
+     * @return random value\r
+     * @throws BindingException\r
+     */\r
+    public Object createRandom(Random random) \r
+    throws BindingException\r
+    {\r
+       try {\r
+               return accept(new RandomValue( random ));\r
+       } catch (RuntimeBindingException e) {\r
+               throw e.getCause();\r
+       }\r
+    }\r
+    
+    public Object createRandomUnchecked(int seed)
+    throws RuntimeBindingException
+    {
+               return accept(new RandomValue( seed ));
+    }\r
+    \r
+    public String toString(Object value) throws BindingException {\r
+       BindingPrintContext ctx = new BindingPrintContext();\r
+       toString(value, ctx);\r
+       return ctx.b.toString();\r
+    }\r
+\r
+    public String toStringUnchecked(Object value) {\r
+       try {\r
+               BindingPrintContext ctx = new BindingPrintContext();\r
+               toString(value, ctx);\r
+               return ctx.b.toString();\r
+       } catch ( BindingException e ) {\r
+               return e.toString();\r
+       }\r
+    }\r
+    \r
+    public String toString(Object value, boolean singleLine) throws BindingException {\r
+       BindingPrintContext ctx = new BindingPrintContext();\r
+       ctx.singleLine = singleLine;\r
+       toString(value, ctx);\r
+       return ctx.b.toString();\r
+    }\r
+\r
+       protected abstract void toString(Object value, BindingPrintContext ctx) throws BindingException;\r
+       \r
+       \r
+       /** \r
+        * Get component binding count\r
+        * \r
+        * @return component count\r
+        */\r
+       public abstract int getComponentCount();\r
+       \r
+       /**\r
+        * Get a component value of a structured data object.\r
+        * @param object The structured data object\r
+        * @param ref The child component reference\r
+        * @return The value of the data component\r
+        * @throws BindingException\r
+        */\r
+       public Object getComponent(Object object, ChildReference ref) throws BindingException {\r
+           Variant value = new MutableVariant(this, object);\r
+           try {\r
+            return value.getComponent(ref).getValue();\r
+        } catch ( AccessorConstructionException e ) {\r
+            throw new BindingException("Component access failed.", e);\r
+        }\r
+       }\r
+       \r
+       /**\r
+        * Get a component value of a structured data object.\r
+     * @param object The structured data object\r
+     * @param ref The child component reference\r
+        * @param binding The output data binding for the component value\r
+     * @return The value of the data component\r
+        * @throws BindingException\r
+        */\r
+       public Object getComponent(Object object, ChildReference ref, Binding binding) throws BindingException {\r
+           Binding componentBinding = getComponentBinding( ref );\r
+           Variant value = new MutableVariant(this, object);\r
+           try {\r
+            return Bindings.adapt( value.getComponent( ref ), componentBinding, binding );\r
+        } catch ( AdaptException | AccessorConstructionException e ) {\r
+            throw new BindingException("Component access failed.", e);\r
+        }\r
+       }\r
+       \r
+       /**\r
+        * Set the value of a component in a structured data object.\r
+     * @param object The structured data object\r
+     * @param ref The child component reference\r
+        * @param binding Data type binding for the component value\r
+        * @param componentValue The new child component value\r
+        * @throws BindingException\r
+        */\r
+    public void setComponent( Object object, ChildReference ref, Binding binding, Object componentValue ) throws BindingException {\r
+        MutableVariant value = new MutableVariant( this, object );\r
+        try {\r
+            value.setComponent( ref, binding, componentValue );\r
+        } catch ( AccessorException | AccessorConstructionException e ) {\r
+            throw new BindingException("Component access failed.", e );\r
+        }\r
+    }\r
+    \r
+       /**\r
+        * Get component binding\r
+        * @param index\r
+        * @return binding\r
+        */\r
+    public abstract Binding getComponentBinding(int index);\r
+    \r
+    /**\r
+     * Get component binding\r
+     * @param path child path or <tt>null</tt> to return this.\r
+     * @return binding \r
+     * @throws IllegalArgumentException if path cannot be applied to this binding\r
+     */\r
+    public abstract Binding getComponentBinding(ChildReference path);\r
+       \r
+    @Override\r
+    /**\r
+     * Each child class implements #deepEquals or #baseEquals or neither, depending on\r
+     * whether it includes references to child Binding instances or other fields.\r
+     * @see java.lang.Object#equals(java.lang.Object)\r
+     */\r
+    final public boolean equals(Object obj) {\r
+       if (this == obj) return true;\r
+       if (obj == null) return false;\r
+       if (this.getClass() != obj.getClass()) return false;\r
+       \r
+       return equals(obj, new HashSet<IdentityPair<Binding, Binding>>());\r
+    }\r
+    \r
+    /**\r
+     * Perform a deep equality check between this Binding object and another,\r
+     * with a memory for recursive references. Child classes should implement either the\r
+     * #deepEquals or #baseEquals method, or neither if there is no new data to compare.\r
+     */\r
+    final protected boolean equals(Object obj, Set<IdentityPair<Binding, Binding>> compareHistory) {\r
+        if (this == obj) return true;\r
+        if (this.getClass() != obj.getClass()) return false;\r
+        \r
+        IdentityPair<Binding, Binding> pair = new IdentityPair<Binding, Binding>(this, (Binding)obj); \r
+        if (compareHistory.contains(pair)) return true;\r
+        \r
+        compareHistory.add(pair);\r
+        return deepEquals(obj, compareHistory);\r
+    }\r
+    \r
+    /**\r
+     * Perform a comparison of the fields of this Binding instance. Always make a call to super.baseEquals().\r
+     */\r
+    protected boolean baseEquals(Object obj) {\r
+       return type == null ? ((Binding)obj).type == null : type.equals(((Binding)obj).type);\r
+    }\r
+    \r
+    /**\r
+     * Perform a deep comparison of this Binding object with another.\r
+     * Matching child Binding instances must be compared with #equals(Object, Set<IdentityPair<Binding, Binding>>).\r
+     * Child classes should always make a call to super.deepEquals().\r
+     */\r
+    protected boolean deepEquals(Object obj, Set<IdentityPair<Binding, Binding>> compareHistory) {\r
+       return baseEquals(obj);\r
+    }\r
+    \r
+    @Override\r
+    /**\r
+     * Each child class implements #deepHashCode, #baseHashCode or neither, depending on whether it\r
+     * includes child Binding references or other fields.\r
+     */\r
+    final public int hashCode() {\r
+       return deepHashCode(new IdentityHashMap<Object,Object>());\r
+    }\r
+    \r
+    /**\r
+     * Calculate a deep hash code for this Binding instance.\r
+     * Child classes should implement either deepHashCode or baseHashCode, or neither, if there is no new data.\r
+     */\r
+    final protected int hashCode(IdentityHashMap<Object, Object> hashedObjects) {\r
+        if (hashedObjects.containsKey(this)) return 0;\r
+        hashedObjects.put(this, null);\r
+        return deepHashCode(hashedObjects);\r
+    }\r
+    \r
+    /**\r
+     * Calculate a hash code based on the fields of this Binding instance. Child classes must always make a call to super.baseHashCode(). \r
+     */\r
+    protected int baseHashCode() {\r
+       return getClass().hashCode() + (type != null ? 3 * type.hashCode() : 0);\r
+    }\r
+    \r
+    /**\r
+     * Perform deep hash code calculation for this Binding instance.\r
+     * Child instance hash codes must be calculated with #hashCode(IdentityHashMap<Object, Object>),\r
+     * passing on the value provided to #deepHashCode.\r
+     */\r
+    protected int deepHashCode(IdentityHashMap<Object, Object> hashedObjects) {\r
+       return baseHashCode();\r
+    }\r
+}\r