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