/**
* Copyright (C) 2015 Valkyrie RCP
*
* 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 org.valkyriercp.widget.table;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* This {@link Accessor} uses a chaining implementation of getter methods to allow nested properties.
*
* @author Jan Hoskens
* @since 0.5.0
*/
public class NestedAccessor implements Accessor
{
/** Lazily created accessor to access the nested property on the top level property object. */
private Accessor wrappedAccessor;
/**
* The nested property. Will be used to create an accessor together with the return type of the top level
* property object.
*/
final private String nestedProperty;
/** Getter method to access the top level property object. */
final private Method getter;
/**
* Convenience constructor. Creates a getter method for the given class and property and reroutes to
* {@link NestedAccessor#NestedAccessor(Method, String)}.
*
* @param clazz
* type with the nested property.
* @param propertyName
* the first part of the nested property.
* @param nestedPropertyName
* the nested property, possibly containing more nesting levels.
* @see #NestedAccessor(Method, String)
*/
public NestedAccessor(final Class<?> clazz, final String propertyName, final String nestedPropertyName)
{
this(ClassUtils.getReadMethod(clazz, propertyName), nestedPropertyName);
}
/**
* Constructor. The getter will deliver the first level of the nesting. It's return value will be used to
* construct the next accessor for the nestedProperty.
*
* @param getter
* method delivering the first part of the nesting.
* @param nestedProperty
* property that should be available on the return object of the getter. Possibly nested as
* well.
* @see #NestedAccessor(Class, String, String)
*/
public NestedAccessor(final Method getter, final String nestedProperty)
{
this.nestedProperty = nestedProperty;
this.getter = getter;
}
/**
* Get the value from the source entity. If at any point the chaining results in a null value. The
* chaining should end and return <code>null</code>.
*
* @param fromEntity
* the entity on which the getter should operate.
* @return <code>null</code> if at any point in the chaining a property returned <code>null</code> or
* the value of the nested property.
*/
public Object getValue(Object fromEntity) throws IllegalAccessException, InvocationTargetException
{
Object propertyValue = getter.invoke(fromEntity);
return propertyValue == null ? null : getWrappedAccessor(propertyValue.getClass()).getValue(
propertyValue);
}
/**
* <p>
* Get the wrapped accessor, instantiate it lazily if needed.
* </p>
*
* <p>
* Normally the return type of the getter method delivers the correct type on which the nested property
* can be found. There is however a specific case in which this isn't true. It may be that a specific type
* is only known at runtime and that you need to access a property of that specific type. The specific
* type can be found at runtime when the wrapped accessor is constructed. But it does imply that all other
* access will result in the same type.
* </p>
*
* <p>
* A specific type implementation is found in PeriodicValueAdapter->BTWPercentage, here a property of
* BTWPercentage can be accessed through the adapter, but all other adapters should contain a
* BTWPercentage.
* </p>
*
* @param propertyType
* property type to use if getter doesn't yield the correct one.
* @return an {@link Accessor} for the wrapped property.
*/
private Accessor getWrappedAccessor(Class<?> propertyType)
{
if (wrappedAccessor == null)
{
try
{
wrappedAccessor = ClassUtils.getAccessorForProperty(getter.getReturnType(), nestedProperty);
}
catch (NoSuchMethodError nsme)
{
if (propertyType == null)
throw nsme;
wrappedAccessor = ClassUtils.getAccessorForProperty(propertyType, nestedProperty);
}
}
return wrappedAccessor;
}
/**
* {@inheritDoc}
*/
public Class<?> getPropertyType()
{
return getWrappedAccessor(null).getPropertyType();
}
}