/* * Copyright 2000-2016 Vaadin Ltd. * * 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 com.vaadin.v7.data.util; import static com.vaadin.util.ReflectTools.convertPrimitiveType; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.vaadin.data.Binder; import com.vaadin.data.ValueProvider; import com.vaadin.server.Setter; import com.vaadin.shared.util.SharedUtil; import com.vaadin.v7.data.Property; import com.vaadin.v7.data.util.MethodProperty.MethodException; /** * Nested accessor based property for a bean. * * The property is specified in the dotted notation, e.g. "address.street", and * can contain multiple levels of nesting. * * When accessing the property value, all intermediate getters must exist and * should return non-null values when the property value is accessed. If an * intermediate getter returns null, a null value will be returned. * * @see MethodProperty * * @since 6.6 * * @deprecated As of 8.0, replaced by {@link ValueProvider}, {@link Setter}, see {@link Binder} */ @Deprecated public class NestedMethodProperty<T> extends AbstractProperty<T> { // needed for de-serialization private String propertyName; // chain of getter methods private transient List<Method> getMethods; /** * The setter method. */ private transient Method setMethod; /** * Bean instance used as a starting point for accessing the property value. */ private Object instance; private Class<? extends T> type; /* Special serialization to handle method references */ private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // getMethods and setMethod are reconstructed on read based on // propertyName } /* Special serialization to handle method references */ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); initialize(instance.getClass(), propertyName); } /** * Constructs a nested method property for a given object instance. The * property name is a dot separated string pointing to a nested property, * e.g. "manager.address.street". * <p> * Calling getValue will return null if any intermediate getter returns null * * @param instance * top-level bean to which the property applies * @param propertyName * dot separated nested property name * @throws IllegalArgumentException * if the property name is invalid */ public NestedMethodProperty(Object instance, String propertyName) { this.instance = instance; initialize(instance.getClass(), propertyName); } /** * For internal use to deduce property type etc. without a bean instance. * Calling {@link #setValue(Object)} or {@link #getValue()} on properties * constructed this way is not supported. * * @param instanceClass * class of the top-level bean * @param propertyName */ NestedMethodProperty(Class<?> instanceClass, String propertyName) { instance = null; initialize(instanceClass, propertyName); } /** * Initializes most of the internal fields based on the top-level bean * instance and property name (dot-separated string). * * @param beanClass * class of the top-level bean to which the property applies * @param propertyName * dot separated nested property name * @throws IllegalArgumentException * if the property name is invalid */ private void initialize(Class<?> beanClass, String propertyName) throws IllegalArgumentException { List<Method> getMethods = new ArrayList<Method>(); String lastSimplePropertyName = propertyName; Class<?> lastClass = beanClass; // first top-level property, then go deeper in a loop Class<?> propertyClass = beanClass; String[] simplePropertyNames = propertyName.split("\\."); if (propertyName.endsWith(".") || 0 == simplePropertyNames.length) { throw new IllegalArgumentException( "Invalid property name '" + propertyName + "'"); } for (int i = 0; i < simplePropertyNames.length; i++) { String simplePropertyName = simplePropertyNames[i].trim(); if (simplePropertyName.length() > 0) { lastSimplePropertyName = simplePropertyName; lastClass = propertyClass; try { Method getter = MethodProperty.initGetterMethod( simplePropertyName, propertyClass); propertyClass = getter.getReturnType(); getMethods.add(getter); } catch (final java.lang.NoSuchMethodException e) { throw new IllegalArgumentException("Bean property '" + simplePropertyName + "' not found", e); } } else { throw new IllegalArgumentException( "Empty or invalid bean property identifier in '" + propertyName + "'"); } } // In case the get method is found, resolve the type Method lastGetMethod = getMethods.get(getMethods.size() - 1); Class<?> type = lastGetMethod.getReturnType(); // Finds the set method Method setMethod = null; try { // Assure that the first letter is upper cased (it is a common // mistake to write firstName, not FirstName). lastSimplePropertyName = SharedUtil .capitalize(lastSimplePropertyName); setMethod = lastClass.getMethod("set" + lastSimplePropertyName, new Class[] { type }); } catch (final NoSuchMethodException skipped) { } this.type = (Class<? extends T>) convertPrimitiveType(type); this.propertyName = propertyName; this.getMethods = getMethods; this.setMethod = setMethod; } @Override public Class<? extends T> getType() { return type; } @Override public boolean isReadOnly() { return super.isReadOnly() || (null == setMethod); } /** * Gets the value stored in the Property. The value is resolved by calling * the specified getter methods on the current instance: * * @return the value of the Property * @see #getInstance() */ @Override public T getValue() { try { Object object = instance; for (Method m : getMethods) { object = m.invoke(object); if (object == null) { return null; } } return (T) object; } catch (final Throwable e) { throw new MethodException(this, e); } } /** * Sets the value of the property. The new value must be assignable to the * type of this property. * * @param newValue * the New value of the property. * @throws <code>Property.ReadOnlyException</code> * if the object is in read-only mode. * @see #invokeSetMethod(Object) */ @Override public void setValue(T newValue) throws ReadOnlyException { // Checks the mode if (isReadOnly()) { throw new Property.ReadOnlyException(); } invokeSetMethod(newValue); fireValueChange(); } /** * Internal method to actually call the setter method of the wrapped * property. * * @param value */ protected void invokeSetMethod(T value) { try { Object object = instance; for (int i = 0; i < getMethods.size() - 1; i++) { object = getMethods.get(i).invoke(object); } setMethod.invoke(object, new Object[] { value }); } catch (final InvocationTargetException e) { throw new MethodException(this, e.getTargetException()); } catch (final Exception e) { throw new MethodException(this, e); } } /** * Returns an unmodifiable list of getter methods to call in sequence to get * the property value. * * This API may change in future versions. * * @return unmodifiable list of getter methods corresponding to each segment * of the property name */ protected List<Method> getGetMethods() { return Collections.unmodifiableList(getMethods); } /** * The instance used by this property * * @return the instance used for fetching the property value * @since 7.7.7 */ public Object getInstance() { return instance; } /** * Sets the instance used by this property. * <p> * The new instance must be of the same type as the old instance * <p> * To be consistent with {@link #setValue(Object)}, this method will fire a * value change event even if the value stays the same * * @param instance * the instance to use * @since 7.7.7 */ public void setInstance(Object instance) { if (this.instance.getClass() != instance.getClass()) { throw new IllegalArgumentException("The new instance is of type " + instance.getClass().getName() + " which does not match the old instance type " + this.instance.getClass().getName()); } this.instance = instance; fireValueChange(); } }