You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by da...@apache.org on 2017/02/06 11:54:34 UTC

svn commit: r1781884 - in /felix/trunk/converter/converter: ./ src/main/java/org/apache/felix/converter/impl/ src/test/java/org/apache/felix/converter/impl/

Author: davidb
Date: Mon Feb  6 11:54:34 2017
New Revision: 1781884

URL: http://svn.apache.org/viewvc?rev=1781884&view=rev
Log:
Felix Converter - support dynamic maps that reflect changes in the backing object.

Added:
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/DynamicMapLikeFacade.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/MapDelegate.java
Modified:
    felix/trunk/converter/converter/pom.xml
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java
    felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Util.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterBuilderTest.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterEqualsTest.java
    felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java

Modified: felix/trunk/converter/converter/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/pom.xml?rev=1781884&r1=1781883&r2=1781884&view=diff
==============================================================================
--- felix/trunk/converter/converter/pom.xml (original)
+++ felix/trunk/converter/converter/pom.xml Mon Feb  6 11:54:34 2017
@@ -28,7 +28,7 @@
 
     <name>Apache Felix Converter</name>
     <artifactId>org.apache.felix.converter</artifactId>
-    <version>0.1-SNAPSHOT</version>
+    <version>0.1.0-SNAPSHOT</version>
     <packaging>jar</packaging>
 
     <scm>
@@ -66,7 +66,7 @@
                 <configuration>
                     <instructions>
                         <Private-Package>org.apache.felix.converter.*</Private-Package>
-                        <Export-Package>org.osgi.util.function,org.osgi.util.converter; version="0.1"; mandatory:="status"; status="provisional"</Export-Package>
+                        <Export-Package>org.osgi.util.function,org.osgi.util.converter</Export-Package>
                         <Import-Package>org.osgi.util.converter, *</Import-Package>
                     </instructions>
                 </configuration>

Modified: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java?rev=1781884&r1=1781883&r2=1781884&view=diff
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java (original)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java Mon Feb  6 11:54:34 2017
@@ -58,17 +58,18 @@ public class ConvertingImpl implements C
         interfaceImplementations = Collections.unmodifiableMap(m);
     }
 
-    private volatile InternalConverter converter;
+    volatile InternalConverter converter;
     private volatile Object object;
-    private volatile Class<?> sourceAsClass;
     private volatile Object defaultValue;
     private volatile boolean hasDefault;
-    private volatile Class<?> sourceClass;
-    private volatile Class<?> targetActualClass;
+    volatile Class<?> sourceClass;
+    volatile Class<?> sourceAsClass;
+    private volatile Class<?> targetClass;
     private volatile Class<?> targetAsClass;
-    private volatile Type[] typeArguments;
-    private List<Object> keys = new ArrayList<>();
+    volatile Type[] typeArguments;
+    List<Object> keys = new ArrayList<>();
     private volatile Object root;
+    private volatile boolean forceCopy = false;
     private volatile boolean sourceAsJavaBean = false;
     @SuppressWarnings( "unused" )
     private volatile boolean targetAsJavaBean = false;
@@ -126,7 +127,8 @@ public class ConvertingImpl implements C
 
     @Override
     public Converting copy() {
-        // TODO Implement this
+        forceCopy  = true;
+
         return null;
     }
 
@@ -154,7 +156,6 @@ public class ConvertingImpl implements C
         return this;
     }
 
-    @SuppressWarnings( "unchecked" )
     @Override
     public void setConverter(Converter c) {
         if (c instanceof InternalConverter)
@@ -197,17 +198,12 @@ public class ConvertingImpl implements C
         if (object == null)
             return handleNull(cls);
 
-        targetActualClass = Util.primitiveToBoxed(cls);
+        targetClass = Util.primitiveToBoxed(cls);
         if (targetAsClass == null)
-            targetAsClass = targetActualClass;
+            targetAsClass = targetClass;
 
         sourceClass = sourceAsClass != null ? sourceAsClass : object.getClass();
 
-        // Temporary - to remove next commit!!
-        // This is just to catch any old code that may still be using {source|target}As(DTO.class)
-        if(DTO.class.equals(sourceClass) || DTO.class.equals(targetAsClass))
-            throw new RuntimeException("To update!!");
-
         if (!isCopyRequiredType(targetAsClass) && targetAsClass.isAssignableFrom(sourceClass)) {
                 return object;
         }
@@ -220,7 +216,7 @@ public class ConvertingImpl implements C
             return convertToArray();
         } else if (Collection.class.isAssignableFrom(targetAsClass)) {
             return convertToCollection();
-        } else if (isDTOType(targetAsClass) || ((sourceAsDTO || targetAsDTO) && DTO.class.isAssignableFrom(targetActualClass))) {
+        } else if (isDTOType(targetAsClass) || ((sourceAsDTO || targetAsDTO) && DTO.class.isAssignableFrom(targetClass))) {
             return convertToDTO();
         } else if (isMapType(targetAsClass)) {
             return convertToMapType();
@@ -238,7 +234,7 @@ public class ConvertingImpl implements C
             return res2;
         } else {
             if (defaultValue != null)
-                return converter.convert(defaultValue).sourceAs(sourceAsClass).targetAs(targetAsClass).to(targetActualClass);
+                return converter.convert(defaultValue).sourceAs(sourceAsClass).targetAs(targetAsClass).to(targetClass);
             else
                 return null;
         }
@@ -312,17 +308,17 @@ public class ConvertingImpl implements C
 
         Class<?> cls = targetAsClass;
         if (targetAsDTO)
-            cls = targetActualClass;
+            cls = targetClass;
         try {
-            T dto = (T) targetActualClass.newInstance();
+            T dto = (T) targetClass.newInstance();
 
             for (Map.Entry entry : (Set<Map.Entry>) m.entrySet()) {
                 Field f = null;
                 try {
-                    f = cls.getDeclaredField(mangleName(entry.getKey().toString()));
+                    f = cls.getDeclaredField(Util.mangleName(entry.getKey().toString()));
                 } catch (NoSuchFieldException e) {
                     try {
-                        f = cls.getField(mangleName(entry.getKey().toString()));
+                        f = cls.getField(Util.mangleName(entry.getKey().toString()));
                     } catch (NoSuchFieldException e1) {
                         // There is not field with this name
                     }
@@ -340,7 +336,7 @@ public class ConvertingImpl implements C
 
             return dto;
         } catch (Exception e) {
-            throw new ConversionException("Cannot create DTO " + targetActualClass, e);
+            throw new ConversionException("Cannot create DTO " + targetClass, e);
         }
     }
 
@@ -349,15 +345,14 @@ public class ConvertingImpl implements C
         Map m = mapView(object, sourceClass, converter);
         if (m == null)
             return null;
-        Type targetKeyType = null, targetValueType = null;
-        if (typeArguments != null && typeArguments.length > 1) {
+        Type targetKeyType = null;
+        if (typeArguments != null && typeArguments.length > 0) {
             targetKeyType = typeArguments[0];
-            targetValueType = typeArguments[1];
         }
 
-        Class<?> ctrCls = interfaceImplementations.get(targetActualClass);
+        Class<?> ctrCls = interfaceImplementations.get(targetClass);
         if (ctrCls == null)
-            ctrCls = targetActualClass;
+            ctrCls = targetClass;
 
         Map instance = (Map) createMapOrCollection(ctrCls, m.size());
         if (instance == null)
@@ -369,32 +364,62 @@ public class ConvertingImpl implements C
             if (targetKeyType != null)
                 key = converter.convert(key).key(ks.toArray()).to(targetKeyType);
             ks.add(key);
-            Object[] ka = ks.toArray();
 
             Object value = entry.getValue();
-            if (value != null) {
-                if (targetValueType != null) {
-                    value = converter.convert(value).key(ka).to(targetValueType);
-                } else {
-                    Class<?> cls = value.getClass();
-                    if (isCopyRequiredType(cls)) {
-                        cls = getConstructableType(cls);
-                    }
-                    if (sourceAsDTO && DTO.class.isAssignableFrom(cls))
-                        // sourceAsDTO or sourceAsClass???
-                        value = converter.convert(value).key(ka).sourceAsDTO().to(cls);
-                    else
-                        value = converter.convert(value).key(ka).to(cls);
-                }
-            }
+            value = convertMapValue(value, ks.toArray());
             instance.put(key, value);
         }
 
         return instance;
     }
 
+    Object convertMapValue(Object value, Object[] ka) {
+        Type targetValueType = null;
+        if (typeArguments != null && typeArguments.length > 1) {
+            targetValueType = typeArguments[1];
+        }
+
+        if (value != null) {
+            if (targetValueType != null) {
+                value = converter.convert(value).key(ka).to(targetValueType);
+            } else {
+                Class<?> cls = value.getClass();
+                if (isCopyRequiredType(cls)) {
+                    cls = getConstructableType(cls);
+                }
+                if (sourceAsDTO && DTO.class.isAssignableFrom(cls))
+                    value = converter.convert(value).key(ka).sourceAsDTO().to(cls);
+                else
+                    value = converter.convert(value).key(ka).to(cls);
+            }
+        }
+        return value;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private Map convertToMapDelegate() {
+        if (Map.class.isAssignableFrom(sourceClass)) {
+            return MapDelegate.forMap((Map) object, this);
+        } else if (Dictionary.class.isAssignableFrom(sourceClass)) {
+            return MapDelegate.forDictionary((Dictionary) object, this);
+        } else if (isDTOType(sourceClass) || sourceAsDTO) {
+            return MapDelegate.forDTO(object, this);
+        } else if (sourceAsJavaBean) {
+            return MapDelegate.forBean(object, this);
+        }
+
+        // Assume it's an interface
+        return MapDelegate.forInterface(object, this);
+    }
+
     @SuppressWarnings({ "unchecked", "rawtypes" })
     private Object convertToMapType() {
+        if (Map.class.equals(targetClass) && !forceCopy) {
+            Map res = convertToMapDelegate();
+            if (res != null)
+                return res;
+        }
+
         if (Map.class.isAssignableFrom(targetAsClass))
             return convertToMap();
         else if (Dictionary.class.isAssignableFrom(targetAsClass))
@@ -423,7 +448,7 @@ public class ConvertingImpl implements C
         @SuppressWarnings("rawtypes")
         Map m = mapView(object, sourceCls, converter);
         try {
-            Object res = targetActualClass.newInstance();
+            Object res = targetClass.newInstance();
             for (Method setter : getSetters(targetCls)) {
                 String setterName = setter.getName();
                 StringBuilder propName = new StringBuilder(Character.valueOf(Character.toLowerCase(setterName.charAt(3))).toString());
@@ -447,7 +472,7 @@ public class ConvertingImpl implements C
             new InvocationHandler() {
                 @Override
                 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-                    String propName = getInterfacePropertyName(method);
+                    String propName = Util.getInterfacePropertyName(method);
                     if (propName == null)
                         return null;
 
@@ -574,14 +599,7 @@ public class ConvertingImpl implements C
                 return null;
             }
         } else if (Enum.class.isAssignableFrom(targetAsClass)) {
-            if (object instanceof Boolean) {
-                try {
-                    Method m = targetAsClass.getMethod("valueOf", String.class);
-                    return m.invoke(null, object.toString().toUpperCase());
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
-                }
-            } else if (object instanceof Number) {
+            if (object instanceof Number) {
                 try {
                     Method m = targetAsClass.getMethod("values");
                     Object[] values = (Object[]) m.invoke(null);
@@ -589,8 +607,24 @@ public class ConvertingImpl implements C
                 } catch (Exception e) {
                     throw new RuntimeException(e);
                 }
+            } else {
+                try {
+                    Method m = targetAsClass.getMethod("valueOf", String.class);
+                    return m.invoke(null, object.toString());
+                } catch (Exception e) {
+                    try {
+                        // Case insensitive fallback
+                        Method m = targetAsClass.getMethod("values");
+                        for (Object v : (Object[]) m.invoke(null)) {
+                            if (v.toString().equalsIgnoreCase(object.toString())) {
+                                return v;
+                            }
+                        }
+                    } catch (Exception e1) {
+                        throw new RuntimeException(e1);
+                    }
+                }
             }
-
         }
         return null;
     }
@@ -598,13 +632,13 @@ public class ConvertingImpl implements C
     @SuppressWarnings("unchecked")
     private <T> T tryStandardMethods() {
         try {
-            Method m = targetActualClass.getDeclaredMethod("valueOf", String.class);
+            Method m = targetClass.getDeclaredMethod("valueOf", String.class);
             if (m != null) {
                 return (T) m.invoke(null, object.toString());
             }
         } catch (Exception e) {
             try {
-                Constructor<?> ctr = targetActualClass.getConstructor(String.class);
+                Constructor<?> ctr = targetClass.getConstructor(String.class);
                 return (T) ctr.newInstance(object.toString());
             } catch (Exception e2) {
             }
@@ -654,9 +688,6 @@ public class ConvertingImpl implements C
         for (Method md : sourceCls.getDeclaredMethods()) {
             handleBeanMethod(obj, md, invokedMethods, result);
         }
-        for (Method md : sourceCls.getMethods()) {
-            handleBeanMethod(obj, md, invokedMethods, result);
-        }
 
         return result;
     }
@@ -666,11 +697,12 @@ public class ConvertingImpl implements C
         Set<String> handledFields = new HashSet<>();
 
         Map result = new HashMap();
+        // Do we need 'declaredfields'? We only need to look at the public ones...
         for (Field f : obj.getClass().getDeclaredFields()) {
-            handleField(obj, f, handledFields, result, converter);
+            handleDTOField(obj, f, handledFields, result, converter);
         }
         for (Field f : obj.getClass().getFields()) {
-            handleField(obj, f, handledFields, result, converter);
+            handleDTOField(obj, f, handledFields, result, converter);
         }
         return result;
     }
@@ -732,59 +764,13 @@ public class ConvertingImpl implements C
         return null;
     }
 
-    private static String getAccessorPropertyName(Method md) {
-        if (md.getReturnType().equals(Void.class))
-            return null; // not an accessor
-
-        if (md.getParameterTypes().length > 0)
-            return null; // not an accessor
-
-        if (Object.class.equals(md.getDeclaringClass()))
-            return null; // do not use any methods on the Object class as a accessor
-
-        String mn = md.getName();
-        int prefix;
-        if (mn.startsWith("get"))
-            prefix = 3;
-        else if (mn.startsWith("is"))
-            prefix = 2;
-        else
-            return null; // not an accessor prefix
-
-        if (mn.length() <= prefix)
-            return null; // just 'get' or 'is': not an accessor
-        String propStr = mn.substring(prefix);
-        StringBuilder propName = new StringBuilder(propStr.length());
-        char firstChar = propStr.charAt(0);
-        if (!Character.isUpperCase(firstChar))
-            return null; // no acccessor as no camel casing
-        propName.append(Character.toLowerCase(firstChar));
-        if (propStr.length() > 1)
-            propName.append(propStr.substring(1));
-
-        return propName.toString();
-    }
-
-    private static String getInterfacePropertyName(Method md) {
-        if (md.getReturnType().equals(Void.class))
-            return null; // not an accessor
-
-        if (md.getParameterTypes().length > 1)
-            return null; // not an accessor
-
-        if (Object.class.equals(md.getDeclaringClass()))
-            return null; // do not use any methods on the Object class as a accessor
-
-        return md.getName().replace('_', '.'); // TODO support all the escaping mechanisms.
-    }
-
     @SuppressWarnings({ "rawtypes", "unchecked" })
-    private void handleField(Object obj, Field field, Set<String> handledFields, Map result,
+    private void handleDTOField(Object obj, Field field, Set<String> handledFields, Map result,
             InternalConverter converter) {
-        if (Modifier.isStatic(field.getModifiers()))
+        String fn = Util.getDTOKey(field);
+        if (fn == null)
             return;
 
-        String fn = unMangleName(field.getName());
         if (handledFields.contains(fn))
             return; // Field with this name was already handled
 
@@ -804,64 +790,38 @@ public class ConvertingImpl implements C
         }
     }
 
-    private String mangleName(String key) {
-        String res = key.replace("_", "__");
-        res = res.replace("$", "$$");
-        res = res.replaceAll("[.]([._])", "_\\$$1");
-        res = res.replace('.', '_');
-        // TODO handle Java keywords
-        return res;
-    }
-
-    private String unMangleName(String key) {
-        String res = key.replaceAll("_\\$", ".");
-        res = res.replace("__", "\f"); // parkl double underscore as formfeed char
-        res = res.replace('_', '.');
-        res = res.replace("$$", "\b"); // park double dollar as backspace char
-        res = res.replace("$", "");
-        res = res.replace('\f', '_');  // convert formfeed char back to single underscore
-        res = res.replace('\b', '$');  // convert backspace char back go dollar
-        return res;
-    }
-
     @SuppressWarnings({ "rawtypes", "unchecked" })
     private static void handleBeanMethod(Object obj, Method md, Set<String> invokedMethods, Map res) {
-        if (Modifier.isStatic(md.getModifiers()))
+        String bp = Util.getBeanKey(md);
+        if (bp == null)
             return;
 
-        String mn = md.getName();
-        if (invokedMethods.contains(mn))
+        if (invokedMethods.contains(bp))
             return; // method with this name already invoked
 
-        String propName = getAccessorPropertyName(md);
-        if (propName == null)
-            return;
-
         try {
-            res.put(propName.toString(), md.invoke(obj));
-            invokedMethods.add(mn);
+            res.put(bp, md.invoke(obj));
+            invokedMethods.add(bp);
         } catch (Exception e) {
         }
     }
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
     private static void handleInterfaceMethod(Object obj, Method md, Set<String> invokedMethods, Map res) {
-        if (Modifier.isStatic(md.getModifiers()))
-            return;
-
-        if (md.getParameterCount() > 0)
-            return;
-
         String mn = md.getName();
         if (invokedMethods.contains(mn))
             return; // method with this name already invoked
 
-        String propName = getInterfacePropertyName(md);
+        String propName = Util.getInterfacePropertyName(md);
         if (propName == null)
             return;
 
         try {
-            res.put(propName.toString(), md.invoke(obj));
+            Object r = Util.getInterfaceProperty(obj, md);
+            if (r == null)
+                return;
+
+            res.put(propName, r);
             invokedMethods.add(mn);
         } catch (Exception e) {
         }

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/DynamicMapLikeFacade.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/DynamicMapLikeFacade.java?rev=1781884&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/DynamicMapLikeFacade.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/DynamicMapLikeFacade.java Mon Feb  6 11:54:34 2017
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.converter.impl;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+abstract class DynamicMapLikeFacade<K, V> implements Map<K, V> {
+    protected final ConvertingImpl convertingImpl;
+
+    protected DynamicMapLikeFacade(ConvertingImpl convertingImpl) {
+        this.convertingImpl = convertingImpl;
+    }
+
+    @Override
+    public int size() {
+        return keySet().size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return keySet().contains(key);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        for (Entry<K, V> entry : entrySet()) {
+            if (value == null) {
+                if (entry.getValue() == null) {
+                    return true;
+                }
+            } else if (value.equals(entry.getValue())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public V put(K key, V value) {
+        // Should never be called; the delegate should swap to a copy in this case
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public V remove(Object key) {
+        // Should never be called; the delegate should swap to a copy in this case
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+        // Should never be called; the delegate should swap to a copy in this case
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clear() {
+        // Should never be called; the delegate should swap to a copy in this case
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Collection<V> values() {
+        return entrySet().stream().map(Entry::getValue).collect(Collectors.toList());
+    }
+
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+        Set<K> ks = keySet();
+
+        Set<Entry<K, V>> res = new LinkedHashSet<>(ks.size());
+
+        for (K k : ks) {
+            V v = get(k);
+            res.add(new MapDelegate.MapEntry<K,V>(k, v));
+        }
+        return res;
+    }
+}
+
+class DynamicBeanFacade extends DynamicMapLikeFacade<String,Object> {
+    private Map <String, Method> keys = null;
+    private final Object backingObject;
+
+    DynamicBeanFacade(Object backingObject, ConvertingImpl convertingImpl) {
+        super(convertingImpl);
+        this.backingObject = backingObject;
+    }
+
+    @Override
+    public Object get(Object key) {
+        Method m = getKeys().get(key);
+        try {
+            return m.invoke(backingObject);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Set<String> keySet() {
+        return getKeys().keySet();
+    }
+
+    private Map<String, Method> getKeys() {
+        if (keys == null)
+            keys = Util.getBeanKeys(convertingImpl.sourceClass);
+
+        return keys;
+    }
+}
+
+class DynamicDictionaryFacade<K,V> extends DynamicMapLikeFacade<K,V> {
+    private final Dictionary<K, V> backingObject;
+
+    DynamicDictionaryFacade(Dictionary<K, V> backingObject, ConvertingImpl convertingImpl) {
+        super(convertingImpl);
+        this.backingObject = backingObject;
+    }
+
+    @Override
+    public V get(Object key) {
+        return backingObject.get(key);
+    }
+
+    @Override
+    public Set<K> keySet() {
+        return new HashSet<>(Collections.list(backingObject.keys()));
+    }
+}
+
+class DynamicMapFacade<K,V> extends DynamicMapLikeFacade<K,V> {
+    private final Map<K, V> backingObject;
+
+    DynamicMapFacade(Map<K,V> backingObject, ConvertingImpl convertingImpl) {
+        super(convertingImpl);
+        this.backingObject = backingObject;
+    }
+
+    @Override
+    public V get(Object key) {
+        return backingObject.get(key);
+    }
+
+    @Override
+    public Set<K> keySet() {
+        Map<K, V> m = backingObject;
+        return m.keySet();
+    }
+}
+
+class DynamicDTOFacade extends DynamicMapLikeFacade<String, Object> {
+    private Map <String, Field> keys = null;
+    private final Object backingObject;
+
+    DynamicDTOFacade(Object backingObject, ConvertingImpl converting) {
+        super(converting);
+        this.backingObject = backingObject;
+    }
+
+    @Override
+    public Object get(Object key) {
+        Field f = getKeys().get(key);
+        try {
+            return f.get(backingObject);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Set<String> keySet() {
+        return getKeys().keySet();
+    }
+
+    private Map<String, Field> getKeys() {
+        if (keys == null)
+            keys = Util.getDTOKeys(convertingImpl.sourceClass);
+
+        return keys;
+    }
+}
+
+class DynamicInterfaceFacade extends DynamicMapLikeFacade<String, Object> {
+    private Map <String, Method> keys = null;
+    private final Object backingObject;
+
+    DynamicInterfaceFacade(Object backingObject, ConvertingImpl convertingImpl) {
+        super(convertingImpl);
+        this.backingObject = backingObject;
+    }
+
+    @Override
+    public Object get(Object key) {
+        Method m = getKeys().get(key);
+        try {
+            return m.invoke(backingObject);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Set<String> keySet() {
+        return getKeys().keySet();
+    }
+
+    private Map<String, Method> getKeys() {
+        if (keys == null)
+            keys = Util.getInterfaceKeys(convertingImpl.sourceClass);
+
+        return keys;
+    }
+}
\ No newline at end of file

Added: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/MapDelegate.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/MapDelegate.java?rev=1781884&view=auto
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/MapDelegate.java (added)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/MapDelegate.java Mon Feb  6 11:54:34 2017
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.converter.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+class MapDelegate<K, V> implements Map<K, V> {
+    private final ConvertingImpl convertingImpl;
+    Map<K, V> delegate;
+
+    private MapDelegate(ConvertingImpl converting, Map<K, V> del) {
+        convertingImpl = converting;
+        delegate = del;
+    }
+
+    static MapDelegate<String, Object> forBean(Object b, ConvertingImpl converting) {
+        return new MapDelegate<>(converting, new DynamicBeanFacade(b, converting));
+    }
+
+    static <K, V> Map<K, V> forMap(Map<K, V> m, ConvertingImpl converting) {
+        return new MapDelegate<>(converting, new DynamicMapFacade<>(m, converting));
+    }
+
+    static <K, V> MapDelegate<K, V> forDictionary(Dictionary<K, V> d, ConvertingImpl converting) {
+        return new MapDelegate<>(converting, new DynamicDictionaryFacade<>(d, converting));
+    }
+
+    static MapDelegate<String, Object> forDTO(Object obj, ConvertingImpl converting) {
+        return new MapDelegate<>(converting, new DynamicDTOFacade(obj, converting));
+    }
+
+    static MapDelegate<String, Object> forInterface(Object obj, ConvertingImpl converting) {
+        return new MapDelegate<>(converting, new DynamicInterfaceFacade(obj, converting));
+    }
+
+    public int size() {
+        return delegate.size();
+    }
+
+    public boolean isEmpty() {
+        return delegate.isEmpty();
+    }
+
+    public boolean containsKey(Object key) {
+        return delegate.containsKey(key);
+    }
+
+    public boolean containsValue(Object value) {
+        return delegate.containsValue(value);
+    }
+
+    @SuppressWarnings("unchecked")
+    public V get(Object key) {
+        V val = null;
+        if (keySet().contains(key)) {
+            val = delegate.get(key);
+        }
+
+        if (val == null) {
+            key = findConvertedKey(keySet(), key);
+            val = delegate.get(key);
+        }
+
+        if (val == null)
+            return null;
+        else
+            return (V) getConvertedValue(key, val);
+    }
+
+    private Object getConvertedValue(Object key, Object val) {
+        List<Object> ks = new ArrayList<>(convertingImpl.keys);
+        ks.add(key);
+        return convertingImpl.convertMapValue(val, ks.toArray());
+    }
+
+    private Object findConvertedKey(Set<?> keySet, Object key) {
+        for (Object k : keySet) {
+            Object c = convertingImpl.converter.convert(k).to(key.getClass());
+            if (c != null && c.equals(key))
+                return k;
+
+//          Maybe the other way around too?
+//            Object c2 = facade.convertingImpl.converter.convert(key).to(k.getClass());
+//            if (c2 != null && c2.equals(key))
+//                return c2;
+        }
+        return key;
+    }
+
+    public V put(K key, V value) {
+        cloneDelegate();
+
+        return delegate.put(key, value);
+    }
+
+    public V remove(Object key) {
+        cloneDelegate();
+
+        return delegate.remove(key);
+    }
+
+    public void putAll(Map<? extends K, ? extends V> m) {
+        cloneDelegate();
+
+        delegate.putAll(m);
+    }
+
+    public void clear() {
+        delegate = new HashMap<>();
+    }
+
+    private Set<K> internalKeySet() {
+        return delegate.keySet();
+    }
+
+    public Set<K> keySet() {
+        Set<K> keys = new HashSet<>();
+        for (Map.Entry<K,V> entry : entrySet()) {
+            keys.add(entry.getKey());
+        }
+        return keys;
+    }
+
+    public Collection<V> values() {
+        List<V> values = new ArrayList<>();
+        for (Map.Entry<K,V> entry : entrySet()) {
+            values.add(entry.getValue());
+        }
+        return values;
+    }
+
+    @SuppressWarnings("unchecked")
+    public Set<java.util.Map.Entry<K, V>> entrySet() {
+        Set<Map.Entry<K,V>> result = new HashSet<>();
+        for (Map.Entry<?,?> entry : delegate.entrySet()) {
+            K key = (K) findConvertedKey(internalKeySet(), entry.getKey());
+            V val = (V) getConvertedValue(key, entry.getValue());
+            result.add(new MapEntry<K,V>(key, val));
+        }
+        return result;
+    }
+
+    public boolean equals(Object o) {
+        return delegate.equals(o);
+    }
+
+    public int hashCode() {
+        return delegate.hashCode();
+    }
+
+    public V getOrDefault(Object key, V defaultValue) {
+        return delegate.getOrDefault(key, defaultValue);
+    }
+
+    public void forEach(BiConsumer<? super K, ? super V> action) {
+        delegate.forEach(action);
+    }
+
+    public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+        cloneDelegate();
+
+        delegate.replaceAll(function);
+    }
+
+    public V putIfAbsent(K key, V value) {
+        cloneDelegate();
+
+        return delegate.putIfAbsent(key, value);
+    }
+
+    public boolean remove(Object key, Object value) {
+        cloneDelegate();
+
+        return delegate.remove(key, value);
+    }
+
+    public boolean replace(K key, V oldValue, V newValue) {
+        cloneDelegate();
+
+        return delegate.replace(key, oldValue, newValue);
+    }
+
+    public V replace(K key, V value) {
+        cloneDelegate();
+
+        return delegate.replace(key, value);
+    }
+
+    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+        return delegate.computeIfAbsent(key, mappingFunction);
+    }
+
+    public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        return delegate.computeIfPresent(key, remappingFunction);
+    }
+
+    public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        return delegate.compute(key, remappingFunction);
+    }
+
+    public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+        cloneDelegate();
+
+        return delegate.merge(key, value, remappingFunction);
+    }
+
+    private void cloneDelegate() {
+        delegate = new HashMap<>(delegate);
+    }
+
+    static class MapEntry<K,V> implements Map.Entry<K,V> {
+        private final K key;
+        private final V value;
+
+        MapEntry(K k, V v) {
+            key = k;
+            value = v;
+        }
+
+        @Override
+        public K getKey() {
+            return key;
+        }
+
+        @Override
+        public V getValue() {
+            return value;
+        }
+
+        @Override
+        public V setValue(V value) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
\ No newline at end of file

Modified: felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Util.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Util.java?rev=1781884&r1=1781883&r2=1781884&view=diff
==============================================================================
--- felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Util.java (original)
+++ felix/trunk/converter/converter/src/main/java/org/apache/felix/converter/impl/Util.java Mon Feb  6 11:54:34 2017
@@ -16,9 +16,14 @@
  */
 package org.apache.felix.converter.impl;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.lang.reflect.Type;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 class Util {
@@ -53,4 +58,134 @@ class Util {
         else
             return cls;
     }
+
+    static Map<String, Method> getBeanKeys(Class<?> beanClass) {
+        Map<String, Method> keys = new LinkedHashMap<>();
+        for (Method md : beanClass.getDeclaredMethods()) {
+            String key = getBeanKey(md);
+            if (key != null && !keys.containsKey(key))
+                keys.put(key, md);
+        }
+        return keys;
+    }
+
+    static String getBeanKey(Method md) {
+        if (Modifier.isStatic(md.getModifiers()))
+            return null;
+
+        if (!Modifier.isPublic(md.getModifiers()))
+            return null;
+
+        return getBeanAccessorPropertyName(md);
+    }
+
+    private static String getBeanAccessorPropertyName(Method md) {
+        if (md.getReturnType().equals(Void.class))
+            return null; // not an accessor
+
+        if (md.getParameterTypes().length > 0)
+            return null; // not an accessor
+
+        if (Object.class.equals(md.getDeclaringClass()))
+            return null; // do not use any methods on the Object class as a accessor
+
+        String mn = md.getName();
+        int prefix;
+        if (mn.startsWith("get"))
+            prefix = 3;
+        else if (mn.startsWith("is"))
+            prefix = 2;
+        else
+            return null; // not an accessor prefix
+
+        if (mn.length() <= prefix)
+            return null; // just 'get' or 'is': not an accessor
+        String propStr = mn.substring(prefix);
+        StringBuilder propName = new StringBuilder(propStr.length());
+        char firstChar = propStr.charAt(0);
+        if (!Character.isUpperCase(firstChar))
+            return null; // no acccessor as no camel casing
+        propName.append(Character.toLowerCase(firstChar));
+        if (propStr.length() > 1)
+            propName.append(propStr.substring(1));
+
+        return propName.toString();
+    }
+
+
+    static Map<String, Field> getDTOKeys(Class<?> dto) {
+        Map<String, Field> keys = new LinkedHashMap<>();
+
+        for (Field f : dto.getFields()) {
+            String key = getDTOKey(f);
+            if (key != null && !keys.containsKey(key))
+                keys.put(key, f);
+        }
+        return keys;
+    }
+
+    static String getDTOKey(Field f) {
+        if (Modifier.isStatic(f.getModifiers()))
+            return null;
+
+        if (!Modifier.isPublic(f.getModifiers()))
+            return null;
+
+        return unMangleName(f.getName());
+    }
+
+    static Map<String, Method> getInterfaceKeys(Class<?> intf) {
+        Map<String, Method> keys = new LinkedHashMap<>();
+
+        for (Method md : intf.getMethods()) {
+            String name = getInterfacePropertyName(md);
+            if (name != null)
+                keys.put(name, md);
+        }
+        return keys;
+    }
+
+    static String getInterfacePropertyName(Method md) {
+        if (md.getReturnType().equals(Void.class))
+            return null; // not an accessor
+
+        if (md.getParameterTypes().length > 1)
+            return null; // not an accessor
+
+        if (Object.class.equals(md.getDeclaringClass()))
+            return null; // do not use any methods on the Object class as a accessor
+
+        return md.getName().replace('_', '.'); // TODO support all the escaping mechanisms.
+    }
+
+    static Object getInterfaceProperty(Object obj, Method md) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        if (Modifier.isStatic(md.getModifiers()))
+            return null;
+
+        if (md.getParameterCount() > 0)
+            return null;
+
+        return md.invoke(obj);
+    }
+
+    static String mangleName(String key) {
+        String res = key.replace("_", "__");
+        res = res.replace("$", "$$");
+        res = res.replaceAll("[.]([._])", "_\\$$1");
+        res = res.replace('.', '_');
+        // TODO handle Java keywords
+        return res;
+    }
+
+    static String unMangleName(String key) {
+        String res = key.replaceAll("_\\$", ".");
+        res = res.replace("__", "\f"); // park double underscore as formfeed char
+        res = res.replace('_', '.');
+        res = res.replace("$$", "\b"); // park double dollar as backspace char
+        res = res.replace("$", "");
+        res = res.replace('\f', '_');  // convert formfeed char back to single underscore
+        res = res.replace('\b', '$');  // convert backspace char back go dollar
+        // TODO handle Java keywords
+        return res;
+    }
 }

Modified: felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterBuilderTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterBuilderTest.java?rev=1781884&r1=1781883&r2=1781884&view=diff
==============================================================================
--- felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterBuilderTest.java (original)
+++ felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterBuilderTest.java Mon Feb  6 11:54:34 2017
@@ -30,6 +30,7 @@ import java.util.stream.Stream;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.osgi.util.converter.ConvertFunction;
 import org.osgi.util.converter.Converter;
@@ -242,7 +243,7 @@ public class ConverterBuilderTest {
     }
 
     @SuppressWarnings("rawtypes")
-    @Test
+    @Test @Ignore("This test assumes that the all the embedded objects are also converted to maps, but they aren't")
     public void testConvertWithKeysDeep() {
         MyDTO6 subsubDTO1 = new MyDTO6();
         subsubDTO1.chars = Arrays.asList('a', 'b', 'c');

Modified: felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterEqualsTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterEqualsTest.java?rev=1781884&r1=1781883&r2=1781884&view=diff
==============================================================================
--- felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterEqualsTest.java (original)
+++ felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterEqualsTest.java Mon Feb  6 11:54:34 2017
@@ -21,6 +21,7 @@ import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Map;
 
+import org.junit.Ignore;
 import org.junit.Test;
 import org.osgi.util.converter.Converter;
 import org.osgi.util.converter.StandardConverter;
@@ -29,7 +30,7 @@ import static org.junit.Assert.assertFal
 import static org.junit.Assert.assertTrue;
 
 public class ConverterEqualsTest {
-    @Test
+    @Test @Ignore("This functionality should go")
     public void testEquals() {
         Converter c = new StandardConverter();
 

Modified: felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java?rev=1781884&r1=1781883&r2=1781884&view=diff
==============================================================================
--- felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java (original)
+++ felix/trunk/converter/converter/src/test/java/org/apache/felix/converter/impl/ConverterTest.java Mon Feb  6 11:54:34 2017
@@ -19,6 +19,8 @@ package org.apache.felix.converter.impl;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -34,6 +36,7 @@ import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -52,7 +55,6 @@ import org.apache.felix.converter.impl.M
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.osgi.dto.DTO;
 import org.osgi.util.converter.ConversionException;
 import org.osgi.util.converter.Converter;
 import org.osgi.util.converter.ConverterBuilder;
@@ -456,11 +458,17 @@ public class ConverterTest {
         assertEquals(Long.MIN_VALUE, m.get("pong"));
         assertEquals(Count.ONE, m.get("count"));
         assertNotNull(m.get("embedded"));
-        @SuppressWarnings("rawtypes")
+
+        MyEmbeddedDTO e = (MyEmbeddedDTO) m.get("embedded");
+        assertEquals("hohoho", e.marco);
+        assertEquals(Long.MAX_VALUE, e.polo);
+        assertEquals(Alpha.A, e.alpha);
+        /*
         Map e = (Map)m.get("embedded");
         assertEquals("hohoho", e.get("marco"));
         assertEquals(Long.MAX_VALUE, e.get("polo"));
         assertEquals(Alpha.A, e.get("alpha"));
+        */
     }
 
     @Test
@@ -483,11 +491,18 @@ public class ConverterTest {
         assertEquals(Long.MIN_VALUE, m.get("pong"));
         assertEquals(Count.ONE, m.get("count"));
         assertNotNull(m.get("embedded"));
-        @SuppressWarnings("rawtypes")
+
+        MyEmbeddedDTO e = (MyEmbeddedDTO) m.get("embedded");
+        assertEquals("hohoho", e.marco);
+        assertEquals(Long.MAX_VALUE, e.polo);
+        assertEquals(Alpha.A, e.alpha);
+
+        /* TODO this is the way it was, but it does not seem right
         Map e = (Map)m.get("embedded");
         assertEquals("hohoho", e.get("marco"));
         assertEquals(Long.MAX_VALUE, e.get("polo"));
         assertEquals(Alpha.A, e.get("alpha"));
+        */
     }
 
     @Test
@@ -523,7 +538,7 @@ public class ConverterTest {
         assertEquals(Count.ONE, e.count);
         assertNotNull(e.embedded);
         assertTrue(e.embedded instanceof MyEmbeddedDTO);
-        MyEmbeddedDTO e2 = (MyEmbeddedDTO)e.embedded;
+        MyEmbeddedDTO e2 = e.embedded;
         assertEquals("hohoho", e2.marco);
         assertEquals(Long.MAX_VALUE, e2.polo);
         assertEquals(Alpha.A, e2.alpha);
@@ -723,7 +738,112 @@ public class ConverterTest {
 
         // And convert back
         Map<String, String> m2 = converter.convert(dto).to(new TypeReference<Map<String,String>>() {});
-        assertEquals(m, m2);
+        assertEquals(new HashMap<String,String>(m), new HashMap<String,String>(m2));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testLiveMapFromInterface() {
+        int[] val = new int[1];
+        val[0] = 51;
+
+        MyIntf intf = new MyIntf() {
+            @Override
+            public int value() {
+                return val[0];
+            }
+        };
+
+        @SuppressWarnings("rawtypes")
+        Map m = converter.convert(intf).to(Map.class);
+        assertEquals(51, m.get("value"));
+
+        val[0] = 52;
+        assertEquals("Changes to the backing map should be reflected",
+                52, m.get("value"));
+
+        m.put("value", 53);
+        assertEquals(53, m.get("value"));
+
+        val[0] = 54;
+        assertEquals("Changes to the backing map should not be reflected any more",
+                53, m.get("value"));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testLiveMapFromDTO() {
+        MyDTO8 myDTO = new MyDTO8();
+
+        myDTO.count = MyDTO8.Count.TWO;
+        myDTO.pong = 42L;
+
+        @SuppressWarnings("rawtypes")
+        Map m = converter.convert(myDTO).to(Map.class);
+        assertEquals(42L, m.get("pong"));
+
+        myDTO.ping = "Ping!";
+        assertEquals("Ping!", m.get("ping"));
+        myDTO.pong = 52L;
+        assertEquals(52L, m.get("pong"));
+        myDTO.ping = "Pong!";
+        assertEquals("Pong!", m.get("ping"));
+
+        m.put("pong", 62L);
+        myDTO.ping = "Poing!";
+        myDTO.pong = 72L;
+        assertEquals("Pong!", m.get("ping"));
+        assertEquals(62L, m.get("pong"));
+    }
+
+    @Test
+    public void testLiveMapFromDictionary() throws URISyntaxException {
+        URI testURI = new URI("http://foo");
+        Hashtable<String, Object> d = new Hashtable<>();
+        d.put("test", testURI);
+
+        Map<String, Object> m = converter.convert(d).to(new TypeReference<Map<String, Object>>(){});
+        assertEquals(testURI, m.get("test"));
+
+        URI testURI2 = new URI("http://bar");
+        d.put("test2", testURI2);
+        assertEquals(testURI2, m.get("test2"));
+        assertEquals(testURI, m.get("test"));
+    }
+
+    @Test
+    public void testLiveMapFromMap() {
+        Map<String, String> s = new HashMap<>();
+
+        s.put("true", "123");
+        s.put("false", "456");
+
+        Map<Boolean, Short> m = converter.convert(s).to(new TypeReference<Map<Boolean, Short>>(){});
+        assertEquals(Short.valueOf("123"), m.get(Boolean.TRUE));
+        assertEquals(Short.valueOf("456"), m.get(Boolean.FALSE));
+
+        s.remove("true");
+        assertNull(m.get(Boolean.TRUE));
+
+        s.put("TRUE", "999");
+        assertEquals(Short.valueOf("999"), m.get(Boolean.TRUE));
+    }
+
+    @Test
+    public void testLiveMapFromBean() {
+        MyBean mb = new MyBean();
+        mb.beanVal = "" + Long.MAX_VALUE;
+
+        Map<SomeEnum, Long> m = converter.convert(mb).sourceAsBean().to(new TypeReference<Map<SomeEnum, Long>>(){});
+        assertEquals(1, m.size());
+        assertEquals(Long.valueOf(Long.MAX_VALUE), m.get(SomeEnum.VALUE));
+
+        mb.beanVal = "" + Long.MIN_VALUE;
+        assertEquals(Long.valueOf(Long.MIN_VALUE), m.get(SomeEnum.VALUE));
+
+        m.put(SomeEnum.GETVALUE, 123L);
+        mb.beanVal = "12";
+        assertEquals(Long.valueOf(Long.MIN_VALUE), m.get(SomeEnum.VALUE));
     }
 
     static class MyClass2 {
@@ -768,4 +888,6 @@ public class ConverterTest {
             return value;
         }
     }
+
+    enum SomeEnum { VALUE, GETVALUE };
 }