Skip to main content

How to veto a property change?

Posted by malenkov on November 22, 2007 at 3:01 AM PST

Correct usage of constrained properties seems still remain unclear for many users.

As is the convention in the Java, the IllegalArgumentException is thrown if a method receives illegal arguments which cannot be used to execute the method. However, this approach is not applicable for the set methods, since it is impossible to identify whether all values can be used or not. That is why using the PropertyVetoException is considered the proper solution to identify constrained properties.

With constrained properties, an outside object (either the source bean itself or a listener) can veto a property change.

How the bean can veto a property change?

It is quite simple. The property setter should declare that it throws PropertyVetoException. Let's consider a bean that prohibits setting the negative value of a property:

public class SimpleBean { 
    private int value;

    public int getValue() {
        return this.value;
    }

    public void setValue(int value) throws PropertyVetoException {
        if (value < 0) {
            throw new PropertyVetoException(
                "the value change rejected",
                new PropertyChangeEvent(this, "value", this.value, value));
        }
        this.value = value;
    }
}

How a listener can veto a property change?

The JavaBeans API provides the event mechanism for handling constrained properties. The VetoableChangeSupport class facilitates creating constrained properties. Also, this class supports adding VetoableChangeListeners, which can veto a property change. Let's add a listener to manage a property change:

public class VetoableBean { 
    private final VetoableChangeSupport vcs = new VetoableChangeSupport(this);

    public void addVetoableChangeListener(VetoableChangeListener listener) {
        this.vcs.addVetoableChangeListener(listener);
    }

    public void removeVetoableChangeListener(VetoableChangeListener listener) {
        this.vcs.removeVetoableChangeListener(listener);
    }

    private int value;

    public int getValue() {
        return this.value;
    }

    public void setValue(int value) throws PropertyVetoException {
        this.vcs.fireVetoableChange("value", this.value, value);
        this.value = value;
    }
}

In this case, the bean examines all listeners. If one throws PropertyVetoException, the property value will not be set. The following example shows the class that vetoes setting the negative value of a property:

private static class Listener implements VetoableChangeListener { 
    public void vetoableChange(PropertyChangeEvent event) throws PropertyVetoException {
        if (event.getPropertyName().equals("value"))
            if ((Integer) event.getNewValue() < 0)
                throw new PropertyVetoException("I object!", event);
    }
}

Javadoc specification says: if anyone vetoes the change, then fire a new event reverting everyone to the old value and then rethrow the PropertyVetoException. Some developers consider this event as "undo" event and suppose it useless, because it is hard to distinguish between a normal event and an "undo" event. Actually, this is not so true. Let's modify the Listener class to skip "undo" events:

private static class Listener implements VetoableChangeListener { 
    public void vetoableChange(PropertyChangeEvent event) throws PropertyVetoException {
        if (!isUndo(event))
            if (event.getPropertyName().equals("value"))
                if ((Integer) event.getNewValue() < 0)
                    throw new PropertyVetoException("I object!", event);
    }

    private static boolean isUndo(PropertyChangeEvent event) {
        try {
            return event.getNewValue().equals(getCurrentValue(event));
        } catch (Exception exception) {
            return false;
        }
    }

    private static Object getCurrentValue(PropertyChangeEvent event) throws Exception {
        Object bean = event.getSource();
        String name = event.getPropertyName();
        BeanInfo info = Introspector.getBeanInfo(bean.getClass());
        for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
            if (pd.getName().equals(name)) {
                return pd.getReadMethod().invoke(bean);
            }
        }
        throw new IllegalArgumentException(name);
    }
}

This common solution could be optimized for the specific case.

JDK 7 changes the behaviour

Specification mentions that the new event will notify all listeners if the change is rolled back. I disagree with this statement. The "undo" events must be fired only for those listeners, which received and approved the property change event. This functionality is available in JDK7. For example, we have 3 listeners and the second listener vetoes the change. The following event sequence will be generated for this case:

    Old behavior in JDK 6:

  • first listener notified about changing A to B
  • second listener vetoes changing A to B
  • first listener notified about changing B to A (undo)
  • second listener notified about changing B to A (undo)
  • third listener notified about changing B to A (undo)

    New behavior in JDK 7:

  • first listener notified about changing A to B
  • second listener vetoes changing A to B
  • first listener notified about changing B to A (undo)

In other words, the second listener will be not notified, because it vetoed the property change, while the third listener will not be notified, because it simply does not know about the change.

Related Topics >>