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.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 keyClass = argumentsAnnotation.value()[0]; if (keyClass==null) { throw new BindingConstructionException("Cannot determine key class, use @Arguments annotation"); } keyRequest = new BindingRequest(keyClass, componentAnnotations); } else { binding.type().keyType = binding.getKeyBinding().type(); } if (binding.getValueBinding() == null) { Class valueClass = argumentsAnnotation.value()[1]; if (valueClass==null) { throw new BindingConstructionException("Cannot determine value class, use @Arguments annotation"); } valueRequest = new BindingRequest(valueClass, componentAnnotations); } else { binding.type().valueType = binding.getValueBinding().type(); } inprogress.put(request, result); if (keyRequest!=null) { Binding keyBinding = construct( keyRequest ); binding.type().keyType = keyBinding.type(); binding.setKeyBinding(keyBinding); } if (valueRequest!=null) { Binding valueBinding = construct( valueRequest ); binding.type().valueType = valueBinding.type(); binding.setValueBinding(valueBinding); } inprogress.remove(request); } /// 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(); } }