1 /*******************************************************************************
\r
2 * Copyright (c) 2010 Association for Decentralized Information Management in
\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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.databoard.binding;
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
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
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
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
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
65 * @see IntegerBinding
\r
68 * @see DoubleBinding
\r
69 * @see StringBinding
\r
70 * @see RecordBinding
\r
73 * @see OptionalBinding
\r
75 * @see VariantBinding
\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
82 public abstract class Binding implements Comparator<Object> {
84 protected Datatype type;
85 protected transient Serializer binarySerializer;
\r
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
\r
138 * constructed by {@link SerializerFactory}.
\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
144 public Serializer cachedSerializer() {
\r
145 return binarySerializer;
\r
149 * Absolutely for databoard-internal use only. Used in caching serializers
\r
150 * constructed by {@link SerializerFactory}.
\r
152 * @param serializer the cached serializer to set for this binding
\r
153 * @since Simantics 1.15.1
\r
155 public void cacheSerializer(Serializer serializer) {
\r
156 this.binarySerializer = serializer;
\r
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
\r
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);
\r
172 if (binarySerializer==null) {
173 synchronized (this) {
174 if (binarySerializer==null) binarySerializer = Bindings.serializationFactory.getSerializerUnchecked(this);
\r
177 return binarySerializer;
\r
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.
\r
195 * @param srcBinding
\r
197 * @param dst valid object of this binding
\r
198 * @throws BindingException
\r
200 public abstract void readFrom(Binding srcBinding, Object src, Object dst) throws BindingException;
203 * Read values from another object.
\r
205 * @param srcBinding
\r
207 * @param dst valid object of this binding
\r
208 * @throws RuntimeBindingException
\r
210 public void readFromUnchecked(Binding srcBinding, Object src, Object dst) throws RuntimeBindingException
\r
213 readFrom(srcBinding, src, dst);
\r
214 } catch (BindingException e) {
\r
215 throw new RuntimeBindingException( e );
\r
220 * Read values from one object to another.
\r
222 * @param srcBinding
\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
228 public Object readFromTry(Binding srcBinding, Object src, Object dst) throws BindingException
\r
230 readFrom(srcBinding, src, dst);
\r
233 public Object readFromTryUnchecked(Binding srcBinding, Object src, Object dst) throws BindingException
\r
236 return readFromTry(srcBinding, src, dst);
\r
237 } catch (BindingException e) {
\r
238 throw new RuntimeBindingException( e );
\r
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);
\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
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.
\r
525 * @param seed random seed
\r
526 * @return random value
\r
527 * @throws BindingException
\r
529 public Object createRandom(int seed)
\r
530 throws BindingException
\r
533 return accept(new RandomValue( seed ));
\r
534 } catch (RuntimeBindingException e) {
\r
535 throw e.getCause();
\r
540 * Create random valid value.
\r
542 * @param rv random seed
\r
543 * @return random value
\r
544 * @throws BindingException
\r
546 public Object createRandom(RandomValue rv)
\r
547 throws BindingException
\r
551 } catch (RuntimeBindingException e) {
\r
552 throw e.getCause();
\r
557 * Create random valid value.
\r
559 * @param random random seed
\r
560 * @return random value
\r
561 * @throws BindingException
\r
563 public Object createRandom(Random random)
\r
564 throws BindingException
\r
567 return accept(new RandomValue( random ));
\r
568 } catch (RuntimeBindingException e) {
\r
569 throw e.getCause();
\r
574 public Object createRandomUnchecked(int seed)
575 throws RuntimeBindingException
577 return accept(new RandomValue( seed ));
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
586 public String toStringUnchecked(Object value) {
\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
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
603 protected abstract void toString(Object value, BindingPrintContext ctx) throws BindingException;
\r
607 * Get component binding count
\r
609 * @return component count
\r
611 public abstract int getComponentCount();
\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
620 public Object getComponent(Object object, ChildReference ref) throws BindingException {
\r
621 Variant value = new MutableVariant(this, object);
\r
623 return value.getComponent(ref).getValue();
\r
624 } catch ( AccessorConstructionException e ) {
\r
625 throw new BindingException("Component access failed.", e);
\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
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
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
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
655 public void setComponent( Object object, ChildReference ref, Binding binding, Object componentValue ) throws BindingException {
\r
656 MutableVariant value = new MutableVariant( this, object );
\r
658 value.setComponent( ref, binding, componentValue );
\r
659 } catch ( AccessorException | AccessorConstructionException e ) {
\r
660 throw new BindingException("Component access failed.", e );
\r
665 * Get component binding
\r
669 public abstract Binding getComponentBinding(int index);
\r
672 * Get component binding
\r
673 * @param path child path or <tt>null</tt> to return this.
\r
675 * @throws IllegalArgumentException if path cannot be applied to this binding
\r
677 public abstract Binding getComponentBinding(ChildReference path);
\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
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
690 return equals(obj, new HashSet<IdentityPair<Binding, Binding>>());
\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
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
702 IdentityPair<Binding, Binding> pair = new IdentityPair<Binding, Binding>(this, (Binding)obj);
\r
703 if (compareHistory.contains(pair)) return true;
\r
705 compareHistory.add(pair);
\r
706 return deepEquals(obj, compareHistory);
\r
710 * Perform a comparison of the fields of this Binding instance. Always make a call to super.baseEquals().
\r
712 protected boolean baseEquals(Object obj) {
\r
713 return type == null ? ((Binding)obj).type == null : type.equals(((Binding)obj).type);
\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
721 protected boolean deepEquals(Object obj, Set<IdentityPair<Binding, Binding>> compareHistory) {
\r
722 return baseEquals(obj);
\r
727 * Each child class implements #deepHashCode, #baseHashCode or neither, depending on whether it
\r
728 * includes child Binding references or other fields.
\r
730 final public int hashCode() {
\r
731 return deepHashCode(new IdentityHashMap<Object,Object>());
\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
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
745 * Calculate a hash code based on the fields of this Binding instance. Child classes must always make a call to super.baseHashCode().
\r
747 protected int baseHashCode() {
\r
748 return getClass().hashCode() + (type != null ? 3 * type.hashCode() : 0);
\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
756 protected int deepHashCode(IdentityHashMap<Object, Object> hashedObjects) {
\r
757 return baseHashCode();
\r