WORK AROUND
Name: jk109818 Date: 07/13/2000
/**
* @author Oliver Imbusch
**/
public class IndexedPropertyChangeEvent
extends PropertyChangeEvent {
// we extends PropertyChangeEvent so we can safely deliver this
// new Event while existing code simply sees the
// PropertyChangeEvent part, ignoring the additional index
// information
/**
* Constructs a new IndexedPropertyChangeEvent object.
*
* @param source The bean that fired the event.
* @param propertyName The programmatic name of the property that
* was changed.
*
* @param index Indicator specifying the property element
* that was changed.
*
* Currently this must be of type
* <code>Integer</code>, but this may change
* as soon as non-int property indices (keys)
* are allowed.
*
* @param oldValue The old value of the property.
* @param newValue The new value of the property.
*
* @throws UnsupportedOperation if index is not a non-null
* <code>Integer</code>
**/
public IndexedPropertyChangeEvent (Object source, String propertyName,
Object index,
Object oldValue, Object newValue) {
// NOTE: we don't throw a NullPointerException if index is
// null, and null-ness does also not mean that the index is
// unknown. Hopefully, some days MappedProperties become
// popular, and null keys may be allowed by the used map.
//
// FIXME: how to check whether null-keys are supported by the
// map? How do we know that a map is used without an orgy of
// instanceof checks?
//
// FIME: to be honest, we currently do check for null-ness of
// index, and we even assure that it's of type Integer. I have
// no idea how we could otherwise check whether the indexed
// property elements have changed in
// IndexedPropertyChangeSupport.fireIndexedPropertyChange ()
// methods. At least we have a future-proof interface. Note
// that UnsupportedOperationException is a RuntimeException
// and thus needn't appear in a throw clause. We intentionally
// don't mention it in a throw clause but in the JavaDoc only
// to easily remove the restriction in the future without
// breaking then-existing code.
super (source, propertyName, oldValue, newValue);
try {
this.index = ((Integer)index).intValue ();
} catch (Exception e) {
// ClassCastExcpetion, NullPointerException
throw new UnsupportedOperationException
("index must be non-null Integer (was: "
+ ((null == index) ? "null" : index.getClass ().toString ()));
}
}
private int index;
/**
* @return The index specifying the property element that was
* changed, expressed as an Object.
*
* Currently this is of type <code>Integer</code>, but this may
* change as soon as non-int property indices (keys) are allowed.
**/
public Object getIndex () {
// FIXME:
return new Integer (index);
}
}
import java.lang.reflect.Array;
/**
* @author Oliver Imbusch
**/
public class IndexedPropertyChangeSupport
extends PropertyChangeSupport {
/**
* Constructs an IndexedPropertyChangeSupport object.
*
* @param sourceBean The bean to be given as the source for any events.
**/
public IndexedPropertyChangeSupport (Object sourceBean) {
super (sourceBean);
// PropertyChangeSupport doesn't give us access to this, we
// have to store it ourselves
this.source = sourceBean;
}
/**
* Report a bound indexed property update to any registered
* listeners.
*
* No event is fired if old and new property element at the
* specified index are equal and non-null.
*
* @param propertyName The programmatic name of the property that
* was changed.
* @param index Indicator specifying the property element
* that was changed.
*
* Currently this must be of type
* <code>Integer</code>, but this may change
* as soon as non-int property indices (keys)
* are allowed.
*
* @param oldValue The old value of the property.
* @param newValue The new value of the property.
**/
public void fireIndexedPropertyChange (String propertyName, Object index,
Object oldValue, Object newValue) {
fireIndexedPropertyChange (new IndexedPropertyChangeEvent
(source, propertyName, index, oldValue, newValue));
}
/**
* Fire an existing IndexedPropertyChangeEvent to any registered
* listeners.
*
* No event is fired if old and new property element at the
* specified index are equal and non-null.
*
* @param evt The IndexedPropertyChangeEvent object.
**/
public void fireIndexedPropertyChange (IndexedPropertyChangeEvent evt) {
Object oldValue = evt.getOldValue ();
Object newValue = evt.getNewValue ();
// FIXME: currently IndexedPropertyChangeEvent constructor
// assures that index is of type Integer and non-null, so we
// can safely cast and extract the int value; this will change
// with mapped properties.
int index = ((Integer)evt.getIndex ()).intValue ();
if ((null == oldValue) || (null == newValue)) {
// FIXME: at least one property is null, so we cannot
// check whether the elements are equal. Should we
// disallow this case? For the moment, we remove the
// useless index information so called clients don't have
// to check.
PropertyChangeEvent newEvt = new PropertyChangeEvent
(evt.getSource (), evt.getPropertyName (), oldValue, newValue);
// why don't we have a constructor that takes the
// propagationId?
newEvt.setPropagationId (evt.getPropagationId ());
super.firePropertyChange (newEvt);
} else {
// FIXME: for now we know that old an new values are
// arrays so we can easily extract the indexed property
// element. But the tricky part comes when this is no
// longer the case. We could still get the old element
// from the source bean (using
//
// PropertyDescriptor pd =
// new PropertyDescriptor (evt.getPropertyName (),
// source.getClass);
// Method getter = pd.getReadMethod ();
// Object oldElement =
// getter.invoke (source, new Object[] { index });
//
// thus ignoring the oldValue parameter), but how do we
// deal with the newValue element? Some instanceof games
// could be acceptable if only a limited set of containers
// from the Collection framework are allowed as property
// element containers.
boolean bothOutOfBounds = false;
Object oldElement;
if (Array.getLength (oldValue) <= index) {
// this is allowed: the new property has increased in
// size, that is, the element was appended and we
// won't find in the old property
//
// FIXME: theoretically, this differs from the case
// where a null-valued element was part of an Object
// array property, but in practise it shouldn't make
// difference
oldElement = null;
bothOutOfBounds = true;
} else {
// NOTE: if index < 0, we intentionally get an Exception here
oldElement = Array.get (oldValue, index);
}
Object newElement;
if (Array.getLength (newValue) <= index) {
// equivalent, property maybe has shrunk
newElement = null;
} else {
// NOTE: if index < 0, we intentionally get an Exception here
newElement = Array.get (newValue, index);
bothOutOfBounds = false;
}
if (bothOutOfBounds) {
// this is definitely an error
throw new ArrayIndexOutOfBoundsException (index);
}
if (!( (null != oldValue)
&& (null != newValue)
&& oldValue.equals (newValue))) {
super.firePropertyChange (evt);
}
}
}
private Object source;
}
======================================================================
|