package za.co.enerweb.toolbox.reflection;
import static java.lang.String.format;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import lombok.val;
import org.apache.commons.lang.StringUtils;
import za.co.enerweb.toolbox.objects.ObjectUtils;
/**
* Commons property utils does uses the plain Introspector which sometimes has
* weird capitilation of the first character of a field eg. it thinks iProperty
* should be IProperty ?!
*/
public class PropertyUtils {
/**
* @return all the properties that have getters and setters
*/
public static List<String> getAllProperties(Class<?> type) {
List<PropertyDescriptor> propertyDescriptors
= getAllPropertyDescriptors(type);
List<String> ret = new ArrayList<String>(propertyDescriptors.size());
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
ret.add(propertyDescriptor.getName());
}
return ret;
}
public static List<PropertyDescriptor> getAllPropertyDescriptors(
Class<?> type) {
BeanInfo beanInfo = getBeanInfo(type);
PropertyDescriptor[] propertyDescriptors = beanInfo
.getPropertyDescriptors();
List<PropertyDescriptor> ret = new ArrayList<PropertyDescriptor>(
propertyDescriptors.length);
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
if (propertyDescriptor.getWriteMethod() != null) {
propertyDescriptor.setName(StringUtils
.uncapitalize(propertyDescriptor.getName()));
ret.add(propertyDescriptor);
}
}
return ret;
}
public static List<PropertyDescriptor> getAllReadablePropertyDescriptors(
Class<?> type) {
BeanInfo beanInfo = getBeanInfo(type);
PropertyDescriptor[] propertyDescriptors = beanInfo
.getPropertyDescriptors();
List<PropertyDescriptor> ret = new ArrayList<PropertyDescriptor>(
propertyDescriptors.length);
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
propertyDescriptor.setName(StringUtils
.uncapitalize(propertyDescriptor.getName()));
ret.add(propertyDescriptor);
}
return ret;
}
private static BeanInfo getBeanInfo(Class<?> type) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(type);
return beanInfo;
} catch (IntrospectionException e) {
throw new IllegalStateException("Could not introspect " + type, e);
}
}
/**
* Generic way to get a property on a object TODO: support isBoolean methods
*
* @throws InvalidPropertyException if then property could not be accessed
*/
@SuppressWarnings("unchecked")
public static <T> T getProperty(final Object bean, final String fieldName) {
try {
return (T) getPropertyDescriptor(bean, fieldName).getReadMethod()
.invoke(bean);
} catch (Exception e) {
throw new InvalidPropertyException("Could not get "
+ bean.getClass().getSimpleName() + "." + fieldName, e);
}
}
/**
* Generic way to set a property on a object
* @throws InvalidPropertyException if then property could not be accessed
*/
public static void setProperty(final Object bean, final String fieldName,
final Object value) {
try {
getPropertyDescriptor(bean, fieldName).getWriteMethod().invoke(
bean, value);
} catch (Exception e) {
throw new InvalidPropertyException("Could not set "
+ bean.getClass().getSimpleName() + "." + fieldName + " to "
+ value, e);
}
}
public static PropertyDescriptor getPropertyDescriptor(final Object object,
final String fieldName) {
BeanInfo beanInfo = getBeanInfo(object.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo
.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String name = StringUtils
.uncapitalize(propertyDescriptor.getName());
if (name.equals(fieldName)) {
return propertyDescriptor;
// } else {
// System.out.println(fieldName + " != " + name);
}
}
throw new InvalidPropertyException("Unknown property: " + fieldName);
}
/**
* @param type
* @return true if each property of b1 is equal to the corresponding
* property of b2
*/
public static <T> boolean equalBeans(T b1, T b2,
Class<T> type) {
if (!ObjectUtils.equals(b1, b2)) {
return false;
}
List<PropertyDescriptor> props = getAllPropertyDescriptors(type);
for (PropertyDescriptor prop : props) {
val readMethod = prop.getReadMethod();
try {
Object v1 = readMethod.invoke(b1);
Object v2 = readMethod.invoke(b2);
if (!ObjectUtils.equals(v1, v2)) {
return false;
}
} catch (Exception e) {
new InvalidPropertyException("Could not compare beans of " +
"type " + type.getName(), e);
}
}
return true;
}
public static <T> void assertEqualBeans(T b1, T b2,
Class<T> type) throws AssertionError {
if (!ObjectUtils.equals(b1, b2)) {
throw new AssertionError(b1 + " != "
+ b2 + " (using Object.equals())");
}
List<PropertyDescriptor> props = getAllPropertyDescriptors(type);
for (PropertyDescriptor prop : props) {
val readMethod = prop.getReadMethod();
try {
Object v1 = readMethod.invoke(b1);
Object v2 = readMethod.invoke(b2);
if (!ObjectUtils.equals(v1, v2)) {
throw new AssertionError(format(
"%s.%s() = %s != %s.%s() = %s",
b1, readMethod.getName(), v1,
b2, readMethod.getName(), v2));
}
} catch (Exception e) {
new InvalidPropertyException("Could not compare beans of " +
"type " + type.getName(), e);
}
}
}
// if you wanna make a Set<T> version of this, you'll have
// to check that the sets contains each other
public static <T> boolean equalBeans(List<T> bl1, List<T> bl2,
Class<T> type) {
if (bl1.size() != bl2.size()) {
return false;
}
Iterator<T> i1 = bl1.iterator();
Iterator<T> i2 = bl2.iterator();
while (i1.hasNext() && i2.hasNext()) {
if (!equalBeans(i1.next(), i2.next(), type)) {
return false;
}
}
return true;
}
public static <T> void assertEqualBeans(List<T> bl1, List<T> bl2,
Class<T> type) throws AssertionError {
if (bl1.size() != bl2.size()) {
throw new AssertionError("Lists are not the same length.");
}
Iterator<T> i1 = bl1.iterator();
Iterator<T> i2 = bl2.iterator();
while (i1.hasNext() && i2.hasNext()) {
assertEqualBeans(i1.next(), i2.next(), type);
}
}
}