package org.springframework.roo.classpath.details.annotations.populator; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.Validate; import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue; import org.springframework.roo.classpath.details.annotations.AnnotationMetadata; import org.springframework.roo.classpath.details.annotations.ArrayAttributeValue; import org.springframework.roo.classpath.details.annotations.BooleanAttributeValue; import org.springframework.roo.classpath.details.annotations.CharAttributeValue; import org.springframework.roo.classpath.details.annotations.ClassAttributeValue; import org.springframework.roo.classpath.details.annotations.DoubleAttributeValue; import org.springframework.roo.classpath.details.annotations.EnumAttributeValue; import org.springframework.roo.classpath.details.annotations.IntegerAttributeValue; import org.springframework.roo.classpath.details.annotations.LongAttributeValue; import org.springframework.roo.classpath.details.annotations.StringAttributeValue; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; /** * Automatically populates the {@link AutoPopulate} annotated fields on a given * {@link Object}. * * @author Ben Alex * @since 1.0 */ public abstract class AutoPopulationUtils { private static final Map<Field, JavaSymbolName> attributeNameForEachField = new HashMap<Field, JavaSymbolName>(); private static final Map<Class<?>, List<Field>> cachedIntrospections = new HashMap<Class<?>, List<Field>>(); /** * Introspects the target {@link Object} for its declared fields, locating * all {@link AutoPopulate} annotated fields. For each field, an attempt * will be made to locate the value from the passed * {@link AnnotationMetadata}. The annotation value will be converted into * the required field type, or silently skipped if this is not possible (eg * the user edited source code and made a formatting error). As such it is * important that the caller. * * @param target to put values into (mandatory, cannot be null) * @param annotation to obtain values from (can be null, for convenience of * the caller) */ public static void populate(final Object target, final AnnotationMetadata annotation) { Validate.notNull(target, "Target required"); if (annotation == null) { return; } List<Field> fields = cachedIntrospections.get(target.getClass()); if (fields == null) { // Go and cache them fields = new ArrayList<Field>(); for (final Field field : target.getClass().getDeclaredFields()) { // Determine if this field even contains the necessary // annotation final AutoPopulate ap = field.getAnnotation(AutoPopulate.class); if (ap == null) { continue; } // Determine attribute name we should be looking for in the // annotation String attribute = ap.value(); if ("".equals(ap.value())) { attribute = field.getName(); } // Ensure field is accessible if (!field.isAccessible()) { field.setAccessible(true); } final JavaSymbolName attributeName = new JavaSymbolName(attribute); // Store the info fields.add(field); attributeNameForEachField.put(field, attributeName); } cachedIntrospections.put(target.getClass(), fields); } for (final Field field : fields) { // Lookup whether this attribute name was provided final JavaSymbolName attributeName = attributeNameForEachField.get(field); if (attributeName == null) { throw new IllegalStateException("Expected cached attribute name lookup"); } if (annotation.getAttributeNames().contains(attributeName)) { // Get the value final AnnotationAttributeValue<?> value = annotation.getAttribute(attributeName); // Assign the value to the target object try { final Class<?> fieldType = field.getType(); if (value instanceof BooleanAttributeValue && (fieldType.equals(Boolean.class) || fieldType.equals(Boolean.TYPE))) { field.set(target, value.getValue()); } else if (value instanceof CharAttributeValue && (fieldType.equals(Character.class) || fieldType.equals(Character.TYPE))) { field.set(target, value.getValue()); } else if (value instanceof ClassAttributeValue && fieldType.equals(JavaType.class)) { field.set(target, value.getValue()); } else if (value instanceof DoubleAttributeValue && (fieldType.equals(Double.class) || fieldType.equals(Double.TYPE))) { field.set(target, value.getValue()); } else if (value instanceof EnumAttributeValue && Enum.class.isAssignableFrom(fieldType)) { field.set(target, ((EnumAttributeValue) value).getAsEnum(target)); } else if (value instanceof IntegerAttributeValue && (fieldType.equals(Integer.class) || fieldType.equals(Integer.TYPE))) { field.set(target, value.getValue()); } else if (value instanceof LongAttributeValue && (fieldType.equals(Long.class) || fieldType.equals(Long.TYPE))) { field.set(target, value.getValue()); } else if (value instanceof StringAttributeValue && fieldType.equals(String.class)) { field.set(target, value.getValue()); } else if (value instanceof StringAttributeValue && fieldType.getComponentType() != null && fieldType.getComponentType().equals(String.class)) { // ROO-618 final Object newValue = Array.newInstance(String.class, 1); Array.set(newValue, 0, value.getValue()); field.set(target, newValue); } else if (value instanceof ArrayAttributeValue<?> && fieldType.isArray()) { // The field is a string array, the attribute is an // array, so let's hope it's a string array final ArrayAttributeValue<?> castValue = (ArrayAttributeValue<?>) value; final List<String> result = new ArrayList<String>(); final List<JavaType> result1 = new ArrayList<JavaType>(); for (final AnnotationAttributeValue<?> val : castValue.getValue()) { // For now we'll only support arrays of strings if (fieldType.getComponentType().equals(String.class) && val instanceof StringAttributeValue) { final StringAttributeValue stringValue = (StringAttributeValue) val; result.add(stringValue.getValue()); } else if (fieldType.getComponentType().equals(JavaType.class) && val instanceof ClassAttributeValue) { final ClassAttributeValue classValue = (ClassAttributeValue) val; result1.add(classValue.getValue()); } } if (result.size() > 0) { // We had at least one string array, so we change // the field field.set(target, result.toArray(new String[] {})); } if (result1.size() > 0) { // We had at least one string array, so we change // the field field.set(target, result1.toArray(new JavaType[] {})); } } // If not in the above list, it's unsupported so we silently // skip } catch (final Throwable ignoreFailures) { } } } } }