001 package org.springframework.richclient.widget.table;
002
003 import java.lang.reflect.InvocationTargetException;
004 import java.lang.reflect.Method;
005
006 /**
007 * This {@link Accessor} uses a chaining implementation of getter methods to allow nested properties.
008 *
009 * @author Jan Hoskens
010 * @since 0.5.0
011 */
012 public class NestedAccessor implements Accessor
013 {
014
015 /** Lazily created accessor to access the nested property on the top level property object. */
016 private Accessor wrappedAccessor;
017
018 /**
019 * The nested property. Will be used to create an accessor together with the return type of the top level
020 * property object.
021 */
022 final private String nestedProperty;
023
024 /** Getter method to access the top level property object. */
025 final private Method getter;
026
027 /**
028 * Convenience constructor. Creates a getter method for the given class and property and reroutes to
029 * {@link NestedAccessor#NestedAccessor(Method, String)}.
030 *
031 * @param clazz
032 * type with the nested property.
033 * @param propertyName
034 * the first part of the nested property.
035 * @param nestedPropertyName
036 * the nested property, possibly containing more nesting levels.
037 * @see #NestedAccessor(Method, String)
038 */
039 public NestedAccessor(final Class<?> clazz, final String propertyName, final String nestedPropertyName)
040 {
041 this(ClassUtils.getReadMethod(clazz, propertyName), nestedPropertyName);
042 }
043
044 /**
045 * Constructor. The getter will deliver the first level of the nesting. It's return value will be used to
046 * construct the next accessor for the nestedProperty.
047 *
048 * @param getter
049 * method delivering the first part of the nesting.
050 * @param nestedProperty
051 * property that should be available on the return object of the getter. Possibly nested as
052 * well.
053 * @see #NestedAccessor(Class, String, String)
054 */
055 public NestedAccessor(final Method getter, final String nestedProperty)
056 {
057 this.nestedProperty = nestedProperty;
058 this.getter = getter;
059 }
060
061 /**
062 * Get the value from the source entity. If at any point the chaining results in a null value. The
063 * chaining should end and return <code>null</code>.
064 *
065 * @param fromEntity
066 * the entity on which the getter should operate.
067 * @return <code>null</code> if at any point in the chaining a property returned <code>null</code> or
068 * the value of the nested property.
069 */
070 public Object getValue(Object fromEntity) throws IllegalAccessException, InvocationTargetException
071 {
072 Object propertyValue = getter.invoke(fromEntity);
073 return propertyValue == null ? null : getWrappedAccessor(propertyValue.getClass()).getValue(
074 propertyValue);
075 }
076
077 /**
078 * <p>
079 * Get the wrapped accessor, instantiate it lazily if needed.
080 * </p>
081 *
082 * <p>
083 * Normally the return type of the getter method delivers the correct type on which the nested property
084 * can be found. There is however a specific case in which this isn't true. It may be that a specific type
085 * is only known at runtime and that you need to access a property of that specific type. The specific
086 * type can be found at runtime when the wrapped accessor is constructed. But it does imply that all other
087 * access will result in the same type.
088 * </p>
089 *
090 * <p>
091 * A specific type implementation is found in PeriodicValueAdapter->BTWPercentage, here a property of
092 * BTWPercentage can be accessed through the adapter, but all other adapters should contain a
093 * BTWPercentage.
094 * </p>
095 *
096 * @param propertyType
097 * property type to use if getter doesn't yield the correct one.
098 * @return an {@link Accessor} for the wrapped property.
099 */
100 private Accessor getWrappedAccessor(Class<?> propertyType)
101 {
102 if (wrappedAccessor == null)
103 {
104 try
105 {
106 wrappedAccessor = ClassUtils.getAccessorForProperty(getter.getReturnType(), nestedProperty);
107 }
108 catch (NoSuchMethodError nsme)
109 {
110 if (propertyType == null)
111 throw nsme;
112 wrappedAccessor = ClassUtils.getAccessorForProperty(propertyType, nestedProperty);
113 }
114 }
115
116 return wrappedAccessor;
117 }
118
119 /**
120 * {@inheritDoc}
121 */
122 public Class<?> getPropertyType()
123 {
124 return getWrappedAccessor(null).getPropertyType();
125 }
126 }