/* * 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; } }