wicket.util.lang.PropertyResolver.java Source code

Java tutorial

Introduction

Here is the source code for wicket.util.lang.PropertyResolver.java

Source

/*
 * $Id: PropertyResolver.java 4710 2006-03-02 00:46:15 -0800 (Thu, 02 Mar 2006)
 * eelco12 $ $Revision: 6308 $ $Date: 2006-03-02 00:46:15 -0800 (Thu, 02 Mar
 * 2006) $
 * 
 * ==============================================================================
 * 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 wicket.util.lang;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import wicket.Session;
import wicket.WicketRuntimeException;
import wicket.util.convert.ConversionException;
import wicket.util.string.Strings;

/**
 * This class parses expressions to lookup or set a value on the object that is
 * given. <br/> The supported expressions are:
 * <p>
 * "property": This can can then be a bean property with get and set method. Or
 * if a map is given as an object it will be lookup with the property as a key
 * when there is not get method for that property. <p/>
 * <p>
 * "property1.property2": Both properties are lookup as written above. If
 * property1 evaluates to null then if there is a setMethod (or if it is a map)
 * and the Class of the property has a default constructor then the object will
 * be constructed and set on the object. <p/>
 * <p>
 * "property.index": If the property is a List or Array then the second property
 * can be a index on that list like: 'mylist.0' this expression will also map on
 * a getProperty(index) or setProperty(index,value) methods. If the object is a
 * List then the list will grow automaticaly if the index is greater then the
 * size <p/>
 * <p>
 * Index or map properties can also be written as: "property[index]" or
 * "property[key]" <p/>
 * 
 * @author jcompagner
 */
public final class PropertyResolver {
    private final static Map<Class<? extends Object>, Map<String, IGetAndSet>> classesToGetAndSetters = new ConcurrentHashMap<Class<? extends Object>, Map<String, IGetAndSet>>(
            64);

    /** Log. */
    private static final Log log = LogFactory.getLog(PropertyResolver.class);

    /**
     * Looksup the value from the object with the given expression. If the
     * expresion, the object itself or one property evalutes to null then a null
     * will be returned.
     * 
     * @param expression
     *            The expression string with the property to be lookup.
     * @param object
     *            The object which is evaluated.
     * @return The value that is evaluted. Null something in the expression
     *         evaluted to null.
     */
    public final static Object getValue(final String expression, final Object object) {
        if (expression == null || expression.equals("") || object == null) {
            return object;
        }

        ObjectAndGetSetter getter = getObjectAndGetSetter(expression, object, false);
        if (getter == null) {
            return null;
        }
        return getter.getValue();

    }

    /**
     * Set the value on the object with the given expression. If the expression
     * can't be evaluated then a WicketRuntimeException will be thrown. If a
     * null object is encounted then it will try to generate it by calling the
     * default constructor and set it on the object.
     * 
     * The value will be tried to convert to the right type with the given
     * converter.
     * 
     * @param expression
     *            The expression string with the property to be set.
     * @param object
     *            The object which is evaluated to set the value on.
     * @param value
     *            The value to set.
     * @param converter
     *            The convertor to convert the value if needed to the right
     *            type.
     */
    public final static void setValue(final String expression, final Object object, Object value,
            PropertyResolverConverter converter) {
        if (expression == null || expression.equals("")) {
            throw new WicketRuntimeException("Empty expression setting value: " + value + " on object: " + object);
        }
        if (object == null) {
            throw new WicketRuntimeException(
                    "Null object setting value: " + value + " with expression: " + expression);
        }

        ObjectAndGetSetter setter = getObjectAndGetSetter(expression, object, true);
        if (setter == null) {
            throw new WicketRuntimeException("Null object returned for expression: " + expression
                    + " for setting value: " + value + " on: " + object);
        }
        setter.setValue(value,
                converter == null ? new PropertyResolverConverter(Session.get(), Session.get().getLocale())
                        : converter);
    }

    private static ObjectAndGetSetter getObjectAndGetSetter(final String expression, final Object object,
            boolean tryToCreateNull) {
        final String expressionBracketsSeperated = Strings.replaceAll(expression, "[", ".[").toString();
        int index = expressionBracketsSeperated.indexOf('.');
        int lastIndex = 0;
        Object value = object;
        Class<? extends Object> clz = value.getClass();
        String exp = expressionBracketsSeperated;
        while (index != -1) {
            exp = expressionBracketsSeperated.substring(lastIndex, index);
            IGetAndSet getAndSetter = null;
            try {
                getAndSetter = getGetAndSetter(exp, clz);
            } catch (WicketRuntimeException ex) {

                // expression by it self can't be found. try to find a
                // setPropertyByIndex(int,value) method
                index = expressionBracketsSeperated.indexOf('.', index + 1);
                if (index != -1) {
                    String indexExpression = expressionBracketsSeperated.substring(lastIndex, index);
                    getAndSetter = getGetAndSetter(indexExpression, clz);
                } else {
                    exp = expressionBracketsSeperated.substring(lastIndex);
                    break;
                }
            }
            Object newValue = getAndSetter.getValue(value);
            if (newValue == null) {
                if (tryToCreateNull) {
                    newValue = getAndSetter.newValue(value);
                    if (newValue == null) {
                        return null;
                    }
                } else {
                    return null;
                }
            }
            value = newValue;
            lastIndex = index + 1;
            index = expressionBracketsSeperated.indexOf('.', lastIndex);
            clz = value.getClass();
            if (index == -1) {
                exp = expressionBracketsSeperated.substring(lastIndex);
                break;
            }
        }
        IGetAndSet getAndSetter = getGetAndSetter(exp, clz);
        return new ObjectAndGetSetter(getAndSetter, value);
    }

    private final static IGetAndSet getGetAndSetter(String exp, Class<? extends Object> clz) {
        Map<String, IGetAndSet> getAndSetters = classesToGetAndSetters.get(clz);
        if (getAndSetters == null) {
            getAndSetters = new ConcurrentHashMap<String, IGetAndSet>(8);
            classesToGetAndSetters.put(clz, getAndSetters);
        }

        IGetAndSet getAndSetter = getAndSetters.get(exp);
        if (getAndSetter == null) {
            Method method = null;
            Field field = null;
            if (exp.startsWith("[")) {
                // if expression begins with [ skip method finding and use it as
                // a key/index lookup on a map.
                exp = exp.substring(1, exp.length() - 1);
            } else if (exp.endsWith("()")) {
                // if expression ends with (), don't test for setters just skip
                // directly to method finding.
                method = findMethod(clz, exp);
            } else {
                method = findGetter(clz, exp);
                if (method == null) {
                    // find field.
                    field = findField(clz, exp);
                }
            }
            if (method == null && field == null) {
                if (List.class.isAssignableFrom(clz)) {
                    try {
                        int index = Integer.parseInt(exp);
                        getAndSetter = new ListGetSet(index);
                    } catch (NumberFormatException ex) {
                        // can't parse the exp als a index maybe the exp was a
                        // method.
                        method = findMethod(clz, exp);
                        if (method != null) {
                            getAndSetter = new MethodGetAndSet(method);
                        } else {
                            throw new WicketRuntimeException("The expression '" + exp
                                    + "' is neither an index nor is it a method for the list " + clz);
                        }
                    }
                } else if (Map.class.isAssignableFrom(clz)) {
                    getAndSetter = new MapGetSet(exp);
                } else if (clz.isArray()) {
                    try {
                        int index = Integer.parseInt(exp);
                        getAndSetter = new ArrayGetSet(index);
                    } catch (NumberFormatException ex) {
                        if (exp.equals("length") || exp.equals("size")) {
                            getAndSetter = new ArrayLengthGetSet();
                        } else {
                            throw new WicketRuntimeException(
                                    "can't parse the exp " + exp + " as an index for an array lookup");
                        }
                    }
                } else {
                    method = findMethod(clz, exp);
                    if (method == null) {
                        int index = exp.indexOf('.');
                        if (index != -1) {
                            String propertyName = exp.substring(0, index);
                            String propertyIndex = exp.substring(index + 1);
                            try {

                                int parsedIndex = Integer.parseInt(propertyIndex);
                                // if so then it could be a
                                // getPropertyIndex(int)
                                // and setPropertyIndex(int, object)
                                String name = Character.toUpperCase(propertyName.charAt(0))
                                        + propertyName.substring(1);
                                method = clz.getMethod("get" + name, new Class[] { int.class });
                                getAndSetter = new ArrayPropertyGetSet(method, parsedIndex);

                            } catch (Exception e) {
                                throw new WicketRuntimeException(
                                        "no get method defined for class: " + clz + " expression: " + propertyName);
                            }
                        } else {
                            // We do not look for a public FIELD because that is
                            // not good
                            // programming with beans patterns
                            throw new WicketRuntimeException(
                                    "No get method defined for class: " + clz + " expression: " + exp);
                        }
                    } else {
                        getAndSetter = new MethodGetAndSet(method);
                    }
                }
            } else if (field != null) {
                getAndSetter = new FieldGetAndSetter(field);
            } else {
                getAndSetter = new MethodGetAndSet(method);
            }
            getAndSetters.put(exp, getAndSetter);
        }
        return getAndSetter;
    }

    /**
     * @param clz
     * @param expression
     * @return introspected field
     */
    private static Field findField(Class<? extends Object> clz, String expression) {
        Field field = null;
        try {
            field = clz.getField(expression);
        } catch (Exception e) {
            log.debug("Cannot find field " + clz + "." + expression, e);
        }
        return field;
    }

    /**
     * @param clz
     * @param expression
     * @return The method for the expression null if not found
     */
    private final static Method findGetter(Class<? extends Object> clz, String expression) {
        String name = Character.toUpperCase(expression.charAt(0)) + expression.substring(1);
        Method method = null;
        try {
            method = clz.getMethod("get" + name, (Class[]) null);
        } catch (Exception e) {
        }
        if (method == null) {
            try {
                method = clz.getMethod("is" + name, (Class[]) null);
            } catch (Exception e) {
                log.debug("Cannot find getter " + clz + "." + expression, e);
            }
        }
        return method;
    }

    private final static Method findMethod(Class<? extends Object> clz, String expression) {
        if (expression.endsWith("()")) {
            expression = expression.substring(0, expression.length() - 2);
        }
        Method method = null;
        try {
            method = clz.getMethod(expression, (Class[]) null);
        } catch (Exception e) {
            log.debug("Cannot find method " + clz + "." + expression, e);
        }
        return method;
    }

    /**
     * Utility class: instantiation not allowed.
     */
    private PropertyResolver() {
    }

    /**
     * @author jcompagner
     * 
     */
    private final static class ObjectAndGetSetter {

        private final IGetAndSet getAndSetter;
        private final Object value;

        /**
         * @param getAndSetter
         * @param value
         */
        public ObjectAndGetSetter(IGetAndSet getAndSetter, Object value) {
            this.getAndSetter = getAndSetter;
            this.value = value;
        }

        /**
         * @param value
         * @param converter
         */
        public void setValue(Object value, PropertyResolverConverter converter) {
            getAndSetter.setValue(this.value, value, converter);
        }

        /**
         * @return The value
         */
        public Object getValue() {
            return getAndSetter.getValue(value);
        }

    }

    private static interface IGetAndSet {
        /**
         * @param object
         *            The object where the value must be taken from.
         * 
         * @return The value of this property
         */
        public Object getValue(final Object object);

        /**
         * @param object
         *            The object where the new value must be set on.
         * 
         * @return The new value for the property that is set back on that
         *         object.
         */
        public Object newValue(Object object);

        /**
         * @param object
         * @param value
         * @param converter
         */
        public void setValue(final Object object, final Object value, PropertyResolverConverter converter);

    }

    private static final class MapGetSet implements IGetAndSet {
        final private String key;

        MapGetSet(String key) {
            this.key = key;
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#getValue(java.lang.Object)
         */
        public Object getValue(Object object) {
            return ((Map) object).get(key);
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#setValue(java.lang.Object,
         *      java.lang.Object, wicket.util.convert.IConverter)
         */
        @SuppressWarnings("unchecked")
        public void setValue(Object object, Object value, PropertyResolverConverter converter) {
            ((Map) object).put(key, value);
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#newValue(Object)
         */
        public Object newValue(Object object) {
            // Map can't make a newValue or should it look what is more in the
            // map and try to make one of the class if finds?
            return null;
        }
    }

    private static final class ListGetSet implements IGetAndSet {
        final private int index;

        ListGetSet(int index) {
            this.index = index;
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#getValue(java.lang.Object)
         */
        public Object getValue(Object object) {
            return ((List) object).get(index);
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#setValue(java.lang.Object,
         *      java.lang.Object, wicket.util.convert.IConverter)
         */
        @SuppressWarnings("unchecked")
        public void setValue(Object object, Object value, PropertyResolverConverter converter) {
            List<Object> lst = (List<Object>) object;

            if (lst.size() > index) {
                lst.set(index, value);
            } else if (lst.size() == index) {
                lst.add(value);
            } else {
                while (lst.size() < index) {
                    lst.add(null);
                }
                lst.add(value);
            }
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#newValue(Object)
         */
        public Object newValue(Object object) {
            // List can't make a newValue or should it look what is more in the
            // list and try to make one of the class if finds?
            return null;
        }
    }

    private static final class ArrayGetSet implements IGetAndSet {
        final private int index;

        ArrayGetSet(int index) {
            this.index = index;
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#getValue(java.lang.Object)
         */
        public Object getValue(Object object) {
            return Array.get(object, index);
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#setValue(java.lang.Object,
         *      java.lang.Object, wicket.util.convert.IConverter)
         */
        public void setValue(Object object, Object value, PropertyResolverConverter converter) {
            value = converter.convert(value, object.getClass().getComponentType());
            Array.set(object, index, value);
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#newValue(java.lang.Object)
         */
        public Object newValue(Object object) {
            Class clz = object.getClass().getComponentType();
            Object value = null;
            try {
                value = clz.newInstance();
                Array.set(object, index, value);
            } catch (Exception e) {
                log.warn("Cannot set new value " + value + " at index " + index
                        + " for array holding elements of class " + clz, e);
            }
            return value;
        }
    }

    private static final class ArrayLengthGetSet implements IGetAndSet {
        ArrayLengthGetSet() {
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#getValue(java.lang.Object)
         */
        public Object getValue(Object object) {
            return new Integer(Array.getLength(object));
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#setValue(java.lang.Object,
         *      java.lang.Object, wicket.util.convert.IConverter)
         */
        public void setValue(Object object, Object value, PropertyResolverConverter converter) {
            throw new WicketRuntimeException("Cant set the length on an array");
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#newValue(java.lang.Object)
         */
        public Object newValue(Object object) {
            throw new WicketRuntimeException("Cant get a new value from a length of an array");
        }
    }

    private static final class ArrayPropertyGetSet implements IGetAndSet {
        final private Integer index;
        final private Method getMethod;
        private Method setMethod;

        ArrayPropertyGetSet(Method method, int index) {
            this.index = new Integer(index);
            this.getMethod = method;
        }

        private final static Method findSetter(Method getMethod, Class<? extends Object> clz) {
            String name = getMethod.getName();
            name = "set" + name.substring(3);
            try {
                return clz.getMethod(name, new Class[] { int.class, getMethod.getReturnType() });
            } catch (Exception e) {
                log.debug("Cannot find setter method corresponding to " + getMethod, e);
            }
            return null;
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#getValue(java.lang.Object)
         */
        public Object getValue(Object object) {
            Object ret = null;
            try {
                ret = getMethod.invoke(object, new Object[] { index });
            } catch (InvocationTargetException ex) {
                throw new WicketRuntimeException(
                        "Error calling index property method: " + getMethod + " on object: " + object,
                        ex.getCause());
            } catch (Exception ex) {
                throw new WicketRuntimeException(
                        "Error calling index property method: " + getMethod + " on object: " + object, ex);
            }
            return ret;
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#setValue(java.lang.Object,
         *      java.lang.Object, wicket.util.convert.IConverter)
         */
        public void setValue(Object object, Object value, PropertyResolverConverter converter) {
            if (setMethod == null) {
                setMethod = findSetter(getMethod, object.getClass());
            }
            if (setMethod != null) {
                Object converted = converter.convert(value, getMethod.getReturnType());
                if (converted == null && value != null) {
                    throw new ConversionException("Can't convert value: " + value + " to class: "
                            + getMethod.getReturnType() + " for setting it on " + object);
                }
                try {
                    setMethod.invoke(object, new Object[] { index, converted });
                } catch (InvocationTargetException ex) {
                    throw new WicketRuntimeException(
                            "Error index property calling method: " + setMethod + " on object: " + object,
                            ex.getCause());
                } catch (Exception ex) {
                    throw new WicketRuntimeException(
                            "Error index property calling method: " + setMethod + " on object: " + object, ex);
                }
            } else {
                throw new WicketRuntimeException(
                        "no set method defined for value: " + value + " on object: " + object);
            }
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#newValue(java.lang.Object)
         */
        public Object newValue(Object object) {
            if (setMethod == null) {
                setMethod = findSetter(getMethod, object.getClass());
            }

            if (setMethod == null) {
                log.warn("Null setMethod");
                return null;
            }

            Class clz = getMethod.getReturnType();
            Object value = null;
            try {
                value = clz.newInstance();
                setMethod.invoke(object, new Object[] { index, value });
            } catch (Exception e) {
                log.warn("Cannot set new value " + value + " at index " + index, e);
            }
            return value;
        }
    }

    private static final class MethodGetAndSet implements IGetAndSet {
        private Method getMethod;
        private Method setMethod;

        MethodGetAndSet(Method getMethod) {
            this.getMethod = getMethod;
            this.getMethod.setAccessible(true);
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#getValue(java.lang.Object)
         */
        public final Object getValue(final Object object) {
            Object ret = null;
            try {
                ret = getMethod.invoke(object, (Object[]) null);
            } catch (InvocationTargetException ex) {
                throw new WicketRuntimeException("Error calling method: " + getMethod + " on object: " + object,
                        ex.getCause());
            } catch (Exception ex) {
                throw new WicketRuntimeException("Error calling method: " + getMethod + " on object: " + object,
                        ex);
            }
            return ret;
        }

        /**
         * @param object
         * @param value
         * @param converter
         */
        public final void setValue(final Object object, final Object value, PropertyResolverConverter converter) {
            if (setMethod == null) {
                setMethod = findSetter(getMethod, object.getClass());
            }
            if (setMethod != null) {
                Object converted = converter.convert(value, getMethod.getReturnType());
                if (converted == null) {
                    if (value != null) {
                        throw new ConversionException("Can't convert value: " + value + " to class: "
                                + getMethod.getReturnType() + " for setting it on " + object);
                    } else if (getMethod.getReturnType().isPrimitive()) {
                        throw new ConversionException("Can't convert null value to a primitive class: "
                                + getMethod.getReturnType() + " for setting it on " + object);
                    }
                }
                try {
                    setMethod.invoke(object, new Object[] { converted });
                } catch (InvocationTargetException ex) {
                    throw new WicketRuntimeException("Error calling method: " + setMethod + " on object: " + object,
                            ex.getCause());
                } catch (Exception ex) {
                    throw new WicketRuntimeException("Error calling method: " + setMethod + " on object: " + object,
                            ex);
                }
            } else {
                throw new WicketRuntimeException(
                        "no set method defined for value: " + value + " on object: " + object);
            }
        }

        private final static Method findSetter(Method getMethod, Class<? extends Object> clz) {
            String name = getMethod.getName();
            if (name.startsWith("get")) {
                name = "set" + name.substring(3);
            } else {
                name = "set" + name.substring(2);
            }
            try {
                Method method = clz.getMethod(name, new Class[] { getMethod.getReturnType() });
                if (method != null) {
                    method.setAccessible(true);
                }
                return method;
            } catch (Exception e) {
                log.debug("Cannot find setter corresponding to " + getMethod, e);
            }
            return null;
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#newValue(java.lang.Object)
         */
        public Object newValue(Object object) {
            if (setMethod == null) {
                setMethod = findSetter(getMethod, object.getClass());
            }

            if (setMethod == null) {
                log.warn("Null setMethod");
                return null;
            }

            Class clz = getMethod.getReturnType();
            Object value = null;
            try {
                value = clz.newInstance();
                setMethod.invoke(object, new Object[] { value });
            } catch (Exception e) {
                log.warn("Cannot set new value " + value, e);
            }
            return value;
        }

    }

    /**
     * @author jcompagner
     */
    private static class FieldGetAndSetter implements IGetAndSet {

        private Field field;

        /**
         * Construct.
         * 
         * @param field
         */
        public FieldGetAndSetter(Field field) {
            super();
            this.field = field;
            this.field.setAccessible(true);
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#getValue(java.lang.Object)
         */
        public Object getValue(Object object) {
            try {
                return field.get(object);
            } catch (Exception ex) {
                throw new WicketRuntimeException(
                        "Error getting field value of field " + field + " from object " + object, ex);
            }
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#newValue(java.lang.Object)
         */
        public Object newValue(Object object) {
            Class clz = field.getType();
            Object value = null;
            try {
                value = clz.newInstance();
                field.set(object, value);
            } catch (Exception e) {
                log.warn("Cannot set field " + field + " to " + value, e);
            }
            return value;
        }

        /**
         * @see wicket.util.lang.PropertyResolver.IGetAndSet#setValue(java.lang.Object,
         *      java.lang.Object, wicket.util.convert.IConverter)
         */
        public void setValue(Object object, Object value, PropertyResolverConverter converter) {
            value = converter.convert(value, field.getType());
            try {
                field.set(object, value);
            } catch (Exception ex) {
                throw new WicketRuntimeException(
                        "Error setting field value of field " + field + " on object " + object + ", value " + value,
                        ex);
            }
        }
    }
}