Containers, generics and warnings (and a suggested solution)

First of all congrats on Vaadin 7, I am loving the new features so far!

I am currently working on porting a rather large application from 6 to 7. There is quite a lot of code in the project that interacts with tables and other containers through properties by Table.getItemProperty().setValue(). Now as the setValue(Object) was changed to setValue(T) there are also a lot of “type safety” -warnings.

To deal with the warnings I have used a static helper method and also a wrapper-class that augments a Container with methods that help avoiding the warnings. My solutions are mostly inspired by Joshua Bloch’s great book “Effective Java (2nd Edition)”.


public class PropertyHelper {

// this method helps dealing with raw-property returned from
// Container.getContainerProperty()
@SuppressWarnings("rawtypes")
public static void setPropVal(Property prop, Object val) {
	setValue((Property<?>) prop, val);
}

public static <T> void setValue(Property<T> prop, Object val) {
	if (prop == null) {
		throw new NullPointerException("Property 'prop' was null");
	} else if (!prop.getType().isAssignableFrom(val.getClass())) {
		throw new IllegalArgumentException(
				"Tried to set value of property=" + prop + "; the "
						+ "value (=" + val
						+ ")  was of incompatible type (required type: "
						+ prop.getType() + "; type of value: "
						+ val.getClass() + ").");

	} else {
		prop.setValue(prop.getType().cast(val));
	}
}

public static class GenericsContainerAugmenter implements Container {

	private final Container toAugment;

	public GenericsContainerAugmenter(Container toAugment) {
		this.toAugment = toAugment;
	}

	// these methods act somewhat as if property-id was
	// actually a composite of
	// property-id and required-type of the property (although having two
	// properties differentiated only by value-type is not possible);
	// there could also be errors instead of doing nothing and returning
	// null

	@SuppressWarnings("unchecked")
	public <T> Property<T> getContainerProperty(Object itemId,
			Object propertyId, Class<T> type) {
		Property<?> prop = toAugment.getContainerProperty(itemId,
					propertyId);

		if (prop != null && type.isAssignableFrom(prop.getType())) {
			// this cast should be safe here
			return (Property<T>) prop;
		} else {
			return null;
		}
	}

        // T is not needed here as the implementation is delegated to PropertyHelper
	public void setPropertyValue(Object itemId, Object propertyId,
			Object value) {
		Property<?> prop = toAugment.getContainerProperty(itemId,
				propertyId);
		if (prop != null
				&& prop.getType().isAssignableFrom(value.getClass())) {
			PropertyHelper.setValue(prop, value);
		}
	}

	public <T> T getPropertyValue(Object itemId, Object propertyId,
			Class<T> valType) {

		Property<?> prop = toAugment.getContainerProperty(itemId,
					propertyId);
		if (valType.isAssignableFrom(prop.getType())) {
			return valType.cast(prop.getValue());
		} else {
			return null;
		}
	}
        
        // rest of the interface-methods delegated to "toAugment"

}

}

Now you can write type-safe (or run-time checked with dynamic casts) code that mainly has the added complexity of requiring the extra Class<?> parameter in some method calls.


NativeSelect testSel = new NativeSelect();

GenericsContainerAugmenter cont = new GenericsContainerAugmenter(
				testSel.getContainerDataSource());

cont.addContainerProperty("testProp", String.class, null);

cont.addItem("testItem");

cont.setPropertyValue("testItem", "testProp", "test-value");

Property<String> testProp = cont.getContainerProperty("testItem",
		"testProp", String.class);

testProp.setValue("test-value-2");

String val = cont.getPropertyValue("testItem", "testProp", String.class);

I will probably make a feature request of adding something similar to the core Vaadin. The most convenient place to add needed methods in that case would probably be the Container-interface itself.

That is unless someone points to me that there already exists some similar methods in core Vaadin, or that I can write my code in such a way that I do not need to use the Container.getProperty().setValue() -sequence at all.

(I realize that this is closely related to at least tickets #8791 and #5158 but I think that they did not directly address/solve my problem).

EDIT:
Oops, that code does not handle setting the value of a Property to null.
Better test in the if would be “val == null || prop.getType().isAssignableFrom(val.getClass())”; that should work as Class.cast(null) succeeds and returns null.