package ch.vorburger.blueprints.data.javareflect;
import java.lang.reflect.Field;
import java.util.Set;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;
import ch.vorburger.blueprints.data.DataObject;
import ch.vorburger.blueprints.data.DataObjectImplUtil;
/**
* Wraps a Java Bean to make it implement our (simplified) DataObject interface.
*
* @author Michael Vorburger
*/
/* package-local */
class BeanWrapper implements DataObject {
private static final long serialVersionUID = -5693810356649672157L;
// If ever DataObject must support XPath-like SDO's "department.0/name",
// company.get("department[1]/name"), "department[number=123]", then use
// http://commons.apache.org/jxpath/ or http://jaxen.codehaus.org instead of BeanUtils.
// Could use http://static.springsource.org/spring/docs/3.0.4.RELEASE/javadoc-api/org/springframework/util/ReflectionUtils.html, spring-beans ?
private final Object beanDataObject;
private final boolean directFieldsInsteadOfJavaBean;
BeanWrapper(JavaTypeImpl type) {
this.beanDataObject = type.getObject();
this.directFieldsInsteadOfJavaBean = type.directFieldsInsteadOfJavaBean;
}
private BeanWrapper(Object beanDataObject) {
this.beanDataObject = beanDataObject;
this.directFieldsInsteadOfJavaBean = false;
}
@Override
public Object get(String path) {
DataObjectImplUtil.checkPath(path);
try {
if (!directFieldsInsteadOfJavaBean)
return PropertyUtils.getProperty(beanDataObject, path);
else
return getPrivateField(path);
} catch (Exception e) {
throwInvalidPath(path, e);
return null; // never actually return null, but Java is too dumb!
}
}
@Override
@SuppressWarnings("unchecked")
public <T> T get(String path, Class<T> type) {
DataObjectImplUtil.checkPath(path);
Object o = get(path);
T value = (T) ConvertUtils.convert(o, type);
if (DataObject.class.equals(type) && !(value instanceof DataObject)) {
// If the contained object requires directFieldsInsteadOfJavaBean, it won't work, as we are cheating here:
return (T) new BeanWrapper(value);
}
return value;
}
@Override
public void set(String path, Object value) {
DataObjectImplUtil.checkPath(path);
try {
if (!directFieldsInsteadOfJavaBean)
PropertyUtils.setProperty(beanDataObject, path, value);
else
setPrivateField(path, value);
} catch (Exception e) {
throwInvalidPath(path, e);
}
}
private Object getPrivateField(String path) throws Exception {
Field field = getField(path);
return field.get(beanDataObject);
}
private void setPrivateField(String path, Object value) throws Exception {
Field field = getField(path);
field.set(beanDataObject, value);
}
private Field getField(String path) throws NoSuchFieldException {
// TODO getDeclaredField must be used instead of getField (which doesn't find private), but won't look in parent; should retry-loop here?
Field field = beanDataObject.getClass().getDeclaredField(path);
field.setAccessible(true);
return field;
}
@SuppressWarnings("unchecked")
private void throwInvalidPath(String path, Exception e) throws IllegalArgumentException {
String allProperties;
try {
Set<String> allPropertiesList = PropertyUtils.describe(beanDataObject).keySet();
allPropertiesList.remove("class");
allProperties = allPropertiesList.toString();
} catch (Exception ee) {
allProperties = "??? " + ee.getMessage();
}
throw new IllegalArgumentException(path + " is not valid; allowed properties are: " + allProperties , e);
}
}