Fixing several binding-related bugs
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / binding / reflection / ClassBindingFactory.java
1 /*******************************************************************************
2  * Copyright (c) 2019 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *     Semantum Oy - gitlab #313
12  *******************************************************************************/
13 package org.simantics.databoard.binding.reflection;
14
15 import java.lang.annotation.Annotation;
16 import java.lang.reflect.Array;
17 import java.lang.reflect.Constructor;
18 import java.lang.reflect.Field;
19 import java.lang.reflect.GenericArrayType;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Modifier;
22 import java.lang.reflect.ParameterizedType;
23 import java.lang.reflect.Type;
24 import java.lang.reflect.TypeVariable;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.concurrent.CopyOnWriteArrayList;
32
33 import org.simantics.databoard.Bindings;
34 import org.simantics.databoard.annotations.ArgumentImpl;
35 import org.simantics.databoard.annotations.Arguments;
36 import org.simantics.databoard.annotations.Identifier;
37 import org.simantics.databoard.annotations.Length;
38 import org.simantics.databoard.annotations.MIMEType;
39 import org.simantics.databoard.annotations.Name;
40 import org.simantics.databoard.annotations.Optional;
41 import org.simantics.databoard.annotations.Pattern;
42 import org.simantics.databoard.annotations.Range;
43 import org.simantics.databoard.annotations.Referable;
44 import org.simantics.databoard.annotations.Union;
45 import org.simantics.databoard.annotations.Unit;
46 import org.simantics.databoard.binding.ArrayBinding;
47 import org.simantics.databoard.binding.Binding;
48 import org.simantics.databoard.binding.MapBinding;
49 import org.simantics.databoard.binding.OptionalBinding;
50 import org.simantics.databoard.binding.RecordBinding;
51 import org.simantics.databoard.binding.error.BindingConstructionException;
52 import org.simantics.databoard.binding.error.RuntimeBindingConstructionException;
53 import org.simantics.databoard.binding.factory.BindingRepository;
54 import org.simantics.databoard.binding.factory.DefaultBindingFactory;
55 import org.simantics.databoard.binding.factory.TypeBindingFactory;
56 import org.simantics.databoard.binding.impl.ByteBindingDefault;
57 import org.simantics.databoard.binding.impl.DoubleBindingDefault;
58 import org.simantics.databoard.binding.impl.FloatBindingDefault;
59 import org.simantics.databoard.binding.impl.IntegerBindingDefault;
60 import org.simantics.databoard.binding.impl.LongBindingDefault;
61 import org.simantics.databoard.binding.impl.OptionalBindingDefault;
62 import org.simantics.databoard.binding.impl.StringBindingDefault;
63 import org.simantics.databoard.binding.impl.UnsignedByteBinding;
64 import org.simantics.databoard.binding.impl.UnsignedIntegerBinding;
65 import org.simantics.databoard.binding.impl.UnsignedLongBinding;
66 import org.simantics.databoard.binding.mutable.MutableByteBinding;
67 import org.simantics.databoard.binding.mutable.MutableDoubleBinding;
68 import org.simantics.databoard.binding.mutable.MutableFloatBinding;
69 import org.simantics.databoard.binding.mutable.MutableIntegerBinding;
70 import org.simantics.databoard.binding.mutable.MutableLongBinding;
71 import org.simantics.databoard.binding.mutable.MutableStringBinding;
72 import org.simantics.databoard.primitives.MutableBoolean;
73 import org.simantics.databoard.primitives.MutableByte;
74 import org.simantics.databoard.primitives.MutableDouble;
75 import org.simantics.databoard.primitives.MutableFloat;
76 import org.simantics.databoard.primitives.MutableInteger;
77 import org.simantics.databoard.primitives.MutableLong;
78 import org.simantics.databoard.primitives.MutableString;
79 import org.simantics.databoard.primitives.UnsignedByte;
80 import org.simantics.databoard.primitives.UnsignedInteger;
81 import org.simantics.databoard.primitives.UnsignedLong;
82 import org.simantics.databoard.type.ArrayType;
83 import org.simantics.databoard.type.ByteType;
84 import org.simantics.databoard.type.Component;
85 import org.simantics.databoard.type.DoubleType;
86 import org.simantics.databoard.type.FloatType;
87 import org.simantics.databoard.type.IntegerType;
88 import org.simantics.databoard.type.LongType;
89 import org.simantics.databoard.type.MapType;
90 import org.simantics.databoard.type.OptionalType;
91 import org.simantics.databoard.type.RecordType;
92 import org.simantics.databoard.type.StringType;
93 import org.simantics.databoard.type.UnionType;
94 import org.simantics.databoard.util.ArrayUtils;
95 import org.simantics.databoard.util.IdentityHashSet;
96 import org.simantics.databoard.util.RangeException;
97
98 /**
99  * Type Factory constructs data types from reflection requests.
100  * Successfully constructed types are placed in the repository that was given 
101  * at construction time.
102  *  
103  * @author Toni Kalajainen
104  */
105 public class ClassBindingFactory {
106         
107         /**
108          * Map of failed constructions. 
109          */
110         Map<BindingRequest, BindingConstructionException> failures = new HashMap<BindingRequest, BindingConstructionException>();
111         
112         /**
113          * Map that contains in incomplete constructions.
114          */
115         Map<BindingRequest, Binding> inprogress = new HashMap<BindingRequest, Binding>();
116         
117         /**
118          * Repository where successful constructions are placed. 
119          */
120         BindingRepository repository;   
121         
122         List<BindingProvider> subFactories = new CopyOnWriteArrayList<BindingProvider>();
123
124         /** Asm based binding factory for Classes, this is used if Asm library is available, else null */
125         RecordBindingProvider asmClassBindingFactory;
126         
127         /** Reflection based binding factory for Classes, this has poorer performance than asm factory */
128         RecordBindingProvider refClassBindingFactory;
129
130         /** Factory for creating default bindings for data types. */
131         TypeBindingFactory defaultBindingFactory;
132         
133         /**
134          * Construct a new reflection binding factory 
135          */
136         public ClassBindingFactory() {
137                 init();
138                 this.repository = new BindingRepository();
139                 this.defaultBindingFactory = new DefaultBindingFactory();
140         }
141         
142         /**
143          * Construct a new reflection binding factory that places constructed 
144          * bindings into user given repository.
145          * 
146          * @param repository
147          */
148         public ClassBindingFactory(BindingRepository repository, TypeBindingFactory defaultBindingFactory) {
149                 init();
150                 this.repository = repository;
151                 this.defaultBindingFactory = defaultBindingFactory;
152         }
153         
154         void init() {
155                 refClassBindingFactory = new ReflectionBindingProvider();
156                 try {
157                         // Check ASM Exists
158                         Class.forName("org.objectweb.asm.ClassWriter");
159                         // Create factory
160                         Class<?> y = Class.forName("org.simantics.databoard.binding.reflection.AsmBindingProvider");
161                         Constructor<?> c = y.getConstructor();
162                         asmClassBindingFactory = (RecordBindingProvider) c.newInstance();
163                         return;
164                 } catch (ClassNotFoundException e) {
165                 } catch (InstantiationException e) {
166                 } catch (IllegalAccessException e) {
167                 } catch (IllegalArgumentException e) {
168                 } catch (InvocationTargetException e) {
169                 } catch (SecurityException e) {
170                 } catch (NoSuchMethodException e) {
171                 }
172         }
173         
174         public void addFactory(BindingProvider factory) 
175         {
176                 if (!subFactories.contains(factory)) {
177                         this.subFactories.add(factory);
178                 }
179         }
180         
181         public void removeFactory(BindingProvider factory) {
182                 subFactories.remove(factory);
183         }
184         
185         public BindingRepository getRepository() {
186                 return repository;
187         }
188         
189         /**
190          * Constructs a binding to comply to class request.
191          * This is the method sub-classes implement. 
192          * The implementation should use the inprogress -map for construction of 
193          * bindings that have component types.
194          * 
195          *  e.g. 
196          *   inprogress.put(request, notCompletelyConstructedBinding);
197          *   Binding componentBinding = construct( componentRequest );
198          *   notCompletelyConstructedBinding.setChild( componentBinding );
199          *   inprogress.remove(request);
200          *   
201          * try-finally is not needed.
202          * 
203          * @param request
204          * @return
205          * @throws BindingConstructionException
206          * @throws RangeException
207          */
208         @SuppressWarnings("unchecked")
209     protected Binding doConstruct(BindingRequest request) throws BindingConstructionException, RangeException
210         {
211                 // Optional
212         if(request.hasAnnotation(Optional.class))
213         {               
214                 Optional optional = request.getAnnotation(Optional.class); 
215                 BindingRequest componentRequest = request.withAnnotations(ArrayUtils.dropElements(request.annotations, optional));
216                 OptionalType type = new OptionalType();
217                 OptionalBinding binding = new OptionalBindingDefault(type, null);
218                         inprogress.put(request, binding);
219                         binding.componentBinding = construct( componentRequest );
220                         type.componentType = binding.componentBinding.type();
221                         inprogress.remove(request);
222                         
223                         repository.put(request, binding);
224                         return binding;
225         }
226
227             
228         
229         // Primitive
230                 {               
231                         Range range = request.getAnnotation(Range.class);
232                         Unit unit = request.getAnnotation(Unit.class);
233                         
234                         if (request.getClazz() == Integer.class || request.getClazz() == int.class || request.getClazz() == MutableInteger.class || UnsignedInteger.class.isAssignableFrom(request.getClazz())) {
235                                 Binding binding = null; 
236                                 if (range==null && unit==null) {
237                                         if (request.getClazz() == int.class) binding = Bindings.INTEGER;
238                                         if (request.getClazz() == Integer.class) binding = Bindings.INTEGER;
239                                         if (request.getClazz() == MutableInteger.class) binding = Bindings.MUTABLE_INTEGER;
240                                         if (request.getClazz() == UnsignedInteger.Mutable.class) binding = Bindings.MUTABLE_UNSIGNED_INTEGER;
241                                         if (request.getClazz() == UnsignedInteger.Immutable.class) binding = Bindings.UNSIGNED_INTEGER;
242                                 } else {
243                                         IntegerType type = new IntegerType();
244                                         type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) );
245                                         type.setUnit( unit==null ? null : unit.value() );
246
247                                         if (request.getClazz() == int.class) binding = new IntegerBindingDefault(type);
248                                         if (request.getClazz() == Integer.class) binding = new IntegerBindingDefault(type);
249                                         if (request.getClazz() == MutableInteger.class) binding = new MutableIntegerBinding(type);
250                                         if (request.getClazz() == UnsignedInteger.Mutable.class) binding = new UnsignedIntegerBinding.Mutable(type);
251                                         if (request.getClazz() == UnsignedInteger.Immutable.class) binding = new UnsignedIntegerBinding.Immutable(type);
252                                 }
253                                 if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName());
254                                 
255                                 repository.put(request, binding);
256                                 return binding;
257                         }
258                                 
259                         if (request.getClazz() == Byte.class || request.getClazz() == byte.class || request.getClazz() == MutableByte.class || UnsignedByte.class.isAssignableFrom(request.getClazz())) {
260                                 Binding binding = null; 
261                                 if (range==null && unit==null) {
262                                         if (request.getClazz() == byte.class) binding = Bindings.BYTE;
263                                         if (request.getClazz() == Byte.class) binding = Bindings.BYTE;
264                                         if (request.getClazz() == MutableByte.class) binding = Bindings.MUTABLE_BYTE;
265                                         if (request.getClazz() == UnsignedByte.Mutable.class) binding = Bindings.MUTABLE_UNSIGNED_BYTE;
266                                         if (request.getClazz() == UnsignedByte.Immutable.class) binding = Bindings.UNSIGNED_BYTE;
267                                 } else {
268                                         ByteType type = new ByteType();
269                                         type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) );
270                                         type.setUnit( unit==null ? null : unit.value() );
271
272                                         if (request.getClazz() == byte.class) binding = new ByteBindingDefault(type);
273                                         if (request.getClazz() == Byte.class) binding = new ByteBindingDefault(type);
274                                         if (request.getClazz() == MutableByte.class) binding = new MutableByteBinding(type);
275                                         if (request.getClazz() == UnsignedByte.Mutable.class) binding = new UnsignedByteBinding.Mutable(type);
276                                         if (request.getClazz() == UnsignedByte.Immutable.class) binding = new UnsignedByteBinding.Immutable(type);
277                                 }
278                                 if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName());
279                                 
280                                 repository.put(request, binding);                       
281                                 return binding;
282                         }                       
283                         
284                         if (request.getClazz() == Long.class || request.getClazz() == long.class || request.getClazz() == MutableLong.class || UnsignedLong.class.isAssignableFrom(request.getClazz())) {
285                                 Binding binding = null; 
286                                 if (range==null && unit==null) {
287                                         if (request.getClazz() == long.class) binding = Bindings.LONG;
288                                         if (request.getClazz() == Long.class) binding = Bindings.LONG;
289                                         if (request.getClazz() == MutableLong.class) binding = Bindings.MUTABLE_LONG;
290                                         if (request.getClazz() == UnsignedLong.Mutable.class) binding = Bindings.MUTABLE_UNSIGNED_LONG;
291                                         if (request.getClazz() == UnsignedLong.Immutable.class) binding = Bindings.UNSIGNED_LONG;
292                                 } else {
293                                         LongType type = new LongType();
294                                         type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) );
295                                         type.setUnit( unit==null ? null : unit.value() );
296
297                                         if (request.getClazz() == long.class) binding = new LongBindingDefault(type);
298                                         if (request.getClazz() == Long.class) binding = new LongBindingDefault(type);
299                                         if (request.getClazz() == MutableLong.class) binding = new MutableLongBinding(type);
300                                         if (request.getClazz() == UnsignedLong.Mutable.class) binding = new UnsignedLongBinding.Mutable(type);
301                                         if (request.getClazz() == UnsignedLong.Immutable.class) binding = new UnsignedLongBinding.Immutable(type);
302                                 }
303                                 if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName());
304                                 
305                                 repository.put(request, binding);                       
306                                 return binding;
307                         }
308                         
309                         if (request.getClazz() == Float.class || request.getClazz() == float.class || request.getClazz() == MutableFloat.class) {
310                                 Binding binding = null; 
311                                 if (range==null && unit==null) {
312                                         if (request.getClazz() == float.class) binding = Bindings.FLOAT;
313                                         if (request.getClazz() == Float.class) binding = Bindings.FLOAT;
314                                         if (request.getClazz() == MutableFloat.class) binding = Bindings.MUTABLE_FLOAT;
315                                 } else {
316                                         FloatType type = new FloatType();
317                                         type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) );
318                                         type.setUnit( unit==null ? null : unit.value() );
319
320                                         if (request.getClazz() == float.class) binding = new FloatBindingDefault(type);
321                                         if (request.getClazz() == Float.class) binding = new FloatBindingDefault(type);
322                                         if (request.getClazz() == MutableFloat.class) binding = new MutableFloatBinding(type);
323                                 }
324                                 if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName());
325                                 
326                                 repository.put(request, binding);                       
327                                 return binding;
328                         }
329                         
330                         if (request.getClazz() == Double.class || request.getClazz() == double.class || request.getClazz() == MutableDouble.class) {
331                                 Binding binding = null; 
332                                 if (range==null && unit==null) {
333                                         if (request.getClazz() == double.class) binding = Bindings.DOUBLE;
334                                         if (request.getClazz() == Double.class) binding = Bindings.DOUBLE;
335                                         if (request.getClazz() == MutableDouble.class) binding = Bindings.MUTABLE_DOUBLE;
336                                 } else {
337                                         DoubleType type = new DoubleType();
338                                         type.setRange( range==null ? null : org.simantics.databoard.util.Range.valueOf(range.value()) );
339                                         type.setUnit( unit==null ? null : unit.value() );
340
341                                         if (request.getClazz() == double.class) binding = new DoubleBindingDefault(type);
342                                         if (request.getClazz() == Double.class) binding = new DoubleBindingDefault(type);
343                                         if (request.getClazz() == MutableDouble.class) binding = new MutableDoubleBinding(type);
344                                 }
345                                 
346                                 repository.put(request, binding);                       
347                                 return binding;
348                         }
349
350                         if (request.getClazz() == Boolean.class || request.getClazz() == boolean.class || request.getClazz() == MutableBoolean.class) {
351                                 Binding binding = null;
352                         if (request.getClazz() == Boolean.class || request.getClazz() == boolean.class) binding = Bindings.BOOLEAN;
353                         if (request.getClazz() == MutableBoolean.class) binding = Bindings.MUTABLE_BOOLEAN;             
354                                 if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName());
355                                 
356                                 repository.put(request, binding);                       
357                                 return binding;
358                         }
359
360                         if (request.getClazz() == String.class || request.getClazz() == MutableString.class) {
361                                 Length length = request.getAnnotation(Length.class);
362                                 MIMEType mimeType = request.getAnnotation(MIMEType.class);
363                                 Pattern pattern = request.getAnnotation(Pattern.class);
364                                 Binding binding = null;
365                                 if (length==null && mimeType==null && pattern==null) {
366                                         if (request.getClazz() == String.class) binding = Bindings.STRING;
367                                         if (request.getClazz() == MutableString.class) binding = Bindings.MUTABLE_STRING;
368                                         } else {
369                                                 StringType type = new StringType();
370                                                 type.setLength( length==null ? null : org.simantics.databoard.util.Range.valueOf( length.value()[0] ) );
371                                                 type.setMimeType( mimeType==null ? null : mimeType.value() );
372                                                 type.setPattern( pattern==null ? null : pattern.value() );                                      
373                                         if (request.getClazz() == String.class) binding = new StringBindingDefault(type);
374                                         if (request.getClazz() == MutableString.class) binding = new MutableStringBinding(type);
375                                         }
376                                 if (binding==null) throw new BindingConstructionException("Cannot bind to "+request.getClazz().getSimpleName());
377                                 
378                     repository.put(request, binding);                        
379                                 return binding;
380                         }
381                 }
382
383                 
384         // Custom factories
385         for (BindingProvider factory : subFactories) {
386                 Binding result = factory.provideBinding(this, request);                 
387                 if (result == null) continue;
388
389                 /// Array
390                 // If the result was an arraybinding, complete the composite binding
391                 if (result instanceof ArrayBinding) {
392                         ArrayBinding binding = (ArrayBinding) result;
393                         ArrayType type = binding.type();
394                 Length lengthAnnotation = request.getAnnotation(Length.class);
395                 Arguments argumentsAnnotation = request.getAnnotation(Arguments.class);
396                 Annotation[] componentAnnotations = request.dropAnnotations(1, lengthAnnotation);                               
397                 Class<?> componentClass = argumentsAnnotation!=null ? argumentsAnnotation.value()[0] : request.getClazz().getComponentType();
398
399                 org.simantics.databoard.util.Range[] lengths = null;
400                 if (lengthAnnotation!=null) {
401                         String[] strs = lengthAnnotation.value();
402                         lengths = new org.simantics.databoard.util.Range[strs.length];
403                     for (int i=0; i<strs.length; i++)
404                         lengths[i] = org.simantics.databoard.util.Range.valueOf(strs[i]);                               
405                 }
406                 
407                 if ( binding.componentBinding==null && request.componentBindings!=null ) binding.componentBinding = request.componentBindings[0];
408                 if ( binding.componentBinding == null) {
409                         BindingRequest componentRequest = request.componentRequests != null ? request.componentRequests[0] : null;
410                                 if (componentRequest==null) {
411                                         if (componentClass==null) {
412                                                 componentClass = Object.class;
413                                         // throw new BindingConstructionException("Cannot determine array component type");
414                                         }
415                                         componentRequest = new BindingRequest(componentClass, componentAnnotations);                    
416                                 }
417                                 
418                                 inprogress.put(request, binding);
419                                 binding.componentBinding = construct( componentRequest );
420                                 inprogress.remove(request);
421                         }
422                 
423                         type.componentType = binding.componentBinding.type();
424                 
425                 // Multi-dimensional Array. Apply range to sub-array-types
426                 ArrayType t = type;
427                 if (lengths!=null) {
428                     for (int i=0; i<lengths.length; i++) {
429                         t.setLength( lengths[i] );
430                         if (i<lengths.length-1)
431                             t = (ArrayType) t.componentType;
432                     }
433                 }                   
434                 }
435                 
436                 /// Map
437             // Map Type
438             if ( result instanceof MapBinding ) {
439                 MapBinding binding = (MapBinding) result;
440                 
441                         Arguments argumentsAnnotation = request.getAnnotation(Arguments.class);
442                         Annotation[] componentAnnotations = request.dropAnnotations( 2 );
443                 Class<?>[] arguments = argumentsAnnotation != null ? argumentsAnnotation.value() : null;
444
445                 BindingRequest keyRequest = null;
446                 BindingRequest valueRequest = null;
447                 
448                 Binding keyBinding = null;
449                 Binding valueBinding = null;
450                         
451                         if (binding.getKeyBinding() != null) {
452                             keyBinding = binding.getKeyBinding();
453                         }
454                         else if (request.componentBindings != null) {
455                                 keyBinding = request.componentBindings[0];
456                         }
457                         else if (request.componentRequests != null) {
458                                 keyRequest = request.componentRequests[0];
459                         }
460                         else {
461                                 Class<?> keyClass = arguments != null && arguments.length >= 1 ? arguments[0] : null;
462                     if (keyClass==null) {
463                         keyClass = Object.class;
464                         //throw new BindingConstructionException("Cannot determine key class, use @Arguments annotation");
465                     }
466                     keyRequest = new BindingRequest(keyClass, componentAnnotations);
467                         }
468                         
469                         if (binding.getValueBinding() != null) {
470                     valueBinding = binding.getValueBinding();
471                         }
472                         else if (request.componentBindings != null) {
473                                 valueBinding = request.componentBindings[1];
474                         }
475                         else if (request.componentRequests != null) {
476                                 valueRequest = request.componentRequests[1];
477                         }
478                         else {
479                                 Class<?> valueClass = arguments != null && arguments.length >= 2 ? arguments[1] : null;
480                     if (valueClass==null) {
481                         valueClass = Object.class;
482                         //throw new BindingConstructionException("Cannot determine value class, use @Arguments annotation");
483                     }
484                     valueRequest = new BindingRequest(valueClass, componentAnnotations);
485                         }
486                         
487                         inprogress.put(request, result);
488                         if (keyRequest!=null) {
489                                 keyBinding = construct( keyRequest );
490                         }
491                         if (valueRequest!=null) {
492                                 valueBinding = construct( valueRequest );
493                         }
494                 inprogress.remove(request);
495                 
496                 MapType type = binding.type();
497                 type.keyType = keyBinding.type();
498                 type.valueType = valueBinding.type();
499                         binding.setKeyBinding( keyBinding );
500                         binding.setValueBinding( valueBinding );
501             }
502                 
503                 /// Optional
504                 
505                 
506                 /// Union
507                 
508                 
509                 /// Record
510                 
511                 // Its complete, store to repository
512                         repository.put(request, result);                        
513                 return result;
514         }
515                 
516                 
517         
518         if (request.getClazz().isEnum()) {
519             Enum<?>[] enums = (Enum[]) request.getClazz().getEnumConstants();
520             UnionType type = new UnionType();               
521             type.components = new Component[enums.length];
522             for (int i=0; i<enums.length; i++) {
523                 String name = enums[i].name();
524                 type.components[i] = new Component(name, new RecordType(false));                
525             }                               
526             EnumClassBinding binding = new EnumClassBinding(type, (Class<Enum<?>>) request.getClazz() );                            
527             repository.put(request, binding);
528             return binding;
529         }               
530         
531         // Union type
532         if(request.hasAnnotation(Union.class)) {
533                 Union union = request.getAnnotation(Union.class);                       
534                 UnionType type = new UnionType();
535                         UnionClassBinding binding = new UnionClassBinding(type);
536             Class<?>[] cases = union.value();
537                         int count = cases.length; 
538             Binding[] componentBindings = new Binding[count];
539             type.components = new Component[ count ];
540             binding.componentClasses = new Class<?>[ count ];
541             binding.setComponentBindings(componentBindings);
542             
543                         inprogress.put(request, binding);                   
544             for(int i=0;i<count;++i) {
545                 binding.componentClasses[i]= cases[i];
546                 Component component = type.components[i] = new Component(cases[i].getSimpleName(), null /* updated later*/);
547                 BindingRequest componentRequest = new BindingRequest( cases[i] );
548                 Binding componentBinding = componentBindings[i] = construct( componentRequest );
549                 component.type = componentBinding.type();                       
550             }
551             
552                         inprogress.remove(request);
553                         repository.put(request, binding);
554             return binding;
555         }
556         
557         // Record type
558         {
559                 RecordType type = new RecordType();
560                 ClassInfo ci = ClassInfo.getInfo( request.getClazz() );
561                 boolean publicClass = Modifier.isPublic( request.getClazz().getModifiers() ); 
562                 boolean useAsmBinding = asmClassBindingFactory!=null && publicClass;
563                 RecordBindingProvider f = useAsmBinding ? asmClassBindingFactory : refClassBindingFactory;
564                 RecordBinding binding = f.provideRecordBinding( request.getClazz(), type);
565                 int count = ci.fields.length;
566                 Binding[] componentBindings = binding.getComponentBindings();                   
567             Component[] components = new Component[ count ];
568             type.setReferable( request.getAnnotation(Referable.class) != null );
569             type.setComponents( components );
570
571                         inprogress.put(request, binding);
572                         List<Integer> identifierIndices = new ArrayList<Integer>(1);
573             for(int i=0;i<type.getComponentCount();++i) {
574                 Field field = ci.fields[i];
575                 Annotation[] annotations = getFieldAnnotations(field);
576                 Class<?> fieldClass = field.getType(); 
577                 BindingRequest componentRequest = new BindingRequest(fieldClass, annotations);
578                 
579                 Name nameAnnotation = componentRequest.getAnnotation( Name.class );
580                 String fieldName = nameAnnotation!=null ? nameAnnotation.value() : field.getName();
581                 Component c = components[i] = new Component(fieldName, null /* updated later */);
582                 
583                 Identifier idAnnotation = componentRequest.getAnnotation( Identifier.class );
584                 if ( idAnnotation!=null ) {
585                         componentRequest = componentRequest.withAnnotations(componentRequest.dropAnnotations(1, idAnnotation));
586                         identifierIndices.add( i );
587                 }
588                 Binding componentBinding = componentBindings[i] = construct( componentRequest );
589                 c.type = componentBinding.type();
590             }
591             type.setIdentifiers(identifierIndices);
592                         inprogress.remove(request);
593                         repository.put(request, binding);
594             return binding;
595         }
596         }
597         
598         public Binding construct(BindingRequest request) 
599         throws BindingConstructionException
600         {
601                 if (failures.containsKey(request)) throw failures.get(request);
602                 if (inprogress.containsKey(request)) return inprogress.get(request);
603                 if (repository.containsRequest(request)) return repository.get(request);
604                 
605                 // Start construction
606                 try {                   
607                         Binding binding = doConstruct(request);
608                         
609                         // Avoid creating duplicate binding instances
610                         // Only check bindings that are complete
611                         if (inprogress.isEmpty() || isComplete(binding, new IdentityHashSet<Binding>())) {
612                                 Binding defaultBinding = defaultBindingFactory.getBinding(binding.type());
613                                 if (defaultBinding != null && defaultBinding.equals(binding))
614                                         binding = defaultBinding;
615                         }
616                         
617                         return binding;
618                 } catch (RangeException e) {
619                         inprogress.remove( request );
620                         BindingConstructionException bce = new BindingConstructionException( e ); 
621                         failures.put(request, bce);
622                         throw bce;
623                 } catch (BindingConstructionException e) {
624                         inprogress.remove( request );
625                         failures.put(request, e);
626                         throw e;
627                 } catch (Throwable t) {
628                         BindingConstructionException bce = new BindingConstructionException( t );
629                         inprogress.remove( request );
630                         failures.put(request, bce);
631                         throw bce;
632                 }
633         }
634         
635         boolean isComplete(Binding binding, IdentityHashSet<Binding> checked) {
636                 for (Binding b : inprogress.values())
637                         if (b == binding) return false;
638                 
639                 if (checked.contains(binding)) return true;
640                 
641                 if (binding.getComponentCount() > 0) {
642                         checked.add(binding);
643                         for (int i = 0; i < binding.getComponentCount(); i++) {
644                                 if (!isComplete(binding.getComponentBinding(i), checked))
645                                         return false;
646                         }
647                 }
648                 
649                 return true;
650         }
651
652         /**
653          * Get a binding to a Java Class. Type details can be modified with annotations.
654          * Please see the package org.simantics.databoard.annotations. 
655          * <p>
656          *  
657          * The whether the result binding is a completely mutable or not depends on the
658          * provided classes. For instance, fields such as Boolean, Integer, Long
659          * are not mutable, instead MutableBoolean, MutableInteger and MutableLong are.
660          * The length of Object[] is not mutable, but length of List<Object> is. <p>
661          * 
662          * Note, results are stored with strong reference into the repository assigned
663          * to this factory.<p> 
664          * 
665          * @see ClassBindingFactory  
666          * @param clazz
667          * @return binding
668          * @throws BindingConstructionException
669          */
670     @SuppressWarnings("unchecked")
671         public <T extends Binding> T getBinding(Class<?> clazz)
672     throws BindingConstructionException
673     {
674         return (T) construct( new BindingRequest(clazz) );
675     }
676     
677         /**
678          * Get a binding to a Java Class. Use this method to acquire class 
679          * parameters for a generics class. <p>
680          * 
681          * Example 1: 
682          * 
683          *    Binding binding = getBinding(Map.class, String.class, Integer.class);
684          *    Map<String, Integer> map = (Map<String, Integer>) binding.createDefault();
685          *    
686          * Example 2:
687          *    
688      *  Binding d = getBinding(List.class, Integer.class);
689          *  List<Integer> list = (List<Integer>) d.createRandom(5);
690          *    
691          * Example 3:
692          *    
693      *  Binding d = getBinding(List.class, List.class, Integer.class);
694          *  List<List<Integer>> list = (List<List<Integer>>) d.createRandom(5);
695          * 
696          * @see ClassBindingFactory  
697          * @param clazz
698          * @return binding
699          * @throws BindingConstructionException
700          */
701     @SuppressWarnings("unchecked")
702         public <T extends Binding> T getBinding(Class<?> clazz, Class<?>...parameters)
703     throws BindingConstructionException
704     {
705         Arguments args = new ArgumentImpl(parameters);
706         BindingRequest request = new BindingRequest( clazz, args );
707         return (T) construct( request );
708     }    
709         
710         public Binding getBinding(BindingRequest request) throws BindingConstructionException {         
711                 return construct(request);
712         }
713         
714         public Binding getBindingUnchecked(BindingRequest request) throws RuntimeBindingConstructionException {
715                 try {
716                         return construct(request);
717                 } catch (BindingConstructionException e) {
718                         throw new RuntimeBindingConstructionException(e);
719                 }
720         }       
721         
722         static Class<?>[] NO_CLASSES = new Class<?>[0];
723         
724     public static Annotation[] getFieldAnnotations(Field field) 
725     {
726         Annotation[] annotations = field.getAnnotations().clone();
727         ArrayList<Class<?>> list = new ArrayList<Class<?>>();
728         getTypes( field.getGenericType(), list );
729         Class<?> fieldClass = list.remove(0);
730         Class<?>[] parameterClasses = list.isEmpty() ? NO_CLASSES : list.toArray( NO_CLASSES );
731         
732         if (Set.class.isAssignableFrom(fieldClass) && parameterClasses!=null &&parameterClasses.length==1) {
733                 Annotation[] a2 = new Annotation[annotations.length+1];
734                 System.arraycopy(annotations, 0, a2, 0, annotations.length);
735                 
736                 Class<?> keyType = parameterClasses[0];
737                 a2[annotations.length] = new ArgumentImpl(keyType);                                             
738                 annotations = a2;
739         }       
740         if (Map.class.isAssignableFrom(fieldClass) && parameterClasses!=null && parameterClasses.length==2) {
741                 Annotation[] a2 = new Annotation[annotations.length+1];
742                 System.arraycopy(annotations, 0, a2, 0, annotations.length);
743                 
744                 Class<?> keyType = parameterClasses[0];
745                 Class<?> valueType = parameterClasses[1];
746                 a2[annotations.length] = new ArgumentImpl(keyType, valueType);                                          
747                 annotations = a2;
748         }       
749         if (List.class.isAssignableFrom(fieldClass) && parameterClasses!=null && parameterClasses.length==1) {
750                 Annotation[] a2 = new Annotation[annotations.length+1];
751                 System.arraycopy(annotations, 0, a2, 0, annotations.length);
752                 Class<?> componentType = parameterClasses[0];
753                 a2[annotations.length] = new ArgumentImpl(componentType);
754                 annotations = a2;
755         }       
756         
757         if (parameterClasses!=null && parameterClasses.length>0) {
758                 Annotation[] a2 = new Annotation[annotations.length+1];
759                 System.arraycopy(annotations, 0, a2, 0, annotations.length);
760                 a2[annotations.length] = new ArgumentImpl(parameterClasses);
761                 annotations = a2;
762         }
763         
764         return annotations;
765     }
766     
767     static void getTypes(Type type, Collection<Class<?>> result) 
768     {
769         if ( type instanceof Class ) {
770                 result.add( (Class<?>) type );
771         } else 
772         if ( type instanceof ParameterizedType ) {
773                         ParameterizedType p = (ParameterizedType) type;
774                         getTypes( p.getRawType(), result );
775                 for ( Type x : p.getActualTypeArguments() ) getTypes(x, result);
776         } else
777                 if ( type instanceof GenericArrayType) {
778                         GenericArrayType at = (GenericArrayType) type;
779                         Type componentType = at.getGenericComponentType();
780                         ArrayList<Class<?>> list = new ArrayList<Class<?>>(1);
781                         getTypes( componentType, list );
782                         // To Array class
783                         Object dummy = Array.newInstance(list.get(0), 0);
784                         result.add( dummy.getClass() ); 
785                 } else if ( type instanceof TypeVariable ) {
786                         result.add( Object.class );              
787                 } else
788                 throw new RuntimeException( type.getClass()+ " is not implemented" );
789     }
790     
791     /**
792      * Get all actual parameters types, incl. classes and generic types
793      * 
794      * @param f
795      * @return
796      */
797         static Type[] getParameterTypes(Field f) {
798                 Type t = f.getGenericType();
799                 if (t==null || t instanceof ParameterizedType==false) return new Class[0];              
800                 ParameterizedType p = (ParameterizedType) t;
801                 return p.getActualTypeArguments();
802     }
803     
804
805 }