0c07958d580e7e7b9707b044e79e84c761583238
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / adapter / AdapterFactory.java
1 /*******************************************************************************\r
2  *  Copyright (c) 2010 Association for Decentralized Information Management in\r
3  *  Industry THTH ry.\r
4  *  All rights reserved. This program and the accompanying materials\r
5  *  are made available under the terms of the Eclipse Public License v1.0\r
6  *  which accompanies this distribution, and is available at\r
7  *  http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  *  Contributors:\r
10  *      VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.databoard.adapter;
13
14 import java.util.ArrayList;\r
15 import java.util.Map;\r
16 \r
17 import org.apache.commons.collections.map.ReferenceMap;\r
18 import org.simantics.databoard.Units;\r
19 import org.simantics.databoard.binding.ArrayBinding;\r
20 import org.simantics.databoard.binding.Binding;\r
21 import org.simantics.databoard.binding.BooleanBinding;\r
22 import org.simantics.databoard.binding.MapBinding;\r
23 import org.simantics.databoard.binding.NumberBinding;\r
24 import org.simantics.databoard.binding.OptionalBinding;\r
25 import org.simantics.databoard.binding.RecordBinding;\r
26 import org.simantics.databoard.binding.StringBinding;\r
27 import org.simantics.databoard.binding.UnionBinding;\r
28 import org.simantics.databoard.binding.VariantBinding;\r
29 import org.simantics.databoard.binding.error.BindingException;\r
30 import org.simantics.databoard.binding.error.RuntimeBindingException;\r
31 import org.simantics.databoard.binding.impl.ArrayListBinding;\r
32 import org.simantics.databoard.binding.impl.BooleanArrayBinding;\r
33 import org.simantics.databoard.binding.impl.ByteArrayBinding;\r
34 import org.simantics.databoard.binding.impl.DoubleArrayBinding;\r
35 import org.simantics.databoard.binding.impl.FloatArrayBinding;\r
36 import org.simantics.databoard.binding.impl.IntArrayBinding;\r
37 import org.simantics.databoard.binding.impl.LongArrayBinding;\r
38 import org.simantics.databoard.type.ArrayType;\r
39 import org.simantics.databoard.type.NumberType;\r
40 import org.simantics.databoard.type.RecordType;\r
41 import org.simantics.databoard.type.UnionType;\r
42 import org.simantics.databoard.units.IUnitConverter;\r
43 import org.simantics.databoard.units.IdentityConverter;\r
44 import org.simantics.databoard.units.internal.UnitParseException;\r
45 import org.simantics.databoard.util.ObjectUtils;\r
46
47 /**
48  * AdapterRepository is a factory and a collection of adapters.
49  *
50  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
51  */
52 public class AdapterFactory {
53
54         @SuppressWarnings( "unchecked" )\r
55     Map<AdapterRequest, AbstractAdapter> cache = (Map<AdapterRequest, AbstractAdapter>) new ReferenceMap(ReferenceMap.SOFT, ReferenceMap.HARD);
56                 
57         public synchronized Adapter getAdapter(Binding domain, Binding range, boolean typeAdapter, boolean mustClone)
58         throws AdapterConstructionException
59         {               
60                 if ((!mustClone || domain.isImmutable()) && domain.equals(range)) return PassThruAdapter.PASSTHRU;\r
61                 \r
62                 if (domain.getClass() == range.getClass() &&\r
63                                 ( !mustClone || domain.isImmutable() ) &&\r
64                                 NumberBinding.class.isAssignableFrom( domain.getClass() ) ) {\r
65                         \r
66                         NumberBinding db = (NumberBinding) domain;\r
67                         NumberBinding rb = (NumberBinding) range;\r
68                         String u1 = db.type().getUnit();\r
69                         String u2 = rb.type().getUnit();\r
70                         if (u1==null || u2==null || u1.equals("") || u2.equals("") || u1.equals(u2)) return PassThruAdapter.PASSTHRU;\r
71                 }\r
72                 
73                 return getAdapterUnsynchronized(domain, range, typeAdapter, mustClone);
74         }\r
75
76         private AbstractAdapter getCached(AdapterRequest type) 
77         {
78                 return cache.get(type);
79         }                       
80         
81         private void cache(AdapterRequest type, AbstractAdapter binding) {
82                 cache.put(type, binding);
83         }       
84         
85         private void addToCache(AdapterRequest request, AbstractAdapter impl) {\r
86             impl.request = request;
87                 cache(request, impl);
88                 
89                 // This request applies to "must clone" request aswell, remember this implementation
90                 if (!request.mustClone && impl.clones) {
91                         request = new AdapterRequest(request.domain, request.range, true);
92                         cache(request, impl);
93                 }
94         }
95         
96         /**
97          * Create adapter, does not cache the result.
98          * 
99          * @param domain
100          * @param range
101          * @param typeAdapter if true, primitive conversion is allowed (e.g. int -> double)
102          * @return
103          */
104         private AbstractAdapter getAdapterUnsynchronized(Binding domain, Binding range, boolean typeAdapter, final boolean mustClone)
105         throws AdapterConstructionException
106         {
107                 if ( !mustClone && domain.equals(range) ) return PassThruAdapter.PASSTHRU;
108
109                 AdapterRequest req = new AdapterRequest(domain, range, mustClone);
110                 AbstractAdapter cachedResult = getCached(req);
111                 if (cachedResult!=null) return cachedResult;
112
113                 try {
114                 
115         if (domain instanceof RecordBinding && range instanceof RecordBinding)
116         {               
117                 final RecordBinding domainRecord = (RecordBinding) domain;
118                 final RecordBinding rangeRecord = (RecordBinding) range;
119                 RecordType domainType = domainRecord.type();
120                 RecordType rangeType = rangeRecord.type();  \r
121                 \r
122                 // Field-Map describes the index of the fields in domain for each field in range
123                 boolean requiresTypeAdapting = domainType.getComponentCount() != rangeType.getComponentCount(); 
124                 int fieldMap[] = new int[rangeType.getComponentCount()];
125                 for (int rangeIndex=0; rangeIndex<fieldMap.length; rangeIndex++)
126                 {
127                         String fieldName = rangeType.getComponent(rangeIndex).name;
128                         Integer domainIndex = domainType.getComponentIndex(fieldName);\r
129                         if (domainIndex!=null) {\r
130                                 fieldMap[rangeIndex] = domainIndex;\r
131                                 requiresTypeAdapting |= rangeIndex != domainIndex;\r
132                         } else {\r
133                             fieldMap[rangeIndex] = -1;\r
134                             requiresTypeAdapting = true;\r
135                         }
136                 }
137                 
138                 if (requiresTypeAdapting && !typeAdapter) {
139                         throw new AdapterConstructionException("Type Adapter required.");
140                 }
141                 
142                 final int len = rangeRecord.componentBindings.length;
143                 final AbstractAdapter[] componentAdapters = new AbstractAdapter[len];
144                 AbstractAdapter result = null;
145                 
146                 if (!requiresTypeAdapting) {
147                         // Normal Adapter
148                         result = new AbstractAdapter() {
149                                 @Override
150                                 public Object adapt(Object src) throws AdaptException {
151                                         try {
152                                                 Object values[] = new Object[len];
153                                                 for (int i=0; i<len; i++)
154                                                 {                                       
155                                                         Object srcValue = domainRecord.getComponent(src, i);
156                                                         Object dstValue = componentAdapters[i].adapt(srcValue);
157                                                         values[i] = dstValue;
158                                                 }
159                                                 return rangeRecord.create(values);
160                                         } catch (BindingException e) {
161                                                 throw new AdaptException(e);
162                                         }
163                                 }
164                         };
165                 } else {
166                         // Type Adapter - Type adapter maps fields of different order
167                         final int _fieldMap[] = fieldMap;
168                         result = new AbstractAdapter() {
169                                 @Override
170                                 public Object adapt(Object src) throws AdaptException {
171                                         try {
172                                                 Object values[] = new Object[len];
173                                                 for (int rangeIndex=0; rangeIndex<len; rangeIndex++)
174                                                 {                                                       
175                                                         int domainIndex = _fieldMap[rangeIndex];\r
176                                                         if (domainIndex>=0) {\r
177                                                                 Object srcValue = domainRecord.getComponent(src, domainIndex);\r
178                                                                 Object dstValue = componentAdapters[rangeIndex].adapt(srcValue);\r
179                                                                 values[rangeIndex] = dstValue;\r
180                                                         } else {\r
181                                                                 // Optional value\r
182                                                                 values[rangeIndex] = rangeRecord.componentBindings[rangeIndex].createDefault();\r
183                                                         }
184                                                 }
185                                                 return rangeRecord.create(values);
186                                         } catch (BindingException e) {
187                                                 throw new AdaptException(e);
188                                         }
189                                 }
190                         };
191                         result.typeAdapter = true;
192                 }
193                         
194                 addToCache(req, result);                
195                 result.clones = true;
196                 for (int rangeIndex=0; rangeIndex<len; rangeIndex++)
197                 {
198                         int domainIndex = fieldMap[rangeIndex];\r
199                         if (domainIndex>=0) {
200                                 componentAdapters[rangeIndex] = getAdapterUnsynchronized(domainRecord.componentBindings[domainIndex], rangeRecord.componentBindings[rangeIndex], typeAdapter, mustClone);
201                                 result.typeAdapter |= componentAdapters[rangeIndex].typeAdapter;
202                                 result.clones &= componentAdapters[rangeIndex].clones;\r
203                         }
204                 }
205                 return result;
206         }
207         
208         if (domain instanceof UnionBinding && range instanceof UnionBinding)
209         {
210                 final UnionBinding domainBinding = (UnionBinding) domain;
211                 final UnionBinding rangeBinding = (UnionBinding) range;
212                 UnionType domainType = domainBinding.type();
213                 UnionType rangeType = rangeBinding.type();
214                 
215                 // Tag-Map describes the index of the tag-types in domain for each tag-type in range
216                 boolean requiresTypeAdapting = domainType.getComponentCount() != rangeType.getComponentCount(); 
217                 int tagMap[] = new int[domainType.getComponentCount()];
218                 for (int domainIndex=0; domainIndex<tagMap.length; domainIndex++)
219                 {
220                         String fieldName = domainType.getComponent(domainIndex).name;
221                         Integer rangeIndex = rangeType.getComponentIndex(fieldName);
222                         if (rangeIndex==null) throw new AdapterConstructionException("The range UnionType does not have expected tag \""+fieldName+"\"");
223                         tagMap[domainIndex] = rangeIndex;
224                         requiresTypeAdapting |= rangeIndex != domainIndex;
225                 }                       
226                 
227                 if (requiresTypeAdapting && !typeAdapter) {
228                         throw new AdapterConstructionException("Type Adapter required.");
229                 }
230                 
231                 final AbstractAdapter[] componentAdapters = new AbstractAdapter[domainType.getComponentCount()];
232
233                 AbstractAdapter result = null;
234                 
235                 if (!requiresTypeAdapting) {
236                         // Normal adapter
237                         result = new AbstractAdapter() {
238                                 @Override
239                                 public Object adapt(Object obj) throws AdaptException {
240                                         try { 
241                                                 int tag = domainBinding.getTag(obj);
242                                                 Object srcValue = domainBinding.getValue(obj);                                  
243                                                 Object dstValue = componentAdapters[tag].adapt(srcValue);
244                                                 return rangeBinding.create(tag, dstValue);
245                                         } catch (BindingException e) {
246                                                 throw new AdaptException(e);
247                                         }
248                                 }
249                         };
250                 } else {
251                         // Type adapter, type adapter rearranges tag indices
252                         final int _tagMap[] = tagMap; 
253                         result = new AbstractAdapter() {
254                                 @Override
255                                 public Object adapt(Object obj) throws AdaptException {
256                                         try { 
257                                                 int domainTag = domainBinding.getTag(obj);
258                                                 int rangeTag = _tagMap[domainTag];
259                                                 // Domain Component Binding
260                                                 Object srcValue = domainBinding.getValue(obj);
261                                                 Object dstValue = componentAdapters[domainTag].adapt(srcValue);
262                                                 return rangeBinding.create(rangeTag, dstValue);
263                                         } catch (BindingException e) {
264                                                 throw new AdaptException(e);
265                                         }
266                                 }
267                         };
268                 }
269                 
270                 addToCache(req, result);
271                 result.clones = true;
272                 for (int domainIndex=0; domainIndex<domainType.getComponentCount(); domainIndex++)
273                 {
274                         int rangeIndex = tagMap[domainIndex];
275                         componentAdapters[domainIndex] = getAdapterUnsynchronized(domainBinding.getComponentBindings()[domainIndex], rangeBinding.getComponentBindings()[rangeIndex], typeAdapter, mustClone);
276                         result.typeAdapter |= componentAdapters[domainIndex].typeAdapter;
277                         result.clones &= componentAdapters[domainIndex].clones;
278                 }
279                         return result;
280         }       
281         
282         if (domain instanceof BooleanBinding && range instanceof BooleanBinding)
283         {
284                 final BooleanBinding domainBoolean = (BooleanBinding) domain;
285                 final BooleanBinding rangeBoolean = (BooleanBinding) range;
286                 AbstractAdapter result = new AbstractAdapter() {
287                                 @Override
288                                 public Object adapt(Object obj) throws AdaptException {
289                                         try {
290                                                 boolean value = domainBoolean.getValue_(obj);
291                                                 return rangeBoolean.create(value);
292                                         } catch (BindingException e) {
293                                                 throw new AdaptException(e);
294                                         }                                                                                                       
295                                 }
296                 };
297                 result.clones = mustClone;
298                 result.typeAdapter = true;\r
299                 addToCache(req, result);
300                 return result;
301         }               
302 \r
303         if (domain instanceof BooleanBinding && range instanceof NumberBinding)\r
304         {\r
305                 try {\r
306                         final BooleanBinding domainBoolean = (BooleanBinding) domain;\r
307                         final NumberBinding rangeNumber = (NumberBinding) range;\r
308                                 final Object falseValue = rangeNumber.create( Integer.valueOf(0) );\r
309                         final Object trueValue = rangeNumber.create( Integer.valueOf(1) );\r
310                         AbstractAdapter result = new AbstractAdapter() {\r
311                                         @Override\r
312                                         public Object adapt(Object obj) throws AdaptException {\r
313                                                 try {\r
314                                                         boolean value = domainBoolean.getValue_(obj);\r
315                                                         return value ? trueValue : falseValue;\r
316                                                 } catch (BindingException e) {\r
317                                                         throw new AdaptException(e);\r
318                                                 }                                                                                                       \r
319                                         }\r
320                         };\r
321                         result.clones = true;\r
322                         result.typeAdapter = true;\r
323                         addToCache(req, result);\r
324                         return result;\r
325                         } catch (BindingException e1) {\r
326                                 throw new AdapterConstructionException(e1);\r
327                         }\r
328         }               \r
329         \r
330         if (domain instanceof NumberBinding && range instanceof BooleanBinding)\r
331         {\r
332                 try {\r
333                         final NumberBinding domainNumber = (NumberBinding) domain;\r
334                         final BooleanBinding rangeBoolean = (BooleanBinding) range;\r
335                                 final Object zeroValue = domainNumber.create( Integer.valueOf(0) );\r
336                         AbstractAdapter result = new AbstractAdapter() {\r
337                                         @Override\r
338                                         public Object adapt(Object obj) throws AdaptException {\r
339                                                 try {\r
340                                                         Object value = domainNumber.getValue(obj);\r
341                                                         boolean bool = !domainNumber.equals(value, zeroValue);\r
342                                                         return rangeBoolean.create(bool);\r
343                                                 } catch (BindingException e) {\r
344                                                         throw new AdaptException(e);\r
345                                                 }                                                                                                       \r
346                                         }\r
347                         };\r
348                         result.clones = true;\r
349                         addToCache(req, result);\r
350                         return result;\r
351                         } catch (BindingException e1) {\r
352                                 throw new AdapterConstructionException(e1);\r
353                         }\r
354         }\r
355         
356         if (domain instanceof StringBinding && range instanceof StringBinding)
357         {
358                 final StringBinding domainString = (StringBinding) domain;
359                 final StringBinding rangeString = (StringBinding) range;
360                 AbstractAdapter result = new AbstractAdapter() {
361                                 @Override
362                                 public Object adapt(Object obj) throws AdaptException {
363                                         try {
364                                                 String value = domainString.getValue(obj);
365                                                 return rangeString.create(value);
366                                         } catch (BindingException e) {
367                                                 throw new AdaptException(e);
368                                         }                                                                                                       
369                                 }
370                 };
371                 result.clones = true;
372                 addToCache(req, result);
373                 return result;
374         }           \r
375         \r
376         if(domain instanceof StringBinding && range instanceof NumberBinding)\r
377         {\r
378                 final StringBinding domainString = (StringBinding) domain;\r
379                 final NumberBinding rangeString = (NumberBinding) range;\r
380                 AbstractAdapter result = new AbstractAdapter() {\r
381                                 @Override\r
382                                 public Object adapt(Object obj) throws AdaptException {\r
383                                         try {\r
384                                                 String value = domainString.getValue(obj);\r
385                                                 return rangeString.create(value);\r
386                                         } catch (BindingException e) {\r
387                                                 throw new AdaptException(e);\r
388                                         }                                                                                                       \r
389                                 }\r
390                 };\r
391                 result.clones = true;\r
392                 addToCache(req, result);\r
393                 return result;\r
394         }
395 \r
396         if(domain instanceof StringBinding && range instanceof BooleanBinding)\r
397         {\r
398                 final StringBinding domainString = (StringBinding) domain;\r
399                 final BooleanBinding rangeString = (BooleanBinding) range;\r
400                 AbstractAdapter result = new AbstractAdapter() {\r
401                                 @Override\r
402                                 public Object adapt(Object obj) throws AdaptException {\r
403                                         try {\r
404                                                 String value = domainString.getValue(obj);\r
405                                                 return rangeString.create(Boolean.parseBoolean(value));\r
406                                         } catch (BindingException e) {\r
407                                                 throw new AdaptException(e);\r
408                                         }                                                                                                       \r
409                                 }\r
410                 };\r
411                 result.clones = true;\r
412                 addToCache(req, result);\r
413                 return result;\r
414         }\r
415         
416         // XXX We can optimizes here by using primitives 
417         if (domain instanceof NumberBinding && range instanceof NumberBinding)
418         {
419                         final NumberBinding domainNumber = (NumberBinding) domain;
420                         final NumberBinding rangeNumber = (NumberBinding) range;
421                 
422                         String domainUnit = ((NumberType) domainNumber.type()).getUnit();
423                         String rangeUnit = ((NumberType) rangeNumber.type()).getUnit();\r
424                         IUnitConverter unitConverter;\r
425                         if(domainUnit == null || rangeUnit == null || domainUnit.equals(rangeUnit))\r
426                                 unitConverter = null;\r
427                         else\r
428                                 unitConverter = Units.createConverter(domainUnit, rangeUnit); \r
429                         /*if(domainUnit == null || domainUnit.equals("")) {\r
430                             if(rangeUnit == null || rangeUnit.equals(""))\r
431                                 unitConverter = null;\r
432                             else\r
433                                 unitConverter = null;\r
434 //                              throw new AdapterConstructionException("Cannot convert from a unitless type to a type with unit.");\r
435                         }\r
436                         else {\r
437                             if(rangeUnit == null || rangeUnit.equals(""))\r
438                                 unitConverter = null;                                   \r
439 //                              throw new AdapterConstructionException("Cannot convert from a type with unit to unitless type.");\r
440                             else\r
441                                 unitConverter = Units.createConverter(domainUnit, rangeUnit); \r
442                         }       */                      
443                         boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
444                         boolean doPrimitiveConversion = !domainNumber.type().getClass().equals( rangeNumber.type().getClass() );                                
445                         if (doPrimitiveConversion && !typeAdapter)
446                                 throw new AdapterConstructionException("Type mismatch, use Type Adapter instead.");
447                                                         
448                         AbstractAdapter result;
449                         if (!doUnitConversion) {
450                                 result = new AbstractAdapter() {
451                                         @Override
452                                         public Object adapt(Object obj) throws AdaptException {
453                                                 Number value;
454                                                 try {                                                   
455                                                         value = domainNumber.getValue(obj);
456                                                         return rangeNumber.create(value);
457                                                 } catch (BindingException e) {
458                                                         throw new AdaptException(e);
459                                                 }
460                                         }
461                                 };
462                         } else {
463                                 final IUnitConverter _unitConverter = unitConverter;
464                                 result = new AbstractAdapter() {
465                                         @Override
466                                         public Object adapt(Object obj) throws AdaptException {
467                                                 try {
468                                                         Number value = domainNumber.getValue(obj);
469                                                         double convertedValue = _unitConverter.convert(value.doubleValue());
470                                                         return rangeNumber.create(Double.valueOf(convertedValue));
471                                                 } catch (BindingException e) {
472                                                         throw new AdaptException(e);
473                                                 }
474                                         }
475                                 };
476                         }
477                         result.typeAdapter = doPrimitiveConversion;
478                         result.clones = true;
479                         addToCache(req, result);
480                         return result;                  
481         }               
482         
483         if (domain instanceof BooleanArrayBinding && range instanceof BooleanArrayBinding)
484         {
485                 final BooleanArrayBinding domainArray = (BooleanArrayBinding) domain;
486                 final BooleanArrayBinding rangeArray = (BooleanArrayBinding) range;
487                 AbstractAdapter result = new AbstractAdapter() {
488                                 @Override
489                                 public Object adapt(Object obj) throws AdaptException {
490                                         try {
491                                                 boolean[] data = domainArray.getArray(obj);\r
492                                                 if (mustClone) data = data.clone();
493                                                 return rangeArray.create(data);
494                                         } catch (BindingException e) {
495                                                 throw new AdaptException(e);
496                                         }                                               
497                                 }
498                 };
499                 result.clones = true;
500                 addToCache(req, result);
501                 return result;
502         }               
503
504         if (domain instanceof ByteArrayBinding && range instanceof ByteArrayBinding)
505         {
506                 final ByteArrayBinding domainArray = (ByteArrayBinding) domain;
507                 final ByteArrayBinding rangeArray = (ByteArrayBinding) range;
508                 
509                         String domainUnit = ((NumberType) ((ArrayType)domainArray.type()).componentType).getUnit();
510                         String rangeUnit = ((NumberType) ((ArrayType)rangeArray.type()).componentType).getUnit();               
511                         IUnitConverter unitConverter = ObjectUtils.objectEquals(domainUnit, rangeUnit) ? null : Units.createConverter(domainUnit, rangeUnit);
512                         boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
513
514                         AbstractAdapter result;
515                         if (doUnitConversion) {
516                                 final IUnitConverter _unitConverter = unitConverter; 
517                                 result = new AbstractAdapter() {
518                                         @Override
519                                         public Object adapt(Object obj) throws AdaptException {
520                                                 try {
521                                                         byte[] data = domainArray.getArray(obj);                                
522                                                         for (int i=0; i<data.length; i++) {
523                                                                 byte value = data[i];
524                                                                 double convertedValue = _unitConverter.convert((double)value);
525                                                                 data[i] = (byte) convertedValue;
526                                                         }
527                                                         return rangeArray.create(data);
528                                                 } catch (BindingException e) {
529                                                         throw new AdaptException(e);
530                                                 }
531                                         }
532                                 };
533                         } else {
534                                 result = new AbstractAdapter() {
535                                         @Override
536                                         public Object adapt(Object obj) throws AdaptException {
537                                                 try {
538                                                         byte[] data = domainArray.getArray(obj);\r
539                                                         if (mustClone) data = data.clone();                                                     
540                                                         return rangeArray.create(data);
541                                                 } catch (BindingException e) {
542                                                         throw new AdaptException(e);
543                                                 }                                                       
544                                         }
545                                 };
546                         }
547                 result.clones = true;
548                 addToCache(req, result);
549                 return result;
550         }               
551         
552         if (domain instanceof IntArrayBinding && range instanceof IntArrayBinding)
553         {
554                 final IntArrayBinding domainArray = (IntArrayBinding) domain;
555                 final IntArrayBinding rangeArray = (IntArrayBinding) range;
556                 
557                         String domainUnit = ((NumberType) ((ArrayType)domainArray.type()).componentType).getUnit();
558                         String rangeUnit = ((NumberType) ((ArrayType)rangeArray.type()).componentType).getUnit();               
559                         IUnitConverter unitConverter = ObjectUtils.objectEquals(domainUnit, rangeUnit) ||\r
560                                         domainUnit == null || rangeUnit == null ? null : Units.createConverter(domainUnit, rangeUnit);
561                         boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
562                 
563                         AbstractAdapter result;
564                         if (doUnitConversion) {
565                                 final IUnitConverter _unitConverter = unitConverter; 
566                                 result = new AbstractAdapter() {
567                                         @Override
568                                         public Object adapt(Object obj) throws AdaptException {
569                                                 try {
570                                                         int[] data = domainArray.getArray(obj);                         
571                                                         for (int i=0; i<data.length; i++) {
572                                                                 int value = data[i];
573                                                                 double convertedValue = _unitConverter.convert((double)value);
574                                                                 data[i] = (int) convertedValue;
575                                                         }
576                                                         return rangeArray.create(data);
577                                                 } catch (BindingException e) {
578                                                         throw new AdaptException(e);
579                                                 }
580                                         }
581                                 };
582                         } else {
583                                 result = new AbstractAdapter() {
584                                         @Override
585                                         public Object adapt(Object obj) throws AdaptException {
586                                                 try {
587                                                         int[] data = domainArray.getArray(obj);\r
588                                                         if (mustClone) data = data.clone();                                                     
589                                                         return rangeArray.create(data);
590                                                 } catch (BindingException e) {
591                                                         throw new AdaptException(e);
592                                                 }                                                       
593                                         }
594                                 };
595                         }
596                 
597                 result.clones = true;
598                 addToCache(req, result);
599                 return result;
600         }               
601
602         if (domain instanceof LongArrayBinding && range instanceof LongArrayBinding)
603         {
604                 final LongArrayBinding domainArray = (LongArrayBinding) domain;
605                 final LongArrayBinding rangeArray = (LongArrayBinding) range;
606
607                         String domainUnit = ((NumberType) ((ArrayType)domainArray.type()).componentType).getUnit();
608                         String rangeUnit = ((NumberType) ((ArrayType)rangeArray.type()).componentType).getUnit();               
609                         IUnitConverter unitConverter = ObjectUtils.objectEquals(domainUnit, rangeUnit) ||\r
610                                         domainUnit == null || rangeUnit == null ? null : Units.createConverter(domainUnit, rangeUnit);
611                         boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
612                 
613                         AbstractAdapter result;
614                         if (doUnitConversion) {
615                                 final IUnitConverter _unitConverter = unitConverter; 
616                                 result = new AbstractAdapter() {
617                                         @Override
618                                         public Object adapt(Object obj) throws AdaptException {
619                                                 try {
620                                                         long[] data = domainArray.getArray(obj);                                
621                                                         for (int i=0; i<data.length; i++) {
622                                                                 long value = data[i];
623                                                                 double convertedValue = _unitConverter.convert((double)value);
624                                                                 data[i] = (long) convertedValue;
625                                                         }
626                                                         return rangeArray.create(data);
627                                                 } catch (BindingException e) {
628                                                         throw new AdaptException(e);
629                                                 }
630                                         }
631                                 };
632                         } else {
633                                 result = new AbstractAdapter() {
634                                         @Override
635                                         public Object adapt(Object obj) throws AdaptException {
636                                                 try {
637                                                         long[] data = domainArray.getArray(obj);\r
638                                                         if (mustClone) data = data.clone();                                                     
639                                                         return rangeArray.create(data);
640                                                 } catch (BindingException e) {
641                                                         throw new AdaptException(e);
642                                                 }
643                                         }
644                                 };
645                         }
646
647                 result.clones = true;
648                 addToCache(req, result);
649                 return result;
650         }               
651
652         if (domain instanceof FloatArrayBinding && range instanceof FloatArrayBinding)
653         {
654                 final FloatArrayBinding domainArray = (FloatArrayBinding) domain;
655                 final FloatArrayBinding rangeArray = (FloatArrayBinding) range;
656
657                         String domainUnit = ((NumberType) ((ArrayType)domainArray.type()).componentType).getUnit();
658                         String rangeUnit = ((NumberType) ((ArrayType)rangeArray.type()).componentType).getUnit();               
659                         IUnitConverter unitConverter = ObjectUtils.objectEquals(domainUnit, rangeUnit) ||\r
660                                         domainUnit == null || rangeUnit == null ? null : Units.createConverter(domainUnit, rangeUnit);
661                         boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
662                 
663                         AbstractAdapter result;
664                         if (doUnitConversion) {
665                                 final IUnitConverter _unitConverter = unitConverter; 
666                                 result = new AbstractAdapter() {
667                                         @Override
668                                         public Object adapt(Object obj) throws AdaptException {
669                                                 try {
670                                                         float[] data = domainArray.getArray(obj);                               
671                                                         for (int i=0; i<data.length; i++) {
672                                                                 float value = data[i];
673                                                                 double convertedValue = _unitConverter.convert((double)value);
674                                                                 data[i] = (float) convertedValue;
675                                                         }
676                                                         return rangeArray.create(data);
677                                                 } catch (BindingException e) {
678                                                         throw new AdaptException(e);
679                                                 }
680                                         }
681                                 };
682                         } else {
683                                 result = new AbstractAdapter() {
684                                         @Override
685                                         public Object adapt(Object obj) throws AdaptException {
686                                                 try {
687                                                         float[] data = domainArray.getArray(obj);\r
688                                                         if (mustClone) data = data.clone();                                                     
689                                                         return rangeArray.create(data);
690                                                 } catch (BindingException e) {
691                                                         throw new AdaptException(e);
692                                                 }
693                                         }
694                                 };
695                         }
696                         
697                 result.clones = true;
698                 addToCache(req, result);
699                 return result;
700         }               
701
702         if (domain instanceof DoubleArrayBinding && range instanceof DoubleArrayBinding)
703         {
704                 final DoubleArrayBinding domainArray = (DoubleArrayBinding) domain;
705                 final DoubleArrayBinding rangeArray = (DoubleArrayBinding) range;
706
707                         String domainUnit = ((NumberType) ((ArrayType)domainArray.type()).componentType).getUnit();
708                         String rangeUnit = ((NumberType) ((ArrayType)rangeArray.type()).componentType).getUnit();               
709                         IUnitConverter unitConverter = ObjectUtils.objectEquals(domainUnit, rangeUnit) \r
710                                         || domainUnit == null || rangeUnit == null ? null : Units.createConverter(domainUnit, rangeUnit);
711                         boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
712                 
713                         AbstractAdapter result;
714                         if (doUnitConversion) {
715                                 final IUnitConverter _unitConverter = unitConverter; 
716                                 result = new AbstractAdapter() {
717                                         @Override
718                                         public Object adapt(Object obj) throws AdaptException {
719                                                 try {
720                                                         double[] data = domainArray.getArray(obj);                              
721                                                         for (int i=0; i<data.length; i++) {
722                                                                 double value = data[i];
723                                                                 double convertedValue = _unitConverter.convert(value);
724                                                                 data[i] = convertedValue;
725                                                         }
726                                                         return rangeArray.create(data);
727                                                 } catch (BindingException e) {
728                                                         throw new AdaptException(e);
729                                                 }
730                                         }
731                                 };
732                         } else {
733                                 result = new AbstractAdapter() {
734                                         @Override
735                                         public Object adapt(Object obj) throws AdaptException {
736                                                 try {
737                                                         double[] data = domainArray.getArray(obj);\r
738                                                         if (mustClone) data = data.clone();                                                     
739                                                         return rangeArray.create(data);
740                                                 } catch (BindingException e) {
741                                                         throw new AdaptException(e);
742                                                 }
743                                         }
744                                 };
745                         }
746                 
747                 result.clones = true;
748                 addToCache(req, result);
749                 return result;
750         }               
751
752         if (domain instanceof ArrayBinding && range instanceof ArrayBinding)
753         {
754                 final ArrayBinding domainBinding = (ArrayBinding) domain;
755                 final ArrayBinding rangeBinding = (ArrayBinding) range;
756                 final AbstractAdapter componentAdapter = getAdapterUnsynchronized(domainBinding.getComponentBinding(), rangeBinding.getComponentBinding(), typeAdapter, mustClone);
757                 AbstractAdapter result = new AbstractAdapter() {
758                                 @Override
759                                 public Object adapt(Object obj) throws AdaptException {
760                                         try {
761                                                 int len = domainBinding.size(obj);
762                                                 ArrayList<Object> array = new ArrayList<Object>(len);
763                                                 for (int i=0; i<len; i++)
764                                                 {
765                                                         Object srcValue = domainBinding.get(obj, i);
766                                                         Object dstValue = componentAdapter.adapt(srcValue);
767                                                         array.add(dstValue);
768                                                 }                                       
769                                                 return rangeBinding instanceof ArrayListBinding ? array : rangeBinding.create(array);
770                                         } catch (BindingException e) {
771                                                 throw new AdaptException(e);
772                                         }
773                                 }
774                 };
775                 
776                 result.clones = componentAdapter.clones;
777                 addToCache(req, result);
778                 return result;
779         }
780         
781         if (domain instanceof OptionalBinding && range instanceof OptionalBinding)
782         {
783                 final OptionalBinding domainBinding = (OptionalBinding) domain;
784                 final OptionalBinding rangeBinding = (OptionalBinding) range;
785                 final AbstractAdapter componentAdapter = getAdapterUnsynchronized(domainBinding.componentBinding, rangeBinding.componentBinding, typeAdapter, mustClone);
786                 AbstractAdapter result = new AbstractAdapter() {
787                                 @Override
788                                 public Object adapt(Object obj) throws AdaptException {
789                                         try {
790                                                 if (!domainBinding.hasValue(obj)) return rangeBinding.createNoValue();
791                                                 Object value = domainBinding.getValue(obj);
792                                                 value = componentAdapter.adapt(value); 
793                                                 return rangeBinding.createValue(value);
794                                         } catch (BindingException e) {
795                                                 throw new AdaptException(e);
796                                         }
797                                 }
798                 };
799                 result.typeAdapter = componentAdapter.typeAdapter;
800                 result.clones = componentAdapter.clones;
801                 addToCache(req, result);
802                 return result;
803         }\r
804         \r
805         // Adapt a non-optional value to an optional value\r
806         if (range instanceof OptionalBinding && !(domain instanceof OptionalBinding))\r
807         {\r
808                 final Binding domainBinding = domain;\r
809                 final OptionalBinding rangeBinding = (OptionalBinding) range;\r
810                 final AbstractAdapter componentAdapter = getAdapterUnsynchronized(domainBinding, rangeBinding.componentBinding, typeAdapter, mustClone);\r
811                 AbstractAdapter result = new AbstractAdapter() {\r
812                                 @Override\r
813                                 public Object adapt(Object obj) throws AdaptException {\r
814                                         try {\r
815                                                 obj = componentAdapter.adapt(obj); \r
816                                                 return rangeBinding.createValue(obj);\r
817                                         } catch (BindingException e) {\r
818                                                 throw new AdaptException(e);\r
819                                         }\r
820                                 }\r
821                 };\r
822                 result.typeAdapter = componentAdapter.typeAdapter;\r
823                 result.clones = componentAdapter.clones;\r
824                 addToCache(req, result);\r
825                 return result;                  \r
826         }
827         
828         if (domain instanceof VariantBinding && range instanceof VariantBinding)
829         {               
830                 final VariantBinding domainBinding = (VariantBinding) domain;
831                 final VariantBinding rangeBinding = (VariantBinding) range;
832                 AbstractAdapter result = new AbstractAdapter() {
833                                 @Override
834                                 public Object adapt(Object obj) throws AdaptException {                                 
835                                         try {\r
836                                                 
837                                                 Binding domainValueBinding = domainBinding.getContentBinding(obj);
838                                                 Object domainObject = domainBinding.getContent(obj, domainValueBinding);\r
839                                                 if (mustClone && domainObject!=obj) {\r
840                                                         Adapter valueAdapter = getAdapterUnsynchronized(domainValueBinding, domainValueBinding, false, true);\r
841                                                         domainObject = valueAdapter.adapt(domainObject); \r
842                                                 }
843                                                 Object rangeVariant = rangeBinding.create(domainValueBinding, domainObject);
844                                                 return rangeVariant;
845                                         } catch (BindingException e) {
846                                                 throw new AdaptException(e);
847                                         } catch (AdapterConstructionException e) {\r
848                                                 throw new AdaptException(e);\r
849                                         }
850                                 }
851                 };
852                 result.clones = mustClone;
853                 addToCache(req, result);
854                 return result;
855         }\r
856         \r
857         if (range instanceof VariantBinding && !(domain instanceof VariantBinding))\r
858         {\r
859                 // Default to just wrapping the domain type\r
860                 final VariantBinding rangeBinding = (VariantBinding)range;\r
861                 final Binding domainBinding = domain;\r
862                 AbstractAdapter result = new AbstractAdapter() {\r
863                                 @Override\r
864                                 public Object adapt(Object obj) throws AdaptException {\r
865                                         try {\r
866                                                 if (mustClone) {\r
867                                                         Adapter valueAdapter;\r
868                                                                 valueAdapter = getAdapterUnsynchronized(domainBinding, domainBinding, false, true);\r
869                                                         obj = valueAdapter.adapt(obj);\r
870                                                 }\r
871                                                 return rangeBinding.create(domainBinding, obj);\r
872                                         } catch (AdapterConstructionException | BindingException e) {\r
873                                                 throw new AdaptException(e);\r
874                                         }\r
875                                 }\r
876                 };\r
877                 result.clones = mustClone;\r
878                 addToCache(req, result);\r
879                 return result;\r
880         }
881
882         if (domain instanceof MapBinding && range instanceof MapBinding)
883         {
884                 final MapBinding domainBinding = (MapBinding) domain;
885                 final MapBinding rangeBinding = (MapBinding) range;
886                 final AbstractAdapter keyAdapter = getAdapterUnsynchronized(domainBinding.getKeyBinding(), rangeBinding.getKeyBinding(), typeAdapter, mustClone);
887                 final AbstractAdapter valueAdapter = getAdapterUnsynchronized(domainBinding.getValueBinding(), rangeBinding.getValueBinding(), typeAdapter, mustClone);
888                 AbstractAdapter result = new AbstractAdapter() {
889                                 @Override
890                                 public Object adapt(Object obj) throws AdaptException {
891                                         try {
892                                                 int len = domainBinding.size(obj);
893                                                 Object domainKeys[] = domainBinding.getKeys(obj);
894                                                 Object domainValues[] = domainBinding.getValues(obj);
895                                                 Object rangeKeys[] = new Object[len];
896                                                 Object rangeValues[] = new Object[len];
897                                                 for (int i=0; i<len; i++) {
898                                                         Object domainKey = domainKeys[i];
899                                                         Object domainValue = domainValues[i];
900                                                         Object rangeKey = keyAdapter.adapt(domainKey);
901                                                         Object rangeValue = valueAdapter.adapt(domainValue);
902                                                         rangeKeys[i] = rangeKey;
903                                                         rangeValues[i] = rangeValue;
904                                                 }
905                                                 Object rangeMap = rangeBinding.create(rangeKeys, rangeValues);
906                                                 return rangeMap;
907                                         } catch (BindingException e) {
908                                                 throw new AdaptException(e);
909                                         }
910                                 }
911                 };
912                 result.typeAdapter |= keyAdapter.typeAdapter | valueAdapter.typeAdapter;
913                 result.clones = keyAdapter.clones & valueAdapter.clones;                
914                 addToCache(req, result);
915                 return result;
916         }
917 /*      
918         // Special-Case: Convert Union to its Composite
919         if (domain instanceof UnionBinding) {
920                 final UnionType ut = (UnionType) domain.getDataType();
921                 final UnionBinding ub = (UnionBinding) domain;
922                 Binding[] cbs = ub.getComponentBindings();              
923                 for (int i=0; i<cbs.length; i++)
924                 {
925                         Binding domainCompositeBinding = cbs[i];
926                         if (ut.getComponent(i).type.equals(range.getDataType())) {
927                                 final AbstractAdapter union2CompositeAdapter = getAdapterUnsynchronized(domainCompositeBinding, range, allowPrimitiveConversion);                               
928                                 final int tag = i;
929                         AbstractAdapter result = new AbstractAdapter() {
930                                         @Override
931                                         public Object adapt(Object obj) throws AdaptException {
932                                                 try {
933                                                         Object domainUnion = obj;
934                                                         int domainTag = ub.getTag(domainUnion);
935                                                         if (domainTag != tag) {
936                                                                 throw new AdaptException("This adapter can adapt only "+ub.getDataType().getComponents()[tag].name+"s");
937                                                         }
938                                                         Object domainComposite = ub.getValue(domainUnion);
939                                                         Object rangeComposite = union2CompositeAdapter.adapt(domainComposite);
940                                                         return rangeComposite;
941                                                 } catch (BindingException e) {
942                                                         throw new AdaptException(e);
943                                                 }
944                                         }
945                         };
946                         result.hasPrimitiveConversion = union2CompositeAdapter.hasPrimitiveConversion;
947                         addToCache(pair, result);
948                         return result;
949                         }
950                 }
951         }
952         
953         // Special-Case: Convert Composite to Union
954         if (range instanceof UnionBinding) {
955                 final UnionType ut = (UnionType) range.getDataType();
956                 final UnionBinding ub = (UnionBinding) range;
957                 Binding cbs[] = ub.getComponentBindings();
958                 for (int i=0; i<cbs.length; i++) {
959                         Binding rangeCompositeBinding = cbs[i];
960                         if (ut.getComponent(i).type.equals(domain.getDataType())) {
961                                 final AbstractAdapter domainObject2RangeCompositeAdapter = getAdapterUnsynchronized(rangeCompositeBinding, domain, allowPrimitiveConversion);                                   
962                                 final int tag = i;
963                         AbstractAdapter result = new AbstractAdapter() {
964                                         @Override
965                                         public Object adapt(Object obj) throws AdaptException {
966                                                 try {
967                                                         Object domainObject = obj;
968                                                         Object rangeComposite = domainObject2RangeCompositeAdapter.adapt(domainObject);
969                                                     Object rangeUnion = ub.create(tag, rangeComposite);
970                                                         return rangeUnion;
971                                                 } catch (BindingException e) {
972                                                         throw new AdaptException(e);
973                                                 }
974                                         }
975                         };
976                         result.hasPrimitiveConversion = domainObject2RangeCompositeAdapter.hasPrimitiveConversion;
977                         addToCache(pair, result);
978                         return result;
979                         }
980                 }
981         }
982 */      
983         
984                 } catch (UnitParseException e) {
985                         throw new AdapterConstructionException(e.getMessage(), e); 
986                 }                               
987
988                 StringBuilder error = new StringBuilder();
989                 error.append("Could not create ");
990                 if (mustClone) error.append("cloning ");
991                 if (typeAdapter) error.append("type");
992                 error.append("adapter (");
993                 error.append("domain=");
994                 error.append(domain.type().toSingleLineString());
995                 error.append(", range=");
996                 error.append(range.type().toSingleLineString());
997                 error.append(")");
998                 
999         throw new AdapterConstructionException(error.toString());
1000         }
1001         
1002         
1003         \r
1004     /**\r
1005      * Adapt a value of one type to another. \r
1006      * \r
1007      * @param value\r
1008      * @param domain\r
1009      * @param range\r
1010      * @return adapted value\r
1011      * @throws AdapterConstructionException\r
1012      * @throws AdaptException\r
1013      */\r
1014     public Object adapt(Object value, Binding domain, Binding range)\r
1015     throws AdaptException\r
1016     {\r
1017         if (domain == range) return value;\r
1018         try {\r
1019                 if (range instanceof VariantBinding && !(domain instanceof VariantBinding)) {\r
1020                         // Default to just wrapping the value (avoid adapter construction to save memory)\r
1021                         return ((VariantBinding)range).create(domain, value);\r
1022                 }\r
1023                         return getAdapter(domain, range, true, false).adapt(value);\r
1024                 } catch (AdapterConstructionException | BindingException e) {\r
1025                         throw new AdaptException(e);\r
1026                 }\r
1027     }\r
1028     \r
1029     /**\r
1030      * Adapt a value of one type to another\r
1031      * \r
1032      * @param value\r
1033      * @param domain\r
1034      * @param range\r
1035      * @return adapted value\r
1036      * @throws AdapterConstructionException\r
1037      * @throws AdaptException\r
1038      */\r
1039     public Object adaptUnchecked(Object value, Binding domain, Binding range)\r
1040     throws RuntimeAdapterConstructionException, RuntimeAdaptException\r
1041     {\r
1042         if (domain == range) return value;\r
1043         try {\r
1044                 if (range instanceof VariantBinding && !(domain instanceof VariantBinding)) {\r
1045                         // Default to just wrapping the value (avoid adapter construction to save memory)\r
1046                         return ((VariantBinding)range).create(domain, value);\r
1047                 }\r
1048                 return getAdapter(domain, range, true, false).adaptUnchecked(value);\r
1049                 } catch (RuntimeAdapterConstructionException | RuntimeBindingException e) {\r
1050                         throw new RuntimeAdaptException(new AdaptException(e.getCause()));\r
1051                 } catch (AdapterConstructionException | BindingException e) {\r
1052                         throw new RuntimeAdaptException(new AdaptException(e));\r
1053                 }\r
1054     }\r
1055     \r
1056     /**\r
1057      * Clone a value to a type to another. Bindings that handle immutable values\r
1058      * may return the same instance, others will guarantee a complete copy.  \r
1059      * \r
1060      * @param value\r
1061      * @param domain\r
1062      * @param range\r
1063      * @return adapted value\r
1064      * @throws AdapterConstructionException\r
1065      * @throws AdaptException\r
1066      */\r
1067     public Object clone(Object value, Binding domain, Binding range)\r
1068     throws AdaptException\r
1069     {\r
1070         try {\r
1071                         return getAdapter(domain, range, true, true).adapt(value);\r
1072                 } catch (AdapterConstructionException e) {\r
1073                         throw new AdaptException(e);\r
1074                 }\r
1075     }\r
1076     \r
1077 \r
1078     /**\r
1079      * Clone a value of one binding to another. Bindings that handle immutable values\r
1080      * may return the same instance, others will guarantee a complete copy.\r
1081      * \r
1082      * @param value\r
1083      * @param domain\r
1084      * @param range\r
1085      * @return adapted value\r
1086      * @throws AdapterConstructionException\r
1087      * @throws AdaptException\r
1088      */\r
1089     public Object cloneUnchecked(Object value, Binding domain, Binding range)\r
1090     throws RuntimeAdapterConstructionException, RuntimeAdaptException\r
1091     {\r
1092         try {\r
1093                         return getAdapter(domain, range, true, true).adapt(value);\r
1094                 } catch (AdaptException e) {\r
1095                         throw new RuntimeAdaptException(e);\r
1096                 } catch (RuntimeAdapterConstructionException e) {\r
1097                         throw new RuntimeAdaptException(new AdaptException(e.getCause()));\r
1098                 } catch (AdapterConstructionException e) {\r
1099                         throw new RuntimeAdaptException(new AdaptException(e));\r
1100                 }\r
1101     }    \r
1102                 
1103 }
1104