// tag name represents a property we're going to set
String propertyName = node.getTag();
// find the property if it exists
BeanProperty property = null;
if (ch != null) {
// make sure node tag name matches propertyName
if (!propertyName.equals(ch.getValueProperty().getName())) {
throw new PropertyNotFoundException(propertyName, node.getPath(), obj.getClass(), "Collection can only be configured with a property name of [" + ch.getValueProperty().getName() + "] but [" + propertyName + "] was used instead");
}
property = ch.getValueProperty();
} else {
try {
property = BeanUtil.findBeanProperty(obj.getClass(), propertyName, true);
} catch (IllegalAccessException e) {
throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Illegal access while attempting to reflect property from class", e);
}
}
// if property is null, then this isn't a valid property on this object
if (property == null) {
throw new PropertyNotFoundException(propertyName, node.getPath(), obj.getClass(), "Property [" + propertyName + "] not found");
}
// only some attributes are permitted if we're dealing with a
// collection or map at this point
boolean isCollection = (Collection.class.isAssignableFrom(property.getType()));
boolean isMap = (Map.class.isAssignableFrom(property.getType()));
// were any attributes included?
String typeAttrString = null;
String valueAttrString = null;
String keyAttrString = null;
// check if an annotation is present for the field
if (property.getField() != null) {
XmlBeanProperty annotation = property.getField().getAnnotation(XmlBeanProperty.class);
if (annotation != null) {
if (!StringUtil.isEmpty(annotation.value())) {
valueAttrString = annotation.value();
}
if (!StringUtil.isEmpty(annotation.key())) {
keyAttrString = annotation.key();
}
}
}
// process attributes within the xml itself
if (node.hasAttributes()) {
for (Attribute attr : node.getAttributes()) {
if (attr.getName().equals("type")) {
typeAttrString = attr.getValue();
} else if (attr.getName().equals("value") && (isCollection || isMap)) {
// only permitted on collections or map
valueAttrString = attr.getValue();
} else if (attr.getName().equals("key") && (isMap || (ch != null && ch.isMapType()))) {
// only permitted on map type OR on a map value
keyAttrString = attr.getValue();
} else {
throw new PropertyNoAttributesExpectedException(propertyName, node.getPath(), obj.getClass(), "One or more attributes not allowed for property [" + propertyName + "]");
}
}
}
//
// otherwise, the property exists, attempt to set it
//
// is there actually a "setter" method -- we shouldn't let
// user's be able to configure fields in this case
// unless accessing private properties is allowed
// unless a collection helper is also null
if (ch == null && !this.accessPrivateProperties && property.getAddMethod() == null && property.getSetMethod() == null) {
throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Not permitted to add or set property [" + propertyName + "]");
}
// if we can "add" this property, then turn off checkForDuplicates
// we also don't check for duplicates in the case of a collection
if (ch != null || property.canAdd()) {
checkForDuplicates = false;
}
// was this property already previously set?
// only use this check if an "add" method doesn't exist for the bean
if (checkForDuplicates && properties.containsKey(node.getPath())) {
throw new PropertyAlreadySetException(propertyName, node.getPath(), obj.getClass(), "Property [" + propertyName + "] was already previously set in the xml");
}
// add this property to our hashmap
properties.put(node.getPath(), null);
// if a "type" attribute was included - check that it both exists
// and is compatible with the type of the property it is being added/set to
Class typeAttrClass = null;
if (typeAttrString != null) {
try {
typeAttrClass = Class.forName(typeAttrString);
} catch (ClassNotFoundException e) {
throw new PropertyInvalidTypeException(propertyName, node.getPath(), obj.getClass(), "Unable to find class [" + typeAttrString + "] specified in type attribute of property '" + propertyName + "'");
}
if (!property.getType().isAssignableFrom(typeAttrClass)) {
throw new PropertyInvalidTypeException(propertyName, node.getPath(), obj.getClass(), "Unable to assign a value of specified type [" + typeAttrString + "] to property [" + propertyName + "] which is a type [" + property.getType().getName() + "]");
}
}
// the object we'll eventually add or set
Object value = null;
// get the node's text value
String nodeText = node.getText();
// is this a simple conversion?
if (TypeConverterUtil.isSupported(property.getType())) {
// was any text set? if not, throw an exception
if (nodeText == null) {
throw new PropertyIsEmptyException(propertyName, node.getPath(), obj.getClass(), "Value for property [" + propertyName + "] was empty in xml");
}
// try to convert this to a Java object value
try {
value = TypeConverterUtil.convert(nodeText, property.getType());
} catch (ConversionException e) {
throw new PropertyConversionException(propertyName, node.getPath(), obj.getClass(), "The value [" + nodeText + "] for property [" + propertyName + "] could not be converted to a(n) " + property.getType().getSimpleName() + ". " + e.getMessage());
}
// otherwise, this is a "complicated" type
} else {
// only "get" the property if its possible -- e.g. if there
// is only an addXXXX method available, then this would throw
// an exception, so we'll check to see if getting the property
// is possible first
if (property.canGet()) {
try {
value = property.get(obj);
} catch (IllegalAccessException e) {
throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Illegal access while attempting to get property value from object", e);
} catch (InvocationTargetException e) {
Throwable t = e;
// this generally means the setXXXX method on the object
// threw an exception -- we want to unwrap that and just
// return that exception instead
if (e.getCause() != null) {
t = e.getCause();
}
throw new PropertyInvocationException(propertyName, node.getPath(), obj.getClass(), "The existing value for property [" + propertyName + "] caused an exception during get", t.getMessage(), t);
}
}
// if null, then we need to create a new instance of it
if (value == null) {
Class newType = property.getType();
// create a new instance of either the actual type OR the
// type specified in the "type" attribute
if (typeAttrClass != null) {
newType = typeAttrClass;
}
try {
value = newType.newInstance();
} catch (InstantiationException e) {
throw new XmlBeanClassException("Failed while attempting to create object of type " + newType.getName(), e);
} catch (IllegalAccessException e) {
throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Illegal access while attempting to create new instance of " + newType.getName(), e);
}
}
// special handling for "collections" -- required for handling
// the values in configuring of child objects
CollectionHelper newch = null;
if (value instanceof Collection || value instanceof Map) {
newch = createCollectionHelper(node, obj, value, propertyName, property, valueAttrString, keyAttrString);
}
// recursively configure the next object
doConfigure(node, value, properties, checkForDuplicates, newch);
}
// save this reference object back (since it was successfully configured)
if (ch != null) {
if (ch.isCollectionType()) {
ch.getCollectionObject().add(value);
} else if (ch.isMapType()) {
// need to figure out the key value -- it may either be a value from the value OR a simple type
Object keyValue = null;
if (ch.getKeyProperty() == null) {
// a KEY must have been set!
if (StringUtil.isEmpty(keyAttrString)) {
throw new PropertyIsEmptyException(propertyName, node.getPath(), obj.getClass(), "The XML attribute [key] was null or empty and is required");
} else {
try {
keyValue = TypeConverterUtil.convert(keyAttrString, ch.getKeyClass());
} catch (ConversionException e) {
throw new PropertyConversionException(propertyName, node.getPath(), obj.getClass(), "Unable to cleanly convert key value [" + keyAttrString + "] into type [" + ch.getKeyClass().getName() + ": " + e.getMessage(), e);
}
}
} else {
try {
// extract the key value from the object
keyValue = ch.getKeyProperty().get(value);
} catch (Exception e) {
throw new PropertyPermissionException(propertyName, node.getPath(), value.getClass(), "Unable to access property to get the value of the key: " + e.getMessage(), e);
}
if (keyValue == null) {
throw new PropertyIsEmptyException(propertyName, node.getPath(), obj.getClass(), "The value of the key [" + ch.getKeyProperty().getName() + "] was null; unable to put value onto the map");
}
}
ch.getMapObject().put(keyValue, value);
} else {
throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Unsupported collection/map type used");
}
} else {
try {
property.addOrSet(obj, value);
} catch (InvocationTargetException e) {
Throwable t = e;
// this generally means the setXXXX method on the object
// threw an exception -- we want to unwrap that and just
// return that exception instead