X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.databoard%2Fsrc%2Forg%2Fsimantics%2Fdataboard%2Fbinding%2FBinding.java;fp=bundles%2Forg.simantics.databoard%2Fsrc%2Forg%2Fsimantics%2Fdataboard%2Fbinding%2FBinding.java;h=6bc33f7b75199d86cdf5914560e9bc941466e3fe;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/Binding.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/Binding.java new file mode 100644 index 000000000..6bc33f7b7 --- /dev/null +++ b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/Binding.java @@ -0,0 +1,759 @@ +/******************************************************************************* + * 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(); + } +}