/******************************************************************************* * Copyright (c) 2010 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.databoard.binding; import java.io.IOException; import java.io.Reader; import java.util.Comparator; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Random; import java.util.Set; import org.simantics.databoard.Bindings; import org.simantics.databoard.accessor.error.AccessorConstructionException; import org.simantics.databoard.accessor.error.AccessorException; import org.simantics.databoard.accessor.reference.ChildReference; import org.simantics.databoard.adapter.AdaptException; import org.simantics.databoard.adapter.AdapterConstructionException; import org.simantics.databoard.adapter.RuntimeAdaptException; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.binding.error.RuntimeBindingException; import org.simantics.databoard.binding.impl.BindingPrintContext; import org.simantics.databoard.binding.mutable.MutableVariant; import org.simantics.databoard.binding.mutable.Variant; import org.simantics.databoard.binding.util.DefaultValue; import org.simantics.databoard.binding.util.RandomValue; import org.simantics.databoard.parser.DataParser; import org.simantics.databoard.parser.DataValuePrinter; import org.simantics.databoard.parser.ParseException; import org.simantics.databoard.parser.PrintFormat; import org.simantics.databoard.parser.repository.DataTypeSyntaxError; import org.simantics.databoard.parser.repository.DataValueRepository; import org.simantics.databoard.serialization.RuntimeSerializerConstructionException; import org.simantics.databoard.serialization.Serializer; import org.simantics.databoard.serialization.SerializerFactory; import org.simantics.databoard.serialization.SerializerScheme; import org.simantics.databoard.type.Datatype; import org.simantics.databoard.util.IdentityPair; /** * This class represents connection between abstract datatype and java class. * A binding allows an access to an Object in scope of a datatype.

* * For example, IntegerBinding gives unified access to any integer class * (Integer, int, MutableInteger, UnsignedInteger). There is same unification * for primitive types and constructed types (record, map, array, union, variant).

* * You can get a hold of binding several ways: * 1) Use one of the default bindings e.g. {@link Bindings#BYTE_ARRAY} * 2) Create one using Datatype {@link Bindings#getMutableBinding(Datatype)} * 3) Create one using Reflectiong {@link Bindings#getBinding(Class)} * 4) Instantiate binding your self. e.g. new TreeMapBinding( Bindings.STRING, Bindings.STRING ); * 5) Sub-class one of the abstract binding classes * @see BooleanBinding * @see ByteBinding * @see IntegerBinding * @see LongBinding * @see FloatBinding * @see DoubleBinding * @see StringBinding * @see RecordBinding * @see ArrayBinding * @see MapBinding * @see OptionalBinding * @see UnionBinding * @see VariantBinding * * See examples/BindingExample.java * @see Bindings Facade class Bindings provices extra functionality. * @author Toni Kalajainen * @author Hannu Niemisto */ public abstract class Binding implements Comparator { protected Datatype type; protected transient Serializer binarySerializer; /** * 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 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 accept(Visitor v); /** * Absolutely for databoard-internal use only. Used in caching serializers * constructed by {@link SerializerFactory}. * * @return the serializer that has been cached in this Binding instance or * null if nothing is cached yet * @since Simantics 1.15.1 */ public Serializer cachedSerializer() { return binarySerializer; } /** * Absolutely for databoard-internal use only. Used in caching serializers * constructed by {@link SerializerFactory}. * * @param serializer the cached serializer to set for this binding * @since Simantics 1.15.1 */ public void cacheSerializer(Serializer serializer) { this.binarySerializer = serializer; } /** * Get or create default serializer. * * Binary Serialization format * * @return serializer for this binding * @deprecated Instead use {@link Bindings#getSerializerUnchecked(Binding)} or {@link SerializerScheme#getSerializerUnchecked(Binding)} */ @Deprecated public Serializer serializer() throws RuntimeSerializerConstructionException { //return Bindings.serializationFactory.getSerializerUnchecked(this); if (binarySerializer==null) { synchronized (this) { if (binarySerializer==null) binarySerializer = Bindings.serializationFactory.getSerializerUnchecked(this); } } return binarySerializer; } public abstract boolean isInstance(Object obj); /** * Return true if the value is immutable. * This question excludes the immutability of the component types. * * @return true value if immutable */ public boolean isImmutable() { return false; } /** * Read values from one object to another. * * @param srcBinding * @param src * @param dst valid object of this binding * @throws BindingException */ public abstract void readFrom(Binding srcBinding, Object src, Object dst) throws BindingException; /** * Read values from another object. * * @param srcBinding * @param src * @param dst valid object of this binding * @throws RuntimeBindingException */ public void readFromUnchecked(Binding srcBinding, Object src, Object dst) throws RuntimeBindingException { try { readFrom(srcBinding, src, dst); } catch (BindingException e) { throw new RuntimeBindingException( e ); } } /** * Read values from one object to another. * * @param srcBinding * @param src * @param dst valid object of this binding * @return dst or new instance if could not be read to dst * @throws BindingException */ public Object readFromTry(Binding srcBinding, Object src, Object dst) throws BindingException { readFrom(srcBinding, src, dst); return dst; } public Object readFromTryUnchecked(Binding srcBinding, Object src, Object dst) throws BindingException { try { return readFromTry(srcBinding, src, dst); } catch (BindingException e) { throw new RuntimeBindingException( e ); } } /** * 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 validInstances) throws BindingException; /** * Parse data value from a text to a value instance. * * Datavalue notation * * @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. * * Datavalue Notation * * @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. * * Datavalue Notation * * @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. * * Datavalue notation * * @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); StringBuilder sb = new StringBuilder(); DataValuePrinter vp = new DataValuePrinter(sb, valueRepository); vp.setFormat( PrintFormat.MULTI_LINE ); vp.print(this, value); return sb.toString(); } /** * Print a value to an appendable using data value notation. * * Datavalue Notation * * @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 null * @return hash value */ public abstract int deepHashValue(Object value, IdentityHashMap 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.

* * The implementor must also ensure that the relation is transitive: * ((compare(x, y)>0) && (compare(y, z)>0)) implies * compare(x, z)>0.

* * Finally, the implementor must ensure that compare(x, y)==0 * implies that sgn(compare(x, z))==sgn(compare(y, z)) for all * z.

* * The comparison function is defined at * http://dev.simantics.org/index.php/Org.simantics.databoard_Manual#CompareTo_and_Equals

* * 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.

* * @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 */ @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.

* * 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> 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()); } /** * Create random valid value. * * @param seed random seed * @return random value * @throws BindingException */ public Object createRandom(int seed) throws BindingException { try { return accept(new RandomValue( seed )); } catch (RuntimeBindingException e) { throw e.getCause(); } } /** * Create random valid value. * * @param rv random seed * @return random value * @throws BindingException */ public Object createRandom(RandomValue rv) throws BindingException { try { return accept(rv); } catch (RuntimeBindingException e) { throw e.getCause(); } } /** * Create random valid value. * * @param random random seed * @return random value * @throws BindingException */ public Object createRandom(Random random) throws BindingException { try { return accept(new RandomValue( random )); } catch (RuntimeBindingException e) { throw e.getCause(); } } public Object createRandomUnchecked(int seed) throws RuntimeBindingException { return accept(new RandomValue( seed )); } public String toString(Object value) throws BindingException { BindingPrintContext ctx = new BindingPrintContext(); toString(value, ctx); return ctx.b.toString(); } public String toStringUnchecked(Object value) { try { BindingPrintContext ctx = new BindingPrintContext(); toString(value, ctx); return ctx.b.toString(); } catch ( BindingException e ) { return e.toString(); } } public String toString(Object value, boolean singleLine) throws BindingException { BindingPrintContext ctx = new BindingPrintContext(); ctx.singleLine = singleLine; toString(value, ctx); return ctx.b.toString(); } protected abstract void toString(Object value, BindingPrintContext ctx) throws BindingException; /** * Get component binding count * * @return component count */ public abstract int getComponentCount(); /** * Get a component value of a structured data object. * @param object The structured data object * @param ref The child component reference * @return The value of the data component * @throws BindingException */ public Object getComponent(Object object, ChildReference ref) throws BindingException { Variant value = new MutableVariant(this, object); try { return value.getComponent(ref).getValue(); } catch ( AccessorConstructionException e ) { throw new BindingException("Component access failed.", e); } } /** * Get a component value of a structured data object. * @param object The structured data object * @param ref The child component reference * @param binding The output data binding for the component value * @return The value of the data component * @throws BindingException */ public Object getComponent(Object object, ChildReference ref, Binding binding) throws BindingException { Binding componentBinding = getComponentBinding( ref ); Variant value = new MutableVariant(this, object); try { return Bindings.adapt( value.getComponent( ref ), componentBinding, binding ); } catch ( AdaptException | AccessorConstructionException e ) { throw new BindingException("Component access failed.", e); } } /** * Set the value of a component in a structured data object. * @param object The structured data object * @param ref The child component reference * @param binding Data type binding for the component value * @param componentValue The new child component value * @throws BindingException */ public void setComponent( Object object, ChildReference ref, Binding binding, Object componentValue ) throws BindingException { MutableVariant value = new MutableVariant( this, object ); try { value.setComponent( ref, binding, componentValue ); } catch ( AccessorException | AccessorConstructionException e ) { throw new BindingException("Component access failed.", e ); } } /** * Get component binding * @param index * @return binding */ public abstract Binding getComponentBinding(int index); /** * Get component binding * @param path child path or null to return this. * @return binding * @throws IllegalArgumentException if path cannot be applied to this binding */ public abstract Binding getComponentBinding(ChildReference path); @Override /** * Each child class implements #deepEquals or #baseEquals or neither, depending on * whether it includes references to child Binding instances or other fields. * @see java.lang.Object#equals(java.lang.Object) */ final public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; return equals(obj, new HashSet>()); } /** * Perform a deep equality check between this Binding object and another, * with a memory for recursive references. Child classes should implement either the * #deepEquals or #baseEquals method, or neither if there is no new data to compare. */ final protected boolean equals(Object obj, Set> compareHistory) { if (this == obj) return true; if (this.getClass() != obj.getClass()) return false; IdentityPair pair = new IdentityPair(this, (Binding)obj); if (compareHistory.contains(pair)) return true; compareHistory.add(pair); return deepEquals(obj, compareHistory); } /** * Perform a comparison of the fields of this Binding instance. Always make a call to super.baseEquals(). */ protected boolean baseEquals(Object obj) { return type == null ? ((Binding)obj).type == null : type.equals(((Binding)obj).type); } /** * Perform a deep comparison of this Binding object with another. * Matching child Binding instances must be compared with #equals(Object, Set>). * Child classes should always make a call to super.deepEquals(). */ protected boolean deepEquals(Object obj, Set> compareHistory) { return baseEquals(obj); } @Override /** * Each child class implements #deepHashCode, #baseHashCode or neither, depending on whether it * includes child Binding references or other fields. */ final public int hashCode() { return deepHashCode(new IdentityHashMap()); } /** * Calculate a deep hash code for this Binding instance. * Child classes should implement either deepHashCode or baseHashCode, or neither, if there is no new data. */ final protected int hashCode(IdentityHashMap hashedObjects) { if (hashedObjects.containsKey(this)) return 0; hashedObjects.put(this, null); return deepHashCode(hashedObjects); } /** * Calculate a hash code based on the fields of this Binding instance. Child classes must always make a call to super.baseHashCode(). */ protected int baseHashCode() { return getClass().hashCode() + (type != null ? 3 * type.hashCode() : 0); } /** * Perform deep hash code calculation for this Binding instance. * Child instance hash codes must be calculated with #hashCode(IdentityHashMap), * passing on the value provided to #deepHashCode. */ protected int deepHashCode(IdentityHashMap hashedObjects) { return baseHashCode(); } }