/*******************************************************************************
* 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();
}
}