/*
* The contents of this file are subject to the Sapient Public License
* Version 1.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://carbon.sf.net/License.html.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
*
* The Original Code is The Carbon Component Framework.
*
* The Initial Developer of the Original Code is Sapient Corporation
*
* Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
*/
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.StringTokenizer;
/**
* This class implements the ability to get and set properties on a
* bean. This included the concept of embedded properties that may
* be referenced like Bean.property.property.property
.
*
* Copyright 2002 Sapient
* @since carbon 1.0
* @author Greg Hinkle, January 2002
* @version $Revision: 1.11 $ ($Author: dvoet $)
*/
public final class BeanUtil {
/**
* String used to separate multiple properties inside of embedded
* beans.
*/
private static final String PROPERTY_SEPARATOR = ".";
/**
* An empty class array used for null parameter method reflection
*/
private static Class[] NO_PARAMETERS_ARRAY = new Class[] {
};
/**
* an empty object array used for null parameter method reflection
*/
private static Object[] NO_ARGUMENTS_ARRAY = new Object[] {
};
/**
* The constructor is private so that new cannot be used.
*/
private BeanUtil() {
}
/**
* Retreives a property descriptor object for a given property.
*
* Uses the classes in java.beans
to get back
* a descriptor for a property. Read-only and write-only
* properties will have a slower return time.
*
*
* @param propertyName The programmatic name of the property
* @param beanClass The Class object for the target bean.
* For example sun.beans.OurButton.class.
* @return a PropertyDescriptor for a property that follows the
* standard Java naming conventions.
* @throws PropertyNotFoundException indicates that the property
* could not be found on the bean class.
*/
private static final PropertyDescriptor getPropertyDescriptor(
String propertyName,
Class beanClass) {
PropertyDescriptor resultPropertyDescriptor = null;
char[] pNameArray = propertyName.toCharArray();
pNameArray[0] = Character.toLowerCase(pNameArray[0]);
propertyName = new String(pNameArray);
try {
resultPropertyDescriptor =
new PropertyDescriptor(propertyName, beanClass);
} catch (IntrospectionException e1) {
// Read-only and write-only properties will throw this
// exception. The properties must be looked up using
// brute force.
// This will get the list of all properties and iterate
// through looking for one that matches the property
// name passed into the method.
try {
BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
PropertyDescriptor[] propertyDescriptors =
beanInfo.getPropertyDescriptors();
for (int i = 0; i < propertyDescriptors.length; i++) {
if (propertyDescriptors[i]
.getName()
.equals(propertyName)) {
// If the names match, this this describes the
// property being searched for. Break out of
// the iteration.
resultPropertyDescriptor = propertyDescriptors[i];
break;
}
}
} catch (IntrospectionException e2) {
e2.printStackTrace();
}
}
// If no property descriptor was found, then this bean does not
// have a property matching the name passed in.
if (resultPropertyDescriptor == null) {
System.out.println("resultPropertyDescriptor == null");
}
return resultPropertyDescriptor;
}
/**
* Gets the specified attribute from the specified object. For example,
* getObjectAttribute(o, "address.line1")
will return
* the result of calling o.getAddress().getLine1()
.
*
* The attribute specified may contain as many levels as you like. If at
* any time a null reference is acquired by calling one of the successive
* getter methods, then the return value from this method is also null.
*
* When reading from a boolean property the underlying bean introspector
* first looks for an is<Property> read method, not finding one it will
* still look for a get<Property> read method. Not finding either, the
* property is considered write-only.
*
* @param bean the bean to set the property on
* @param propertyNames the name of the propertie(s) to retrieve. If this is
* null or the empty string, then bean
will be returned.
* @return the object value of the bean attribute
*
* @throws PropertyNotFoundException indicates the the given property
* could not be found on the bean
* @throws NoSuchMethodException Not thrown
* @throws InvocationTargetException if a specified getter method throws an
* exception.
* @throws IllegalAccessException if a getter method is
* not public or property is write-only.
*/
public static Object getObjectAttribute(Object bean, String propertyNames)
throws
NoSuchMethodException,
InvocationTargetException,
IllegalAccessException {
Object result = bean;
StringTokenizer propertyTokenizer =
new StringTokenizer(propertyNames, PROPERTY_SEPARATOR);
// Run through the tokens, calling get methods and
// replacing result with the new object each time.
// If the result equals null, then simply return null.
while (propertyTokenizer.hasMoreElements() && result != null) {
Class resultClass = result.getClass();
String currentPropertyName = propertyTokenizer.nextToken();
PropertyDescriptor propertyDescriptor =
getPropertyDescriptor(currentPropertyName, resultClass);
Method readMethod = propertyDescriptor.getReadMethod();
if (readMethod == null) {
throw new IllegalAccessException(
"User is attempting to "
+ "read from a property that has no read method. "
+ " This is likely a write-only bean property. Caused "
+ "by property ["
+ currentPropertyName
+ "] on class ["
+ resultClass
+ "]");
}
result = readMethod.invoke(result, NO_ARGUMENTS_ARRAY);
}
return result;
}
/**
* Sets the specified attribute on the specified object. For example,
* getObjectAttribute(o, "address.line1", value)
will call
* o.getAddress().setLine1(value)
.
*
* The attribute specified may contain as many levels as you like. If at
* any time a null reference is acquired by calling one of the successive
* getter methods, then a NullPointerException
is thrown.
*
* @param bean the bean to call the getters on
* @param propertyNames the name of the attribute(s) to set. If this is
* null or the empty string, then an exception is thrown.
* @param value the value of the object to set on the bean property
*
* @throws PropertyNotFoundException indicates the the given property
* could not be found on the bean
* @throws IllegalArgumentException if the supplied parameter is not of
* a valid type
* @throws NoSuchMethodException never
* @throws IllegalAccessException if a getter or setter method is
* not public or property is read-only.
* @throws InvocationTargetException if a specified getter method throws an
* exception.
*/
public static void setObjectAttribute(
Object bean,
String propertyNames,
Object value)
throws
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException,
NoSuchMethodException {
Object result = bean;
String propertyName = propertyNames;
// Check if this has some embedded properties. If it does, use the
// getObjectAttribute to get the proper object to call this on.
int indexOfLastPropertySeparator =
propertyName.lastIndexOf(PROPERTY_SEPARATOR);
if (indexOfLastPropertySeparator >= 0) {
String embeddedProperties =
propertyName.substring(0, indexOfLastPropertySeparator);
// Grab the final property name after the last property separator
propertyName =
propertyName.substring(
indexOfLastPropertySeparator + PROPERTY_SEPARATOR.length());
result = getObjectAttribute(result, embeddedProperties);
}
Class resultClass = result.getClass();
PropertyDescriptor propertyDescriptor =
getPropertyDescriptor(propertyName, resultClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
if (writeMethod == null) {
throw new IllegalAccessException(
"User is attempting to write "
+ "to a property that has no write method. This is likely "
+ "a read-only bean property. Caused by property ["
+ propertyName
+ "] on class ["
+ resultClass
+ "]");
}
writeMethod.invoke(result, new Object[] { value });
}
}