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; + } +}