X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.databoard%2Fsrc%2Forg%2Fsimantics%2Fdataboard%2Fbinding%2Freflection%2FClassBindingFactory.java;h=1295c1099ac0c8731b3be597dd967370e0b52afc;hp=94d3ed5f6d4f323950cfec22df2095193d9e8a13;hb=95bce3521a3c97f463c3d533a36a606c7ae6f0aa;hpb=43ddca759254b8e38029c1041d91cbdd7890c9b5 diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/ClassBindingFactory.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/ClassBindingFactory.java index 94d3ed5f6..1295c1099 100644 --- a/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/ClassBindingFactory.java +++ b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/ClassBindingFactory.java @@ -1,795 +1,805 @@ -package org.simantics.databoard.binding.reflection; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.simantics.databoard.Bindings; -import org.simantics.databoard.annotations.ArgumentImpl; -import org.simantics.databoard.annotations.Arguments; -import org.simantics.databoard.annotations.Identifier; -import org.simantics.databoard.annotations.Length; -import org.simantics.databoard.annotations.MIMEType; -import org.simantics.databoard.annotations.Name; -import org.simantics.databoard.annotations.Optional; -import org.simantics.databoard.annotations.Pattern; -import org.simantics.databoard.annotations.Range; -import org.simantics.databoard.annotations.Referable; -import org.simantics.databoard.annotations.Union; -import org.simantics.databoard.annotations.Unit; -import org.simantics.databoard.binding.ArrayBinding; -import org.simantics.databoard.binding.Binding; -import org.simantics.databoard.binding.MapBinding; -import org.simantics.databoard.binding.OptionalBinding; -import org.simantics.databoard.binding.RecordBinding; -import org.simantics.databoard.binding.error.BindingConstructionException; -import org.simantics.databoard.binding.error.RuntimeBindingConstructionException; -import org.simantics.databoard.binding.factory.BindingRepository; -import org.simantics.databoard.binding.factory.DefaultBindingFactory; -import org.simantics.databoard.binding.factory.TypeBindingFactory; -import org.simantics.databoard.binding.impl.ByteBindingDefault; -import org.simantics.databoard.binding.impl.DoubleBindingDefault; -import org.simantics.databoard.binding.impl.FloatBindingDefault; -import org.simantics.databoard.binding.impl.IntegerBindingDefault; -import org.simantics.databoard.binding.impl.LongBindingDefault; -import org.simantics.databoard.binding.impl.OptionalBindingDefault; -import org.simantics.databoard.binding.impl.StringBindingDefault; -import org.simantics.databoard.binding.impl.UnsignedByteBinding; -import org.simantics.databoard.binding.impl.UnsignedIntegerBinding; -import org.simantics.databoard.binding.impl.UnsignedLongBinding; -import org.simantics.databoard.binding.mutable.MutableByteBinding; -import org.simantics.databoard.binding.mutable.MutableDoubleBinding; -import org.simantics.databoard.binding.mutable.MutableFloatBinding; -import org.simantics.databoard.binding.mutable.MutableIntegerBinding; -import org.simantics.databoard.binding.mutable.MutableLongBinding; -import org.simantics.databoard.binding.mutable.MutableStringBinding; -import org.simantics.databoard.primitives.MutableBoolean; -import org.simantics.databoard.primitives.MutableByte; -import org.simantics.databoard.primitives.MutableDouble; -import org.simantics.databoard.primitives.MutableFloat; -import org.simantics.databoard.primitives.MutableInteger; -import org.simantics.databoard.primitives.MutableLong; -import org.simantics.databoard.primitives.MutableString; -import org.simantics.databoard.primitives.UnsignedByte; -import org.simantics.databoard.primitives.UnsignedInteger; -import org.simantics.databoard.primitives.UnsignedLong; -import org.simantics.databoard.type.ArrayType; -import org.simantics.databoard.type.ByteType; -import org.simantics.databoard.type.Component; -import org.simantics.databoard.type.DoubleType; -import org.simantics.databoard.type.FloatType; -import org.simantics.databoard.type.IntegerType; -import org.simantics.databoard.type.LongType; -import org.simantics.databoard.type.MapType; -import org.simantics.databoard.type.OptionalType; -import org.simantics.databoard.type.RecordType; -import org.simantics.databoard.type.StringType; -import org.simantics.databoard.type.UnionType; -import org.simantics.databoard.util.ArrayUtils; -import org.simantics.databoard.util.IdentityHashSet; -import org.simantics.databoard.util.RangeException; - -/** - * Type Factory constructs data types from reflection requests. - * Successfully constructed types are placed in the repository that was given - * at construction time. - * - * @author Toni Kalajainen - */ -public class ClassBindingFactory { - - /** - * Map of failed constructions. - */ - Map failures = new HashMap(); - - /** - * Map that contains in incomplete constructions. - */ - Map inprogress = new HashMap(); - - /** - * Repository where successful constructions are placed. - */ - BindingRepository repository; - - List subFactories = new CopyOnWriteArrayList(); - - /** Asm based binding factory for Classes, this is used if Asm library is available, else null */ - RecordBindingProvider asmClassBindingFactory; - - /** Reflection based binding factory for Classes, this has poorer performance than asm factory */ - RecordBindingProvider refClassBindingFactory; - - /** Factory for creating default bindings for data types. */ - TypeBindingFactory defaultBindingFactory; - - /** - * Construct a new reflection binding factory - */ - public ClassBindingFactory() { - init(); - this.repository = new BindingRepository(); - this.defaultBindingFactory = new DefaultBindingFactory(); - } - - /** - * Construct a new reflection binding factory that places constructed - * bindings into user given repository. - * - * @param repository - */ - public ClassBindingFactory(BindingRepository repository, TypeBindingFactory defaultBindingFactory) { - init(); - this.repository = repository; - this.defaultBindingFactory = defaultBindingFactory; - } - - void init() { - refClassBindingFactory = new ReflectionBindingProvider(); - try { - // Check ASM Exists - Class.forName("org.objectweb.asm.ClassWriter"); - // Create factory - Class y = Class.forName("org.simantics.databoard.binding.reflection.AsmBindingProvider"); - Constructor c = y.getConstructor(); - asmClassBindingFactory = (RecordBindingProvider) c.newInstance(); - return; - } catch (ClassNotFoundException e) { - } catch (InstantiationException e) { - } catch (IllegalAccessException e) { - } catch (IllegalArgumentException e) { - } catch (InvocationTargetException e) { - } catch (SecurityException e) { - } catch (NoSuchMethodException e) { - } - } - - public void addFactory(BindingProvider factory) - { - if (!subFactories.contains(factory)) { - this.subFactories.add(factory); - } - } - - public void removeFactory(BindingProvider factory) { - subFactories.remove(factory); - } - - public BindingRepository getRepository() { - return repository; - } - - /** - * Constructs a binding to comply to class request. - * This is the method sub-classes implement. - * The implementation should use the inprogress -map for construction of - * bindings that have component types. - * - * e.g. - * inprogress.put(request, notCompletelyConstructedBinding); - * Binding componentBinding = construct( componentRequest ); - * notCompletelyConstructedBinding.setChild( componentBinding ); - * inprogress.remove(request); - * - * try-finally is not needed. - * - * @param request - * @return - * @throws BindingConstructionException - * @throws RangeException - */ - @SuppressWarnings("unchecked") - protected Binding doConstruct(BindingRequest request) throws BindingConstructionException, RangeException - { - // Optional - if(request.hasAnnotation(Optional.class)) - { - Optional optional = request.getAnnotation(Optional.class); - Annotation[] newAnnotations = ArrayUtils.dropElements(request.annotations, optional); - BindingRequest componentRequest = new BindingRequest(request.getClazz(), newAnnotations); - OptionalType type = new OptionalType(); - OptionalBinding binding = new OptionalBindingDefault(type, null); - inprogress.put(request, binding); - binding.componentBinding = construct( componentRequest ); - type.componentType = binding.componentBinding.type(); - inprogress.remove(request); - - return binding; - } - - - - // Primitive - { - Range range = request.getAnnotation(Range.class); - Unit unit = request.getAnnotation(Unit.class); - - if (request.getClazz() == Integer.class || request.getClazz() == int.class || request.getClazz() == MutableInteger.class || UnsignedInteger.class.isAssignableFrom(request.getClazz())) { - Binding binding = null; - if (range==null && unit==null) { - if (request.getClazz() == int.class) binding = Bindings.INTEGER; - if (request.getClazz() == Integer.class) binding = Bindings.INTEGER; - if (request.getClazz() == MutableInteger.class) binding = Bindings.MUTABLE_INTEGER; - if (request.getClazz() == UnsignedInteger.Mutable.class) binding = Bindings.MUTABLE_UNSIGNED_INTEGER; - if (request.getClazz() == UnsignedInteger.Immutable.class) binding = Bindings.UNSIGNED_INTEGER; - } else { - IntegerType type = new IntegerType(); - type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) ); - type.setUnit( unit==null ? null : unit.value() ); - - if (request.getClazz() == int.class) binding = new IntegerBindingDefault(type); - if (request.getClazz() == Integer.class) binding = new IntegerBindingDefault(type); - if (request.getClazz() == MutableInteger.class) binding = new MutableIntegerBinding(type); - if (request.getClazz() == UnsignedInteger.Mutable.class) binding = new UnsignedIntegerBinding.Mutable(type); - if (request.getClazz() == UnsignedInteger.Immutable.class) binding = new UnsignedIntegerBinding.Immutable(type); - } - if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); - - repository.put(request, binding); - return binding; - } - - if (request.getClazz() == Byte.class || request.getClazz() == byte.class || request.getClazz() == MutableByte.class || UnsignedByte.class.isAssignableFrom(request.getClazz())) { - Binding binding = null; - if (range==null && unit==null) { - if (request.getClazz() == byte.class) binding = Bindings.BYTE; - if (request.getClazz() == Byte.class) binding = Bindings.BYTE; - if (request.getClazz() == MutableByte.class) binding = Bindings.MUTABLE_BYTE; - if (request.getClazz() == UnsignedByte.Mutable.class) binding = Bindings.MUTABLE_UNSIGNED_BYTE; - if (request.getClazz() == UnsignedByte.Immutable.class) binding = Bindings.UNSIGNED_BYTE; - } else { - ByteType type = new ByteType(); - type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) ); - type.setUnit( unit==null ? null : unit.value() ); - - if (request.getClazz() == byte.class) binding = new ByteBindingDefault(type); - if (request.getClazz() == Byte.class) binding = new ByteBindingDefault(type); - if (request.getClazz() == MutableByte.class) binding = new MutableByteBinding(type); - if (request.getClazz() == UnsignedByte.Mutable.class) binding = new UnsignedByteBinding.Mutable(type); - if (request.getClazz() == UnsignedByte.Immutable.class) binding = new UnsignedByteBinding.Immutable(type); - } - if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); - - repository.put(request, binding); - return binding; - } - - if (request.getClazz() == Long.class || request.getClazz() == long.class || request.getClazz() == MutableLong.class || UnsignedLong.class.isAssignableFrom(request.getClazz())) { - Binding binding = null; - if (range==null && unit==null) { - if (request.getClazz() == long.class) binding = Bindings.LONG; - if (request.getClazz() == Long.class) binding = Bindings.LONG; - if (request.getClazz() == MutableLong.class) binding = Bindings.MUTABLE_LONG; - if (request.getClazz() == UnsignedLong.Mutable.class) binding = Bindings.MUTABLE_UNSIGNED_LONG; - if (request.getClazz() == UnsignedLong.Immutable.class) binding = Bindings.UNSIGNED_LONG; - } else { - LongType type = new LongType(); - type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) ); - type.setUnit( unit==null ? null : unit.value() ); - - if (request.getClazz() == long.class) binding = new LongBindingDefault(type); - if (request.getClazz() == Long.class) binding = new LongBindingDefault(type); - if (request.getClazz() == MutableLong.class) binding = new MutableLongBinding(type); - if (request.getClazz() == UnsignedLong.Mutable.class) binding = new UnsignedLongBinding.Mutable(type); - if (request.getClazz() == UnsignedLong.Immutable.class) binding = new UnsignedLongBinding.Immutable(type); - } - if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); - - repository.put(request, binding); - return binding; - } - - if (request.getClazz() == Float.class || request.getClazz() == float.class || request.getClazz() == MutableFloat.class) { - Binding binding = null; - if (range==null && unit==null) { - if (request.getClazz() == float.class) binding = Bindings.FLOAT; - if (request.getClazz() == Float.class) binding = Bindings.FLOAT; - if (request.getClazz() == MutableFloat.class) binding = Bindings.MUTABLE_FLOAT; - } else { - FloatType type = new FloatType(); - type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) ); - type.setUnit( unit==null ? null : unit.value() ); - - if (request.getClazz() == float.class) binding = new FloatBindingDefault(type); - if (request.getClazz() == Float.class) binding = new FloatBindingDefault(type); - if (request.getClazz() == MutableFloat.class) binding = new MutableFloatBinding(type); - } - if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); - - repository.put(request, binding); - return binding; - } - - if (request.getClazz() == Double.class || request.getClazz() == double.class || request.getClazz() == MutableDouble.class) { - Binding binding = null; - if (range==null && unit==null) { - if (request.getClazz() == double.class) binding = Bindings.DOUBLE; - if (request.getClazz() == Double.class) binding = Bindings.DOUBLE; - if (request.getClazz() == MutableDouble.class) binding = Bindings.MUTABLE_DOUBLE; - } else { - DoubleType type = new DoubleType(); - type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) ); - type.setUnit( unit==null ? null : unit.value() ); - - if (request.getClazz() == double.class) binding = new DoubleBindingDefault(type); - if (request.getClazz() == Double.class) binding = new DoubleBindingDefault(type); - if (request.getClazz() == MutableDouble.class) binding = new MutableDoubleBinding(type); - } - - repository.put(request, binding); - return binding; - } - - if (request.getClazz() == Boolean.class || request.getClazz() == boolean.class || request.getClazz() == MutableBoolean.class) { - Binding binding = null; - if (request.getClazz() == Boolean.class || request.getClazz() == boolean.class) binding = Bindings.BOOLEAN; - if (request.getClazz() == MutableBoolean.class) binding = Bindings.MUTABLE_BOOLEAN; - if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); - - repository.put(request, binding); - return binding; - } - - if (request.getClazz() == String.class || request.getClazz() == MutableString.class) { - Length length = request.getAnnotation(Length.class); - MIMEType mimeType = request.getAnnotation(MIMEType.class); - Pattern pattern = request.getAnnotation(Pattern.class); - Binding binding = null; - if (length==null && mimeType==null && pattern==null) { - if (request.getClazz() == String.class) binding = Bindings.STRING; - if (request.getClazz() == MutableString.class) binding = Bindings.MUTABLE_STRING; - } else { - StringType type = new StringType(); - type.setLength( length==null ? null : org.simantics.databoard.util.Range.valueOf( length.value()[0] ) ); - type.setMimeType( mimeType==null ? null : mimeType.value() ); - type.setPattern( pattern==null ? null : pattern.value() ); - if (request.getClazz() == String.class) binding = new StringBindingDefault(type); - if (request.getClazz() == MutableString.class) binding = new MutableStringBinding(type); - } - if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); - - repository.put(request, binding); - return binding; - } - } - - - // Custom factories - for (BindingProvider factory : subFactories) { - Binding result = factory.provideBinding(this, request); - if (result == null) continue; - - /// Array - // If the result was an arraybinding, complete the composite binding - if (result instanceof ArrayBinding) { - ArrayBinding binding = (ArrayBinding) result; - ArrayType type = binding.type(); - Length lengthAnnotation = request.getAnnotation(Length.class); - Arguments argumentsAnnotation = request.getAnnotation(Arguments.class); - Annotation[] componentAnnotations = request.dropAnnotations(1, lengthAnnotation); - Class componentClass = argumentsAnnotation!=null ? argumentsAnnotation.value()[0] : request.getClazz().getComponentType(); - - org.simantics.databoard.util.Range[] lengths = null; - if (lengthAnnotation!=null) { - String[] strs = lengthAnnotation.value(); - lengths = new org.simantics.databoard.util.Range[strs.length]; - for (int i=0; i[] arguments = argumentsAnnotation != null ? argumentsAnnotation.value() : null; - - BindingRequest keyRequest = null; - BindingRequest valueRequest = null; - - Binding keyBinding = null; - Binding valueBinding = null; - - if (binding.getKeyBinding() != null) { - keyBinding = binding.getKeyBinding(); - } - else if (request.componentBindings != null) { - keyBinding = request.componentBindings[0]; - } - else if (request.componentRequests != null) { - keyRequest = request.componentRequests[0]; - } - else { - Class keyClass = arguments != null && arguments.length >= 1 ? arguments[0] : null; - if (keyClass==null) { - keyClass = Object.class; - //throw new BindingConstructionException("Cannot determine key class, use @Arguments annotation"); - } - keyRequest = new BindingRequest(keyClass, componentAnnotations); - } - - if (binding.getValueBinding() != null) { - valueBinding = binding.getValueBinding(); - } - else if (request.componentBindings != null) { - valueBinding = request.componentBindings[1]; - } - else if (request.componentRequests != null) { - valueRequest = request.componentRequests[1]; - } - else { - Class valueClass = arguments != null && arguments.length >= 2 ? arguments[1] : null; - if (valueClass==null) { - valueClass = Object.class; - //throw new BindingConstructionException("Cannot determine value class, use @Arguments annotation"); - } - valueRequest = new BindingRequest(valueClass, componentAnnotations); - } - - inprogress.put(request, result); - if (keyRequest!=null) { - keyBinding = construct( keyRequest ); - } - if (valueRequest!=null) { - valueBinding = construct( valueRequest ); - } - inprogress.remove(request); - - MapType type = binding.type(); - type.keyType = keyBinding.type(); - type.valueType = valueBinding.type(); - binding.setKeyBinding( keyBinding ); - binding.setValueBinding( valueBinding ); - } - - /// Optional - - - /// Union - - - /// Record - - // Its complete, store to repository - repository.put(request, result); - return result; - } - - - - if (request.getClazz().isEnum()) { - Enum[] enums = (Enum[]) request.getClazz().getEnumConstants(); - UnionType type = new UnionType(); - type.components = new Component[enums.length]; - for (int i=0; i>) request.getClazz() ); - repository.put(request, binding); - return binding; - } - - // Union type - if(request.hasAnnotation(Union.class)) { - Union union = request.getAnnotation(Union.class); - UnionType type = new UnionType(); - UnionClassBinding binding = new UnionClassBinding(type); - Class[] cases = union.value(); - int count = cases.length; - Binding[] componentBindings = new Binding[count]; - type.components = new Component[ count ]; - binding.componentClasses = new Class[ count ]; - binding.setComponentBindings(componentBindings); - - inprogress.put(request, binding); - for(int i=0;i identifierIndices = new ArrayList(1); - for(int i=0;i fieldClass = field.getType(); - BindingRequest componentRequest = new BindingRequest(fieldClass, annotations); - - Name nameAnnotation = componentRequest.getAnnotation( Name.class ); - String fieldName = nameAnnotation!=null ? nameAnnotation.value() : field.getName(); - Component c = components[i] = new Component(fieldName, null /* updated later */); - - Identifier idAnnotation = componentRequest.getAnnotation( Identifier.class ); - if ( idAnnotation!=null ) { - componentRequest.dropAnnotations(1, idAnnotation); - identifierIndices.add( i ); - } - Binding componentBinding = componentBindings[i] = construct( componentRequest ); - c.type = componentBinding.type(); - } - type.setIdentifiers(identifierIndices); - inprogress.remove(request); - repository.put(request, binding); - return binding; - } - } - - public Binding construct(BindingRequest request) - throws BindingConstructionException - { - if (failures.containsKey(request)) throw failures.get(request); - if (inprogress.containsKey(request)) return inprogress.get(request); - if (repository.containsRequest(request)) return repository.get(request); - - // Start construction - try { - Binding binding = doConstruct(request); - - // Avoid creating duplicate binding instances - // Only check bindings that are complete - if (inprogress.isEmpty() || isComplete(binding, new IdentityHashSet())) { - Binding defaultBinding = defaultBindingFactory.getBinding(binding.type()); - if (defaultBinding != null && defaultBinding.equals(binding)) - binding = defaultBinding; - } - - repository.put(request, binding); - - return binding; - } catch (RangeException e) { - inprogress.remove( request ); - BindingConstructionException bce = new BindingConstructionException( e ); - failures.put(request, bce); - throw bce; - } catch (BindingConstructionException e) { - inprogress.remove( request ); - failures.put(request, e); - throw e; - } catch (Throwable t) { - BindingConstructionException bce = new BindingConstructionException( t ); - inprogress.remove( request ); - failures.put(request, bce); - throw bce; - } - } - - boolean isComplete(Binding binding, IdentityHashSet checked) { - for (Binding b : inprogress.values()) - if (b == binding) return false; - - if (checked.contains(binding)) return true; - - if (binding.getComponentCount() > 0) { - checked.add(binding); - for (int i = 0; i < binding.getComponentCount(); i++) { - if (!isComplete(binding.getComponentBinding(i), checked)) - return false; - } - } - - return true; - } - - /** - * Get a binding to a Java Class. Type details can be modified with annotations. - * Please see the package org.simantics.databoard.annotations. - *

- * - * The whether the result binding is a completely mutable or not depends on the - * provided classes. For instance, fields such as Boolean, Integer, Long - * are not mutable, instead MutableBoolean, MutableInteger and MutableLong are. - * The length of Object[] is not mutable, but length of List is.

- * - * Note, results are stored with strong reference into the repository assigned - * to this factory.

- * - * @see ClassBindingFactory - * @param clazz - * @return binding - * @throws BindingConstructionException - */ - @SuppressWarnings("unchecked") - public T getBinding(Class clazz) - throws BindingConstructionException - { - return (T) construct( new BindingRequest(clazz) ); - } - - /** - * Get a binding to a Java Class. Use this method to acquire class - * parameters for a generics class.

- * - * Example 1: - * - * Binding binding = getBinding(Map.class, String.class, Integer.class); - * Map map = (Map) binding.createDefault(); - * - * Example 2: - * - * Binding d = getBinding(List.class, Integer.class); - * List list = (List) d.createRandom(5); - * - * Example 3: - * - * Binding d = getBinding(List.class, List.class, Integer.class); - * List> list = (List>) d.createRandom(5); - * - * @see ClassBindingFactory - * @param clazz - * @return binding - * @throws BindingConstructionException - */ - @SuppressWarnings("unchecked") - public T getBinding(Class clazz, Class...parameters) - throws BindingConstructionException - { - Arguments args = new ArgumentImpl(parameters); - BindingRequest request = new BindingRequest( clazz, args ); - return (T) construct( request ); - } - - public Binding getBinding(BindingRequest request) throws BindingConstructionException { - return construct(request); - } - - public Binding getBindingUnchecked(BindingRequest request) throws RuntimeBindingConstructionException { - try { - return construct(request); - } catch (BindingConstructionException e) { - throw new RuntimeBindingConstructionException(e); - } - } - - static Class[] NO_CLASSES = new Class[0]; - - public static Annotation[] getFieldAnnotations(Field field) - { - Annotation[] annotations = field.getAnnotations().clone(); - ArrayList> list = new ArrayList>(); - getTypes( field.getGenericType(), list ); - Class fieldClass = list.remove(0); - Class[] parameterClasses = list.isEmpty() ? NO_CLASSES : list.toArray( NO_CLASSES ); - - if (Set.class.isAssignableFrom(fieldClass) && parameterClasses!=null &¶meterClasses.length==1) { - Annotation[] a2 = new Annotation[annotations.length+1]; - System.arraycopy(annotations, 0, a2, 0, annotations.length); - - Class keyType = parameterClasses[0]; - a2[annotations.length] = new ArgumentImpl(keyType); - annotations = a2; - } - if (Map.class.isAssignableFrom(fieldClass) && parameterClasses!=null && parameterClasses.length==2) { - Annotation[] a2 = new Annotation[annotations.length+1]; - System.arraycopy(annotations, 0, a2, 0, annotations.length); - - Class keyType = parameterClasses[0]; - Class valueType = parameterClasses[1]; - a2[annotations.length] = new ArgumentImpl(keyType, valueType); - annotations = a2; - } - if (List.class.isAssignableFrom(fieldClass) && parameterClasses!=null && parameterClasses.length==1) { - Annotation[] a2 = new Annotation[annotations.length+1]; - System.arraycopy(annotations, 0, a2, 0, annotations.length); - Class componentType = parameterClasses[0]; - a2[annotations.length] = new ArgumentImpl(componentType); - annotations = a2; - } - - if (parameterClasses!=null && parameterClasses.length>0) { - Annotation[] a2 = new Annotation[annotations.length+1]; - System.arraycopy(annotations, 0, a2, 0, annotations.length); - a2[annotations.length] = new ArgumentImpl(parameterClasses); - annotations = a2; - } - - return annotations; - } - - static void getTypes(Type type, Collection> result) - { - if ( type instanceof Class ) { - result.add( (Class) type ); - } else - if ( type instanceof ParameterizedType ) { - ParameterizedType p = (ParameterizedType) type; - getTypes( p.getRawType(), result ); - for ( Type x : p.getActualTypeArguments() ) getTypes(x, result); - } else - if ( type instanceof GenericArrayType) { - GenericArrayType at = (GenericArrayType) type; - Type componentType = at.getGenericComponentType(); - ArrayList> list = new ArrayList>(1); - getTypes( componentType, list ); - // To Array class - Object dummy = Array.newInstance(list.get(0), 0); - result.add( dummy.getClass() ); - } else if ( type instanceof TypeVariable ) { - result.add( Object.class ); - } else - throw new RuntimeException( type.getClass()+ " is not implemented" ); - } - - /** - * Get all actual parameters types, incl. classes and generic types - * - * @param f - * @return - */ - static Type[] getParameterTypes(Field f) { - Type t = f.getGenericType(); - if (t==null || t instanceof ParameterizedType==false) return new Class[0]; - ParameterizedType p = (ParameterizedType) t; - return p.getActualTypeArguments(); - } - - -} +/******************************************************************************* + * Copyright (c) 2019 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 + * Semantum Oy - gitlab #313 + *******************************************************************************/ +package org.simantics.databoard.binding.reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.simantics.databoard.Bindings; +import org.simantics.databoard.annotations.ArgumentImpl; +import org.simantics.databoard.annotations.Arguments; +import org.simantics.databoard.annotations.Identifier; +import org.simantics.databoard.annotations.Length; +import org.simantics.databoard.annotations.MIMEType; +import org.simantics.databoard.annotations.Name; +import org.simantics.databoard.annotations.Optional; +import org.simantics.databoard.annotations.Pattern; +import org.simantics.databoard.annotations.Range; +import org.simantics.databoard.annotations.Referable; +import org.simantics.databoard.annotations.Union; +import org.simantics.databoard.annotations.Unit; +import org.simantics.databoard.binding.ArrayBinding; +import org.simantics.databoard.binding.Binding; +import org.simantics.databoard.binding.MapBinding; +import org.simantics.databoard.binding.OptionalBinding; +import org.simantics.databoard.binding.RecordBinding; +import org.simantics.databoard.binding.error.BindingConstructionException; +import org.simantics.databoard.binding.error.RuntimeBindingConstructionException; +import org.simantics.databoard.binding.factory.BindingRepository; +import org.simantics.databoard.binding.factory.DefaultBindingFactory; +import org.simantics.databoard.binding.factory.TypeBindingFactory; +import org.simantics.databoard.binding.impl.ByteBindingDefault; +import org.simantics.databoard.binding.impl.DoubleBindingDefault; +import org.simantics.databoard.binding.impl.FloatBindingDefault; +import org.simantics.databoard.binding.impl.IntegerBindingDefault; +import org.simantics.databoard.binding.impl.LongBindingDefault; +import org.simantics.databoard.binding.impl.OptionalBindingDefault; +import org.simantics.databoard.binding.impl.StringBindingDefault; +import org.simantics.databoard.binding.impl.UnsignedByteBinding; +import org.simantics.databoard.binding.impl.UnsignedIntegerBinding; +import org.simantics.databoard.binding.impl.UnsignedLongBinding; +import org.simantics.databoard.binding.mutable.MutableByteBinding; +import org.simantics.databoard.binding.mutable.MutableDoubleBinding; +import org.simantics.databoard.binding.mutable.MutableFloatBinding; +import org.simantics.databoard.binding.mutable.MutableIntegerBinding; +import org.simantics.databoard.binding.mutable.MutableLongBinding; +import org.simantics.databoard.binding.mutable.MutableStringBinding; +import org.simantics.databoard.primitives.MutableBoolean; +import org.simantics.databoard.primitives.MutableByte; +import org.simantics.databoard.primitives.MutableDouble; +import org.simantics.databoard.primitives.MutableFloat; +import org.simantics.databoard.primitives.MutableInteger; +import org.simantics.databoard.primitives.MutableLong; +import org.simantics.databoard.primitives.MutableString; +import org.simantics.databoard.primitives.UnsignedByte; +import org.simantics.databoard.primitives.UnsignedInteger; +import org.simantics.databoard.primitives.UnsignedLong; +import org.simantics.databoard.type.ArrayType; +import org.simantics.databoard.type.ByteType; +import org.simantics.databoard.type.Component; +import org.simantics.databoard.type.DoubleType; +import org.simantics.databoard.type.FloatType; +import org.simantics.databoard.type.IntegerType; +import org.simantics.databoard.type.LongType; +import org.simantics.databoard.type.MapType; +import org.simantics.databoard.type.OptionalType; +import org.simantics.databoard.type.RecordType; +import org.simantics.databoard.type.StringType; +import org.simantics.databoard.type.UnionType; +import org.simantics.databoard.util.ArrayUtils; +import org.simantics.databoard.util.IdentityHashSet; +import org.simantics.databoard.util.RangeException; + +/** + * Type Factory constructs data types from reflection requests. + * Successfully constructed types are placed in the repository that was given + * at construction time. + * + * @author Toni Kalajainen + */ +public class ClassBindingFactory { + + /** + * Map of failed constructions. + */ + Map failures = new HashMap(); + + /** + * Map that contains in incomplete constructions. + */ + Map inprogress = new HashMap(); + + /** + * Repository where successful constructions are placed. + */ + BindingRepository repository; + + List subFactories = new CopyOnWriteArrayList(); + + /** Asm based binding factory for Classes, this is used if Asm library is available, else null */ + RecordBindingProvider asmClassBindingFactory; + + /** Reflection based binding factory for Classes, this has poorer performance than asm factory */ + RecordBindingProvider refClassBindingFactory; + + /** Factory for creating default bindings for data types. */ + TypeBindingFactory defaultBindingFactory; + + /** + * Construct a new reflection binding factory + */ + public ClassBindingFactory() { + init(); + this.repository = new BindingRepository(); + this.defaultBindingFactory = new DefaultBindingFactory(); + } + + /** + * Construct a new reflection binding factory that places constructed + * bindings into user given repository. + * + * @param repository + */ + public ClassBindingFactory(BindingRepository repository, TypeBindingFactory defaultBindingFactory) { + init(); + this.repository = repository; + this.defaultBindingFactory = defaultBindingFactory; + } + + void init() { + refClassBindingFactory = new ReflectionBindingProvider(); + try { + // Check ASM Exists + Class.forName("org.objectweb.asm.ClassWriter"); + // Create factory + Class y = Class.forName("org.simantics.databoard.binding.reflection.AsmBindingProvider"); + Constructor c = y.getConstructor(); + asmClassBindingFactory = (RecordBindingProvider) c.newInstance(); + return; + } catch (ClassNotFoundException e) { + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + } catch (IllegalArgumentException e) { + } catch (InvocationTargetException e) { + } catch (SecurityException e) { + } catch (NoSuchMethodException e) { + } + } + + public void addFactory(BindingProvider factory) + { + if (!subFactories.contains(factory)) { + this.subFactories.add(factory); + } + } + + public void removeFactory(BindingProvider factory) { + subFactories.remove(factory); + } + + public BindingRepository getRepository() { + return repository; + } + + /** + * Constructs a binding to comply to class request. + * This is the method sub-classes implement. + * The implementation should use the inprogress -map for construction of + * bindings that have component types. + * + * e.g. + * inprogress.put(request, notCompletelyConstructedBinding); + * Binding componentBinding = construct( componentRequest ); + * notCompletelyConstructedBinding.setChild( componentBinding ); + * inprogress.remove(request); + * + * try-finally is not needed. + * + * @param request + * @return + * @throws BindingConstructionException + * @throws RangeException + */ + @SuppressWarnings("unchecked") + protected Binding doConstruct(BindingRequest request) throws BindingConstructionException, RangeException + { + // Optional + if(request.hasAnnotation(Optional.class)) + { + Optional optional = request.getAnnotation(Optional.class); + BindingRequest componentRequest = request.withAnnotations(ArrayUtils.dropElements(request.annotations, optional)); + OptionalType type = new OptionalType(); + OptionalBinding binding = new OptionalBindingDefault(type, null); + inprogress.put(request, binding); + binding.componentBinding = construct( componentRequest ); + type.componentType = binding.componentBinding.type(); + inprogress.remove(request); + + repository.put(request, binding); + return binding; + } + + + + // Primitive + { + Range range = request.getAnnotation(Range.class); + Unit unit = request.getAnnotation(Unit.class); + + if (request.getClazz() == Integer.class || request.getClazz() == int.class || request.getClazz() == MutableInteger.class || UnsignedInteger.class.isAssignableFrom(request.getClazz())) { + Binding binding = null; + if (range==null && unit==null) { + if (request.getClazz() == int.class) binding = Bindings.INTEGER; + if (request.getClazz() == Integer.class) binding = Bindings.INTEGER; + if (request.getClazz() == MutableInteger.class) binding = Bindings.MUTABLE_INTEGER; + if (request.getClazz() == UnsignedInteger.Mutable.class) binding = Bindings.MUTABLE_UNSIGNED_INTEGER; + if (request.getClazz() == UnsignedInteger.Immutable.class) binding = Bindings.UNSIGNED_INTEGER; + } else { + IntegerType type = new IntegerType(); + type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) ); + type.setUnit( unit==null ? null : unit.value() ); + + if (request.getClazz() == int.class) binding = new IntegerBindingDefault(type); + if (request.getClazz() == Integer.class) binding = new IntegerBindingDefault(type); + if (request.getClazz() == MutableInteger.class) binding = new MutableIntegerBinding(type); + if (request.getClazz() == UnsignedInteger.Mutable.class) binding = new UnsignedIntegerBinding.Mutable(type); + if (request.getClazz() == UnsignedInteger.Immutable.class) binding = new UnsignedIntegerBinding.Immutable(type); + } + if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); + + repository.put(request, binding); + return binding; + } + + if (request.getClazz() == Byte.class || request.getClazz() == byte.class || request.getClazz() == MutableByte.class || UnsignedByte.class.isAssignableFrom(request.getClazz())) { + Binding binding = null; + if (range==null && unit==null) { + if (request.getClazz() == byte.class) binding = Bindings.BYTE; + if (request.getClazz() == Byte.class) binding = Bindings.BYTE; + if (request.getClazz() == MutableByte.class) binding = Bindings.MUTABLE_BYTE; + if (request.getClazz() == UnsignedByte.Mutable.class) binding = Bindings.MUTABLE_UNSIGNED_BYTE; + if (request.getClazz() == UnsignedByte.Immutable.class) binding = Bindings.UNSIGNED_BYTE; + } else { + ByteType type = new ByteType(); + type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) ); + type.setUnit( unit==null ? null : unit.value() ); + + if (request.getClazz() == byte.class) binding = new ByteBindingDefault(type); + if (request.getClazz() == Byte.class) binding = new ByteBindingDefault(type); + if (request.getClazz() == MutableByte.class) binding = new MutableByteBinding(type); + if (request.getClazz() == UnsignedByte.Mutable.class) binding = new UnsignedByteBinding.Mutable(type); + if (request.getClazz() == UnsignedByte.Immutable.class) binding = new UnsignedByteBinding.Immutable(type); + } + if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); + + repository.put(request, binding); + return binding; + } + + if (request.getClazz() == Long.class || request.getClazz() == long.class || request.getClazz() == MutableLong.class || UnsignedLong.class.isAssignableFrom(request.getClazz())) { + Binding binding = null; + if (range==null && unit==null) { + if (request.getClazz() == long.class) binding = Bindings.LONG; + if (request.getClazz() == Long.class) binding = Bindings.LONG; + if (request.getClazz() == MutableLong.class) binding = Bindings.MUTABLE_LONG; + if (request.getClazz() == UnsignedLong.Mutable.class) binding = Bindings.MUTABLE_UNSIGNED_LONG; + if (request.getClazz() == UnsignedLong.Immutable.class) binding = Bindings.UNSIGNED_LONG; + } else { + LongType type = new LongType(); + type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) ); + type.setUnit( unit==null ? null : unit.value() ); + + if (request.getClazz() == long.class) binding = new LongBindingDefault(type); + if (request.getClazz() == Long.class) binding = new LongBindingDefault(type); + if (request.getClazz() == MutableLong.class) binding = new MutableLongBinding(type); + if (request.getClazz() == UnsignedLong.Mutable.class) binding = new UnsignedLongBinding.Mutable(type); + if (request.getClazz() == UnsignedLong.Immutable.class) binding = new UnsignedLongBinding.Immutable(type); + } + if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); + + repository.put(request, binding); + return binding; + } + + if (request.getClazz() == Float.class || request.getClazz() == float.class || request.getClazz() == MutableFloat.class) { + Binding binding = null; + if (range==null && unit==null) { + if (request.getClazz() == float.class) binding = Bindings.FLOAT; + if (request.getClazz() == Float.class) binding = Bindings.FLOAT; + if (request.getClazz() == MutableFloat.class) binding = Bindings.MUTABLE_FLOAT; + } else { + FloatType type = new FloatType(); + type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) ); + type.setUnit( unit==null ? null : unit.value() ); + + if (request.getClazz() == float.class) binding = new FloatBindingDefault(type); + if (request.getClazz() == Float.class) binding = new FloatBindingDefault(type); + if (request.getClazz() == MutableFloat.class) binding = new MutableFloatBinding(type); + } + if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); + + repository.put(request, binding); + return binding; + } + + if (request.getClazz() == Double.class || request.getClazz() == double.class || request.getClazz() == MutableDouble.class) { + Binding binding = null; + if (range==null && unit==null) { + if (request.getClazz() == double.class) binding = Bindings.DOUBLE; + if (request.getClazz() == Double.class) binding = Bindings.DOUBLE; + if (request.getClazz() == MutableDouble.class) binding = Bindings.MUTABLE_DOUBLE; + } else { + DoubleType type = new DoubleType(); + type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) ); + type.setUnit( unit==null ? null : unit.value() ); + + if (request.getClazz() == double.class) binding = new DoubleBindingDefault(type); + if (request.getClazz() == Double.class) binding = new DoubleBindingDefault(type); + if (request.getClazz() == MutableDouble.class) binding = new MutableDoubleBinding(type); + } + + repository.put(request, binding); + return binding; + } + + if (request.getClazz() == Boolean.class || request.getClazz() == boolean.class || request.getClazz() == MutableBoolean.class) { + Binding binding = null; + if (request.getClazz() == Boolean.class || request.getClazz() == boolean.class) binding = Bindings.BOOLEAN; + if (request.getClazz() == MutableBoolean.class) binding = Bindings.MUTABLE_BOOLEAN; + if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); + + repository.put(request, binding); + return binding; + } + + if (request.getClazz() == String.class || request.getClazz() == MutableString.class) { + Length length = request.getAnnotation(Length.class); + MIMEType mimeType = request.getAnnotation(MIMEType.class); + Pattern pattern = request.getAnnotation(Pattern.class); + Binding binding = null; + if (length==null && mimeType==null && pattern==null) { + if (request.getClazz() == String.class) binding = Bindings.STRING; + if (request.getClazz() == MutableString.class) binding = Bindings.MUTABLE_STRING; + } else { + StringType type = new StringType(); + type.setLength( length==null ? null : org.simantics.databoard.util.Range.valueOf( length.value()[0] ) ); + type.setMimeType( mimeType==null ? null : mimeType.value() ); + type.setPattern( pattern==null ? null : pattern.value() ); + if (request.getClazz() == String.class) binding = new StringBindingDefault(type); + if (request.getClazz() == MutableString.class) binding = new MutableStringBinding(type); + } + if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName()); + + repository.put(request, binding); + return binding; + } + } + + + // Custom factories + for (BindingProvider factory : subFactories) { + Binding result = factory.provideBinding(this, request); + if (result == null) continue; + + /// Array + // If the result was an arraybinding, complete the composite binding + if (result instanceof ArrayBinding) { + ArrayBinding binding = (ArrayBinding) result; + ArrayType type = binding.type(); + Length lengthAnnotation = request.getAnnotation(Length.class); + Arguments argumentsAnnotation = request.getAnnotation(Arguments.class); + Annotation[] componentAnnotations = request.dropAnnotations(1, lengthAnnotation); + Class componentClass = argumentsAnnotation!=null ? argumentsAnnotation.value()[0] : request.getClazz().getComponentType(); + + org.simantics.databoard.util.Range[] lengths = null; + if (lengthAnnotation!=null) { + String[] strs = lengthAnnotation.value(); + lengths = new org.simantics.databoard.util.Range[strs.length]; + for (int i=0; i[] arguments = argumentsAnnotation != null ? argumentsAnnotation.value() : null; + + BindingRequest keyRequest = null; + BindingRequest valueRequest = null; + + Binding keyBinding = null; + Binding valueBinding = null; + + if (binding.getKeyBinding() != null) { + keyBinding = binding.getKeyBinding(); + } + else if (request.componentBindings != null) { + keyBinding = request.componentBindings[0]; + } + else if (request.componentRequests != null) { + keyRequest = request.componentRequests[0]; + } + else { + Class keyClass = arguments != null && arguments.length >= 1 ? arguments[0] : null; + if (keyClass==null) { + keyClass = Object.class; + //throw new BindingConstructionException("Cannot determine key class, use @Arguments annotation"); + } + keyRequest = new BindingRequest(keyClass, componentAnnotations); + } + + if (binding.getValueBinding() != null) { + valueBinding = binding.getValueBinding(); + } + else if (request.componentBindings != null) { + valueBinding = request.componentBindings[1]; + } + else if (request.componentRequests != null) { + valueRequest = request.componentRequests[1]; + } + else { + Class valueClass = arguments != null && arguments.length >= 2 ? arguments[1] : null; + if (valueClass==null) { + valueClass = Object.class; + //throw new BindingConstructionException("Cannot determine value class, use @Arguments annotation"); + } + valueRequest = new BindingRequest(valueClass, componentAnnotations); + } + + inprogress.put(request, result); + if (keyRequest!=null) { + keyBinding = construct( keyRequest ); + } + if (valueRequest!=null) { + valueBinding = construct( valueRequest ); + } + inprogress.remove(request); + + MapType type = binding.type(); + type.keyType = keyBinding.type(); + type.valueType = valueBinding.type(); + binding.setKeyBinding( keyBinding ); + binding.setValueBinding( valueBinding ); + } + + /// Optional + + + /// Union + + + /// Record + + // Its complete, store to repository + repository.put(request, result); + return result; + } + + + + if (request.getClazz().isEnum()) { + Enum[] enums = (Enum[]) request.getClazz().getEnumConstants(); + UnionType type = new UnionType(); + type.components = new Component[enums.length]; + for (int i=0; i>) request.getClazz() ); + repository.put(request, binding); + return binding; + } + + // Union type + if(request.hasAnnotation(Union.class)) { + Union union = request.getAnnotation(Union.class); + UnionType type = new UnionType(); + UnionClassBinding binding = new UnionClassBinding(type); + Class[] cases = union.value(); + int count = cases.length; + Binding[] componentBindings = new Binding[count]; + type.components = new Component[ count ]; + binding.componentClasses = new Class[ count ]; + binding.setComponentBindings(componentBindings); + + inprogress.put(request, binding); + for(int i=0;i identifierIndices = new ArrayList(1); + for(int i=0;i fieldClass = field.getType(); + BindingRequest componentRequest = new BindingRequest(fieldClass, annotations); + + Name nameAnnotation = componentRequest.getAnnotation( Name.class ); + String fieldName = nameAnnotation!=null ? nameAnnotation.value() : field.getName(); + Component c = components[i] = new Component(fieldName, null /* updated later */); + + Identifier idAnnotation = componentRequest.getAnnotation( Identifier.class ); + if ( idAnnotation!=null ) { + componentRequest = componentRequest.withAnnotations(componentRequest.dropAnnotations(1, idAnnotation)); + identifierIndices.add( i ); + } + Binding componentBinding = componentBindings[i] = construct( componentRequest ); + c.type = componentBinding.type(); + } + type.setIdentifiers(identifierIndices); + inprogress.remove(request); + repository.put(request, binding); + return binding; + } + } + + public Binding construct(BindingRequest request) + throws BindingConstructionException + { + if (failures.containsKey(request)) throw failures.get(request); + if (inprogress.containsKey(request)) return inprogress.get(request); + if (repository.containsRequest(request)) return repository.get(request); + + // Start construction + try { + Binding binding = doConstruct(request); + + // Avoid creating duplicate binding instances + // Only check bindings that are complete + if (inprogress.isEmpty() || isComplete(binding, new IdentityHashSet())) { + Binding defaultBinding = defaultBindingFactory.getBinding(binding.type()); + if (defaultBinding != null && defaultBinding.equals(binding)) + binding = defaultBinding; + } + + return binding; + } catch (RangeException e) { + inprogress.remove( request ); + BindingConstructionException bce = new BindingConstructionException( e ); + failures.put(request, bce); + throw bce; + } catch (BindingConstructionException e) { + inprogress.remove( request ); + failures.put(request, e); + throw e; + } catch (Throwable t) { + BindingConstructionException bce = new BindingConstructionException( t ); + inprogress.remove( request ); + failures.put(request, bce); + throw bce; + } + } + + boolean isComplete(Binding binding, IdentityHashSet checked) { + for (Binding b : inprogress.values()) + if (b == binding) return false; + + if (checked.contains(binding)) return true; + + if (binding.getComponentCount() > 0) { + checked.add(binding); + for (int i = 0; i < binding.getComponentCount(); i++) { + if (!isComplete(binding.getComponentBinding(i), checked)) + return false; + } + } + + return true; + } + + /** + * Get a binding to a Java Class. Type details can be modified with annotations. + * Please see the package org.simantics.databoard.annotations. + *

+ * + * The whether the result binding is a completely mutable or not depends on the + * provided classes. For instance, fields such as Boolean, Integer, Long + * are not mutable, instead MutableBoolean, MutableInteger and MutableLong are. + * The length of Object[] is not mutable, but length of List is.

+ * + * Note, results are stored with strong reference into the repository assigned + * to this factory.

+ * + * @see ClassBindingFactory + * @param clazz + * @return binding + * @throws BindingConstructionException + */ + @SuppressWarnings("unchecked") + public T getBinding(Class clazz) + throws BindingConstructionException + { + return (T) construct( new BindingRequest(clazz) ); + } + + /** + * Get a binding to a Java Class. Use this method to acquire class + * parameters for a generics class.

+ * + * Example 1: + * + * Binding binding = getBinding(Map.class, String.class, Integer.class); + * Map map = (Map) binding.createDefault(); + * + * Example 2: + * + * Binding d = getBinding(List.class, Integer.class); + * List list = (List) d.createRandom(5); + * + * Example 3: + * + * Binding d = getBinding(List.class, List.class, Integer.class); + * List> list = (List>) d.createRandom(5); + * + * @see ClassBindingFactory + * @param clazz + * @return binding + * @throws BindingConstructionException + */ + @SuppressWarnings("unchecked") + public T getBinding(Class clazz, Class...parameters) + throws BindingConstructionException + { + Arguments args = new ArgumentImpl(parameters); + BindingRequest request = new BindingRequest( clazz, args ); + return (T) construct( request ); + } + + public Binding getBinding(BindingRequest request) throws BindingConstructionException { + return construct(request); + } + + public Binding getBindingUnchecked(BindingRequest request) throws RuntimeBindingConstructionException { + try { + return construct(request); + } catch (BindingConstructionException e) { + throw new RuntimeBindingConstructionException(e); + } + } + + static Class[] NO_CLASSES = new Class[0]; + + public static Annotation[] getFieldAnnotations(Field field) + { + Annotation[] annotations = field.getAnnotations().clone(); + ArrayList> list = new ArrayList>(); + getTypes( field.getGenericType(), list ); + Class fieldClass = list.remove(0); + Class[] parameterClasses = list.isEmpty() ? NO_CLASSES : list.toArray( NO_CLASSES ); + + if (Set.class.isAssignableFrom(fieldClass) && parameterClasses!=null &¶meterClasses.length==1) { + Annotation[] a2 = new Annotation[annotations.length+1]; + System.arraycopy(annotations, 0, a2, 0, annotations.length); + + Class keyType = parameterClasses[0]; + a2[annotations.length] = new ArgumentImpl(keyType); + annotations = a2; + } + if (Map.class.isAssignableFrom(fieldClass) && parameterClasses!=null && parameterClasses.length==2) { + Annotation[] a2 = new Annotation[annotations.length+1]; + System.arraycopy(annotations, 0, a2, 0, annotations.length); + + Class keyType = parameterClasses[0]; + Class valueType = parameterClasses[1]; + a2[annotations.length] = new ArgumentImpl(keyType, valueType); + annotations = a2; + } + if (List.class.isAssignableFrom(fieldClass) && parameterClasses!=null && parameterClasses.length==1) { + Annotation[] a2 = new Annotation[annotations.length+1]; + System.arraycopy(annotations, 0, a2, 0, annotations.length); + Class componentType = parameterClasses[0]; + a2[annotations.length] = new ArgumentImpl(componentType); + annotations = a2; + } + + if (parameterClasses!=null && parameterClasses.length>0) { + Annotation[] a2 = new Annotation[annotations.length+1]; + System.arraycopy(annotations, 0, a2, 0, annotations.length); + a2[annotations.length] = new ArgumentImpl(parameterClasses); + annotations = a2; + } + + return annotations; + } + + static void getTypes(Type type, Collection> result) + { + if ( type instanceof Class ) { + result.add( (Class) type ); + } else + if ( type instanceof ParameterizedType ) { + ParameterizedType p = (ParameterizedType) type; + getTypes( p.getRawType(), result ); + for ( Type x : p.getActualTypeArguments() ) getTypes(x, result); + } else + if ( type instanceof GenericArrayType) { + GenericArrayType at = (GenericArrayType) type; + Type componentType = at.getGenericComponentType(); + ArrayList> list = new ArrayList>(1); + getTypes( componentType, list ); + // To Array class + Object dummy = Array.newInstance(list.get(0), 0); + result.add( dummy.getClass() ); + } else if ( type instanceof TypeVariable ) { + result.add( Object.class ); + } else + throw new RuntimeException( type.getClass()+ " is not implemented" ); + } + + /** + * Get all actual parameters types, incl. classes and generic types + * + * @param f + * @return + */ + static Type[] getParameterTypes(Field f) { + Type t = f.getGenericType(); + if (t==null || t instanceof ParameterizedType==false) return new Class[0]; + ParameterizedType p = (ParameterizedType) t; + return p.getActualTypeArguments(); + } + + +}