PROPOSAL: Method and Field Literals

Frédéric Martini frederic.martini at gmail.com
Thu Mar 12 03:41:54 PDT 2009


Hello,


Before to start, sorry for my poor english. This is not my spoken language :(



My advice for this proposal : this is a must-have : this will allow to
use secure-code with reflection !



But I want to extends this proposal with the support of beans' property.
In fact, access to field is very limited due to visibility : there are
more often protected or private...
For example: we cannot use Exception#message or Exception#cause :(


It will be more interresting to use the beans' property notion, by
exemple with a Property's class like this (this is a scratch exemple,
with RuntimeException to be replaced by a more precise unchecked
exception...) :



-----------------------------------------------------------------------

/**
 * Immutable class
 *
 * @param <B> The Bean's type
 * @param <T> The property's type
 */
final class Property<B,T> {

	/** Name of the property */
	private final String name;
	/** Read-method of the property */
	private final Method get;
	/** Write-method of the property */
	private final Method set;
	
	/**
	 * Private Constructor
	 * @see Property#getReadableProperty(Class, Class, String)
	 * @see Property#getWritableProperty(Class, Class, String)
	 */
	private Property(String name, Method get, Method set) {
		this.name = name;
		this.get = get;
		this.set = set;
	}
	
	/**
	 * @return the property name
	 */
	public String name() {
		return this.name;
	}
	
	/**
	 * Call the get-method on the specified instance.
	 */
	public T get(B beans) {
		try {
			@SuppressWarnings("unchecked")
			T result = (T) this.get.invoke(beans);
			return result;
		} catch (Exception e) {
			throw new RuntimeException("get()", e);
		}
	}
	
	/**
	 * @return true if this property is writable (have write-method)
	 */
	public boolean isWritable() {
		return this.set!=null;
	}
	
	/**
	 * Call the set-method on the specified instance.
	 */
	public void set(B beans, T value) {
		try {
			this.set.invoke(beans, value);
		} catch (Exception e) {
			throw new RuntimeException("set()", e);
		}
	}
	
	/**
	 * Find an create an Property.
	 * @param <B> The Bean's type
	 * @param <T> The property's type
	 * @param beanClass The Bean's type
	 * @param propertyClass The property's type
	 * @param propertyName The property's name
	 * @return A Property for the specified beans-property
	 * @throws RuntimeException if the property cannot be found
	 */
	private static <B,T> Property<B,T> findProperty(Class<B> beanClass,
Class<T> propertyClass, String propertyName) {
		try {
			for (PropertyDescriptor descriptor :
Introspector.getBeanInfo(beanClass).getPropertyDescriptors() ) {
				if (descriptor.getName().equals(propertyName) &&
propertyClass.equals(descriptor.getPropertyType())) {
					return new Property<B,T>(descriptor.getName(),
descriptor.getReadMethod(), descriptor.getWriteMethod());
				}
			}
		} catch (IntrospectionException e) {
			throw new RuntimeException("Property not found : " + propertyName, e);
		}
		throw new RuntimeException("Property not found : " + propertyName);
	}

	/**
	 * Get a read/write property
	 * @param <B> The Bean's type
	 * @param <T> The property's type
	 * @param beanClass The Bean's type
	 * @param propertyClass The property's type
	 * @param propertyName The property's name
	 * @return A Property for the specified beans-property, with get/set method
	 * @throws RuntimeException if the property cannot be found, or is not writable
	 */
	public static <B,T> Property<B,T> getWritableProperty(Class<B>
beanClass, Class<T> propertyClass, String propertyName) {
		Property<B,T> property = findProperty(beanClass, propertyClass, propertyName);
		if (!property.isWritable()) {
			throw new RuntimeException("Non-writable property : " + propertyName);
		}
		return property;
	}
	
	/**
	 * Get a read-only property.
	 * The return type is Property<B,? extends T> in order to forbid the
use of the set() method
	 * @param <B> The Bean's type
	 * @param <T> The property's type
	 * @param beanClass The Bean's type
	 * @param propertyClass The property's type
	 * @param propertyName The property's name
	 * @return A Property for the specified beans-property, with only get method
	 * @throws RuntimeException if the property cannot be found
	 */
	public static <B,T> Property<B,? extends T>
getReadableProperty(Class<B> beanClass, Class<T> propertyClass, String
propertyName) {
		return findProperty(beanClass, propertyClass, propertyName);
	}
}

-----------------------------------------------------------------------




Property.getWritableProperty() return a property with read/write
access, and Property.getReadableProperty() return a property with
read-only access, declared as Property<B,? extends T> in order to have
compile-time error if the set method is used...



Exemple of use :

-----------------------------------------------------------------------

public class MyObject {
	
	private final String name;
	private int value;
	
	public MyObject(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	public int getValue() {
		return value;
	}
	public void setValue(int value) {
		this.value = value;
	}
	
	
	public static void main(String[] args) {
		MyObject beans = new MyObject("name");

		Property<MyObject, Integer> valueProperty =
Property.getWritableProperty(MyObject.class, int.class, "value");
		int value = valueProperty.get(beans);	// OK
		valueProperty.set(beans, 100);		// OK
		
		Property<MyObject, ? extends String> nameProperty =
Property.getReadableProperty(MyObject.class, String.class, "name");
		String name = nameProperty.get(beans);	// OK
		nameProperty.set(beans, "hello");	// Compile error (is not applicable...)
	}
}

-----------------------------------------------------------------------


My proposal is to use a literal in order to replace the static method
call (unsafe because based on a String) with a checked literral :

So this :

-----------------------------------------------------------------------
Property<MyObject, Integer> valueProperty =
Property.getWritableProperty(MyObject.class, int.class, "value");
Property<MyObject, ? extends String> nameProperty =
Property.getReadableProperty(MyObject.class, String.class, "name");
-----------------------------------------------------------------------

May be remplaced with something like (or equivalent) :
-----------------------------------------------------------------------
Property<MyObject, Integer> value = MyObject#value;
Property<MyObject, ? extends String> name = MyObject#name;
-----------------------------------------------------------------------

(We can use the same notation that the Field literral, based on
context, or any other syntaxe...)


MyObject#value is matched to Property<MyObject, Integer> because the
MyObject's class have a property called "value", with a getter
(getValue()) and a setter (setValue()).
MyObject#name is matched to Property<MyObject, ? extends String>
because the name's property only have a getter and no-setter !


Of course the literral MyObject#value will be checked at compile time,
to produce compile-time error on bad usage.

This will allow the literral to be more useful and usable with almost
all classes :
-----------------------------------------------------------------------
Field message = Throwable#message; // ERROR : message don't exist or
is private :(
Property<Throwable, ? extends String> message = Throwable#message; // OK
-----------------------------------------------------------------------


What do you think about this ?



Thanks for reading ;)

Fred,



More information about the coin-dev mailing list