1 /*******************************************************************************
2 * Copyright (c) 2010 Association for Decentralized Information Management in
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.databoard.binding;
14 import java.io.IOException;
15 import java.io.Reader;
16 import java.util.Comparator;
17 import java.util.HashSet;
18 import java.util.IdentityHashMap;
19 import java.util.Random;
22 import org.simantics.databoard.Bindings;
23 import org.simantics.databoard.accessor.error.AccessorConstructionException;
24 import org.simantics.databoard.accessor.error.AccessorException;
25 import org.simantics.databoard.accessor.reference.ChildReference;
26 import org.simantics.databoard.adapter.AdaptException;
27 import org.simantics.databoard.adapter.AdapterConstructionException;
28 import org.simantics.databoard.adapter.RuntimeAdaptException;
29 import org.simantics.databoard.binding.error.BindingException;
30 import org.simantics.databoard.binding.error.RuntimeBindingException;
31 import org.simantics.databoard.binding.impl.BindingPrintContext;
32 import org.simantics.databoard.binding.mutable.MutableVariant;
33 import org.simantics.databoard.binding.mutable.Variant;
34 import org.simantics.databoard.binding.util.DefaultValue;
35 import org.simantics.databoard.binding.util.RandomValue;
36 import org.simantics.databoard.parser.DataParser;
37 import org.simantics.databoard.parser.DataValuePrinter;
38 import org.simantics.databoard.parser.ParseException;
39 import org.simantics.databoard.parser.PrintFormat;
40 import org.simantics.databoard.parser.repository.DataTypeSyntaxError;
41 import org.simantics.databoard.parser.repository.DataValueRepository;
42 import org.simantics.databoard.serialization.RuntimeSerializerConstructionException;
43 import org.simantics.databoard.serialization.Serializer;
44 import org.simantics.databoard.serialization.SerializerFactory;
45 import org.simantics.databoard.serialization.SerializerScheme;
46 import org.simantics.databoard.type.Datatype;
47 import org.simantics.databoard.util.IdentityPair;
50 * This class represents connection between abstract datatype and java class.
51 * A binding allows an access to an Object in scope of a datatype. <p>
53 * For example, IntegerBinding gives unified access to any integer class
54 * (Integer, int, MutableInteger, UnsignedInteger). There is same unification
55 * for primitive types and constructed types (record, map, array, union, variant). <p>
57 * You can get a hold of binding several ways:
58 * 1) Use one of the default bindings e.g. {@link Bindings#BYTE_ARRAY}
59 * 2) Create one using Datatype {@link Bindings#getMutableBinding(Datatype)}
60 * 3) Create one using Reflectiong {@link Bindings#getBinding(Class)}
61 * 4) Instantiate binding your self. e.g. new TreeMapBinding( Bindings.STRING, Bindings.STRING );
62 * 5) Sub-class one of the abstract binding classes
73 * @see OptionalBinding
77 * See examples/BindingExample.java
78 * @see Bindings Facade class Bindings provices extra functionality.
79 * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
80 * @author Hannu Niemisto
82 public abstract class Binding implements Comparator<Object> {
84 protected Datatype type;
85 protected transient Serializer binarySerializer;
92 public Datatype type() {
96 protected void setType(Datatype type) {
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);
116 public abstract void accept(Visitor1 v, Object obj);
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);
134 public abstract <T> T accept(Visitor<T> v);
137 * Absolutely for databoard-internal use only. Used in caching serializers
138 * constructed by {@link SerializerFactory}.
140 * @return the serializer that has been cached in this Binding instance or
141 * <code>null</code> if nothing is cached yet
142 * @since Simantics 1.15.1
144 public Serializer cachedSerializer() {
145 return binarySerializer;
149 * Absolutely for databoard-internal use only. Used in caching serializers
150 * constructed by {@link SerializerFactory}.
152 * @param serializer the cached serializer to set for this binding
153 * @since Simantics 1.15.1
155 public void cacheSerializer(Serializer serializer) {
156 this.binarySerializer = serializer;
160 * Get or create default serializer.
162 * <a href="http://dev.simantics.org/index.php/Org.simantics.databoard_Manual#Binary_Serialization">Binary Serialization format</a>
164 * @return serializer for this binding
165 * @deprecated Instead use {@link Bindings#getSerializerUnchecked(Binding)} or {@link SerializerScheme#getSerializerUnchecked(Binding)}
168 public Serializer serializer()
169 throws RuntimeSerializerConstructionException
171 //return Bindings.serializationFactory.getSerializerUnchecked(this);
172 if (binarySerializer==null) {
173 synchronized (this) {
174 if (binarySerializer==null) binarySerializer = Bindings.serializationFactory.getSerializerUnchecked(this);
177 return binarySerializer;
180 public abstract boolean isInstance(Object obj);
183 * Return true if the value is immutable.
184 * This question excludes the immutability of the component types.
186 * @return <code>true</code> value if immutable
188 public boolean isImmutable() {
193 * Read values from one object to another.
197 * @param dst valid object of this binding
198 * @throws BindingException
200 public abstract void readFrom(Binding srcBinding, Object src, Object dst) throws BindingException;
203 * Read values from another object.
207 * @param dst valid object of this binding
208 * @throws RuntimeBindingException
210 public void readFromUnchecked(Binding srcBinding, Object src, Object dst) throws RuntimeBindingException
213 readFrom(srcBinding, src, dst);
214 } catch (BindingException e) {
215 throw new RuntimeBindingException( e );
220 * Read values from one object to another.
224 * @param dst valid object of this binding
225 * @return dst or new instance if could not be read to dst
226 * @throws BindingException
228 public Object readFromTry(Binding srcBinding, Object src, Object dst) throws BindingException
230 readFrom(srcBinding, src, dst);
233 public Object readFromTryUnchecked(Binding srcBinding, Object src, Object dst) throws BindingException
236 return readFromTry(srcBinding, src, dst);
237 } catch (BindingException e) {
238 throw new RuntimeBindingException( e );
243 * Assert the obj is valid data type
245 * @param obj the instance
246 * @throws BindingException on invalid instance
248 public void assertInstaceIsValid(Object obj)
249 throws BindingException
251 assertInstaceIsValid(obj, null);
255 * Assert the obj is valid data type
257 * @param obj the instance
258 * @param validInstances optional set of already validated instances
259 * @throws BindingException on invalid instance
261 public abstract void assertInstaceIsValid(Object obj, Set<Object> validInstances)
262 throws BindingException;
265 * Parse data value from a text to a value instance.
267 * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue notation</a>
271 * @throws BindingException
272 * @throws ParseException
274 public Object parseValue(Reader stream, DataValueRepository repository) throws DataTypeSyntaxError, BindingException
276 DataParser parser = new DataParser(stream);
278 return new DataValueRepository().translate(parser.value(), this);
279 } catch(ParseException e) {
280 throw new DataTypeSyntaxError(e);
285 * Parse data value from a text to a value instance.
287 * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue Notation</a>
291 * @throws BindingException
292 * @throws ParseException
294 public Object parseValue(String text, DataValueRepository repository) throws DataTypeSyntaxError, BindingException
296 return repository.translate(text, this);
300 * Parse data value from a text to a value instance.
302 * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue Notation</a>
306 * @throws BindingException
307 * @throws ParseException
309 public Object parseValueDefinition(String text) throws DataTypeSyntaxError, BindingException
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);
322 * Print a value as a data value repository.
324 * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue notation</a>
328 * @return the value in ascii format
329 * @throws IOException
330 * @throws BindingException
332 public String printValueDefinition(Object value, boolean singleLine) throws IOException, BindingException
334 DataValueRepository valueRepository = new DataValueRepository();
335 valueRepository.put("value", this, value);
336 StringBuilder sb = new StringBuilder();
337 DataValuePrinter vp = new DataValuePrinter(sb, valueRepository);
338 vp.setFormat( PrintFormat.MULTI_LINE );
339 vp.print(this, value);
340 return sb.toString();
344 * Print a value to an appendable using data value notation.
346 * <a href="http://dev.simantics.org/index.php/Data_value_notation">Datavalue Notation</a>
351 * @throws IOException
352 * @throws BindingException
354 public void printValue(Object value, Appendable out, DataValueRepository valueRepository, boolean singleLine) throws IOException, BindingException
356 DataValuePrinter writable = new DataValuePrinter( out, valueRepository );
357 writable.setFormat(singleLine ? PrintFormat.SINGLE_LINE : PrintFormat.MULTI_LINE);
358 writable.print(this, value);
362 * Calculate Hash code for a Data Value.
365 * ------------------------------------------------
366 * Boolean true=1231, false=1237
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);
379 * *) In case of recursion, the function (hash or compareTo) will not enter same value twice. 0 is returned in such a case.
383 * @throws BindingException
385 public int hashValue(Object value) throws BindingException
387 return deepHashValue(value, null);
391 * Calculate hash value
394 * @param hashedObjects collection of already hashed object or optionally <code>null</code>
397 public abstract int deepHashValue(Object value, IdentityHashMap<Object, Object> hashedObjects) throws BindingException;
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>
404 * The implementor must also ensure that the relation is transitive:
405 * <code>((compare(x, y)>0) && (compare(y, z)>0))</code> implies
406 * <code>compare(x, z)>0</code>.<p>
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
412 * The comparison function is defined at
413 * http://dev.simantics.org/index.php/Org.simantics.databoard_Manual#CompareTo_and_Equals <p>
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>
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
424 * @throws BindingException if object cannot be handled by a binding
427 public int compare(Object o1, Object o2)
428 throws RuntimeBindingException
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");
435 return deepCompare(o1, o2, null);
436 } catch (BindingException e) {
437 throw new RuntimeBindingException(e);
442 * Compare two Java Objects of this binding for equality.
446 * @return true if equal
447 * @throws RuntimeBindingException
449 public boolean equals(Object o1, Object o2)
450 throws RuntimeBindingException
452 int dif = compare(o1, o2);
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>
460 * Note, this is a generic implementation, override for better performance.
462 * @param o to be cloned
463 * @return a complete copy
464 * @throws AdapterConstructionException
465 * @throws AdaptException
467 public Object clone(Object o) throws AdaptException {
469 return Bindings.adapterFactory.getAdapter(this, this, false, true).adapt(o);
470 } catch (AdapterConstructionException e) {
472 throw new AdaptException(e);
476 public Object cloneUnchecked(Object o) throws RuntimeAdaptException {
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));
487 public abstract int deepCompare(Object o1, Object o2, Set<IdentityPair<Object, Object>> compareHistory)
488 throws BindingException;
491 * Create a value with valid default values.
494 * Byte, Integer, Long 0
496 * String "" (may not follow pattern)
499 * Record each field with default value
500 * Array min range number of elements
502 * Variant Optional with no value
504 * @return default value
506 public Object createDefault()
507 throws BindingException
510 return accept(new DefaultValue());
511 } catch (RuntimeBindingException e) {
516 public Object createDefaultUnchecked()
517 throws RuntimeBindingException
519 return accept(new DefaultValue());
523 * Create random valid value.
525 * @param seed random seed
526 * @return random value
527 * @throws BindingException
529 public Object createRandom(int seed)
530 throws BindingException
533 return accept(new RandomValue( seed ));
534 } catch (RuntimeBindingException e) {
540 * Create random valid value.
542 * @param rv random seed
543 * @return random value
544 * @throws BindingException
546 public Object createRandom(RandomValue rv)
547 throws BindingException
551 } catch (RuntimeBindingException e) {
557 * Create random valid value.
559 * @param random random seed
560 * @return random value
561 * @throws BindingException
563 public Object createRandom(Random random)
564 throws BindingException
567 return accept(new RandomValue( random ));
568 } catch (RuntimeBindingException e) {
574 public Object createRandomUnchecked(int seed)
575 throws RuntimeBindingException
577 return accept(new RandomValue( seed ));
580 public String toString(Object value) throws BindingException {
581 BindingPrintContext ctx = new BindingPrintContext();
582 toString(value, ctx);
583 return ctx.b.toString();
586 public String toStringUnchecked(Object value) {
588 BindingPrintContext ctx = new BindingPrintContext();
589 toString(value, ctx);
590 return ctx.b.toString();
591 } catch ( BindingException e ) {
596 public String toString(Object value, boolean singleLine) throws BindingException {
597 BindingPrintContext ctx = new BindingPrintContext();
598 ctx.singleLine = singleLine;
599 toString(value, ctx);
600 return ctx.b.toString();
603 protected abstract void toString(Object value, BindingPrintContext ctx) throws BindingException;
607 * Get component binding count
609 * @return component count
611 public abstract int getComponentCount();
614 * Get a component value of a structured data object.
615 * @param object The structured data object
616 * @param ref The child component reference
617 * @return The value of the data component
618 * @throws BindingException
620 public Object getComponent(Object object, ChildReference ref) throws BindingException {
621 Variant value = new MutableVariant(this, object);
623 return value.getComponent(ref).getValue();
624 } catch ( AccessorConstructionException e ) {
625 throw new BindingException("Component access failed.", e);
630 * Get a component value of a structured data object.
631 * @param object The structured data object
632 * @param ref The child component reference
633 * @param binding The output data binding for the component value
634 * @return The value of the data component
635 * @throws BindingException
637 public Object getComponent(Object object, ChildReference ref, Binding binding) throws BindingException {
638 Binding componentBinding = getComponentBinding( ref );
639 Variant value = new MutableVariant(this, object);
641 return Bindings.adapt( value.getComponent( ref ), componentBinding, binding );
642 } catch ( AdaptException | AccessorConstructionException e ) {
643 throw new BindingException("Component access failed.", e);
648 * Set the value of a component in a structured data object.
649 * @param object The structured data object
650 * @param ref The child component reference
651 * @param binding Data type binding for the component value
652 * @param componentValue The new child component value
653 * @throws BindingException
655 public void setComponent( Object object, ChildReference ref, Binding binding, Object componentValue ) throws BindingException {
656 MutableVariant value = new MutableVariant( this, object );
658 value.setComponent( ref, binding, componentValue );
659 } catch ( AccessorException | AccessorConstructionException e ) {
660 throw new BindingException("Component access failed.", e );
665 * Get component binding
669 public abstract Binding getComponentBinding(int index);
672 * Get component binding
673 * @param path child path or <tt>null</tt> to return this.
675 * @throws IllegalArgumentException if path cannot be applied to this binding
677 public abstract Binding getComponentBinding(ChildReference path);
681 * Each child class implements #deepEquals or #baseEquals or neither, depending on
682 * whether it includes references to child Binding instances or other fields.
683 * @see java.lang.Object#equals(java.lang.Object)
685 final public boolean equals(Object obj) {
686 if (this == obj) return true;
687 if (obj == null) return false;
688 if (this.getClass() != obj.getClass()) return false;
690 return equals(obj, new HashSet<IdentityPair<Binding, Binding>>());
694 * Perform a deep equality check between this Binding object and another,
695 * with a memory for recursive references. Child classes should implement either the
696 * #deepEquals or #baseEquals method, or neither if there is no new data to compare.
698 final protected boolean equals(Object obj, Set<IdentityPair<Binding, Binding>> compareHistory) {
699 if (this == obj) return true;
700 if (this.getClass() != obj.getClass()) return false;
702 IdentityPair<Binding, Binding> pair = new IdentityPair<Binding, Binding>(this, (Binding)obj);
703 if (compareHistory.contains(pair)) return true;
705 compareHistory.add(pair);
706 return deepEquals(obj, compareHistory);
710 * Perform a comparison of the fields of this Binding instance. Always make a call to super.baseEquals().
712 protected boolean baseEquals(Object obj) {
713 return type == null ? ((Binding)obj).type == null : type.equals(((Binding)obj).type);
717 * Perform a deep comparison of this Binding object with another.
718 * Matching child Binding instances must be compared with #equals(Object, Set<IdentityPair<Binding, Binding>>).
719 * Child classes should always make a call to super.deepEquals().
721 protected boolean deepEquals(Object obj, Set<IdentityPair<Binding, Binding>> compareHistory) {
722 return baseEquals(obj);
727 * Each child class implements #deepHashCode, #baseHashCode or neither, depending on whether it
728 * includes child Binding references or other fields.
730 final public int hashCode() {
731 return deepHashCode(new IdentityHashMap<Object,Object>());
735 * Calculate a deep hash code for this Binding instance.
736 * Child classes should implement either deepHashCode or baseHashCode, or neither, if there is no new data.
738 final protected int hashCode(IdentityHashMap<Object, Object> hashedObjects) {
739 if (hashedObjects.containsKey(this)) return 0;
740 hashedObjects.put(this, null);
741 return deepHashCode(hashedObjects);
745 * Calculate a hash code based on the fields of this Binding instance. Child classes must always make a call to super.baseHashCode().
747 protected int baseHashCode() {
748 return getClass().hashCode() + (type != null ? 3 * type.hashCode() : 0);
752 * Perform deep hash code calculation for this Binding instance.
753 * Child instance hash codes must be calculated with #hashCode(IdentityHashMap<Object, Object>),
754 * passing on the value provided to #deepHashCode.
756 protected int deepHashCode(IdentityHashMap<Object, Object> hashedObjects) {
757 return baseHashCode();