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