You are viewing a plain text version of this content. The canonical link for it is here.
Posted to xbean-scm@geronimo.apache.org by da...@apache.org on 2008/04/17 00:30:54 UTC

svn commit: r648883 - in /geronimo/xbean/trunk/xbean-reflect/src: main/java/org/apache/xbean/recipe/ObjectRecipe.java main/java/org/apache/xbean/recipe/ReflectionUtil.java test/java/org/apache/xbean/recipe/MatchBytypeTest.java

Author: dain
Date: Wed Apr 16 15:30:47 2008
New Revision: 648883

URL: http://svn.apache.org/viewvc?rev=648883&view=rev
Log:
Added experimental support for match by type properties

Added:
    geronimo/xbean/trunk/xbean-reflect/src/test/java/org/apache/xbean/recipe/MatchBytypeTest.java
      - copied, changed from r648017, geronimo/xbean/trunk/xbean-reflect/src/test/java/org/apache/xbean/recipe/HiddenPropertiesTest.java
Modified:
    geronimo/xbean/trunk/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java
    geronimo/xbean/trunk/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java

Modified: geronimo/xbean/trunk/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java
URL: http://svn.apache.org/viewvc/geronimo/xbean/trunk/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java?rev=648883&r1=648882&r2=648883&view=diff
==============================================================================
--- geronimo/xbean/trunk/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java (original)
+++ geronimo/xbean/trunk/xbean-reflect/src/main/java/org/apache/xbean/recipe/ObjectRecipe.java Wed Apr 16 15:30:47 2008
@@ -190,6 +190,10 @@
         setProperty(new SetterProperty(name), value);
     }
 
+    public void setAutoMatchProperty(String type, Object value){
+        setProperty(new AutoMatchProperty(type), value);
+    }
+
     private void setProperty(Property key, Object value) {
         if (value instanceof UnsetPropertiesRecipe) {
             allow(Option.IGNORE_MISSING_PROPERTIES);
@@ -378,6 +382,48 @@
             } else if (propertyName instanceof FieldProperty){
                 FieldMember member = new FieldMember(ReflectionUtil.findField(clazz, propertyName.name, propertyValue, options));
                 members.add(member);
+            } else if (propertyName instanceof AutoMatchProperty){
+                MissingAccessorException noField = null;
+                if (options.contains(Option.FIELD_INJECTION)) {
+                    List<Field> fieldsByType = null;
+                    try {
+                        fieldsByType = ReflectionUtil.findAllFieldsByType(clazz, propertyValue, options);
+                        FieldMember member = new FieldMember(fieldsByType.iterator().next());
+                        members.add(member);
+                    } catch (MissingAccessorException e) {
+                        noField = e;
+                    }
+
+                    // if we got more then one matching field, that is an immidate error
+                    if (fieldsByType != null && fieldsByType.size() > 1) {
+                        List<String> matches = new ArrayList<String>();
+                        for (Field field : fieldsByType) {
+                            matches.add(field.getName());
+                        }
+                        throw new MissingAccessorException("Property of type " + propertyValue.getClass().getName() + " can be mapped to more then one field: " + matches, 0);
+                    }
+                }
+
+                // if we didn't find any fields, try the setters
+                if (members.isEmpty()) {
+                    List<Method> settersByType;
+                    try {
+                        settersByType = ReflectionUtil.findAllSettersByType(clazz, propertyValue, options);
+                        MethodMember member = new MethodMember(settersByType.iterator().next());
+                        members.add(member);
+                    } catch (MissingAccessorException noSetter) {
+                        throw (noField == null || noSetter.getMatchLevel() > noField.getMatchLevel())? noSetter: noField;
+                    }
+
+                    // if we got more then one matching field, that is an immidate error
+                    if (settersByType != null && settersByType.size() > 1) {
+                        List<String> matches = new ArrayList<String>();
+                        for (Method setter : settersByType) {
+                            matches.add(setter.getName());
+                        }
+                        throw new MissingAccessorException("Property of type " + propertyValue.getClass().getName() + " can be mapped to more then one setter: " + matches, 0);
+                    }
+                }
             } else {
                 // add setter members
                 MissingAccessorException noSetter = null;
@@ -404,6 +450,43 @@
                         }
                     }
                 }
+
+//                if (options.contains(Option.MATCH_BY_TYPE) && members.isEmpty()) {
+//                    try {
+//                        List<java.lang.reflect.Member> membersByType = ReflectionUtil.findAllMembersByType(clazz, propertyValue, options);
+//                        if (membersByType.size() > 1) {
+//                            List<String> matches = new ArrayList<String>();
+//                            for (java.lang.reflect.Member member : membersByType) {
+//                                if (member instanceof Field) {
+//                                    Field field = (Field) member;
+//                                    matches.add(field.getName());
+//                                } else if (member instanceof Method) {
+//                                    Method setter = (Method) member;
+//                                    matches.add(setter.getName());
+//                                }
+//                            }
+//
+//                            throw new MissingAccessorException("Property of type " + propertyValue.getClass().getName() + " can be mapped to more then one field or setter: " + matches, 0);
+//                        }
+//
+//                        java.lang.reflect.Member member  = membersByType.iterator().next();
+//                        if (member instanceof Field) {
+//                            Field field = (Field) member;
+//                            members.add(new FieldMember(field));
+//                        } else if (member instanceof Method) {
+//                            Method setter = (Method) member;
+//                            members.add(new MethodMember(setter));
+//                        }
+//                    } catch (MissingAccessorException e) {
+//                        if (noSetter != null && noSetter.getMatchLevel() > e.getMatchLevel()) {
+//                            e = noSetter;
+//                        }
+//                        if (noField != null && noField.getMatchLevel() > e.getMatchLevel()) {
+//                            e = noField;
+//                        }
+//                        throw e;
+//                    }
+//                }
             }
         } catch (MissingAccessorException e) {
             if (options.contains(Option.IGNORE_MISSING_PROPERTIES)) {
@@ -642,6 +725,19 @@
         }
         public String toString() {
             return "[field] "+ super.toString();
+        }
+    }
+
+    public static class AutoMatchProperty extends Property {
+        public AutoMatchProperty(String type) {
+            super(type);
+        }
+
+        public int hashCode() {
+            return super.hashCode()+1;
+        }
+        public String toString() {
+            return "[auto-match] "+ super.toString();
         }
     }
 }

Modified: geronimo/xbean/trunk/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java
URL: http://svn.apache.org/viewvc/geronimo/xbean/trunk/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java?rev=648883&r1=648882&r2=648883&view=diff
==============================================================================
--- geronimo/xbean/trunk/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java (original)
+++ geronimo/xbean/trunk/xbean-reflect/src/main/java/org/apache/xbean/recipe/ReflectionUtil.java Wed Apr 16 15:30:47 2008
@@ -17,6 +17,7 @@
  */
 package org.apache.xbean.recipe;
 
+import java.lang.annotation.Annotation;
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -24,7 +25,6 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.Type;
-import java.lang.annotation.Annotation;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.ArrayList;
@@ -32,10 +32,10 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumSet;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
-import java.util.LinkedList;
-import java.util.LinkedHashSet;
 
 import static org.apache.xbean.recipe.RecipeHelper.isAssignableFrom;
 
@@ -313,6 +313,176 @@
                 buffer.append(propertyValue.getClass().getName());
             }
             buffer.append(")");
+            throw new MissingAccessorException(buffer.toString(), -1);
+        }
+    }
+
+    public static List<Field> findAllFieldsByType(Class typeClass, Object propertyValue, Set<Option> options) {
+        if (typeClass == null) throw new NullPointerException("typeClass is null");
+        if (options == null) options = EnumSet.noneOf(Option.class);
+
+        int matchLevel = 0;
+        MissingAccessorException missException = null;
+
+        List<Field> fields = new ArrayList<Field>(Arrays.asList(typeClass.getDeclaredFields()));
+        Class parent = typeClass.getSuperclass();
+        while (parent != null){
+            fields.addAll(Arrays.asList(parent.getDeclaredFields()));
+            parent = parent.getSuperclass();
+        }
+
+        boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES);
+        boolean allowStatic = options.contains(Option.STATIC_PROPERTIES);
+
+        LinkedList<Field> validFields = new LinkedList<Field>();
+        for (Field field : fields) {
+            Class fieldType = field.getType();
+            if (RecipeHelper.isInstance(fieldType, propertyValue) || RecipeHelper.isConvertable(fieldType, propertyValue)) {
+                if (!allowPrivate && !Modifier.isPublic(field.getModifiers())) {
+                    if (matchLevel < 4) {
+                        matchLevel = 4;
+                        missException = new MissingAccessorException("Field is not public: " + field, matchLevel);
+                    }
+                    continue;
+                }
+
+                if (!allowStatic && Modifier.isStatic(field.getModifiers())) {
+                    if (matchLevel < 4) {
+                        matchLevel = 4;
+                        missException = new MissingAccessorException("Field is static: " + field, matchLevel);
+                    }
+                    continue;
+                }
+
+
+                if (fieldType.isPrimitive() && propertyValue == null) {
+                    if (matchLevel < 6) {
+                        matchLevel = 6;
+                        missException = new MissingAccessorException("Null can not be assigned to " +
+                                fieldType.getName() + ": " + field, matchLevel);
+                    }
+                    continue;
+                }
+
+                if (allowPrivate && !Modifier.isPublic(field.getModifiers())) {
+                    setAccessible(field);
+                }
+
+                if (RecipeHelper.isInstance(fieldType, propertyValue)) {
+                    // This field requires no conversion, which means there can not be a conversion error.
+                    // Therefore this setter is perferred and put a the head of the list
+                    validFields.addFirst(field);
+                } else {
+                    validFields.add(field);
+                }
+            }
+        }
+
+        if (!validFields.isEmpty()) {
+            // remove duplicate methods (can happen with inheritance)
+            return new ArrayList<Field>(new LinkedHashSet<Field>(validFields));
+        }
+
+        if (missException != null) {
+            throw missException;
+        } else {
+            StringBuffer buffer = new StringBuffer("Unable to find a valid field ");
+            if (propertyValue instanceof Recipe) {
+                buffer.append("for ").append(propertyValue == null ? "null" : propertyValue);
+            } else {
+                buffer.append("of type ").append(propertyValue == null ? "null" : propertyValue.getClass().getName());
+            }
+            buffer.append(" in class ").append(typeClass.getName());
+            throw new MissingAccessorException(buffer.toString(), -1);
+        }
+    }
+    public static List<Method> findAllSettersByType(Class typeClass, Object propertyValue, Set<Option> options) {
+        if (typeClass == null) throw new NullPointerException("typeClass is null");
+        if (options == null) options = EnumSet.noneOf(Option.class);
+
+        int matchLevel = 0;
+        MissingAccessorException missException = null;
+
+        boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES);
+        boolean allowStatic = options.contains(Option.STATIC_PROPERTIES);
+
+        LinkedList<Method> validSetters = new LinkedList<Method>();
+        List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods()));
+        methods.addAll(Arrays.asList(typeClass.getDeclaredMethods()));
+        for (Method method : methods) {
+            if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && (RecipeHelper.isInstance(method.getParameterTypes()[0], propertyValue) || RecipeHelper.isConvertable(method.getParameterTypes()[0], propertyValue))) {
+                if (method.getReturnType() != Void.TYPE) {
+                    if (matchLevel < 2) {
+                        matchLevel = 2;
+                        missException = new MissingAccessorException("Setter returns a value: " + method, matchLevel);
+                    }
+                    continue;
+                }
+
+                if (Modifier.isAbstract(method.getModifiers())) {
+                    if (matchLevel < 3) {
+                        matchLevel = 3;
+                        missException = new MissingAccessorException("Setter is abstract: " + method, matchLevel);
+                    }
+                    continue;
+                }
+
+                if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) {
+                    if (matchLevel < 4) {
+                        matchLevel = 4;
+                        missException = new MissingAccessorException("Setter is not public: " + method, matchLevel);
+                    }
+                    continue;
+                }
+
+                Class methodParameterType = method.getParameterTypes()[0];
+                if (methodParameterType.isPrimitive() && propertyValue == null) {
+                    if (matchLevel < 6) {
+                        matchLevel = 6;
+                        missException = new MissingAccessorException("Null can not be assigned to " +
+                                methodParameterType.getName() + ": " + method, matchLevel);
+                    }
+                    continue;
+                }
+
+                if (!allowStatic && Modifier.isStatic(method.getModifiers())) {
+                    if (matchLevel < 4) {
+                        matchLevel = 4;
+                        missException = new MissingAccessorException("Setter is static: " + method, matchLevel);
+                    }
+                    continue;
+                }
+
+                if (allowPrivate && !Modifier.isPublic(method.getModifiers())) {
+                    setAccessible(method);
+                }
+
+                if (RecipeHelper.isInstance(methodParameterType, propertyValue)) {
+                    // This setter requires no conversion, which means there can not be a conversion error.
+                    // Therefore this setter is perferred and put a the head of the list
+                    validSetters.addFirst(method);
+                } else {
+                    validSetters.add(method);
+                }
+            }
+
+        }
+
+        if (!validSetters.isEmpty()) {
+            // remove duplicate methods (can happen with inheritance)
+            return new ArrayList<Method>(new LinkedHashSet<Method>(validSetters));
+        }
+
+        if (missException != null) {
+            throw missException;
+        } else {
+            StringBuffer buffer = new StringBuffer("Unable to find a valid setter ");
+            if (propertyValue instanceof Recipe) {
+                buffer.append("for ").append(propertyValue == null ? "null" : propertyValue);
+            } else {
+                buffer.append("of type ").append(propertyValue == null ? "null" : propertyValue.getClass().getName());
+            }
+            buffer.append(" in class ").append(typeClass.getName());
             throw new MissingAccessorException(buffer.toString(), -1);
         }
     }

Copied: geronimo/xbean/trunk/xbean-reflect/src/test/java/org/apache/xbean/recipe/MatchBytypeTest.java (from r648017, geronimo/xbean/trunk/xbean-reflect/src/test/java/org/apache/xbean/recipe/HiddenPropertiesTest.java)
URL: http://svn.apache.org/viewvc/geronimo/xbean/trunk/xbean-reflect/src/test/java/org/apache/xbean/recipe/MatchBytypeTest.java?p2=geronimo/xbean/trunk/xbean-reflect/src/test/java/org/apache/xbean/recipe/MatchBytypeTest.java&p1=geronimo/xbean/trunk/xbean-reflect/src/test/java/org/apache/xbean/recipe/HiddenPropertiesTest.java&r1=648017&r2=648883&rev=648883&view=diff
==============================================================================
--- geronimo/xbean/trunk/xbean-reflect/src/test/java/org/apache/xbean/recipe/HiddenPropertiesTest.java (original)
+++ geronimo/xbean/trunk/xbean-reflect/src/test/java/org/apache/xbean/recipe/MatchBytypeTest.java Wed Apr 16 15:30:47 2008
@@ -23,27 +23,31 @@
 /**
  * @version $Rev$ $Date$
  */
-public class HiddenPropertiesTest extends TestCase {
+public class MatchBytypeTest extends TestCase {
 
-    public void test() throws Exception {
+    public void testMatch() throws Exception {
         ObjectRecipe recipe = new ObjectRecipe(Child.class);
-        recipe.setProperty("age", 10);
-        recipe.setProperty("website", "http://foo.com");
-        recipe.setProperty(Child.class.getName()+"/name", "TheChild");
-        recipe.setProperty(Parent.class.getName()+"/name", "TheParent");
-        recipe.setProperty(Child.class.getName()+"/color", "Red");
-        recipe.setProperty(Parent.class.getName()+"/color", "Blue");
+        recipe.setAutoMatchProperty("java.net.URL", new URL("http://foo.com"));
 
         recipe.allow(Option.FIELD_INJECTION);
         recipe.allow(Option.PRIVATE_PROPERTIES);
 
         Child child = (Child) recipe.create();
 
-        assertEquals("Child.getChildName()", "TheChild", child.getChildName());
-        assertEquals("Child.getParentName()", "TheParent", child.getParentName());
-        assertEquals("Child.getChildColor()", "Red", child.getChildColor());
-        assertEquals("Child.getParentColor()", "Blue", child.getParentColor());
         assertEquals("Child.getWebsite()", new URL("http://foo.com"), child.getWebsite());
-        assertEquals("Child.getAge()", 10, child.getAge());
     }
-}
+
+    public void testNoMatch() throws Exception {
+        ObjectRecipe recipe = new ObjectRecipe(Child.class);
+        recipe.setAutoMatchProperty("java.lang.String", "some string value");
+
+        recipe.allow(Option.FIELD_INJECTION);
+        recipe.allow(Option.PRIVATE_PROPERTIES);
+
+        try {
+            recipe.create();
+            fail("Expected MissingAccessorException");
+        } catch (MissingAccessorException expected) {
+        }
+    }
+}
\ No newline at end of file