Author: shane.bryzak(a)jboss.com
Date: 2010-05-30 07:54:06 -0400 (Sun, 30 May 2010)
New Revision: 6360
Added:
extensions/trunk/src/main/java/org/jboss/weld/extensions/util/AnnotatedBeanProperty.java
Log:
WELDX-104
Added:
extensions/trunk/src/main/java/org/jboss/weld/extensions/util/AnnotatedBeanProperty.java
===================================================================
---
extensions/trunk/src/main/java/org/jboss/weld/extensions/util/AnnotatedBeanProperty.java
(rev 0)
+++
extensions/trunk/src/main/java/org/jboss/weld/extensions/util/AnnotatedBeanProperty.java 2010-05-30
11:54:06 UTC (rev 6360)
@@ -0,0 +1,367 @@
+package org.jboss.weld.extensions.util;
+
+import java.beans.Introspector;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.lang.ExceptionInInitializerError;
+
+/**
+ * A convenience class for working with an annotated property (either a field or method)
of
+ * a JavaBean class. By providing an isMatch() method in a concrete implementation
+ * of this class, annotations may be matched on their attribute values or other
+ * conditions.
+ *
+ * @author Shane Bryzak
+ */
+public class AnnotatedBeanProperty<T extends Annotation>
+{
+ private Field propertyField;
+ private Method propertyGetter;
+ private Method propertySetter;
+ private String name;
+ private Type propertyType;
+ private T annotation;
+
+ private boolean isFieldProperty;
+ private boolean set = false;
+
+ private Class<?> targetClass;
+ private Class<T> annotationClass;
+ private boolean scanned = false;
+
+ /**
+ * Default constructor
+ *
+ * @param cls The class to scan for the property
+ * @param annotationClass The annotation class to scan for. Specified attribute
+ * values may be scanned for by providing an implementation of the isMatch() method.
+ */
+ public AnnotatedBeanProperty(Class<?> cls, Class<T> annotationClass)
+ {
+ this.targetClass = cls;
+ this.annotationClass = annotationClass;
+ }
+
+ /**
+ * Scans the target class to locate the annotated property
+ */
+ private void scan()
+ {
+ // First check declared fields
+ for (Field f : targetClass.getDeclaredFields())
+ {
+ if (f.isAnnotationPresent(annotationClass) &&
+ isMatch(f.getAnnotation(annotationClass)))
+ {
+ setupFieldProperty(f);
+ this.annotation = f.getAnnotation(annotationClass);
+ set = true;
+ return;
+ }
+ }
+
+ // Then check public fields, in case it's inherited
+ for (Field f : targetClass.getFields())
+ {
+ if (f.isAnnotationPresent(annotationClass) &&
+ isMatch(f.getAnnotation(annotationClass)))
+ {
+ this.annotation = f.getAnnotation(annotationClass);
+ setupFieldProperty(f);
+ set = true;
+ return;
+ }
+ }
+
+ // Then check public methods (we ignore private methods)
+ for (Method m : targetClass.getMethods())
+ {
+ if (m.isAnnotationPresent(annotationClass) &&
+ isMatch(m.getAnnotation(annotationClass)))
+ {
+ this.annotation = m.getAnnotation(annotationClass);
+ String methodName = m.getName();
+
+ if ( m.getName().startsWith("get") )
+ {
+ this.name = Introspector.decapitalize( m.getName().substring(3) );
+ }
+ else if ( methodName.startsWith("is") )
+ {
+ this.name = Introspector.decapitalize( m.getName().substring(2) );
+ }
+
+ if (this.name != null)
+ {
+ this.propertyGetter = getGetterMethod(targetClass, this.name);
+ this.propertySetter = getSetterMethod(targetClass, this.name);
+ this.propertyType = this.propertyGetter.getGenericReturnType();
+ isFieldProperty = false;
+ set = true;
+ }
+ else
+ {
+ throw new IllegalStateException("Invalid accessor method, must start
with 'get' or 'is'. " +
+ "Method: " + m + " in class: " + targetClass);
+ }
+ }
+ }
+
+ scanned = true;
+ }
+
+ /**
+ * This method may be overridden by a subclass. It can be used to scan
+ * for an annotation with particular attribute values, or to allow a match
+ * based on more complex logic.
+ *
+ * @param annotation The potential match
+ * @return true if the specified annotation is a match
+ */
+ protected boolean isMatch(T annotation)
+ {
+ return true;
+ }
+
+ /**
+ * This method sets the property value for a specified bean to the specified
+ * value. The property to be set is either a field or setter method that
+ * matches the specified annotation class and returns true for the isMatch()
+ * method.
+ *
+ * @param bean The bean containing the property to set
+ * @param value The new property value
+ * @throws Exception
+ */
+ public void setValue(Object bean, Object value) throws Exception
+ {
+ if (!scanned) scan();
+
+ if (isFieldProperty)
+ {
+ setFieldValue(propertyField, bean, value);
+ }
+ else
+ {
+ invokeMethod(propertySetter, bean, value);
+ }
+ }
+
+ /**
+ * Returns the property value for the specified bean. The property to be
+ * returned is either a field or getter method that matches the specified
+ * annotation class and returns true for the isMatch() method.
+ *
+ * @param bean The bean to read the property from
+ * @return The property value
+ * @throws Exception
+ */
+ public Object getValue(Object bean) throws Exception
+ {
+ if (!scanned) scan();
+
+ if (isFieldProperty)
+ {
+ return getFieldValue(propertyField, bean);
+ }
+ else
+ {
+ return invokeMethod(propertyGetter, bean);
+ }
+ }
+
+ /**
+ * Returns the name of the property. If the property is a field, then the
+ * field name is returned. Otherwise, if the property is a method, then the
+ * name that is returned is the getter method name without the "get" or
"is"
+ * prefix, and a lower case first letter.
+ *
+ * @return The name of the property
+ */
+ public String getName()
+ {
+ if (!scanned) scan();
+ return name;
+ }
+
+ /**
+ * Returns the annotation type
+ *
+ * @return The annotation type
+ */
+ public T getAnnotation()
+ {
+ if (!scanned) scan();
+ return annotation;
+ }
+
+ /**
+ * Returns the property type
+ *
+ * @return The property type
+ */
+ public Type getPropertyType()
+ {
+ if (!scanned) scan();
+ return propertyType;
+ }
+
+ /**
+ * Returns true if the property has been successfully located, otherwise
+ * returns false.
+ *
+ * @return
+ */
+ public boolean isSet()
+ {
+ if (!scanned) scan();
+ return set;
+ }
+
+ private void setupFieldProperty(Field propertyField)
+ {
+ this.propertyField = propertyField;
+ isFieldProperty = true;
+ this.name = propertyField.getName();
+ this.propertyType = propertyField.getGenericType();
+ }
+
+ private Object getFieldValue(Field field, Object obj)
+ {
+ field.setAccessible(true);
+ try
+ {
+ return field.get(obj);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new RuntimeException(buildGetFieldValueErrorMessage(field, obj), e);
+ }
+ catch (NullPointerException ex)
+ {
+ NullPointerException ex2 = new NullPointerException(
+ buildGetFieldValueErrorMessage(field, obj));
+ ex2.initCause(ex.getCause());
+ throw ex2;
+ }
+ }
+
+ private String buildGetFieldValueErrorMessage(Field field, Object obj)
+ {
+ return String.format("Exception reading [%s] field from object [%s].",
+ field.getName(), obj);
+ }
+
+ private void setFieldValue(Field field, Object obj, Object value)
+ {
+ field.setAccessible(true);
+ try
+ {
+ field.set(obj, value);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new RuntimeException(buildSetFieldValueErrorMessage(field, obj, value),
e);
+ }
+ catch (NullPointerException ex)
+ {
+ NullPointerException ex2 = new NullPointerException(
+ buildSetFieldValueErrorMessage(field, obj, value));
+ ex2.initCause(ex.getCause());
+ throw ex2;
+ }
+ }
+
+ private String buildSetFieldValueErrorMessage(Field field, Object obj, Object value)
+ {
+ return String.format("Exception setting [%s] field on object [%s] to value
[%s]",
+ field.getName(), obj, value);
+ }
+
+ private Object invokeMethod(Method method, Object obj, Object... args)
+ {
+ try
+ {
+ return method.invoke(obj, args);
+ }
+ catch (IllegalAccessException ex)
+ {
+ throw new RuntimeException(buildInvokeMethodErrorMessage(method, obj, args),
ex);
+ }
+ catch (IllegalArgumentException ex)
+ {
+ throw new IllegalArgumentException(buildInvokeMethodErrorMessage(method, obj,
args), ex.getCause());
+ }
+ catch (InvocationTargetException ex)
+ {
+ throw new RuntimeException(buildInvokeMethodErrorMessage(method, obj, args),
ex);
+ }
+ catch (NullPointerException ex)
+ {
+ NullPointerException ex2 = new
NullPointerException(buildInvokeMethodErrorMessage(method, obj, args));
+ ex2.initCause(ex.getCause());
+ throw ex2;
+ }
+ catch (ExceptionInInitializerError e)
+ {
+ throw new RuntimeException(buildInvokeMethodErrorMessage(method, obj, args),
e);
+ }
+ }
+
+ private String buildInvokeMethodErrorMessage(Method method, Object obj, Object...
args)
+ {
+ StringBuilder message = new StringBuilder(String.format(
+ "Exception invoking method [%s] on object [%s], using arguments
[",
+ method.getName(), obj));
+ if (args != null) for (int i = 0; i < args.length; i++) message.append((i > 0
? "," : "") + args[i]);
+ message.append("]");
+ return message.toString();
+ }
+
+ private Method getSetterMethod(Class<?> clazz, String name)
+ {
+ Method[] methods = clazz.getMethods();
+ for (Method method: methods)
+ {
+ String methodName = method.getName();
+ if ( methodName.startsWith("set") &&
method.getParameterTypes().length==1 )
+ {
+ if ( Introspector.decapitalize( methodName.substring(3) ).equals(name) )
+ {
+ return method;
+ }
+ }
+ }
+ throw new IllegalArgumentException("no such setter method: " +
clazz.getName() + '.' + name);
+ }
+
+ private Method getGetterMethod(Class<?> clazz, String name)
+ {
+ Method[] methods = clazz.getMethods();
+ for (Method method: methods)
+ {
+ String methodName = method.getName();
+ if ( method.getParameterTypes().length==0 )
+ {
+ if ( methodName.startsWith("get") )
+ {
+ if ( Introspector.decapitalize( methodName.substring(3) ).equals(name) )
+ {
+ return method;
+ }
+ }
+ else if ( methodName.startsWith("is") )
+ {
+ if ( Introspector.decapitalize( methodName.substring(2) ).equals(name) )
+ {
+ return method;
+ }
+ }
+ }
+ }
+ throw new IllegalArgumentException("no such getter method: " +
clazz.getName() + '.' + name);
+ }
+}
\ No newline at end of file