All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.v7.data.util.MethodProperty Maven / Gradle / Ivy

There is a newer version: 8.27.3
Show newest version
/*
 * Copyright (C) 2000-2024 Vaadin Ltd
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 * See  for the full
 * license.
 */

package com.vaadin.v7.data.util;

import static com.vaadin.util.ReflectTools.convertPrimitiveType;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

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.util.SerializerHelper;

/**
 * 

* Proxy class for creating Properties from pairs of getter and setter methods * of a Bean property. An instance of this class can be thought as having been * attached to a field of an object. Accessing the object through the Property * interface directly manipulates the underlying field. *

* *

* It's assumed that the return value returned by the getter method is * assignable to the type of the property, and the setter method parameter is * assignable to that value. *

* *

* A valid getter method must always be available, but instance of this class * can be constructed with a null setter method in which case the * resulting MethodProperty is read-only. *

* *

* MethodProperty implements Property.ValueChangeNotifier, but does not * automatically know whether or not the getter method will actually return a * new value - value change listeners are always notified when setValue is * called, without verifying what the getter returns. *

* * @author Vaadin Ltd. * @since 3.0 * * @deprecated As of 8.0, replaced by {@link ValueProvider}, {@link Setter}, see * {@link Binder} */ @Deprecated @SuppressWarnings("serial") public class MethodProperty extends AbstractProperty { /** * The object that includes the property the MethodProperty is bound to. */ private transient Object instance; /** * Argument arrays for the getter and setter methods. */ private transient Object[] setArgs, getArgs; /** * The getter and setter methods. */ private transient Method setMethod, getMethod; /** * Index of the new value in the argument list for the setter method. If the * setter method requires several parameters, this index tells which one is * the actual value to change. */ private int setArgumentIndex; /** * Type of the property. */ private transient Class type; private static final Object[] DEFAULT_GET_ARGS = new Object[0]; private static final Object[] DEFAULT_SET_ARGS = new Object[1]; /* Special serialization to handle method references */ private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); SerializerHelper.writeClass(out, type); out.writeObject(instance); out.writeObject(setArgs); out.writeObject(getArgs); if (setMethod != null) { out.writeObject(setMethod.getName()); SerializerHelper.writeClassArray(out, setMethod.getParameterTypes()); } else { out.writeObject(null); out.writeObject(null); } if (getMethod != null) { out.writeObject(getMethod.getName()); SerializerHelper.writeClassArray(out, getMethod.getParameterTypes()); } else { out.writeObject(null); out.writeObject(null); } } /* Special serialization to handle method references */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); try { @SuppressWarnings("unchecked") // business assumption; type parameters not checked at runtime Class class1 = (Class) SerializerHelper.readClass(in); type = class1; instance = in.readObject(); Object[] setArgs = (Object[]) in.readObject(); Object[] getArgs = (Object[]) in.readObject(); setArguments(getArgs, setArgs, setArgumentIndex); String name = (String) in.readObject(); Class[] paramTypes = SerializerHelper.readClassArray(in); if (instance != null && name != null) { setMethod = instance.getClass().getMethod(name, paramTypes); } else { setMethod = null; } name = (String) in.readObject(); paramTypes = SerializerHelper.readClassArray(in); if (instance != null && name != null) { getMethod = instance.getClass().getMethod(name, paramTypes); } else { getMethod = null; } } catch (SecurityException e) { getLogger().log(Level.SEVERE, "Internal deserialization error", e); } catch (NoSuchMethodException e) { getLogger().log(Level.SEVERE, "Internal deserialization error", e); } } /** *

* Creates a new instance of MethodProperty from a named bean * property. This constructor takes an object and the name of a bean * property and initializes itself with the accessor methods for the * property. *

*

* The getter method of a MethodProperty instantiated with this * constructor will be called with no arguments, and the setter method with * only the new value as the sole argument. *

* *

* If the setter method is unavailable, the resulting * MethodProperty will be read-only, otherwise it will be * read-write. *

* *

* Method names are constructed from the bean property by adding * get/is/are/set prefix and capitalising the first character in the name of * the given bean property. *

* * @param instance * the object that includes the property. * @param beanPropertyName * the name of the property to bind to. */ @SuppressWarnings("unchecked") public MethodProperty(Object instance, String beanPropertyName) { final Class beanClass = instance.getClass(); // Assure that the first letter is upper cased (it is a common // mistake to write firstName, not FirstName). beanPropertyName = SharedUtil.capitalize(beanPropertyName); // Find the get method getMethod = null; try { getMethod = initGetterMethod(beanPropertyName, beanClass); } catch (final NoSuchMethodException ignored) { throw new MethodException(this, "Bean property " + beanPropertyName + " can not be found"); } // In case the get method is found, resolve the type Class returnType = getMethod.getReturnType(); // Finds the set method setMethod = null; try { setMethod = beanClass.getMethod("set" + beanPropertyName, new Class[] { returnType }); } catch (final NoSuchMethodException skipped) { } // Gets the return type from get method if (returnType.isPrimitive()) { type = (Class) convertPrimitiveType(returnType); if (type.isPrimitive()) { throw new MethodException(this, "Bean property " + beanPropertyName + " getter return type must not be void"); } } else { type = (Class) returnType; } setArguments(DEFAULT_GET_ARGS, DEFAULT_SET_ARGS, 0); this.instance = instance; } /** *

* Creates a new instance of MethodProperty from named getter * and setter methods. The getter method of a MethodProperty * instantiated with this constructor will be called with no arguments, and * the setter method with only the new value as the sole argument. *

* *

* If the setter method is null, the resulting * MethodProperty will be read-only, otherwise it will be * read-write. *

* * @param type * the type of the property. * @param instance * the object that includes the property. * @param getMethodName * the name of the getter method. * @param setMethodName * the name of the setter method. * */ public MethodProperty(Class type, Object instance, String getMethodName, String setMethodName) { this(type, instance, getMethodName, setMethodName, new Object[] {}, new Object[] { null }, 0); } /** *

* Creates a new instance of MethodProperty with the getter and * setter methods. The getter method of a MethodProperty * instantiated with this constructor will be called with no arguments, and * the setter method with only the new value as the sole argument. *

* *

* If the setter method is null, the resulting * MethodProperty will be read-only, otherwise it will be * read-write. *

* * @param type * the type of the property. * @param instance * the object that includes the property. * @param getMethod * the getter method. * @param setMethod * the setter method. */ public MethodProperty(Class type, Object instance, Method getMethod, Method setMethod) { this(type, instance, getMethod, setMethod, new Object[] {}, new Object[] { null }, 0); } /** *

* Creates a new instance of MethodProperty from named getter * and setter methods and argument lists. The getter method of a * MethodProperty instantiated with this constructor will be * called with the getArgs as arguments. The setArgs will be used as the * arguments for the setter method, though the argument indexed by the * setArgumentIndex will be replaced with the argument passed to the * {@link #setValue(Object newValue)} method. *

* *

* For example, if the setArgs contains A, * B and C, and setArgumentIndex = * 1, the call methodProperty.setValue(X) would result * in the setter method to be called with the parameter set of * {A, X, C} *

* * @param type * the type of the property. * @param instance * the object that includes the property. * @param getMethodName * the name of the getter method. * @param setMethodName * the name of the setter method. * @param getArgs * the fixed argument list to be passed to the getter method. * @param setArgs * the fixed argument list to be passed to the setter method. * @param setArgumentIndex * the index of the argument in setArgs to be * replaced with newValue when * {@link #setValue(Object newValue)} is called. */ @SuppressWarnings("unchecked") public MethodProperty(Class type, Object instance, String getMethodName, String setMethodName, Object[] getArgs, Object[] setArgs, int setArgumentIndex) { // Check the setargs and setargs index if (setMethodName != null && setArgs == null) { throw new IndexOutOfBoundsException("The setArgs can not be null"); } if (setMethodName != null && (setArgumentIndex < 0 || setArgumentIndex >= setArgs.length)) { throw new IndexOutOfBoundsException( "The setArgumentIndex must be >= 0 and < setArgs.length"); } // Set type this.type = type; // Find set and get -methods final Method[] methods = instance.getClass().getMethods(); // Finds get method boolean found = false; for (Method m : methods) { // Tests the name of the get Method if (!m.getName().equals(getMethodName)) { // name does not match, try next method continue; } // Tests return type if (!type.equals(m.getReturnType())) { continue; } // Tests the parameter types final Class[] c = m.getParameterTypes(); if (c.length != getArgs.length) { // not the right amount of parameters, try next method continue; } int j = 0; while (j < c.length) { if (getArgs[j] != null && !c[j].isAssignableFrom(getArgs[j].getClass())) { // parameter type does not match, try next method break; } j++; } if (j == c.length) { // all parameters matched if (found) { throw new MethodException(this, "Could not uniquely identify " + getMethodName + "-method"); } else { found = true; getMethod = m; } } } if (!found) { throw new MethodException(this, "Could not find " + getMethodName + "-method"); } // Finds set method if (setMethodName != null) { // Finds setMethod found = false; for (Method m : methods) { // Checks name if (!m.getName().equals(setMethodName)) { // name does not match, try next method continue; } // Checks parameter compatibility final Class[] c = m.getParameterTypes(); if (c.length != setArgs.length) { // not the right amount of parameters, try next method continue; } int j = 0; while (j < c.length) { if (setArgs[j] != null && !c[j].isAssignableFrom(setArgs[j].getClass())) { // parameter type does not match, try next method break; } else if (j == setArgumentIndex && !c[j].equals(type)) { // Property type is not the same as setArg type break; } j++; } if (j == c.length) { // all parameters match if (found) { throw new MethodException(this, "Could not identify unique " + setMethodName + "-method"); } else { found = true; setMethod = m; } } } if (!found) { throw new MethodException(this, "Could not identify " + setMethodName + "-method"); } } // Gets the return type from get method this.type = (Class) convertPrimitiveType(type); setArguments(getArgs, setArgs, setArgumentIndex); this.instance = instance; } /** *

* Creates a new instance of MethodProperty from the getter and * setter methods, and argument lists. *

*

* This constructor behaves exactly like * {@link #MethodProperty(Class type, Object instance, String getMethodName, String setMethodName, Object [] getArgs, Object [] setArgs, int setArgumentIndex)} * except that instead of names of the getter and setter methods this * constructor is given the actual methods themselves. *

* * @param type * the type of the property. * @param instance * the object that includes the property. * @param getMethod * the getter method. * @param setMethod * the setter method. * @param getArgs * the fixed argument list to be passed to the getter method. * @param setArgs * the fixed argument list to be passed to the setter method. * @param setArgumentIndex * the index of the argument in setArgs to be * replaced with newValue when * {@link #setValue(Object newValue)} is called. */ @SuppressWarnings("unchecked") // cannot use "Class" because of automatic primitive type // conversions public MethodProperty(Class type, Object instance, Method getMethod, Method setMethod, Object[] getArgs, Object[] setArgs, int setArgumentIndex) { if (getMethod == null) { throw new MethodException(this, "Property GET-method cannot not be null: " + type); } if (setMethod != null) { if (setArgs == null) { throw new IndexOutOfBoundsException( "The setArgs can not be null"); } if (setArgumentIndex < 0 || setArgumentIndex >= setArgs.length) { throw new IndexOutOfBoundsException( "The setArgumentIndex must be >= 0 and < setArgs.length"); } } // Gets the return type from get method Class convertedType = (Class) convertPrimitiveType( type); this.getMethod = getMethod; this.setMethod = setMethod; setArguments(getArgs, setArgs, setArgumentIndex); this.instance = instance; this.type = convertedType; } /** * Find a getter method for a property (getXyz(), isXyz() or areXyz()). * * @param propertyName * name of the property * @param beanClass * class in which to look for the getter methods * @return Method * @throws NoSuchMethodException * if no getter found */ static Method initGetterMethod(String propertyName, final Class beanClass) throws NoSuchMethodException { propertyName = SharedUtil.capitalize(propertyName); Method getMethod = null; try { getMethod = beanClass.getMethod("get" + propertyName, new Class[] {}); } catch (final NoSuchMethodException ignored) { try { getMethod = beanClass.getMethod("is" + propertyName, new Class[] {}); } catch (final NoSuchMethodException ignoredAsWell) { getMethod = beanClass.getMethod("are" + propertyName, new Class[] {}); } } return getMethod; } /** * Returns the type of the Property. The methods getValue and * setValue must be compatible with this type: one must be able * to safely cast the value returned from getValue to the given * type and pass any variable assignable to this type as an argument to * setValue. * * @return type of the Property */ @Override public final Class getType() { return type; } /** * Tests if the object is in read-only mode. In read-only mode calls to * setValue will throw ReadOnlyException and will * not modify the value of the Property. * * @return true if the object is in read-only mode, * false if it's not */ @Override public boolean isReadOnly() { return super.isReadOnly() || (setMethod == null); } /** * Gets the value stored in the Property. The value is resolved by calling * the specified getter method with the argument specified at instantiation. * * @return the value of the Property */ @Override public T getValue() { try { if (instance == null) { return null; } else { return (T) getMethod.invoke(instance, getArgs); } } catch (final Throwable e) { throw new MethodException(this, e); } } /** *

* Sets the setter method and getter method argument lists. *

* * @param getArgs * the fixed argument list to be passed to the getter method. * @param setArgs * the fixed argument list to be passed to the setter method. * @param setArgumentIndex * the index of the argument in setArgs to be * replaced with newValue when * {@link #setValue(Object newValue)} is called. */ public void setArguments(Object[] getArgs, Object[] setArgs, int setArgumentIndex) { if (getArgs.length == 0) { this.getArgs = DEFAULT_GET_ARGS; } else { this.getArgs = Arrays.copyOf(getArgs, getArgs.length); } if (Arrays.equals(setArgs, DEFAULT_SET_ARGS)) { this.setArgs = DEFAULT_SET_ARGS; } else { this.setArgs = Arrays.copyOf(setArgs, setArgs.length); } this.setArgumentIndex = setArgumentIndex; } /** * Sets the value of the property. * * Note that since Vaadin 7, no conversions are performed and the value must * be of the correct type. * * @param newValue * the New value of the property. * @throws ReadOnlyException if the property has been set to read only * @see #invokeSetMethod(Object) */ @Override public void setValue(T newValue) throws Property.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 { // Construct a temporary argument array only if needed if (setArgs.length == 1) { setMethod.invoke(instance, new Object[] { value }); } else { // Sets the value to argument array final Object[] args = new Object[setArgs.length]; for (int i = 0; i < setArgs.length; i++) { args[i] = (i == setArgumentIndex) ? value : setArgs[i]; } setMethod.invoke(instance, args); } } catch (final InvocationTargetException e) { final Throwable targetException = e.getTargetException(); throw new MethodException(this, targetException); } catch (final Exception e) { throw new MethodException(this, e); } } /** * Exception object that signals that there were problems * calling or finding the specified getter or setter methods of the * property. * * @author Vaadin Ltd. * @since 3.0 */ @SuppressWarnings("rawtypes") @Deprecated // Exceptions cannot be parameterized, ever. public static class MethodException extends RuntimeException { /** * The method property from which the exception originates from */ private final Property property; /** * Cause of the method exception */ private Throwable cause; /** * Constructs a new MethodException with the specified * detail message. * * @param property * the property. * @param msg * the detail message. */ public MethodException(Property property, String msg) { super(msg); this.property = property; } /** * Constructs a new MethodException from another exception. * * @param property * the property. * @param cause * the cause of the exception. */ public MethodException(Property property, Throwable cause) { this.property = property; this.cause = cause; } /** * @see java.lang.Throwable#getCause() */ @Override public Throwable getCause() { return cause; } /** * Gets the method property this exception originates from. * * @return MethodProperty or null if not a valid MethodProperty */ public MethodProperty getMethodProperty() { return (property instanceof MethodProperty) ? (MethodProperty) property : null; } /** * Gets the method property this exception originates from. * * @return Property from which the exception originates */ public Property getProperty() { return property; } } /** * Sends a value change event to all registered listeners. * * Public for backwards compatibility, visibility may be reduced in future * versions. */ @Override public void fireValueChange() { super.fireValueChange(); } /** * 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. *

* The new instance must be of the same type as the old instance *

* 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(); } private static final Logger getLogger() { return Logger.getLogger(MethodProperty.class.getName()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy