Package ma.glasnost.orika.property

Source Code of ma.glasnost.orika.property.PropertyResolver

/*
* Orika - simpler, better and faster Java bean mapping
*
* Copyright (C) 2011 Orika authors
*
* Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ma.glasnost.orika.property;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ma.glasnost.orika.MapEntry;
import ma.glasnost.orika.MappingException;
import ma.glasnost.orika.metadata.ArrayElementProperty;
import ma.glasnost.orika.metadata.ListElementProperty;
import ma.glasnost.orika.metadata.MapKeyProperty;
import ma.glasnost.orika.metadata.NestedElementProperty;
import ma.glasnost.orika.metadata.NestedProperty;
import ma.glasnost.orika.metadata.Property;
import ma.glasnost.orika.metadata.Type;
import ma.glasnost.orika.metadata.TypeFactory;

/**
* PropertyResolver defines the core functionality for resolving properties;
* specific PropertyResolverStrategy implementations should extend from this
* class, implementing the 'collectProperties' method.
*
* @author matt.deboer@gmail.com
*
*/
public abstract class PropertyResolver implements PropertyResolverStrategy {
   
    /**
     * The prefix used to begin a nested element property
     */
    public static final String ELEMENT_PROPERT_PREFIX = "{";
    /**
     * The suffix used to complete a nested element property
     */
    public static final String ELEMENT_PROPERT_SUFFIX = "}";
   
    private final boolean includePublicFields;
   
    private final Map<java.lang.reflect.Type, Map<String, Property>> propertiesCache = new ConcurrentHashMap<java.lang.reflect.Type, Map<String, Property>>();
    private final Map<java.lang.reflect.Type, Map<String, Property>> inlinePropertiesCache = new ConcurrentHashMap<java.lang.reflect.Type, Map<String, Property>>();
   
    /**
     * Creates a new PropertyResolver instance
     *
     * @param includePublicFields
     *            whether public fields should be included as properties
     */
    public PropertyResolver(boolean includePublicFields) {
        this.includePublicFields = includePublicFields;
    }
   
    /*
     * (non-Javadoc)
     *
     * @see
     * ma.glasnost.orika.property.PropertyResolverStrategy#getProperties(java
     * .lang.reflect.Type)
     */
    public Map<String, Property> getProperties(java.lang.reflect.Type theType) {
       
        Map<String, Property> properties = propertiesCache.get(theType);
        if (properties == null) {
            synchronized (theType) {
                properties = propertiesCache.get(theType);
                if (properties == null) {
                   
                    properties = new LinkedHashMap<String, Property>();
                    Type<?> referenceType;
                   
                    if (theType instanceof Type) {
                        referenceType = (Type<?>) theType;
                    } else if (theType instanceof Class) {
                        referenceType = TypeFactory.valueOf((Class<?>) theType);
                    } else {
                        throw new IllegalArgumentException("type " + theType + " not supported.");
                    }
                   
                    /*
                     * We process the properties for a type by starting with the
                     * type itself, and then continue by processing the classes
                     * and interfaces in the type's hierarchy. Property
                     * definitions from an ancestor should not override those
                     * already defined.
                     */
                    LinkedList<Class<? extends Object>> types = new LinkedList<Class<? extends Object>>();
                    types.addFirst((Class<? extends Object>) referenceType.getRawType());
                    while (!types.isEmpty()) {
                        Class<? extends Object> type = types.removeFirst();
                       
                        collectProperties(type, referenceType, properties);
                       
                        if (type.getSuperclass() != null && !Object.class.equals(type.getSuperclass())) {
                            types.add(type.getSuperclass());
                        }
                       
                        List<? extends Class<? extends Object>> interfaces = Arrays.<Class<? extends Object>> asList(type.getInterfaces());
                        types.addAll(interfaces);
                    }
                   
                    if (includePublicFields) {
                        /*
                         * Call this outside of the loop because the fields
                         * returned are already inclusive of ancestors.
                         */
                        collectPublicFieldProperties(referenceType, properties);
                    }
                   
                    propertiesCache.put(theType, Collections.unmodifiableMap(properties));
                }
            }
        }
        return properties;
    }
   
    /**
     * Attempt to resolve the generic type, using refereceType to resolve
     * TypeVariables
     *
     * @param genericType
     *            the type to resolve
     * @param referenceType
     *            the reference type to use for lookup of type variables
     * @return
     */
    private Type<?> resolveGenericType(java.lang.reflect.Type genericType, Type<?> referenceType) {
        Type<?> resolvedType = null;
        Type<?> reference = referenceType;
        do {
            if (genericType instanceof TypeVariable && reference.isParameterized()) {
                java.lang.reflect.Type t = reference.getTypeByVariable((TypeVariable<?>) genericType);
                if (t != null) {
                    resolvedType = TypeFactory.valueOf(t);
                }
            } else if (genericType instanceof ParameterizedType) {
                if (reference.isParameterized()) {
                    resolvedType = TypeFactory.resolveValueOf((ParameterizedType) genericType, reference);
                } else {
                    resolvedType = TypeFactory.valueOf((ParameterizedType) genericType);
                }
            }
            reference = reference.getSuperType();
        } while (resolvedType == null && reference != TypeFactory.TYPE_OF_OBJECT);
        return resolvedType;
    }
   
    /**
     * Convert the first character of the provided string to uppercase.
     *
     * @param string
     * @return the String with the first character converter to uppercase.
     */
    protected String capitalize(String string) {
        return string.substring(0, 1).toUpperCase() + string.substring(1);
    }
   
    /**
     * Resolves the raw property type from a property descriptor; if a read
     * method is available, use it to refine the type. The results of
     * pd.getPropertyType() are sometimes inconsistent across platforms.
     *
     * @param pd
     * @return
     */
    private Class<?> resolveRawPropertyType(Class<?> rawType, Method readMethod) {
        try {
            return (readMethod == null ? rawType : readMethod.getDeclaringClass()
                    .getDeclaredMethod(readMethod.getName(), new Class[0])
                    .getReturnType());
        } catch (Exception e) {
            return rawType;
        }
    }
   
    /**
     * Processes a property, adding it to the map of properties for the owning
     * type being processed
     *
     * @param propertyName
     *            the name of the property
     * @param propertyType
     *            the Class of the property
     * @param readMethod
     *            the read method for the property
     * @param writeMethod
     *            the write method for the property
     * @param owningType
     *            the owning type for which the properties are being resolved
     * @param referenceType
     *            a reference type to be used for resolving generic parameters
     * @param properties
     */
    protected void processProperty(String propertyName, Class<?> propertyType, Method readMethod, Method writeMethod, Class<?> owningType,
            Type<?> referenceType, Map<String, Property> properties) {
        final Property.Builder builder = new Property.Builder();
       
        builder.expression(propertyName);
        builder.name(propertyName);
       
        if (readMethod != null) {
            builder.getter(readMethod.getName() + "()");
        }
        if (writeMethod != null) {
            builder.setter(writeMethod.getName() + "(%s)");
        }
       
        if (readMethod != null || writeMethod != null) {
           
            builder.type(resolvePropertyType(readMethod, propertyType, owningType, referenceType));
            Property property = builder.build(this);
           
            Property existing = properties.get(propertyName);
            if (existing == null) {
                properties.put(propertyName, builder.build(this));
            } else if (existing.getType().isAssignableFrom(property.getType()) && !existing.getType().equals(property.getType())) {
                /*
                 * The type has been refined by the generic information in a
                 * super-type
                 */
                properties.put(propertyName, builder.merge(existing).build(this));
            }
        }
    }
   
    /**
     * Resolves the type of a property from the provided input factors.
     *
     * @param readMethod
     * @param rawType
     * @param owningType
     * @param referenceType
     * @return the resolved Type of the property
     */
    public Type<?> resolvePropertyType(Method readMethod, Class<?> rawType, Class<?> owningType, Type<?> referenceType) {
       
        rawType = resolveRawPropertyType(rawType, readMethod);
       
        Type<?> resolvedGenericType = null;
        if (referenceType.isParameterized() || owningType.getTypeParameters().length > 0 || rawType.getTypeParameters().length > 0) {
           
            if (readMethod != null) {
                try {
                    resolvedGenericType = resolveGenericType(
                            readMethod.getDeclaringClass().getDeclaredMethod(readMethod.getName(), new Class[0]).getGenericReturnType(),
                            referenceType);
                } catch (NoSuchMethodException e) {
                    throw new IllegalStateException("readMethod does not exist", e);
                }
            }
        }
       
        if (resolvedGenericType == null || resolvedGenericType.isAssignableFrom(rawType)) {
            resolvedGenericType = TypeFactory.valueOf(rawType);
        }
        return resolvedGenericType;
    }
   
    /**
     * Add public non-static fields as properties
     *
     * @param referenceType
     *            the type for which to collect public field properties
     * @param properties
     *            the collected properties for this type
     */
    protected void collectPublicFieldProperties(Type<?> referenceType, Map<String, Property> properties) {
       
        for (Field f : referenceType.getRawType().getFields()) {
            if (!Modifier.isStatic(f.getModifiers())) {
                final Property.Builder builder = new Property.Builder();
                builder.expression(f.getName());
                builder.name(f.getName());
               
                Class<?> rawType = f.getType();
                Type<?> genericType = resolveGenericType(f.getGenericType(), referenceType);
                if (genericType != null && !genericType.isAssignableFrom(rawType)) {
                    builder.type(genericType);
                } else {
                    builder.type(TypeFactory.valueOf(rawType));
                }
               
                Property existing = properties.get(f.getName());
                builder.setter(f.getName() + " = %s");
                if (existing == null) {
                    builder.getter(f.getName());
                    properties.put(f.getName(), builder.build(this));
                } else if (existing.getSetter() == null) {
                    builder.merge(existing);
                    properties.put(f.getName(), builder.build(this));
                }
            }
        }
    }
   
    /**
     * Determines whether the provided string is a valid nested property
     * expression
     *
     * @param expression
     *            the expression to evaluate
     * @return true of the expression represents a nested property
     */
    protected boolean isNestedPropertyExpression(String expression) {
        return expression.replaceAll("\\:\\{" + NON_NESTED_PROPERTY_CHARACTERS + "\\}", "").indexOf('.') != -1;
    }
   
    /**
     * Determines whether the provided string is a valid element property
     * expression
     *
     * @param expression
     *            the expression to evaluate
     * @return true if the expression represents an element property
     */
    protected boolean isElementPropertyExpression(String expression) {
        return expression.replaceAll("\\:\\{" + DYNAMIC_PROPERTY_CHARACTERS + "\\}", "").indexOf('{') != -1;
    }
   
   
    /**
     * Determines whether the provided string is a valid element property
     * expression
     *
     * @param expression
     *            the expression to evaluate
     * @return true if the expression represents an element property
     */
    protected boolean isIndividualElementExpression(String expression) {
        String normalized = expression.replaceAll("\\:\\{" + DYNAMIC_PROPERTY_CHARACTERS + "\\}", "");
        return normalized.contains("[") && normalized.endsWith("]");
    }
   
    /**
     * Determines if the provided property expression is a element
     * self-reference.
     *
     * @param expr
     *            the expression to evaluate
     * @return
     */
    private boolean isSelfReferenceExpression(String expr) {
        return "".equals(expr);
    }
   
    private static final String DYNAMIC_PROPERTY_CHARACTERS = "[\\w.='\"\\|\\%,\\(\\)\\$\\<\\> ]+";
    private static final String NON_NESTED_PROPERTY_CHARACTERS = "[\\w.='\"\\|\\%,\\(\\)\\$\\[\\]\\{\\} ]+";
   
   
    private static final String NESTED_PROPERTY_SPLITTER = "(?!\\:\\{" + DYNAMIC_PROPERTY_CHARACTERS + ")[.](?!" + DYNAMIC_PROPERTY_CHARACTERS
            + "\\})";
   
    private static final String ELEMENT_PROPERTY_SPLITTER = "(?!\\:\\{" + DYNAMIC_PROPERTY_CHARACTERS + ")\\{";
   
   
    /*
     * (non-Javadoc)
     *
     * @see
     * ma.glasnost.orika.property.PropertyResolverStrategy#getNestedProperty
     * (java.lang.reflect.Type, java.lang.String)
     */
    public NestedProperty getNestedProperty(java.lang.reflect.Type type, String p) {
        return getNestedProperty(type, p, null);
    }
    /*
     * (non-Javadoc)
     *
     * @see
     * ma.glasnost.orika.property.PropertyResolverStrategy#getNestedProperty
     * (java.lang.reflect.Type, java.lang.String)
     */
    protected NestedProperty getNestedProperty(java.lang.reflect.Type type, String p, Property owner) {
       
        String typeName = type.toString();
        Property property = null;
        java.lang.reflect.Type propertyType = type;
        final List<Property> path = new ArrayList<Property>();
        final StringBuilder expression = new StringBuilder();
        Property container = owner;
        if (p.indexOf('.') != -1) {
            final String[] ps = p.split(NESTED_PROPERTY_SPLITTER);
            int i = 0;
            while (i < ps.length) {
                try {
                    property = getProperty(propertyType, ps[i], (i < ps.length - 1), container);
                    propertyType = property.getType();
                    container = null;
                } catch (MappingException e) {
                    throw new MappingException("could not resolve nested property [" + p + "] on " + type + ", because "
                            + e.getLocalizedMessage());
                }
               
                i++;
                if (i < ps.length) {
                    path.add(property);
                    expression.append(property.getExpression() + ".");
                } else {
                    expression.append(property.getExpression());
                }
            }
        }
       
        if (property == null) {
            throw new RuntimeException(typeName + " does not contain property [" + p + "]");
        }
       
        return new NestedProperty(expression.toString(), property, path.toArray(new Property[path.size()]));
    }
   
    public Property getElementProperty(java.lang.reflect.Type type, String p) {
        return getElementProperty(type, p, null);
    }
   
    /**
     * @param type
     * @param p
     * @return the Property represented by the specified element property
     *         expression
     */
    @SuppressWarnings("unchecked")
    public Property getElementProperty(java.lang.reflect.Type type, String p, Property owner) {
       
        String[] ps = p.split(ELEMENT_PROPERTY_SPLITTER, 2);
        String elementPropertyExpression = ps[1].substring(0, ps[1].length() - 1);
       
        Property owningProperty;
        if (owner != null) {
            if (type.equals(owner.getType())) {
                owningProperty = owner;
            } else {
                owningProperty = getProperty(type, ps[0], false, owner);
            }
        } else {
            owningProperty = getProperty(type, ps[0]);
        }
       
        Type<?> elementType;
       
        Property elementProperty;
        /*if (isIndividualElementExpression(elementPropertyExpression)) {
            elementProperty = getProperty(owningProperty.getType(), elementPropertyExpression, false, owningProperty);
        } else*/ if (owningProperty.isMap()) {
            elementType = MapEntry.concreteEntryType((Type<Map<Object, Object>>) owningProperty.getType());
            elementProperty = getProperty(elementType, elementPropertyExpression, false, owningProperty);
        } else if (owningProperty.isCollection()) {
            elementType = owningProperty.getType().getNestedType(0);
            elementProperty = getProperty(elementType, elementPropertyExpression, false, owningProperty);
        } else if (owningProperty.isArray()) {
            elementType = owningProperty.getType().getComponentType();
            elementProperty = getProperty(elementType, elementPropertyExpression, false, owningProperty);
        } else {
            throw new IllegalArgumentException("'" + p + "' is not a valid element property for " + type);
        }
       
        return new NestedElementProperty(owningProperty, elementProperty, this);
    }
   
    /**
     * @param type
     * @param p
     * @return the Property represented by the specified element property
     *         expression
     */
    @SuppressWarnings("unchecked")
    public Property getIndividualElementProperty(java.lang.reflect.Type type, String p, Property owner) {
       
        String[] ps = p.split("\\[", 2);
        String elementPropertyExpression = ps[1].substring(0, ps[1].length()-1);
       
//        Property owningProperty = owner != null ? owner : getProperty(type, ps[0]);
        Property owningProperty;
        if (owner != null) {
            if (type.equals(owner.getType())) {
                owningProperty = owner;
            } else {
                owningProperty = getProperty(type, ps[0], false, owner);
            }
        } else {
            owningProperty = getProperty(type, ps[0]);
        }
       
       
        Type<?> elementType;
        Property elementProperty;
        if (owningProperty.isMap()) {
            elementType = MapEntry.concreteEntryType((Type<Map<Object, Object>>) owningProperty.getType());
            String key = elementPropertyExpression.substring(1, elementPropertyExpression.length()-1);
            elementProperty = new MapKeyProperty(key, elementType.getNestedType(1), null);
        } else if (owningProperty.isCollection()) {
            elementType = owningProperty.getType().getNestedType(0);
            try {
                int index = Integer.valueOf(elementPropertyExpression.replaceAll("[\\[\\]]", ""));
                elementProperty = new ListElementProperty(index, elementType, null);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("'" + p + "' is not a valid element property for " + type);
            }
        } else if (owningProperty.isArray()) {
            elementType = owningProperty.getType().getComponentType();
            try {
                int index = Integer.valueOf(elementPropertyExpression);
                elementProperty = new ArrayElementProperty(index, elementType, null);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("'" + p + "' is not a valid element property for " + type);
            }
        } else {
            throw new IllegalArgumentException("'" + p + "' is not a valid element property for " + type);
        }
       
        if (!"".equals(owningProperty.getName())) {
            elementProperty = new NestedProperty(p, elementProperty, new Property[]{owningProperty});
        }
        return elementProperty;
       
    }
   
   
    public Property getProperty(java.lang.reflect.Type type, String expr) {
       
        return getProperty(type, expr, false, null);
    }
   
    public Property getProperty(Property owner, String expr) {
       
        return getProperty(owner.getType(), expr, false, owner);
    }
   
   
    /**
     * Resolves the specified property expression
     *
     * @param type
     *            the property's owning type
     * @param expr
     *            the property expression to resolve
     * @param properties
     *            the known properties for the type
     * @return the resolved Property
     * @throws MappingException
     *             if the expression cannot be resolved to a property for the
     *             type
     */
    protected Property getProperty(java.lang.reflect.Type type, String expr, boolean isNestedLookup, Property owner) throws MappingException {
        Property property = null;
       
        if (isSelfReferenceExpression(expr)) {
            property = new Property.Builder().name("").getter("").setter(" = %s").type(TypeFactory.valueOf(type)).container(owner).build(this);
        } else if (isNestedPropertyExpression(expr) && !isElementPropertyExpression(expr)) {
            property = getNestedProperty(type, expr, owner);
        } else if (isElementPropertyExpression(expr)) {
            property = getElementProperty(type, expr, owner);
        } else if (isIndividualElementExpression(expr)) {
            property = getIndividualElementProperty(type, expr, owner);
        } else {
            // TODO: perhaps in-line properties should be isolated to a given
            // ClassMapBuilder instance, rather than made available for other
            // mappings
            // of the class; can this cause problems?
            Map<String, Property> inlinePoperties = inlinePropertiesCache.get(type);
            if (inlinePoperties != null) {
                property = inlinePoperties.get(expr);
            }
            if (property == null) {
                Map<String, Property> properties = getProperties(type);
                if (properties.containsKey(expr)) {
                    property = properties.get(expr);
                } else if (isInlinePropertyExpression(expr)) {
                    property = resolveInlineProperty(type, expr);
                    if (property != null) {
                        synchronized (type) {
                            if (inlinePoperties == null) {
                                inlinePoperties = new HashMap<String, Property>(1);
                                inlinePropertiesCache.put(type, inlinePoperties);
                            }
                            inlinePoperties.put(property.getName(), property);
                        }
                    }
                } else {
                    throw new MappingException(expr + " does not belong to " + type);
                }
               
                if (owner != null) {
                    property = new Property.Builder().merge(property).container(owner).build();
                }
            }
        }
        return property;
    }
   
    private static final Pattern INLINE_PROPERTY_PATTERN = Pattern.compile("([\\w]+)\\:\\{\\s*([\\w\\(\\)'\"\\% ]+)\\s*\\|\\s*([\\w\\(\\)'\"\\%, ]+)\\s*\\|?\\s*(?:(?:type=)([\\w.\\$ \\<\\>]+))?\\}");
   
    /**
     * Determines whether the provided string is a valid in-line property
     * expression
     *
     * @param expression
     *            the expression to evaluate
     * @return true if the expression represents an in-line property
     */
    protected boolean isInlinePropertyExpression(String expression) {
        return INLINE_PROPERTY_PATTERN.matcher(expression).matches();
    }
   
    /**
     * Resolves in-line properties, which are defined with the following format:<br>
     * "name{getterName|setterName|type=typeName}".<br>
     * <br>
     * Setter name can be omitted, as well as type name; if getter name is
     * omitted, then setter name must be preceded by '|', like so: <br>
     * "name{|setterName|type=typeName}", or like "name{|setterName}". <br>
     * <br>
     * At least one of getter or setter must be provided.<br>
     * Getter or setter 'names' can optionally be the java source of the method
     * call including static arguments, like so: <br>
     * "name{getTheName(\"someString\")|setTheName(\"someString\", %s)}"<br>
     * If the setter is defined in this way, it should contain the string '%s'
     * which represents the value being set.
     *
     *
     *
     * @param type
     * @param expr
     * @return an in-line Property as defined by the provided expression
     */
    public Property resolveInlineProperty(java.lang.reflect.Type type, String expr) {
        Type<?> theType = TypeFactory.valueOf(type);
        Matcher matcher = INLINE_PROPERTY_PATTERN.matcher(expr);
       
        if (matcher.matches()) {
            Property.Builder builder = new Property.Builder(theType, matcher.group(1));
            builder.getter(matcher.group(2));
            builder.setter(matcher.group(3));
            builder.type(matcher.group(4));
            return builder.build(this);
        } else {
            throw new IllegalArgumentException("'" + expr + "' is not a valid dynamic property expression");
        }
    }
   
    /**
     * Collects all properties for the specified type.
     *
     * @param type
     *            the type for which to collect properties
     * @param referenceType
     *            the reference type for use in resolving generic parameters as
     *            needed
     * @param properties
     *            the properties collected for the current type
     */
    protected abstract void collectProperties(Class<?> type, Type<?> referenceType, Map<String, Property> properties);
   
}
TOP

Related Classes of ma.glasnost.orika.property.PropertyResolver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.