]> gerrit.simantics Code Review - simantics/platform.git/blob - 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
1 /*******************************************************************************\r
2  *  Copyright (c) 2010 Association for Decentralized Information Management in\r
3  *  Industry THTH ry.\r
4  *  All rights reserved. This program and the accompanying materials\r
5  *  are made available under the terms of the Eclipse Public License v1.0\r
6  *  which accompanies this distribution, and is available at\r
7  *  http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  *  Contributors:\r
10  *      VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.databoard.binding;
13
14 import java.io.IOException;\r
15 import java.io.Reader;\r
16 import java.util.Comparator;\r
17 import java.util.HashSet;\r
18 import java.util.IdentityHashMap;\r
19 import java.util.Random;\r
20 import java.util.Set;\r
21 \r
22 import org.simantics.databoard.Bindings;\r
23 import org.simantics.databoard.accessor.error.AccessorConstructionException;\r
24 import org.simantics.databoard.accessor.error.AccessorException;\r
25 import org.simantics.databoard.accessor.reference.ChildReference;\r
26 import org.simantics.databoard.adapter.AdaptException;\r
27 import org.simantics.databoard.adapter.AdapterConstructionException;\r
28 import org.simantics.databoard.adapter.RuntimeAdaptException;\r
29 import org.simantics.databoard.binding.error.BindingException;\r
30 import org.simantics.databoard.binding.error.RuntimeBindingException;\r
31 import org.simantics.databoard.binding.impl.BindingPrintContext;\r
32 import org.simantics.databoard.binding.mutable.MutableVariant;\r
33 import org.simantics.databoard.binding.mutable.Variant;\r
34 import org.simantics.databoard.binding.util.DefaultValue;\r
35 import org.simantics.databoard.binding.util.RandomValue;\r
36 import org.simantics.databoard.parser.DataParser;\r
37 import org.simantics.databoard.parser.DataValuePrinter;\r
38 import org.simantics.databoard.parser.ParseException;\r
39 import org.simantics.databoard.parser.PrintFormat;\r
40 import org.simantics.databoard.parser.repository.DataTypeSyntaxError;\r
41 import org.simantics.databoard.parser.repository.DataValueRepository;\r
42 import org.simantics.databoard.serialization.RuntimeSerializerConstructionException;\r
43 import org.simantics.databoard.serialization.Serializer;\r
44 import org.simantics.databoard.serialization.SerializerFactory;\r
45 import org.simantics.databoard.serialization.SerializerScheme;\r
46 import org.simantics.databoard.type.Datatype;\r
47 import org.simantics.databoard.util.IdentityPair;\r
48
49 /**\r
50  * This class represents connection between abstract datatype and java class.\r
51  * A binding allows an access to an Object in scope of a datatype. <p>\r
52  * \r
53  * For example, IntegerBinding gives unified access to any integer class\r
54  * (Integer, int, MutableInteger, UnsignedInteger). There is same unification\r
55  * for primitive types and constructed types (record, map, array, union, variant). <p>\r
56  * \r
57  * You can get a hold of binding several ways:\r
58  *   1) Use one of the default bindings e.g. {@link Bindings#BYTE_ARRAY}\r
59  *   2) Create one using Datatype {@link Bindings#getMutableBinding(Datatype)}\r
60  *   3) Create one using Reflectiong {@link Bindings#getBinding(Class)}\r
61  *   4) Instantiate binding your self. e.g. new TreeMapBinding( Bindings.STRING, Bindings.STRING );\r
62  *   5) Sub-class one of the abstract binding classes \r
63  *              @see BooleanBinding\r
64  *              @see ByteBinding\r
65  *              @see IntegerBinding\r
66  *              @see LongBinding\r
67  *              @see FloatBinding\r
68  *              @see DoubleBinding\r
69  *              @see StringBinding\r
70  *              @see RecordBinding\r
71  *              @see ArrayBinding\r
72  *              @see MapBinding\r
73  *              @see OptionalBinding\r
74  *              @see UnionBinding\r
75  *              @see VariantBinding\r
76  * \r
77  * See examples/BindingExample.java \r
78  * @see Bindings Facade class Bindings provices extra functionality. \r
79  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
80  * @author Hannu Niemisto
81  */
82 public abstract class Binding implements Comparator<Object> {
83
84         protected Datatype type;
85         protected transient Serializer binarySerializer;\r
86         
87         /**
88          * Get Value Type
89          * 
90          * @return value type
91          */
92         public Datatype type() {
93                 return type;
94         }
95         
96         protected void setType(Datatype type) {
97                 this.type = type;
98         }               
99         
100     public interface Visitor1 {
101         void visit(ArrayBinding b, Object obj);
102         void visit(BooleanBinding b, Object obj);
103         void visit(DoubleBinding b, Object obj);
104         void visit(FloatBinding b, Object obj);
105         void visit(IntegerBinding b, Object obj);
106         void visit(ByteBinding b, Object obj);
107         void visit(LongBinding b, Object obj);
108         void visit(OptionalBinding b, Object obj);
109         void visit(RecordBinding b, Object obj);
110         void visit(StringBinding b, Object obj);
111         void visit(UnionBinding b, Object obj);
112         void visit(VariantBinding b, Object obj);
113         void visit(MapBinding b, Object obj);
114     }
115         
116     public abstract void accept(Visitor1 v, Object obj);
117     
118     public interface Visitor<T> {
119         T visit(ArrayBinding b);
120         T visit(BooleanBinding b);
121         T visit(DoubleBinding b);
122         T visit(FloatBinding b);
123         T visit(IntegerBinding b);
124         T visit(ByteBinding b);
125         T visit(LongBinding b);
126         T visit(OptionalBinding b);
127         T visit(RecordBinding b);
128         T visit(StringBinding b);
129         T visit(UnionBinding b);
130         T visit(VariantBinding b);
131         T visit(MapBinding b);
132     }
133     
134     public abstract <T> T accept(Visitor<T> v);
135     \r
136     /**\r
137      * Absolutely for databoard-internal use only. Used in caching serializers\r
138      * constructed by {@link SerializerFactory}.\r
139      * \r
140      * @return the serializer that has been cached in this Binding instance or\r
141      *         <code>null</code> if nothing is cached yet\r
142      * @since Simantics 1.15.1\r
143      */\r
144     public Serializer cachedSerializer() {\r
145         return binarySerializer;\r
146     }\r
147 \r
148     /**\r
149      * Absolutely for databoard-internal use only. Used in caching serializers\r
150      * constructed by {@link SerializerFactory}.\r
151      * \r
152      * @param serializer the cached serializer to set for this binding\r
153      * @since Simantics 1.15.1\r
154      */\r
155     public void cacheSerializer(Serializer serializer) {\r
156         this.binarySerializer = serializer;\r
157     }\r
158
159     /**
160      * Get or create default serializer.
161      * 
162      * <a href="http://dev.simantics.org/index.php/Org.simantics.databoard_Manual#Binary_Serialization">Binary Serialization format</a>
163      *  
164      * @return serializer for this binding\r
165      * @deprecated Instead use {@link Bindings#getSerializerUnchecked(Binding)} or {@link SerializerScheme#getSerializerUnchecked(Binding)} 
166      */\r
167     @Deprecated 
168     public Serializer serializer()
169     throws RuntimeSerializerConstructionException
170     {           \r
171         //return Bindings.serializationFactory.getSerializerUnchecked(this);\r
172         if (binarySerializer==null) {  
173                 synchronized (this) {
174                         if (binarySerializer==null) binarySerializer = Bindings.serializationFactory.getSerializerUnchecked(this);\r
175                 }\r
176         }
177         return binarySerializer;\r
178     }
179     
180     public abstract boolean isInstance(Object obj);    
181
182     /**
183      * Return true if the value is immutable. 
184      * This question excludes the immutability of the component types. 
185      * 
186      * @return <code>true</code> value if immutable 
187      */
188     public boolean isImmutable() {
189         return false;
190     }\r
191     \r
192     /**\r
193      * Read values from one object to another.\r
194      * \r
195      * @param srcBinding\r
196      * @param src\r
197      * @param dst valid object of this binding\r
198      * @throws BindingException\r
199      */\r
200     public abstract void readFrom(Binding srcBinding, Object src, Object dst) throws BindingException;
201     \r
202     /**\r
203      * Read values from another object.\r
204      * \r
205      * @param srcBinding\r
206      * @param src\r
207      * @param dst valid object of this binding\r
208      * @throws RuntimeBindingException\r
209      */\r
210     public void readFromUnchecked(Binding srcBinding, Object src, Object dst) throws RuntimeBindingException\r
211     {\r
212         try {\r
213                         readFrom(srcBinding, src, dst);\r
214                 } catch (BindingException e) {\r
215                         throw new RuntimeBindingException( e );\r
216                 }\r
217     }\r
218     \r
219     /**\r
220      * Read values from one object to another.\r
221      * \r
222      * @param srcBinding\r
223      * @param src\r
224      * @param dst valid object of this binding\r
225      * @return dst or new instance if could not be read to dst\r
226      * @throws BindingException\r
227      */\r
228     public Object readFromTry(Binding srcBinding, Object src, Object dst) throws BindingException\r
229     {\r
230         readFrom(srcBinding, src, dst);\r
231         return dst;\r
232     }\r
233     public Object readFromTryUnchecked(Binding srcBinding, Object src, Object dst) throws BindingException\r
234     {\r
235         try {\r
236                 return readFromTry(srcBinding, src, dst);\r
237                 } catch (BindingException e) {\r
238                         throw new RuntimeBindingException( e );\r
239                 }\r
240     }\r
241     
242     /**
243      * Assert the obj is valid data type
244      * 
245      * @param obj the instance
246      * @throws BindingException on invalid instance
247      */
248     public void assertInstaceIsValid(Object obj)
249     throws BindingException
250     {
251         assertInstaceIsValid(obj, null);
252     }
253     
254     /**
255      * Assert the obj is valid data type
256      * 
257      * @param obj the instance
258      * @param validInstances optional set of already validated instances
259      * @throws BindingException on invalid instance
260      */
261     public abstract void assertInstaceIsValid(Object obj, Set<Object> validInstances)
262     throws BindingException;
263     
264     /**
265      * Parse data value from a text to a value instance.
266      * 
267      * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue notation</a>
268      * 
269      * @param stream
270      * @return the value
271      * @throws BindingException 
272      * @throws ParseException 
273      */
274     public Object parseValue(Reader stream, DataValueRepository repository) throws DataTypeSyntaxError, BindingException
275     {
276         DataParser parser = new DataParser(stream);
277         try {
278                 return new DataValueRepository().translate(parser.value(), this);
279         } catch(ParseException e) {
280                 throw new DataTypeSyntaxError(e);
281         }
282     }
283
284     /**
285      * Parse data value from a text to a value instance.
286      * 
287      * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue Notation</a>
288      * 
289      * @param text
290      * @return the value
291      * @throws BindingException 
292      * @throws ParseException 
293      */    
294     public Object parseValue(String text, DataValueRepository repository) throws DataTypeSyntaxError, BindingException
295     {           
296         return repository.translate(text, this);
297     }
298     
299     /**
300      * Parse data value from a text to a value instance.
301      * 
302      * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue Notation</a>
303      * 
304      * @param text
305      * @return the value
306      * @throws BindingException 
307      * @throws ParseException 
308      */    
309     public Object parseValueDefinition(String text) throws DataTypeSyntaxError, BindingException
310     {           
311         try {
312                 DataValueRepository repo = new DataValueRepository();
313                 String name = repo.addValueDefinition("value : Variant = " + text);
314                 MutableVariant value = repo.get(name);
315                         return value.getValue(this);
316                 } catch (AdaptException e) {
317                         throw new BindingException(e);
318                 }
319     }
320     
321     /**
322      * Print a value as a data value repository.
323      * 
324      * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue notation</a>
325      * 
326      * @param value
327      * @param singleLine
328      * @return the value in ascii format
329      * @throws IOException
330      * @throws BindingException
331      */
332     public String printValueDefinition(Object value, boolean singleLine) throws IOException, BindingException
333     {
334                 DataValueRepository valueRepository = new DataValueRepository();
335                 valueRepository.put("value", this, value);\r
336                 StringBuilder sb = new StringBuilder();\r
337                 DataValuePrinter vp = new DataValuePrinter(sb, valueRepository);\r
338                 vp.setFormat( PrintFormat.MULTI_LINE );\r
339                 vp.print(this, value);\r
340                 return sb.toString();\r
341     }
342     
343     /**
344      * Print a value to an appendable using data value notation.
345      * 
346      * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue Notation</a>
347      * 
348      * @param value
349      * @param out
350      * @param singleLine
351      * @throws IOException
352      * @throws BindingException
353      */
354     public void printValue(Object value, Appendable out, DataValueRepository valueRepository, boolean singleLine) throws IOException, BindingException
355     {
356                 DataValuePrinter writable = new DataValuePrinter( out, valueRepository );
357                 writable.setFormat(singleLine ? PrintFormat.SINGLE_LINE : PrintFormat.MULTI_LINE);
358                 writable.print(this, value);
359     }
360     
361     /**
362      * Calculate Hash code for a Data Value.
363      * 
364      * Type         Hash Function
365      * ------------------------------------------------
366      * Boolean      true=1231, false=1237
367          * Integer          value
368          * Long             lower 32-bits ^ higher 32-bits
369          * Float            IEEE 754 floating-point "single format" bit layout as is.
370          * Double           lower 32-bits ^ higher 32-bits of IEEE 754 floating-point "double format" bit layout.
371          * Optional     no value = 0, else hash(value)   
372          * Array            int result = 1; for (int element : array) result = 31 * result + hash(element);
373          * Record           int result = 3; for (field : record) result = 31 * result + hash(field) (See *);
374          * Variant          hash(type) + hash(value)
375          * Union            tag + hash(value)
376          * Map          int result = 0; for (entry : map) result += hash(key) ^ hash(value);
377          * Byte             value
378          * 
379          * *) In case of recursion, the function (hash or compareTo) will not enter same value twice. 0 is returned in such a case.
380      * 
381      * @param value
382      * @return hash value
383      * @throws BindingException
384      */
385     public int hashValue(Object value) throws BindingException
386     {
387         return deepHashValue(value, null);
388     }
389     
390     /**
391      * Calculate hash value
392      * 
393      * @param value
394      * @param hashedObjects collection of already hashed object or optionally <code>null</code>
395      * @return hash value
396      */
397     public abstract int deepHashValue(Object value, IdentityHashMap<Object, Object> hashedObjects) throws BindingException;
398     
399     /**
400      * Compares its two data values for order.  Returns a negative integer,
401      * zero, or a positive integer as the first argument is less than, equal
402      * to, or greater than the second.<p>
403      * 
404      * The implementor must also ensure that the relation is transitive:
405      * <code>((compare(x, y)&gt;0) &amp;&amp; (compare(y, z)&gt;0))</code> implies
406      * <code>compare(x, z)&gt;0</code>.<p>
407      *
408      * Finally, the implementor must ensure that <code>compare(x, y)==0</code>
409      * implies that <code>sgn(compare(x, z))==sgn(compare(y, z))</code> for all
410      * <code>z</code>.<p>
411      * 
412      * The comparison function is defined at 
413      * http://dev.simantics.org/index.php/Org.simantics.databoard_Manual#CompareTo_and_Equals <p>
414      * 
415      * Note, comparing 2 different number types will not result a value comparison.
416      * Instead values have the following type precedence ByteType, IntegerType, LongType,
417      * FloatType, and the highest DoubleType. <p>
418      *
419      * @param o1 the first object to be compared.
420      * @param o2 the second object to be compared.
421      * @return a negative integer, zero, or a positive integer as the
422      *         first argument is less than, equal to, or greater than the
423      *         second.
424      * @throws BindingException if object cannot be handled by a binding
425      */    \r
426     @Override
427     public int compare(Object o1, Object o2)
428     throws RuntimeBindingException
429     {
430                 if (o1==o2) return 0;
431                 if (!isInstance(o1)) throw new IllegalArgumentException(o1+" is not of expected class");
432                 if (!isInstance(o2)) throw new IllegalArgumentException(o2+" is not of expected class");
433         
434         try {
435                         return deepCompare(o1, o2, null);
436                 } catch (BindingException e) {
437                         throw new RuntimeBindingException(e);
438                 }
439     }   
440     
441      /**
442       * Compare two Java Objects of this binding for equality.
443       * 
444       * @param o1
445       * @param o2
446       * @return true if equal
447       * @throws RuntimeBindingException
448       */
449     public boolean equals(Object o1, Object o2)
450     throws RuntimeBindingException 
451     {
452         int dif = compare(o1, o2);
453         return dif == 0;
454     }
455     
456     /**
457      * Make a complete copy of a the java object. Bindings that handle immutable values
458      * may return the same instance, others will guarantee a complete copy.<p>
459      * 
460      * Note, this is a generic implementation, override for better performance.
461      * 
462      * @param o to be cloned
463      * @return a complete copy
464      * @throws AdapterConstructionException 
465      * @throws AdaptException 
466      */
467     public Object clone(Object o) throws AdaptException {
468         try {
469                         return Bindings.adapterFactory.getAdapter(this, this, false, true).adapt(o);
470                 } catch (AdapterConstructionException e) {
471                         // Should not occur
472                         throw new AdaptException(e);
473                 }
474     }
475     
476     public Object cloneUnchecked(Object o) throws RuntimeAdaptException {
477                 try {
478                         return Bindings.adapterFactory.getAdapter(this, this, false, true).adapt(o);
479                 } catch (AdaptException e) {
480                         throw new RuntimeAdaptException(e);
481                 } catch (AdapterConstructionException e) {
482                         throw new RuntimeAdaptException(new AdaptException(e));
483                 }
484     }
485     
486     
487     public abstract int deepCompare(Object o1, Object o2, Set<IdentityPair<Object, Object>> compareHistory) 
488     throws BindingException; 
489     
490     /**
491      * Create a value with valid default values.  
492      * 
493      * Boolean                  false
494      * Byte, Integer, Long      0
495      * Float, Double            0.0
496      * String                   "" (may not follow pattern)
497      * Optional                 *novalue*
498      * Union                    tag 0
499      * Record                   each field with default value
500      * Array                    min range number of elements
501      * Map                      no entries
502      * Variant                  Optional with no value
503      * 
504      * @return default value
505      */
506     public Object createDefault()
507     throws BindingException
508     {
509         try {
510                 return accept(new DefaultValue());
511         } catch (RuntimeBindingException e) {
512                 throw e.getCause();
513         }
514     }
515  
516     public Object createDefaultUnchecked()
517     throws RuntimeBindingException
518     {
519                 return accept(new DefaultValue());
520     }
521     
522     /**\r
523      * Create random valid value.\r
524      * \r
525      * @param seed random seed\r
526      * @return random value\r
527      * @throws BindingException\r
528      */\r
529     public Object createRandom(int seed) \r
530     throws BindingException\r
531     {\r
532         try {\r
533                 return accept(new RandomValue( seed ));\r
534         } catch (RuntimeBindingException e) {\r
535                 throw e.getCause();\r
536         }\r
537     }\r
538     \r
539     /**\r
540      * Create random valid value.\r
541      * \r
542      * @param rv random seed\r
543      * @return random value\r
544      * @throws BindingException\r
545      */\r
546     public Object createRandom(RandomValue rv) \r
547     throws BindingException\r
548     {\r
549         try {\r
550                 return accept(rv);\r
551         } catch (RuntimeBindingException e) {\r
552                 throw e.getCause();\r
553         }\r
554     }\r
555     \r
556     /**\r
557      * Create random valid value.\r
558      * \r
559      * @param random random seed\r
560      * @return random value\r
561      * @throws BindingException\r
562      */\r
563     public Object createRandom(Random random) \r
564     throws BindingException\r
565     {\r
566         try {\r
567                 return accept(new RandomValue( random ));\r
568         } catch (RuntimeBindingException e) {\r
569                 throw e.getCause();\r
570         }\r
571     }\r
572     
573  
574     public Object createRandomUnchecked(int seed)
575     throws RuntimeBindingException
576     {
577                 return accept(new RandomValue( seed ));
578     }\r
579     \r
580     public String toString(Object value) throws BindingException {\r
581         BindingPrintContext ctx = new BindingPrintContext();\r
582         toString(value, ctx);\r
583         return ctx.b.toString();\r
584     }\r
585 \r
586     public String toStringUnchecked(Object value) {\r
587         try {\r
588                 BindingPrintContext ctx = new BindingPrintContext();\r
589                 toString(value, ctx);\r
590                 return ctx.b.toString();\r
591         } catch ( BindingException e ) {\r
592                 return e.toString();\r
593         }\r
594     }\r
595     \r
596     public String toString(Object value, boolean singleLine) throws BindingException {\r
597         BindingPrintContext ctx = new BindingPrintContext();\r
598         ctx.singleLine = singleLine;\r
599         toString(value, ctx);\r
600         return ctx.b.toString();\r
601     }\r
602 \r
603         protected abstract void toString(Object value, BindingPrintContext ctx) throws BindingException;\r
604         \r
605         \r
606         /** \r
607          * Get component binding count\r
608          * \r
609          * @return component count\r
610          */\r
611         public abstract int getComponentCount();\r
612         \r
613         /**\r
614          * Get a component value of a structured data object.\r
615          * @param object The structured data object\r
616          * @param ref The child component reference\r
617          * @return The value of the data component\r
618          * @throws BindingException\r
619          */\r
620         public Object getComponent(Object object, ChildReference ref) throws BindingException {\r
621             Variant value = new MutableVariant(this, object);\r
622             try {\r
623             return value.getComponent(ref).getValue();\r
624         } catch ( AccessorConstructionException e ) {\r
625             throw new BindingException("Component access failed.", e);\r
626         }\r
627         }\r
628         \r
629         /**\r
630          * Get a component value of a structured data object.\r
631      * @param object The structured data object\r
632      * @param ref The child component reference\r
633          * @param binding The output data binding for the component value\r
634      * @return The value of the data component\r
635          * @throws BindingException\r
636          */\r
637         public Object getComponent(Object object, ChildReference ref, Binding binding) throws BindingException {\r
638             Binding componentBinding = getComponentBinding( ref );\r
639             Variant value = new MutableVariant(this, object);\r
640             try {\r
641             return Bindings.adapt( value.getComponent( ref ), componentBinding, binding );\r
642         } catch ( AdaptException | AccessorConstructionException e ) {\r
643             throw new BindingException("Component access failed.", e);\r
644         }\r
645         }\r
646         \r
647         /**\r
648          * Set the value of a component in a structured data object.\r
649      * @param object The structured data object\r
650      * @param ref The child component reference\r
651          * @param binding Data type binding for the component value\r
652          * @param componentValue The new child component value\r
653          * @throws BindingException\r
654          */\r
655     public void setComponent( Object object, ChildReference ref, Binding binding, Object componentValue ) throws BindingException {\r
656         MutableVariant value = new MutableVariant( this, object );\r
657         try {\r
658             value.setComponent( ref, binding, componentValue );\r
659         } catch ( AccessorException | AccessorConstructionException e ) {\r
660             throw new BindingException("Component access failed.", e );\r
661         }\r
662     }\r
663     \r
664         /**\r
665          * Get component binding\r
666          * @param index\r
667          * @return binding\r
668          */\r
669     public abstract Binding getComponentBinding(int index);\r
670     \r
671     /**\r
672      * Get component binding\r
673      * @param path child path or <tt>null</tt> to return this.\r
674      * @return binding \r
675      * @throws IllegalArgumentException if path cannot be applied to this binding\r
676      */\r
677     public abstract Binding getComponentBinding(ChildReference path);\r
678         \r
679     @Override\r
680     /**\r
681      * Each child class implements #deepEquals or #baseEquals or neither, depending on\r
682      * whether it includes references to child Binding instances or other fields.\r
683      * @see java.lang.Object#equals(java.lang.Object)\r
684      */\r
685     final public boolean equals(Object obj) {\r
686         if (this == obj) return true;\r
687         if (obj == null) return false;\r
688         if (this.getClass() != obj.getClass()) return false;\r
689         \r
690         return equals(obj, new HashSet<IdentityPair<Binding, Binding>>());\r
691     }\r
692     \r
693     /**\r
694      * Perform a deep equality check between this Binding object and another,\r
695      * with a memory for recursive references. Child classes should implement either the\r
696      * #deepEquals or #baseEquals method, or neither if there is no new data to compare.\r
697      */\r
698     final protected boolean equals(Object obj, Set<IdentityPair<Binding, Binding>> compareHistory) {\r
699         if (this == obj) return true;\r
700         if (this.getClass() != obj.getClass()) return false;\r
701         \r
702         IdentityPair<Binding, Binding> pair = new IdentityPair<Binding, Binding>(this, (Binding)obj); \r
703         if (compareHistory.contains(pair)) return true;\r
704         \r
705         compareHistory.add(pair);\r
706         return deepEquals(obj, compareHistory);\r
707     }\r
708     \r
709     /**\r
710      * Perform a comparison of the fields of this Binding instance. Always make a call to super.baseEquals().\r
711      */\r
712     protected boolean baseEquals(Object obj) {\r
713         return type == null ? ((Binding)obj).type == null : type.equals(((Binding)obj).type);\r
714     }\r
715     \r
716     /**\r
717      * Perform a deep comparison of this Binding object with another.\r
718      * Matching child Binding instances must be compared with #equals(Object, Set<IdentityPair<Binding, Binding>>).\r
719      * Child classes should always make a call to super.deepEquals().\r
720      */\r
721     protected boolean deepEquals(Object obj, Set<IdentityPair<Binding, Binding>> compareHistory) {\r
722         return baseEquals(obj);\r
723     }\r
724     \r
725     @Override\r
726     /**\r
727      * Each child class implements #deepHashCode, #baseHashCode or neither, depending on whether it\r
728      * includes child Binding references or other fields.\r
729      */\r
730     final public int hashCode() {\r
731         return deepHashCode(new IdentityHashMap<Object,Object>());\r
732     }\r
733     \r
734     /**\r
735      * Calculate a deep hash code for this Binding instance.\r
736      * Child classes should implement either deepHashCode or baseHashCode, or neither, if there is no new data.\r
737      */\r
738     final protected int hashCode(IdentityHashMap<Object, Object> hashedObjects) {\r
739         if (hashedObjects.containsKey(this)) return 0;\r
740         hashedObjects.put(this, null);\r
741         return deepHashCode(hashedObjects);\r
742     }\r
743     \r
744     /**\r
745      * Calculate a hash code based on the fields of this Binding instance. Child classes must always make a call to super.baseHashCode(). \r
746      */\r
747     protected int baseHashCode() {\r
748         return getClass().hashCode() + (type != null ? 3 * type.hashCode() : 0);\r
749     }\r
750     \r
751     /**\r
752      * Perform deep hash code calculation for this Binding instance.\r
753      * Child instance hash codes must be calculated with #hashCode(IdentityHashMap<Object, Object>),\r
754      * passing on the value provided to #deepHashCode.\r
755      */\r
756     protected int deepHashCode(IdentityHashMap<Object, Object> hashedObjects) {\r
757         return baseHashCode();\r
758     }\r
759 }\r