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