org.mypsycho.beans.DefaultInvoker.java Source code

Java tutorial

Introduction

Here is the source code for org.mypsycho.beans.DefaultInvoker.java

Source

/*
 * Copyright (C) 2011 Peransin Nicolas.
 * Use is subject to license terms.
 */
package org.mypsycho.beans;

import java.beans.IndexedPropertyDescriptor;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.MappedPropertyDescriptor;
import org.apache.commons.beanutils.MethodUtils;

/**
 * Class for ...
 * <p>Details</p>
 *
 * @author Peransin Nicolas
 *
 */
public class DefaultInvoker implements Invoker {

    static final protected Map<Class<?>, Character> PRIMITIVES = new HashMap<Class<?>, Character>();
    static {
        PRIMITIVES.put(Boolean.TYPE, 'Z');
        PRIMITIVES.put(Byte.TYPE, 'B');
        PRIMITIVES.put(Character.TYPE, 'C');
        PRIMITIVES.put(Double.TYPE, 'D');
        PRIMITIVES.put(Float.TYPE, 'F');
        PRIMITIVES.put(Integer.TYPE, 'I');
        PRIMITIVES.put(Long.TYPE, 'J');
        PRIMITIVES.put(Short.TYPE, 'S');
    }

    private static final Invoker instance = new DefaultInvoker();

    /** An empty object array */
    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

    /**
     * Returns the instance.
     *
     * @return the instance
     */
    public static Invoker getInstance() {
        return instance;
    }

    void required(Object bean, PropertyDescriptor prop, Method method, String name) throws NoSuchMethodException {
        if (method == null) {
            throw new NoSuchMethodException("Property '" + prop.getName() + "' has no " + name
                    + " method on bean class '" + bean.getClass() + "'");
        }
    }

    int getSize(Object bean) {
        if (bean.getClass().isArray()) {
            return (Array.getLength(bean));
        } else if (bean instanceof List) {
            return ((List<?>) bean).size();
        } else {
            return -1;
        }
    }

    /**
     * <p>
     * Return an accessible property setter method for this property, if there
     * is one; otherwise return <code>null</code>.
     * </p>
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param clazz The class of the read method will be invoked on
     * @param descriptor Property descriptor to return a setter for
     * @return The write method
     */
    Method getWriteMethod(Class<?> clazz, PropertyDescriptor descriptor) {
        return (MethodUtils.getAccessibleMethod(clazz, descriptor.getWriteMethod()));
    }

    /**
     * <p>
     * Return an accessible property getter method for this property, if there
     * is one; otherwise return <code>null</code>.
     * </p>
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param clazz The class of the read method will be invoked on
     * @param descriptor Property descriptor to return a getter for
     * @return The read method
     */
    Method getReadMethod(Class<?> clazz, PropertyDescriptor descriptor) {
        return (MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod()));
    }

    /*
     * (non-Javadoc)
     *
     * @see com.psycho.beans.Invoker#getSimpleProperty(java.lang.Object,
     * java.beans.PropertyDescriptor)
     */
    public Object get(Object bean, PropertyDescriptor prop)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Method readMethod = getReadMethod(bean.getClass(), prop);
        if (readMethod == null) {
            throw new NoSuchMethodException(
                    "Property '" + prop.getName() + "' has no getter method in class '" + bean.getClass() + "'");
        }

        // Call the property getter and return the value
        return invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
    }

    public void set(Object bean, PropertyDescriptor prop, Object value)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Method writeMethod = getWriteMethod(bean.getClass(), prop);
        required(bean, prop, writeMethod, "setter");

        // Call the property setter method
        Object[] values = new Object[1];
        values[0] = value;
        invokeMethod(writeMethod, bean, values);
    }

    public Object get(Object bean, PropertyDescriptor prop, int index)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        // Call the indexed getter method if there is one
        if (prop instanceof IndexedPropertyDescriptor) {
            Method readMethod = ((IndexedPropertyDescriptor) prop).getIndexedReadMethod();
            readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
            if (readMethod != null) {
                try {
                    return (invokeMethod(readMethod, bean, new Object[] { index }));
                } catch (InvocationTargetException e) {
                    if (e.getTargetException() instanceof IndexOutOfBoundsException) {
                        throw (IndexOutOfBoundsException) e.getTargetException();
                    } else {
                        throw e;
                    }
                }
            }
        }

        // Otherwise, the underlying property must be an array
        Method readMethod = getReadMethod(bean.getClass(), prop);
        required(bean, prop, readMethod, "getter");

        // Call the property getter and return the value
        Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
        try {
            return getIndexed(invokeResult, index);
        } catch (IndexOutOfBoundsException e) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + getSize(invokeResult)
                    + " for property '" + prop.getName() + "'");
        }
    }

    public Object getIndexed(Object bean, int index) {
        if (bean.getClass().isArray()) {
            return (Array.get(bean, index));
        } else if (bean instanceof java.util.List) {
            // get the List's value
            return ((List<?>) bean).get(index);
        } else {
            throw new IllegalArgumentException("Class '" + bean.getClass() + "' is not indexed");
        }
    }

    public void set(Object bean, PropertyDescriptor prop, int index, Object value)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        // Call the indexed setter method if there is one
        if (prop instanceof IndexedPropertyDescriptor) {
            Method writeMethod = ((IndexedPropertyDescriptor) prop).getIndexedWriteMethod();
            writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
            if (writeMethod != null) {
                try {
                    invokeMethod(writeMethod, bean, new Object[] { index, value });
                } catch (InvocationTargetException e) {
                    if (e.getTargetException() instanceof IndexOutOfBoundsException) {
                        throw (IndexOutOfBoundsException) e.getTargetException();
                    }
                    throw e;
                }
                return;
            }
        }

        // Otherwise, the underlying property must be an array or a list
        Method readMethod = getReadMethod(bean.getClass(), prop);
        required(bean, prop, readMethod, "getter");

        // Call the property getter to get the array or list
        Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
        try {
            setIndexed(invokeResult, index, value);
        } catch (IndexOutOfBoundsException e) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + getSize(invokeResult)
                    + " for property '" + prop.getName() + "'");
        }
    }

    public void setIndexed(Object bean, int index, Object value) throws IllegalArgumentException {
        if (bean.getClass().isArray()) {
            // Modify the specified value in the array
            Array.set(bean, index, value);
        } else if (bean instanceof List) {
            // Modify the specified value in the List
            @SuppressWarnings("unchecked")
            List<Object> list = (List<Object>) bean;
            list.set(index, value);
        } else {
            throw new IllegalArgumentException("Class '" + bean.getClass() + "' is not indexed");
        }
    }

    public Object get(Object bean, PropertyDescriptor prop, String key)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        if (prop instanceof MappedPropertyDescriptor) {
            // Call the keyed getter method if there is one
            Method readMethod = ((MappedPropertyDescriptor) prop).getMappedReadMethod();
            readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
            required(bean, prop, readMethod, "mapped getter");

            return invokeMethod(readMethod, bean, new Object[] { key });

        }

        /* means that the result has to be retrieved from a map */

        Method readMethod = getReadMethod(bean.getClass(), prop);
        required(bean, prop, readMethod, "mapped getter");

        Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
        return getMapped(invokeResult, key);
    }

    public Object getMapped(Object bean, String key) {
        if (bean instanceof Map) {
            return ((Map<?, ?>) bean).get(key);
        }

        throw new IllegalArgumentException("Class " + bean.getClass().getName() + " is not mapped");
    }

    public void set(Object bean, PropertyDescriptor prop, String key, Object value)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        if (prop instanceof MappedPropertyDescriptor) {
            // Call the keyed setter method if there is one
            Method mappedWriteMethod = ((MappedPropertyDescriptor) prop).getMappedWriteMethod();
            mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod);
            required(bean, prop, mappedWriteMethod, "mapped setter");

            Object[] params = new Object[] { key, value };
            invokeMethod(mappedWriteMethod, bean, params);
            return;
        }

        /* means that the result has to be retrieved from a map */
        Method readMethod = getReadMethod(bean.getClass(), prop);
        required(bean, prop, null, "mapped getter");

        Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
        /* test and fetch from the map */
        if (invokeResult instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> map = (Map<String, Object>) invokeResult;
            map.put(key, value);
        }
    }

    public void setMapped(Object bean, String key, Object value) {
        if (bean instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> map = (Map<String, Object>) bean;
            map.put(key, value);
        } else {
            throw new IllegalArgumentException("Class '" + bean.getClass() + "' is not mapped");
        }
    }

    /** This just catches and wraps IllegalArgumentException. */
    private Object invokeMethod(Method method, Object bean, Object[] values)
            throws IllegalAccessException, InvocationTargetException {
        if (bean == null) {
            throw new IllegalArgumentException(
                    "No bean specified " + "- this should have been checked before reaching this method");
        }

        Exception cause = null;
        try {
            return method.invoke(bean, values);
        } catch (InvocationTargetException ite) {
            if (ite.getTargetException() instanceof Error) {
                throw (Error) ite.getTargetException();
            } else if (ite.getTargetException() instanceof RuntimeException) {
                throw (RuntimeException) ite.getTargetException();
            }
            throw ite;

        } catch (NullPointerException npe) {
            // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
            // null for a primitive value (JDK 1.5+ throw
            // IllegalArgumentException)
            cause = npe;
        } catch (IllegalArgumentException iae) {
            cause = iae;
        }

        String valueString = "";
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                if (i > 0) {
                    valueString += ", ";
                }
                if (values[i] == null) {
                    valueString += "<null>";
                } else {
                    valueString += (values[i]).getClass().getName();
                }
            }
        }
        String expectedString = "";
        Class<?>[] parTypes = method.getParameterTypes();
        for (int i = 0; i < parTypes.length; i++) {
            if (i > 0) {
                expectedString += ", ";
            }
            expectedString += parTypes[i].getName();
        }

        IllegalArgumentException e = new IllegalArgumentException(
                "Cannot invoke " + method.getDeclaringClass().getName() + "." + method.getName()
                        + " on bean class '" + bean.getClass() + "' - " + cause.getMessage()
                        // as per
                        // https://issues.apache.org/jira/browse/BEANUTILS-224
                        + " - had objects of type \"" + valueString + "\" but expected signature \""
                        + expectedString + "\"");
        BeanUtils.initCause(e, cause);
        throw e;
    }

    static Class<?> createArrayType(Class<?> type) {
        try {
            Character c = PRIMITIVES.get(type);
            if (c != null) { // its a primitive
                return Class.forName("[" + c);
            } else if (type.isArray()) {
                return Class.forName("[" + type.getName());
            } else {
                return Class.forName("[" + type.getName() + ";");
            }
        } catch (ClassNotFoundException e) {
            throw new UnsupportedOperationException(e);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.psycho.beans.Invoker#getPropertyType(java.lang.Object,
     * java.beans.PropertyDescriptor)
     */
    public Class<?> getPropertyType(PropertyDescriptor prop, boolean collection) {
        Class<?> type = prop.getPropertyType();
        if (!collection) {
            if (type != null) {
                return type;
            }

            if (prop instanceof IndexedPropertyDescriptor) {
                type = (((IndexedPropertyDescriptor) prop).getIndexedPropertyType());
                if (type == null) { // is it possible ?
                    return List.class; // Bold guess !!
                }
                return createArrayType(type);
            }
            if (prop instanceof MappedPropertyDescriptor) {
                return Map.class;
            }

            return null;
        }

        if (prop instanceof IndexedPropertyDescriptor) {
            Class<?> indexedType = (((IndexedPropertyDescriptor) prop).getIndexedPropertyType());
            if (indexedType != null) {
                return indexedType;
            }
        }
        if (prop instanceof MappedPropertyDescriptor) {
            Class<?> mappedType = (((MappedPropertyDescriptor) prop).getMappedPropertyType());
            if (mappedType != null) {
                return mappedType;
            }
        }

        return (type == null) ? null : getCollectedType(type);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.psycho.beans.Invoker#isWriteable(java.lang.Object,
     * java.beans.PropertyDescriptor)
     */
    public boolean isWriteable(Object bean, PropertyDescriptor prop, boolean collection) {
        Method writeMethod = getWriteMethod(bean.getClass(), prop);
        if (!collection) {
            return writeMethod != null;
        }

        if (writeMethod == null) {
            if (prop instanceof IndexedPropertyDescriptor) {
                writeMethod = ((IndexedPropertyDescriptor) prop).getIndexedWriteMethod();
            } else if (prop instanceof MappedPropertyDescriptor) {
                writeMethod = ((MappedPropertyDescriptor) prop).getMappedWriteMethod();
            }
        }
        return (writeMethod != null);

    }

    public boolean isReadable(Object bean, PropertyDescriptor prop, boolean collection) {
        Method readMethod = getReadMethod(bean.getClass(), prop);
        if (!collection) {
            return readMethod != null;
        }

        if (readMethod == null) {
            if (prop instanceof IndexedPropertyDescriptor) {
                readMethod = ((IndexedPropertyDescriptor) prop).getIndexedReadMethod();
            } else if (prop instanceof MappedPropertyDescriptor) {
                readMethod = ((MappedPropertyDescriptor) prop).getMappedReadMethod();
            }
        }
        return (readMethod != null);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.psycho.beans.Invoker#isCollection()
     */
    public boolean isCollection(PropertyDescriptor prop) {
        if (prop instanceof MappedPropertyDescriptor) {
            return true;
        }
        if (prop instanceof IndexedPropertyDescriptor) {
            return true;
        }
        return isCollection(prop.getPropertyType());
    }

    public boolean isCollection(Class<?> type) {
        if (type == null) {
            return false;
        }
        return type.isArray() || List.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.psycho.beans.Invoker#getCollectedType(java.lang.Class)
     */
    public Class<?> getCollectedType(Class<?> collectionType) {
        if (collectionType.isArray()) {
            return collectionType.getComponentType();
        }
        if (List.class.isAssignableFrom(collectionType) || Map.class.isAssignableFrom(collectionType)) {
            return Object.class;
        }

        return null;
    }
}