]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.databoard/src/org/simantics/databoard/adapter/AdapterFactory.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / adapter / AdapterFactory.java
diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/adapter/AdapterFactory.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/adapter/AdapterFactory.java
new file mode 100644 (file)
index 0000000..0c07958
--- /dev/null
@@ -0,0 +1,1104 @@
+/*******************************************************************************\r
+ *  Copyright (c) 2010 Association for Decentralized Information Management in\r
+ *  Industry THTH ry.\r
+ *  All rights reserved. This program and the accompanying materials\r
+ *  are made available under the terms of the Eclipse Public License v1.0\r
+ *  which accompanies this distribution, and is available at\r
+ *  http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ *  Contributors:\r
+ *      VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.databoard.adapter;
+
+import java.util.ArrayList;\r
+import java.util.Map;\r
+\r
+import org.apache.commons.collections.map.ReferenceMap;\r
+import org.simantics.databoard.Units;\r
+import org.simantics.databoard.binding.ArrayBinding;\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.binding.BooleanBinding;\r
+import org.simantics.databoard.binding.MapBinding;\r
+import org.simantics.databoard.binding.NumberBinding;\r
+import org.simantics.databoard.binding.OptionalBinding;\r
+import org.simantics.databoard.binding.RecordBinding;\r
+import org.simantics.databoard.binding.StringBinding;\r
+import org.simantics.databoard.binding.UnionBinding;\r
+import org.simantics.databoard.binding.VariantBinding;\r
+import org.simantics.databoard.binding.error.BindingException;\r
+import org.simantics.databoard.binding.error.RuntimeBindingException;\r
+import org.simantics.databoard.binding.impl.ArrayListBinding;\r
+import org.simantics.databoard.binding.impl.BooleanArrayBinding;\r
+import org.simantics.databoard.binding.impl.ByteArrayBinding;\r
+import org.simantics.databoard.binding.impl.DoubleArrayBinding;\r
+import org.simantics.databoard.binding.impl.FloatArrayBinding;\r
+import org.simantics.databoard.binding.impl.IntArrayBinding;\r
+import org.simantics.databoard.binding.impl.LongArrayBinding;\r
+import org.simantics.databoard.type.ArrayType;\r
+import org.simantics.databoard.type.NumberType;\r
+import org.simantics.databoard.type.RecordType;\r
+import org.simantics.databoard.type.UnionType;\r
+import org.simantics.databoard.units.IUnitConverter;\r
+import org.simantics.databoard.units.IdentityConverter;\r
+import org.simantics.databoard.units.internal.UnitParseException;\r
+import org.simantics.databoard.util.ObjectUtils;\r
+
+/**
+ * AdapterRepository is a factory and a collection of adapters.
+ *
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
+ */
+public class AdapterFactory {
+
+       @SuppressWarnings( "unchecked" )\r
+    Map<AdapterRequest, AbstractAdapter> cache = (Map<AdapterRequest, AbstractAdapter>) new ReferenceMap(ReferenceMap.SOFT, ReferenceMap.HARD);
+               
+       public synchronized Adapter getAdapter(Binding domain, Binding range, boolean typeAdapter, boolean mustClone)
+       throws AdapterConstructionException
+       {               
+               if ((!mustClone || domain.isImmutable()) && domain.equals(range)) return PassThruAdapter.PASSTHRU;\r
+               \r
+               if (domain.getClass() == range.getClass() &&\r
+                               ( !mustClone || domain.isImmutable() ) &&\r
+                               NumberBinding.class.isAssignableFrom( domain.getClass() ) ) {\r
+                       \r
+                       NumberBinding db = (NumberBinding) domain;\r
+                       NumberBinding rb = (NumberBinding) range;\r
+                       String u1 = db.type().getUnit();\r
+                       String u2 = rb.type().getUnit();\r
+                       if (u1==null || u2==null || u1.equals("") || u2.equals("") || u1.equals(u2)) return PassThruAdapter.PASSTHRU;\r
+               }\r
+               
+               return getAdapterUnsynchronized(domain, range, typeAdapter, mustClone);
+       }\r
+
+       private AbstractAdapter getCached(AdapterRequest type) 
+       {
+               return cache.get(type);
+       }                       
+       
+       private void cache(AdapterRequest type, AbstractAdapter binding) {
+               cache.put(type, binding);
+       }       
+       
+       private void addToCache(AdapterRequest request, AbstractAdapter impl) {\r
+           impl.request = request;
+               cache(request, impl);
+               
+               // This request applies to "must clone" request aswell, remember this implementation
+               if (!request.mustClone && impl.clones) {
+                       request = new AdapterRequest(request.domain, request.range, true);
+                       cache(request, impl);
+               }
+       }
+       
+       /**
+        * Create adapter, does not cache the result.
+        * 
+        * @param domain
+        * @param range
+        * @param typeAdapter if true, primitive conversion is allowed (e.g. int -> double)
+        * @return
+        */
+       private AbstractAdapter getAdapterUnsynchronized(Binding domain, Binding range, boolean typeAdapter, final boolean mustClone)
+       throws AdapterConstructionException
+       {
+               if ( !mustClone && domain.equals(range) ) return PassThruAdapter.PASSTHRU;
+
+               AdapterRequest req = new AdapterRequest(domain, range, mustClone);
+               AbstractAdapter cachedResult = getCached(req);
+               if (cachedResult!=null) return cachedResult;
+
+               try {
+               
+       if (domain instanceof RecordBinding && range instanceof RecordBinding)
+       {               
+               final RecordBinding domainRecord = (RecordBinding) domain;
+               final RecordBinding rangeRecord = (RecordBinding) range;
+               RecordType domainType = domainRecord.type();
+               RecordType rangeType = rangeRecord.type();  \r
+               \r
+               // Field-Map describes the index of the fields in domain for each field in range
+               boolean requiresTypeAdapting = domainType.getComponentCount() != rangeType.getComponentCount(); 
+               int fieldMap[] = new int[rangeType.getComponentCount()];
+               for (int rangeIndex=0; rangeIndex<fieldMap.length; rangeIndex++)
+               {
+                       String fieldName = rangeType.getComponent(rangeIndex).name;
+                       Integer domainIndex = domainType.getComponentIndex(fieldName);\r
+                       if (domainIndex!=null) {\r
+                               fieldMap[rangeIndex] = domainIndex;\r
+                               requiresTypeAdapting |= rangeIndex != domainIndex;\r
+                       } else {\r
+                           fieldMap[rangeIndex] = -1;\r
+                           requiresTypeAdapting = true;\r
+                       }
+               }
+               
+               if (requiresTypeAdapting && !typeAdapter) {
+                       throw new AdapterConstructionException("Type Adapter required.");
+               }
+               
+               final int len = rangeRecord.componentBindings.length;
+               final AbstractAdapter[] componentAdapters = new AbstractAdapter[len];
+               AbstractAdapter result = null;
+               
+               if (!requiresTypeAdapting) {
+                       // Normal Adapter
+                       result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object src) throws AdaptException {
+                                       try {
+                                               Object values[] = new Object[len];
+                                               for (int i=0; i<len; i++)
+                                               {                                       
+                                                       Object srcValue = domainRecord.getComponent(src, i);
+                                                       Object dstValue = componentAdapters[i].adapt(srcValue);
+                                                       values[i] = dstValue;
+                                               }
+                                               return rangeRecord.create(values);
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       }
+                               }
+                       };
+               } else {
+                       // Type Adapter - Type adapter maps fields of different order
+                       final int _fieldMap[] = fieldMap;
+                       result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object src) throws AdaptException {
+                                       try {
+                                               Object values[] = new Object[len];
+                                               for (int rangeIndex=0; rangeIndex<len; rangeIndex++)
+                                               {                                                       
+                                                       int domainIndex = _fieldMap[rangeIndex];\r
+                                                       if (domainIndex>=0) {\r
+                                                               Object srcValue = domainRecord.getComponent(src, domainIndex);\r
+                                                               Object dstValue = componentAdapters[rangeIndex].adapt(srcValue);\r
+                                                               values[rangeIndex] = dstValue;\r
+                                                       } else {\r
+                                                               // Optional value\r
+                                                               values[rangeIndex] = rangeRecord.componentBindings[rangeIndex].createDefault();\r
+                                                       }
+                                               }
+                                               return rangeRecord.create(values);
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       }
+                               }
+                       };
+                       result.typeAdapter = true;
+               }
+                       
+               addToCache(req, result);                
+               result.clones = true;
+               for (int rangeIndex=0; rangeIndex<len; rangeIndex++)
+               {
+                       int domainIndex = fieldMap[rangeIndex];\r
+                       if (domainIndex>=0) {
+                               componentAdapters[rangeIndex] = getAdapterUnsynchronized(domainRecord.componentBindings[domainIndex], rangeRecord.componentBindings[rangeIndex], typeAdapter, mustClone);
+                               result.typeAdapter |= componentAdapters[rangeIndex].typeAdapter;
+                               result.clones &= componentAdapters[rangeIndex].clones;\r
+                       }
+               }
+               return result;
+       }
+       
+       if (domain instanceof UnionBinding && range instanceof UnionBinding)
+       {
+               final UnionBinding domainBinding = (UnionBinding) domain;
+               final UnionBinding rangeBinding = (UnionBinding) range;
+               UnionType domainType = domainBinding.type();
+               UnionType rangeType = rangeBinding.type();
+               
+               // Tag-Map describes the index of the tag-types in domain for each tag-type in range
+               boolean requiresTypeAdapting = domainType.getComponentCount() != rangeType.getComponentCount(); 
+               int tagMap[] = new int[domainType.getComponentCount()];
+               for (int domainIndex=0; domainIndex<tagMap.length; domainIndex++)
+               {
+                       String fieldName = domainType.getComponent(domainIndex).name;
+                       Integer rangeIndex = rangeType.getComponentIndex(fieldName);
+                       if (rangeIndex==null) throw new AdapterConstructionException("The range UnionType does not have expected tag \""+fieldName+"\"");
+                       tagMap[domainIndex] = rangeIndex;
+                       requiresTypeAdapting |= rangeIndex != domainIndex;
+               }                       
+               
+               if (requiresTypeAdapting && !typeAdapter) {
+                       throw new AdapterConstructionException("Type Adapter required.");
+               }
+               
+               final AbstractAdapter[] componentAdapters = new AbstractAdapter[domainType.getComponentCount()];
+
+               AbstractAdapter result = null;
+               
+               if (!requiresTypeAdapting) {
+                       // Normal adapter
+                       result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object obj) throws AdaptException {
+                                       try { 
+                                               int tag = domainBinding.getTag(obj);
+                                               Object srcValue = domainBinding.getValue(obj);                                  
+                                               Object dstValue = componentAdapters[tag].adapt(srcValue);
+                                               return rangeBinding.create(tag, dstValue);
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       }
+                               }
+                       };
+               } else {
+                       // Type adapter, type adapter rearranges tag indices
+                       final int _tagMap[] = tagMap; 
+                       result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object obj) throws AdaptException {
+                                       try { 
+                                               int domainTag = domainBinding.getTag(obj);
+                                               int rangeTag = _tagMap[domainTag];
+                                               // Domain Component Binding
+                                               Object srcValue = domainBinding.getValue(obj);
+                                               Object dstValue = componentAdapters[domainTag].adapt(srcValue);
+                                               return rangeBinding.create(rangeTag, dstValue);
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       }
+                               }
+                       };
+               }
+               
+               addToCache(req, result);
+               result.clones = true;
+               for (int domainIndex=0; domainIndex<domainType.getComponentCount(); domainIndex++)
+               {
+                       int rangeIndex = tagMap[domainIndex];
+                       componentAdapters[domainIndex] = getAdapterUnsynchronized(domainBinding.getComponentBindings()[domainIndex], rangeBinding.getComponentBindings()[rangeIndex], typeAdapter, mustClone);
+                       result.typeAdapter |= componentAdapters[domainIndex].typeAdapter;
+                       result.clones &= componentAdapters[domainIndex].clones;
+               }
+                       return result;
+       }       
+       
+       if (domain instanceof BooleanBinding && range instanceof BooleanBinding)
+       {
+               final BooleanBinding domainBoolean = (BooleanBinding) domain;
+               final BooleanBinding rangeBoolean = (BooleanBinding) range;
+               AbstractAdapter result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object obj) throws AdaptException {
+                                       try {
+                                               boolean value = domainBoolean.getValue_(obj);
+                                               return rangeBoolean.create(value);
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       }                                                                                                       
+                               }
+               };
+               result.clones = mustClone;
+               result.typeAdapter = true;\r
+               addToCache(req, result);
+               return result;
+       }               
+\r
+       if (domain instanceof BooleanBinding && range instanceof NumberBinding)\r
+       {\r
+               try {\r
+                       final BooleanBinding domainBoolean = (BooleanBinding) domain;\r
+                       final NumberBinding rangeNumber = (NumberBinding) range;\r
+                               final Object falseValue = rangeNumber.create( Integer.valueOf(0) );\r
+                       final Object trueValue = rangeNumber.create( Integer.valueOf(1) );\r
+                       AbstractAdapter result = new AbstractAdapter() {\r
+                                       @Override\r
+                                       public Object adapt(Object obj) throws AdaptException {\r
+                                               try {\r
+                                                       boolean value = domainBoolean.getValue_(obj);\r
+                                                       return value ? trueValue : falseValue;\r
+                                               } catch (BindingException e) {\r
+                                                       throw new AdaptException(e);\r
+                                               }                                                                                                       \r
+                                       }\r
+                       };\r
+                       result.clones = true;\r
+                       result.typeAdapter = true;\r
+                       addToCache(req, result);\r
+                       return result;\r
+                       } catch (BindingException e1) {\r
+                               throw new AdapterConstructionException(e1);\r
+                       }\r
+       }               \r
+       \r
+       if (domain instanceof NumberBinding && range instanceof BooleanBinding)\r
+       {\r
+               try {\r
+                       final NumberBinding domainNumber = (NumberBinding) domain;\r
+                       final BooleanBinding rangeBoolean = (BooleanBinding) range;\r
+                               final Object zeroValue = domainNumber.create( Integer.valueOf(0) );\r
+                       AbstractAdapter result = new AbstractAdapter() {\r
+                                       @Override\r
+                                       public Object adapt(Object obj) throws AdaptException {\r
+                                               try {\r
+                                                       Object value = domainNumber.getValue(obj);\r
+                                                       boolean bool = !domainNumber.equals(value, zeroValue);\r
+                                                       return rangeBoolean.create(bool);\r
+                                               } catch (BindingException e) {\r
+                                                       throw new AdaptException(e);\r
+                                               }                                                                                                       \r
+                                       }\r
+                       };\r
+                       result.clones = true;\r
+                       addToCache(req, result);\r
+                       return result;\r
+                       } catch (BindingException e1) {\r
+                               throw new AdapterConstructionException(e1);\r
+                       }\r
+       }\r
+       
+       if (domain instanceof StringBinding && range instanceof StringBinding)
+       {
+               final StringBinding domainString = (StringBinding) domain;
+               final StringBinding rangeString = (StringBinding) range;
+               AbstractAdapter result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object obj) throws AdaptException {
+                                       try {
+                                               String value = domainString.getValue(obj);
+                                               return rangeString.create(value);
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       }                                                                                                       
+                               }
+               };
+               result.clones = true;
+               addToCache(req, result);
+               return result;
+       }           \r
+       \r
+       if(domain instanceof StringBinding && range instanceof NumberBinding)\r
+       {\r
+               final StringBinding domainString = (StringBinding) domain;\r
+               final NumberBinding rangeString = (NumberBinding) range;\r
+               AbstractAdapter result = new AbstractAdapter() {\r
+                               @Override\r
+                               public Object adapt(Object obj) throws AdaptException {\r
+                                       try {\r
+                                               String value = domainString.getValue(obj);\r
+                                               return rangeString.create(value);\r
+                                       } catch (BindingException e) {\r
+                                               throw new AdaptException(e);\r
+                                       }                                                                                                       \r
+                               }\r
+               };\r
+               result.clones = true;\r
+               addToCache(req, result);\r
+               return result;\r
+       }
+\r
+       if(domain instanceof StringBinding && range instanceof BooleanBinding)\r
+       {\r
+               final StringBinding domainString = (StringBinding) domain;\r
+               final BooleanBinding rangeString = (BooleanBinding) range;\r
+               AbstractAdapter result = new AbstractAdapter() {\r
+                               @Override\r
+                               public Object adapt(Object obj) throws AdaptException {\r
+                                       try {\r
+                                               String value = domainString.getValue(obj);\r
+                                               return rangeString.create(Boolean.parseBoolean(value));\r
+                                       } catch (BindingException e) {\r
+                                               throw new AdaptException(e);\r
+                                       }                                                                                                       \r
+                               }\r
+               };\r
+               result.clones = true;\r
+               addToCache(req, result);\r
+               return result;\r
+       }\r
+       
+       // XXX We can optimizes here by using primitives 
+       if (domain instanceof NumberBinding && range instanceof NumberBinding)
+       {
+                       final NumberBinding domainNumber = (NumberBinding) domain;
+                       final NumberBinding rangeNumber = (NumberBinding) range;
+               
+                       String domainUnit = ((NumberType) domainNumber.type()).getUnit();
+                       String rangeUnit = ((NumberType) rangeNumber.type()).getUnit();\r
+                       IUnitConverter unitConverter;\r
+                       if(domainUnit == null || rangeUnit == null || domainUnit.equals(rangeUnit))\r
+                               unitConverter = null;\r
+                       else\r
+                               unitConverter = Units.createConverter(domainUnit, rangeUnit); \r
+                       /*if(domainUnit == null || domainUnit.equals("")) {\r
+                           if(rangeUnit == null || rangeUnit.equals(""))\r
+                               unitConverter = null;\r
+                           else\r
+                               unitConverter = null;\r
+//                             throw new AdapterConstructionException("Cannot convert from a unitless type to a type with unit.");\r
+                       }\r
+                       else {\r
+                           if(rangeUnit == null || rangeUnit.equals(""))\r
+                               unitConverter = null;                                   \r
+//                             throw new AdapterConstructionException("Cannot convert from a type with unit to unitless type.");\r
+                           else\r
+                               unitConverter = Units.createConverter(domainUnit, rangeUnit); \r
+                       }       */                      
+                       boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
+                       boolean doPrimitiveConversion = !domainNumber.type().getClass().equals( rangeNumber.type().getClass() );                                
+                       if (doPrimitiveConversion && !typeAdapter)
+                               throw new AdapterConstructionException("Type mismatch, use Type Adapter instead.");
+                                                       
+                       AbstractAdapter result;
+                       if (!doUnitConversion) {
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               Number value;
+                                               try {                                                   
+                                                       value = domainNumber.getValue(obj);
+                                                       return rangeNumber.create(value);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                               };
+                       } else {
+                               final IUnitConverter _unitConverter = unitConverter;
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       Number value = domainNumber.getValue(obj);
+                                                       double convertedValue = _unitConverter.convert(value.doubleValue());
+                                                       return rangeNumber.create(Double.valueOf(convertedValue));
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                               };
+                       }
+                       result.typeAdapter = doPrimitiveConversion;
+                       result.clones = true;
+                       addToCache(req, result);
+                       return result;                  
+       }               
+       
+       if (domain instanceof BooleanArrayBinding && range instanceof BooleanArrayBinding)
+       {
+               final BooleanArrayBinding domainArray = (BooleanArrayBinding) domain;
+               final BooleanArrayBinding rangeArray = (BooleanArrayBinding) range;
+               AbstractAdapter result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object obj) throws AdaptException {
+                                       try {
+                                               boolean[] data = domainArray.getArray(obj);\r
+                                               if (mustClone) data = data.clone();
+                                               return rangeArray.create(data);
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       }                                               
+                               }
+               };
+               result.clones = true;
+               addToCache(req, result);
+               return result;
+       }               
+
+       if (domain instanceof ByteArrayBinding && range instanceof ByteArrayBinding)
+       {
+               final ByteArrayBinding domainArray = (ByteArrayBinding) domain;
+               final ByteArrayBinding rangeArray = (ByteArrayBinding) range;
+               
+                       String domainUnit = ((NumberType) ((ArrayType)domainArray.type()).componentType).getUnit();
+                       String rangeUnit = ((NumberType) ((ArrayType)rangeArray.type()).componentType).getUnit();               
+                       IUnitConverter unitConverter = ObjectUtils.objectEquals(domainUnit, rangeUnit) ? null : Units.createConverter(domainUnit, rangeUnit);
+                       boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
+
+                       AbstractAdapter result;
+                       if (doUnitConversion) {
+                               final IUnitConverter _unitConverter = unitConverter; 
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       byte[] data = domainArray.getArray(obj);                                
+                                                       for (int i=0; i<data.length; i++) {
+                                                               byte value = data[i];
+                                                               double convertedValue = _unitConverter.convert((double)value);
+                                                               data[i] = (byte) convertedValue;
+                                                       }
+                                                       return rangeArray.create(data);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                               };
+                       } else {
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       byte[] data = domainArray.getArray(obj);\r
+                                                       if (mustClone) data = data.clone();                                                     
+                                                       return rangeArray.create(data);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }                                                       
+                                       }
+                               };
+                       }
+               result.clones = true;
+               addToCache(req, result);
+               return result;
+       }               
+       
+       if (domain instanceof IntArrayBinding && range instanceof IntArrayBinding)
+       {
+               final IntArrayBinding domainArray = (IntArrayBinding) domain;
+               final IntArrayBinding rangeArray = (IntArrayBinding) range;
+               
+                       String domainUnit = ((NumberType) ((ArrayType)domainArray.type()).componentType).getUnit();
+                       String rangeUnit = ((NumberType) ((ArrayType)rangeArray.type()).componentType).getUnit();               
+                       IUnitConverter unitConverter = ObjectUtils.objectEquals(domainUnit, rangeUnit) ||\r
+                                       domainUnit == null || rangeUnit == null ? null : Units.createConverter(domainUnit, rangeUnit);
+                       boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
+               
+                       AbstractAdapter result;
+                       if (doUnitConversion) {
+                               final IUnitConverter _unitConverter = unitConverter; 
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       int[] data = domainArray.getArray(obj);                         
+                                                       for (int i=0; i<data.length; i++) {
+                                                               int value = data[i];
+                                                               double convertedValue = _unitConverter.convert((double)value);
+                                                               data[i] = (int) convertedValue;
+                                                       }
+                                                       return rangeArray.create(data);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                               };
+                       } else {
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       int[] data = domainArray.getArray(obj);\r
+                                                       if (mustClone) data = data.clone();                                                     
+                                                       return rangeArray.create(data);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }                                                       
+                                       }
+                               };
+                       }
+               
+               result.clones = true;
+               addToCache(req, result);
+               return result;
+       }               
+
+       if (domain instanceof LongArrayBinding && range instanceof LongArrayBinding)
+       {
+               final LongArrayBinding domainArray = (LongArrayBinding) domain;
+               final LongArrayBinding rangeArray = (LongArrayBinding) range;
+
+                       String domainUnit = ((NumberType) ((ArrayType)domainArray.type()).componentType).getUnit();
+                       String rangeUnit = ((NumberType) ((ArrayType)rangeArray.type()).componentType).getUnit();               
+                       IUnitConverter unitConverter = ObjectUtils.objectEquals(domainUnit, rangeUnit) ||\r
+                                       domainUnit == null || rangeUnit == null ? null : Units.createConverter(domainUnit, rangeUnit);
+                       boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
+               
+                       AbstractAdapter result;
+                       if (doUnitConversion) {
+                               final IUnitConverter _unitConverter = unitConverter; 
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       long[] data = domainArray.getArray(obj);                                
+                                                       for (int i=0; i<data.length; i++) {
+                                                               long value = data[i];
+                                                               double convertedValue = _unitConverter.convert((double)value);
+                                                               data[i] = (long) convertedValue;
+                                                       }
+                                                       return rangeArray.create(data);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                               };
+                       } else {
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       long[] data = domainArray.getArray(obj);\r
+                                                       if (mustClone) data = data.clone();                                                     
+                                                       return rangeArray.create(data);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                               };
+                       }
+
+               result.clones = true;
+               addToCache(req, result);
+               return result;
+       }               
+
+       if (domain instanceof FloatArrayBinding && range instanceof FloatArrayBinding)
+       {
+               final FloatArrayBinding domainArray = (FloatArrayBinding) domain;
+               final FloatArrayBinding rangeArray = (FloatArrayBinding) range;
+
+                       String domainUnit = ((NumberType) ((ArrayType)domainArray.type()).componentType).getUnit();
+                       String rangeUnit = ((NumberType) ((ArrayType)rangeArray.type()).componentType).getUnit();               
+                       IUnitConverter unitConverter = ObjectUtils.objectEquals(domainUnit, rangeUnit) ||\r
+                                       domainUnit == null || rangeUnit == null ? null : Units.createConverter(domainUnit, rangeUnit);
+                       boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
+               
+                       AbstractAdapter result;
+                       if (doUnitConversion) {
+                               final IUnitConverter _unitConverter = unitConverter; 
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       float[] data = domainArray.getArray(obj);                               
+                                                       for (int i=0; i<data.length; i++) {
+                                                               float value = data[i];
+                                                               double convertedValue = _unitConverter.convert((double)value);
+                                                               data[i] = (float) convertedValue;
+                                                       }
+                                                       return rangeArray.create(data);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                               };
+                       } else {
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       float[] data = domainArray.getArray(obj);\r
+                                                       if (mustClone) data = data.clone();                                                     
+                                                       return rangeArray.create(data);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                               };
+                       }
+                       
+               result.clones = true;
+               addToCache(req, result);
+               return result;
+       }               
+
+       if (domain instanceof DoubleArrayBinding && range instanceof DoubleArrayBinding)
+       {
+               final DoubleArrayBinding domainArray = (DoubleArrayBinding) domain;
+               final DoubleArrayBinding rangeArray = (DoubleArrayBinding) range;
+
+                       String domainUnit = ((NumberType) ((ArrayType)domainArray.type()).componentType).getUnit();
+                       String rangeUnit = ((NumberType) ((ArrayType)rangeArray.type()).componentType).getUnit();               
+                       IUnitConverter unitConverter = ObjectUtils.objectEquals(domainUnit, rangeUnit) \r
+                                       || domainUnit == null || rangeUnit == null ? null : Units.createConverter(domainUnit, rangeUnit);
+                       boolean doUnitConversion = unitConverter != null && unitConverter != IdentityConverter.INSTANCE;
+               
+                       AbstractAdapter result;
+                       if (doUnitConversion) {
+                               final IUnitConverter _unitConverter = unitConverter; 
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       double[] data = domainArray.getArray(obj);                              
+                                                       for (int i=0; i<data.length; i++) {
+                                                               double value = data[i];
+                                                               double convertedValue = _unitConverter.convert(value);
+                                                               data[i] = convertedValue;
+                                                       }
+                                                       return rangeArray.create(data);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                               };
+                       } else {
+                               result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       double[] data = domainArray.getArray(obj);\r
+                                                       if (mustClone) data = data.clone();                                                     
+                                                       return rangeArray.create(data);
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                               };
+                       }
+               
+               result.clones = true;
+               addToCache(req, result);
+               return result;
+       }               
+
+       if (domain instanceof ArrayBinding && range instanceof ArrayBinding)
+       {
+               final ArrayBinding domainBinding = (ArrayBinding) domain;
+               final ArrayBinding rangeBinding = (ArrayBinding) range;
+               final AbstractAdapter componentAdapter = getAdapterUnsynchronized(domainBinding.getComponentBinding(), rangeBinding.getComponentBinding(), typeAdapter, mustClone);
+               AbstractAdapter result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object obj) throws AdaptException {
+                                       try {
+                                               int len = domainBinding.size(obj);
+                                               ArrayList<Object> array = new ArrayList<Object>(len);
+                                               for (int i=0; i<len; i++)
+                                               {
+                                                       Object srcValue = domainBinding.get(obj, i);
+                                                       Object dstValue = componentAdapter.adapt(srcValue);
+                                                       array.add(dstValue);
+                                               }                                       
+                                               return rangeBinding instanceof ArrayListBinding ? array : rangeBinding.create(array);
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       }
+                               }
+               };
+               
+               result.clones = componentAdapter.clones;
+               addToCache(req, result);
+               return result;
+       }
+       
+       if (domain instanceof OptionalBinding && range instanceof OptionalBinding)
+       {
+               final OptionalBinding domainBinding = (OptionalBinding) domain;
+               final OptionalBinding rangeBinding = (OptionalBinding) range;
+               final AbstractAdapter componentAdapter = getAdapterUnsynchronized(domainBinding.componentBinding, rangeBinding.componentBinding, typeAdapter, mustClone);
+               AbstractAdapter result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object obj) throws AdaptException {
+                                       try {
+                                               if (!domainBinding.hasValue(obj)) return rangeBinding.createNoValue();
+                                               Object value = domainBinding.getValue(obj);
+                                               value = componentAdapter.adapt(value); 
+                                               return rangeBinding.createValue(value);
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       }
+                               }
+               };
+               result.typeAdapter = componentAdapter.typeAdapter;
+               result.clones = componentAdapter.clones;
+               addToCache(req, result);
+               return result;
+       }\r
+       \r
+       // Adapt a non-optional value to an optional value\r
+       if (range instanceof OptionalBinding && !(domain instanceof OptionalBinding))\r
+       {\r
+               final Binding domainBinding = domain;\r
+               final OptionalBinding rangeBinding = (OptionalBinding) range;\r
+               final AbstractAdapter componentAdapter = getAdapterUnsynchronized(domainBinding, rangeBinding.componentBinding, typeAdapter, mustClone);\r
+               AbstractAdapter result = new AbstractAdapter() {\r
+                               @Override\r
+                               public Object adapt(Object obj) throws AdaptException {\r
+                                       try {\r
+                                               obj = componentAdapter.adapt(obj); \r
+                                               return rangeBinding.createValue(obj);\r
+                                       } catch (BindingException e) {\r
+                                               throw new AdaptException(e);\r
+                                       }\r
+                               }\r
+               };\r
+               result.typeAdapter = componentAdapter.typeAdapter;\r
+               result.clones = componentAdapter.clones;\r
+               addToCache(req, result);\r
+               return result;                  \r
+       }
+       
+       if (domain instanceof VariantBinding && range instanceof VariantBinding)
+       {               
+               final VariantBinding domainBinding = (VariantBinding) domain;
+               final VariantBinding rangeBinding = (VariantBinding) range;
+               AbstractAdapter result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object obj) throws AdaptException {                                 
+                                       try {\r
+                                               
+                                               Binding domainValueBinding = domainBinding.getContentBinding(obj);
+                                               Object domainObject = domainBinding.getContent(obj, domainValueBinding);\r
+                                               if (mustClone && domainObject!=obj) {\r
+                                                       Adapter valueAdapter = getAdapterUnsynchronized(domainValueBinding, domainValueBinding, false, true);\r
+                                                       domainObject = valueAdapter.adapt(domainObject); \r
+                                               }
+                                               Object rangeVariant = rangeBinding.create(domainValueBinding, domainObject);
+                                               return rangeVariant;
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       } catch (AdapterConstructionException e) {\r
+                                               throw new AdaptException(e);\r
+                                       }
+                               }
+               };
+               result.clones = mustClone;
+               addToCache(req, result);
+               return result;
+       }\r
+       \r
+       if (range instanceof VariantBinding && !(domain instanceof VariantBinding))\r
+       {\r
+               // Default to just wrapping the domain type\r
+               final VariantBinding rangeBinding = (VariantBinding)range;\r
+               final Binding domainBinding = domain;\r
+               AbstractAdapter result = new AbstractAdapter() {\r
+                               @Override\r
+                               public Object adapt(Object obj) throws AdaptException {\r
+                                       try {\r
+                                               if (mustClone) {\r
+                                                       Adapter valueAdapter;\r
+                                                               valueAdapter = getAdapterUnsynchronized(domainBinding, domainBinding, false, true);\r
+                                                       obj = valueAdapter.adapt(obj);\r
+                                               }\r
+                                               return rangeBinding.create(domainBinding, obj);\r
+                                       } catch (AdapterConstructionException | BindingException e) {\r
+                                               throw new AdaptException(e);\r
+                                       }\r
+                               }\r
+               };\r
+               result.clones = mustClone;\r
+               addToCache(req, result);\r
+               return result;\r
+       }
+
+       if (domain instanceof MapBinding && range instanceof MapBinding)
+       {
+               final MapBinding domainBinding = (MapBinding) domain;
+               final MapBinding rangeBinding = (MapBinding) range;
+               final AbstractAdapter keyAdapter = getAdapterUnsynchronized(domainBinding.getKeyBinding(), rangeBinding.getKeyBinding(), typeAdapter, mustClone);
+               final AbstractAdapter valueAdapter = getAdapterUnsynchronized(domainBinding.getValueBinding(), rangeBinding.getValueBinding(), typeAdapter, mustClone);
+               AbstractAdapter result = new AbstractAdapter() {
+                               @Override
+                               public Object adapt(Object obj) throws AdaptException {
+                                       try {
+                                               int len = domainBinding.size(obj);
+                                               Object domainKeys[] = domainBinding.getKeys(obj);
+                                               Object domainValues[] = domainBinding.getValues(obj);
+                                               Object rangeKeys[] = new Object[len];
+                                               Object rangeValues[] = new Object[len];
+                                               for (int i=0; i<len; i++) {
+                                                       Object domainKey = domainKeys[i];
+                                                       Object domainValue = domainValues[i];
+                                                       Object rangeKey = keyAdapter.adapt(domainKey);
+                                                       Object rangeValue = valueAdapter.adapt(domainValue);
+                                                       rangeKeys[i] = rangeKey;
+                                                       rangeValues[i] = rangeValue;
+                                               }
+                                               Object rangeMap = rangeBinding.create(rangeKeys, rangeValues);
+                                               return rangeMap;
+                                       } catch (BindingException e) {
+                                               throw new AdaptException(e);
+                                       }
+                               }
+               };
+               result.typeAdapter |= keyAdapter.typeAdapter | valueAdapter.typeAdapter;
+               result.clones = keyAdapter.clones & valueAdapter.clones;                
+               addToCache(req, result);
+               return result;
+       }
+/*     
+       // Special-Case: Convert Union to its Composite
+       if (domain instanceof UnionBinding) {
+               final UnionType ut = (UnionType) domain.getDataType();
+               final UnionBinding ub = (UnionBinding) domain;
+               Binding[] cbs = ub.getComponentBindings();              
+               for (int i=0; i<cbs.length; i++)
+               {
+                       Binding domainCompositeBinding = cbs[i];
+                       if (ut.getComponent(i).type.equals(range.getDataType())) {
+                               final AbstractAdapter union2CompositeAdapter = getAdapterUnsynchronized(domainCompositeBinding, range, allowPrimitiveConversion);                               
+                               final int tag = i;
+                       AbstractAdapter result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       Object domainUnion = obj;
+                                                       int domainTag = ub.getTag(domainUnion);
+                                                       if (domainTag != tag) {
+                                                               throw new AdaptException("This adapter can adapt only "+ub.getDataType().getComponents()[tag].name+"s");
+                                                       }
+                                                       Object domainComposite = ub.getValue(domainUnion);
+                                                       Object rangeComposite = union2CompositeAdapter.adapt(domainComposite);
+                                                       return rangeComposite;
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                       };
+                       result.hasPrimitiveConversion = union2CompositeAdapter.hasPrimitiveConversion;
+                       addToCache(pair, result);
+                       return result;
+                       }
+               }
+       }
+       
+       // Special-Case: Convert Composite to Union
+       if (range instanceof UnionBinding) {
+               final UnionType ut = (UnionType) range.getDataType();
+               final UnionBinding ub = (UnionBinding) range;
+               Binding cbs[] = ub.getComponentBindings();
+               for (int i=0; i<cbs.length; i++) {
+                       Binding rangeCompositeBinding = cbs[i];
+                       if (ut.getComponent(i).type.equals(domain.getDataType())) {
+                               final AbstractAdapter domainObject2RangeCompositeAdapter = getAdapterUnsynchronized(rangeCompositeBinding, domain, allowPrimitiveConversion);                                   
+                               final int tag = i;
+                       AbstractAdapter result = new AbstractAdapter() {
+                                       @Override
+                                       public Object adapt(Object obj) throws AdaptException {
+                                               try {
+                                                       Object domainObject = obj;
+                                                       Object rangeComposite = domainObject2RangeCompositeAdapter.adapt(domainObject);
+                                                   Object rangeUnion = ub.create(tag, rangeComposite);
+                                                       return rangeUnion;
+                                               } catch (BindingException e) {
+                                                       throw new AdaptException(e);
+                                               }
+                                       }
+                       };
+                       result.hasPrimitiveConversion = domainObject2RangeCompositeAdapter.hasPrimitiveConversion;
+                       addToCache(pair, result);
+                       return result;
+                       }
+               }
+       }
+*/     
+       
+               } catch (UnitParseException e) {
+                       throw new AdapterConstructionException(e.getMessage(), e); 
+               }                               
+
+               StringBuilder error = new StringBuilder();
+               error.append("Could not create ");
+               if (mustClone) error.append("cloning ");
+               if (typeAdapter) error.append("type");
+               error.append("adapter (");
+               error.append("domain=");
+               error.append(domain.type().toSingleLineString());
+               error.append(", range=");
+               error.append(range.type().toSingleLineString());
+               error.append(")");
+               
+       throw new AdapterConstructionException(error.toString());
+       }
+       
+       
+       \r
+    /**\r
+     * Adapt a value of one type to another. \r
+     * \r
+     * @param value\r
+     * @param domain\r
+     * @param range\r
+     * @return adapted value\r
+     * @throws AdapterConstructionException\r
+     * @throws AdaptException\r
+     */\r
+    public Object adapt(Object value, Binding domain, Binding range)\r
+    throws AdaptException\r
+    {\r
+       if (domain == range) return value;\r
+       try {\r
+               if (range instanceof VariantBinding && !(domain instanceof VariantBinding)) {\r
+                       // Default to just wrapping the value (avoid adapter construction to save memory)\r
+                       return ((VariantBinding)range).create(domain, value);\r
+               }\r
+                       return getAdapter(domain, range, true, false).adapt(value);\r
+               } catch (AdapterConstructionException | BindingException e) {\r
+                       throw new AdaptException(e);\r
+               }\r
+    }\r
+    \r
+    /**\r
+     * Adapt a value of one type to another\r
+     * \r
+     * @param value\r
+     * @param domain\r
+     * @param range\r
+     * @return adapted value\r
+     * @throws AdapterConstructionException\r
+     * @throws AdaptException\r
+     */\r
+    public Object adaptUnchecked(Object value, Binding domain, Binding range)\r
+    throws RuntimeAdapterConstructionException, RuntimeAdaptException\r
+    {\r
+       if (domain == range) return value;\r
+       try {\r
+               if (range instanceof VariantBinding && !(domain instanceof VariantBinding)) {\r
+                       // Default to just wrapping the value (avoid adapter construction to save memory)\r
+                       return ((VariantBinding)range).create(domain, value);\r
+               }\r
+               return getAdapter(domain, range, true, false).adaptUnchecked(value);\r
+               } catch (RuntimeAdapterConstructionException | RuntimeBindingException e) {\r
+                       throw new RuntimeAdaptException(new AdaptException(e.getCause()));\r
+               } catch (AdapterConstructionException | BindingException e) {\r
+                       throw new RuntimeAdaptException(new AdaptException(e));\r
+               }\r
+    }\r
+    \r
+    /**\r
+     * Clone a value to a type to another. Bindings that handle immutable values\r
+     * may return the same instance, others will guarantee a complete copy.  \r
+     * \r
+     * @param value\r
+     * @param domain\r
+     * @param range\r
+     * @return adapted value\r
+     * @throws AdapterConstructionException\r
+     * @throws AdaptException\r
+     */\r
+    public Object clone(Object value, Binding domain, Binding range)\r
+    throws AdaptException\r
+    {\r
+       try {\r
+                       return getAdapter(domain, range, true, true).adapt(value);\r
+               } catch (AdapterConstructionException e) {\r
+                       throw new AdaptException(e);\r
+               }\r
+    }\r
+    \r
+\r
+    /**\r
+     * Clone a value of one binding to another. Bindings that handle immutable values\r
+     * may return the same instance, others will guarantee a complete copy.\r
+     * \r
+     * @param value\r
+     * @param domain\r
+     * @param range\r
+     * @return adapted value\r
+     * @throws AdapterConstructionException\r
+     * @throws AdaptException\r
+     */\r
+    public Object cloneUnchecked(Object value, Binding domain, Binding range)\r
+    throws RuntimeAdapterConstructionException, RuntimeAdaptException\r
+    {\r
+       try {\r
+                       return getAdapter(domain, range, true, true).adapt(value);\r
+               } catch (AdaptException e) {\r
+                       throw new RuntimeAdaptException(e);\r
+               } catch (RuntimeAdapterConstructionException e) {\r
+                       throw new RuntimeAdaptException(new AdaptException(e.getCause()));\r
+               } catch (AdapterConstructionException e) {\r
+                       throw new RuntimeAdaptException(new AdaptException(e));\r
+               }\r
+    }    \r
+               
+}
+