Adding Object in TwinColumnSelect

Hi there,

try to reproduce the Vaadin Sampler example about Forms (http://demo.vaadin.com/sampler/#Components/Forms/FormBasic) with some differences:

  • I’ve added a List called dogs in the Person class and the accompanying getters & setters
  • In the field factory I’ve added a TwinColumnSelect for the dogs list which is populated using Dog objects (not simple Strings)

             ...
			if ("dogs".equals(propertyId)) {
				TwinColSelect tf = new TwinColSelect();
				tf.setNullSelectionAllowed(true);
				tf.setMultiSelect(true);
				tf.setImmediate(true);
				tf.setContainerDataSource(new BeanItemContainer<Dog>(ALL_DOGS));
				// Rem: ALL_DOGS is a List of Dog object instances
				f = tf;
			}
             ...

The problem is when I select some dogs and click on apply (commit the form) I receive a:


com.vaadin.data.Property$ConversionException: java.lang.NoSuchMethodException: java.util.List.(java.lang.String)

I guess it tries to create a list with the selected strings but

  • I want a list of Dog not of String
  • I don’t understand why it tries to create a List (which is an interface)

Any idea ?

I’ve investgated a bit and it seems that when replacing the List with a Set the code works.
Traving Vaadin’s code I’ve spotted why it throws an exception when using a List. Here is MethodProperty.setValue() method:


   public void setValue(Object newValue) throws Property.ReadOnlyException,
            Property.ConversionException {

        // Checks the mode
        if (isReadOnly()) {
            throw new Property.ReadOnlyException();
        }

        // Try to assign the compatible value directly
        if (newValue == null || [b]
type.isAssignableFrom(newValue.getClass())
[/b]) {
            invokeSetMethod(newValue);
        } else {

            Object value;
            try {

                // Gets the string constructor
                final Constructor constr = getType().getConstructor(
                        new Class[] { String.class });

                value = constr
                        .newInstance(new Object[] { newValue.toString() });

            } catch (final java.lang.Exception e) {
                throw new Property.ConversionException(e);
            }

            // Creates new object from the string
            invokeSetMethod(value);
        }
        fireValueChange();
    }

The isAssignable return false and it defaults to finding a Constructor taking a String as argument which triggers an exception.

I am a bit disapointed by this behavior and hoped a smarter one when dealing with collections than replacing the existing collection by a completely new one (as done by MethodProperty.invokeSetMethod()). I am using collections coming from entities and Hiberante dirty-checking mechanism does not like collection replacement much.

I am ok to submit a path that would behave differently: if both types are collections move the missing items to the existing collection.

what do you think ?

Nicolas

While waiting for the fix to be integrated in the next version, you can use the following solution. Basically, it will adjust the return type only when it is about to commit data. When creating the component, just pass in the type which can be easily obtain from your FieldFactory implementation.

Cheers,
Tien


/**
 * @author ttran
 *
 */
public class TwinColSelectEx extends TwinColSelect {

	private Class<?> type;
	private boolean isCommiting;

	public TwinColSelectEx(Class<?> type) {
		super();
		this.type = type;
	}

	@Override
	public Object getValue() {
		final Object retValue = super.getValue();

		// If the return value is not a set
		if (isCommiting && type != null && List.class.isAssignableFrom(type)) {
			if (retValue == null) {
				return new ArrayList(0);
			}
			if (retValue instanceof Set) {
				return Collections.unmodifiableList(new ArrayList((Set) retValue));
			} else if (retValue instanceof Collection) {
				return new ArrayList((Collection) retValue);
			} else {
				final List l = new ArrayList(1);
				if (items.containsId(retValue)) {
					l.add(retValue);
				}
				return l;
			}
		}
		return retValue;
	}

	@Override
	public void commit() throws SourceException, InvalidValueException {
		isCommiting = true;
		try {
			super.commit();
		} finally {
			isCommiting = false;
		}
	}
}

I Overrided method but no effect still getting same exception com.vaadin.data.Property$ConversionException: java.lang.NoSuchMethodException: java.util.List.(java.lang.String)