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