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 2016/05/12 09:04:51 UTC

svn commit: r1743484 - in /felix/trunk/converter/src: main/java/org/apache/felix/converter/impl/ConvertingImpl.java test/java/org/apache/felix/converter/impl/ConverterMapTest.java test/java/org/apache/felix/converter/impl/MyBean.java

Author: davidb
Date: Thu May 12 09:04:51 2016
New Revision: 1743484

URL: http://svn.apache.org/viewvc?rev=1743484&view=rev
Log:
Felix Converter Service: support for conversions to-from Java Beans.

Added:
    felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/MyBean.java
Modified:
    felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java
    felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/ConverterMapTest.java

Modified: felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java?rev=1743484&r1=1743483&r2=1743484&view=diff
==============================================================================
--- felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java (original)
+++ felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConvertingImpl.java Thu May 12 09:04:51 2016
@@ -39,6 +39,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
+import org.osgi.service.converter.ConversionException;
 import org.osgi.service.converter.Converter;
 import org.osgi.service.converter.Converting;
 import org.osgi.service.converter.TypeReference;
@@ -112,12 +113,10 @@ public class ConvertingImpl implements C
         if (cls == null)
             return null;
 
-        Class<?> targetCls = cls;
-
         if (object == null)
             return handleNull(cls);
 
-        targetCls = primitiveToBoxed(targetCls);
+        Class<?> targetCls = primitiveToBoxed(cls);
 
         if (!Map.class.isAssignableFrom(targetCls) &&
                 !Collections.class.isAssignableFrom(targetCls)) {
@@ -256,7 +255,30 @@ public class ConvertingImpl implements C
             return convertToMap(targetCls, typeArguments);
         else if (Dictionary.class.isAssignableFrom(targetCls))
             return new Hashtable(convertToMap(Map.class, typeArguments));
-        return createProxy(targetCls);
+        else if (targetCls.isInterface())
+            return createProxy(targetCls);
+        return createJavaBean(targetCls);
+    }
+
+    private Object createJavaBean(Class<?> targetCls) {
+        @SuppressWarnings("rawtypes")
+        Map m = mapView(object);
+        try {
+            Object res = targetCls.getConstructor().newInstance();
+            for (Method setter : getSetters(targetCls)) {
+                String setterName = setter.getName();
+                StringBuilder propName = new StringBuilder(Character.valueOf(Character.toLowerCase(setterName.charAt(3))).toString());
+                if (setterName.length() > 4)
+                    propName.append(setterName.substring(4));
+
+                Class<?> setterType = setter.getParameterTypes()[0];
+                setter.invoke(res, converter.convert(m.get(propName.toString())).to(setterType));
+            }
+            return res;
+        } catch (Exception e) {
+            throw new ConversionException("Cannot convert to class: " + targetCls.getName() +
+                    ". Not a JavaBean with a Zero-arg Constructor.", e);
+        }
     }
 
     @SuppressWarnings("rawtypes")
@@ -294,6 +316,8 @@ public class ConvertingImpl implements C
         // All interface types that are not Collections are treated as maps
         if (targetCls.isInterface())
             return true;
+        else if (isWriteableJavaBean(targetCls))
+            return true;
         else
             return Dictionary.class.isAssignableFrom(targetCls);
     }
@@ -436,7 +460,10 @@ public class ConvertingImpl implements C
             return null; // just 'get' or 'is': not an accessor
         String propStr = mn.substring(prefix);
         StringBuilder propName = new StringBuilder(propStr.length());
-        propName.append(Character.toLowerCase(propStr.charAt(0)));
+        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));
 
@@ -450,6 +477,8 @@ public class ConvertingImpl implements C
             return; // method with this name already invoked
 
         String propName = getAccessorPropertyName(md);
+        if (propName == null)
+            return;
 
         try {
             res.put(propName.toString(), md.invoke(obj));
@@ -466,4 +495,38 @@ public class ConvertingImpl implements C
         else
             return createMapFromBeanAccessors(obj);
     }
+
+    private boolean isWriteableJavaBean(Class<?> cls) {
+        boolean hasNoArgCtor = false;
+        for (Constructor<?> ctor : cls.getConstructors()) {
+            if (ctor.getParameterTypes().length == 0)
+                hasNoArgCtor = true;
+        }
+        if (!hasNoArgCtor)
+            return false; // A JavaBean must have a public no-arg constructor
+
+        return getSetters(cls).size() > 0;
+    }
+
+    private Set<Method> getSetters(Class<?> cls) {
+        Set<Method> setters = new HashSet<>();
+        while (!Object.class.equals(cls)) {
+            Set<Method> methods = new HashSet<>();
+            methods.addAll(Arrays.asList(cls.getDeclaredMethods()));
+            methods.addAll(Arrays.asList(cls.getMethods()));
+            for (Method md : methods) {
+                if (md.getParameterTypes().length != 1)
+                    continue; // Only setters with a single argument
+                String name = md.getName();
+                if (name.length() < 4)
+                    continue;
+                if (name.startsWith("set") &&
+                        Character.isUpperCase(name.charAt(3)))
+                    setters.add(md);
+            }
+            cls = cls.getSuperclass();
+        }
+        return setters;
+    }
+
 }

Modified: felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/ConverterMapTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/ConverterMapTest.java?rev=1743484&r1=1743483&r2=1743484&view=diff
==============================================================================
--- felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/ConverterMapTest.java (original)
+++ felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/ConverterMapTest.java Thu May 12 09:04:51 2016
@@ -29,7 +29,9 @@ import org.junit.Test;
 import org.osgi.service.converter.Converter;
 import org.osgi.service.converter.TypeReference;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -71,6 +73,49 @@ public class ConverterMapTest {
     }
 
     @Test
+    public void testJavaBeanToMap() {
+        MyBean mb = new MyBean();
+        mb.setMe("You");
+        mb.setF(true);
+        mb.setNumbers(new int[] {3,2,1});
+
+        @SuppressWarnings("rawtypes")
+        Map m = converter.convert(mb).to(Map.class);
+        assertEquals(4, m.size());
+        assertEquals("You", m.get("me"));
+        assertTrue((boolean) m.get("f"));
+        assertFalse((boolean) m.get("enabled"));
+        assertArrayEquals(new int [] {3,2,1}, (int[]) m.get("numbers"));
+    }
+
+    @Test
+    public void testMapToJavaBean() {
+        Map<String, String> m = new HashMap<>();
+
+        m.put("me", "Joe");
+        m.put("enabled", "true");
+        m.put("numbers", "42");
+        m.put("s", "will disappear");
+        MyBean mb = converter.convert(m).to(MyBean.class);
+        assertEquals("Joe", mb.getMe());
+        assertTrue(mb.isEnabled());
+        assertNull(mb.getF());
+        assertArrayEquals(new int[] {42}, mb.getNumbers());
+    }
+
+    public void testMapToJavaBean2() {
+        Map<String, String> m = new HashMap<>();
+
+        m.put("blah", "blahblah");
+        m.put("f", "true");
+        MyBean mb = converter.convert(m).to(MyBean.class);
+        assertNull(mb.getMe());
+        assertTrue(mb.getF());
+        assertFalse(mb.isEnabled());
+        assertNull(mb.getNumbers());
+    }
+
+    @Test
     public void testInterfaceToMap() {
         Object obj = new Object();
         TestInterface impl = new TestInterface() {
@@ -80,7 +125,7 @@ public class ConverterMapTest {
             }
 
             @Override
-            public int getbar() {
+            public int getBar() {
                 return 76543;
             }
 
@@ -130,7 +175,7 @@ public class ConverterMapTest {
 
         TestInterface ti = converter.convert(m).to(TestInterface.class);
         assertEquals("12345", ti.getFoo());
-        assertEquals(999, ti.getbar());
+        assertEquals(999, ti.getBar());
     }
 
     @SuppressWarnings("rawtypes")
@@ -140,7 +185,7 @@ public class ConverterMapTest {
 
         TestInterface ti = converter.convert(m).to(TestInterface.class);
         assertNull(ti.getFoo());
-        assertEquals(0, ti.getbar());
+        assertEquals(0, ti.getBar());
     }
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
@@ -156,6 +201,6 @@ public class ConverterMapTest {
 
     interface TestInterface {
         String getFoo();
-        int getbar();
+        int getBar();
     }
 }

Added: felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/MyBean.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/MyBean.java?rev=1743484&view=auto
==============================================================================
--- felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/MyBean.java (added)
+++ felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/MyBean.java Thu May 12 09:04:51 2016
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+public class MyBean {
+    String me;
+    boolean enabled;
+    Boolean f;
+    int[] numbers;
+
+    public String get() {
+        return "Not a bean accessor because no camel casing";
+    }
+    public String gettisburgh() {
+        return "Not a bean accessor because no camel casing";
+    }
+    public int issue() {
+        return -1; // not a bean accessor as no camel casing
+    }
+    public void sets(String s) {
+        throw new RuntimeException("Not a bean accessor because no camel casing");
+    }
+    public String getMe() {
+        return me;
+    }
+    public void setMe(String me) {
+        this.me = me;
+    }
+    public boolean isEnabled() {
+        return enabled;
+    }
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+    public Boolean getF() {
+        return f;
+    }
+    public void setF(Boolean f) {
+        this.f = f;
+    }
+    public int[] getNumbers() {
+        return numbers;
+    }
+    public void setNumbers(int[] numbers) {
+        this.numbers = numbers;
+    }
+}