001: package org.apache.commons.events.observable;
002:
003: import java.beans.PropertyChangeSupport;
004: import java.beans.PropertyChangeListener;
005: import java.beans.PropertyChangeEvent;
006: import java.util.Map;
007: import java.util.Set;
008: import java.util.Collection;
009:
010: import org.apache.commons.collections.map.AbstractMapDecorator;
011:
012: /**
013: * <p>
014: * Decorates a <code>Map</code> implementation with a <b>bound
015: * property</b> named "collection".
016: * </p>
017: * <p>
018: * Each modifying method call made on this <code>Map</code> is
019: * handled as a change to the "collection" property. This
020: * facility serves to notify subscribers of a change to the collection
021: * but does not allow users the option of vetoing the change. To
022: * gain the ability to veto the change, use a {@link ConstrainedMap}
023: * decorater.
024: * </p>
025: * <p>
026: * Due to the fact that a Map offers several "views" of the
027: * same data, some confusion may arise as to what action caused a
028: * particular event. For instance, entries in the map may be
029: * deleted by <code>Map.values().remove(object)</code>. There is no
030: * known method, short of digging into the implementation of a Map,
031: * to determine <i>which</i> key-value pair was deleted by this operation,
032: * particularly if there is more than one occurence of a specific
033: * value. (Conversely, there is also no means of controlling which
034: * key-value pair is deleted by this method; therefore it's not a
035: * terribly smart thing to do.)
036: * </p>
037: * <p>
038: * This implementation of a bound map makes no attempt to interpret
039: * events generated by these alternate views of the data. It merely
040: * ensures that registered listeners receive <i>all</i> events generated
041: * by <i>any</i> view of the Map's data. It is left to the client
042: * to interpret the events generated by their map's usage.
043: * </p>
044: *
045: * @since Commons Events 1.0
046: * @author Bryce Nordgren / USDA Forest Service
047: */
048:
049: public class BoundMap extends AbstractMapDecorator {
050:
051: // Fields
052: //-----------------------------------------------------------------------
053: /**
054: * Child-accessible factory used to construct
055: * {@link CollectionChangeEvent}s.
056: * This field is <code>final</code> and is set by the constructor, so
057: * while the children may <i>use</i> it to instantiate events, they
058: * may not <i>change</i> it.
059: */
060: protected final CollectionChangeEventFactory eventFactory;
061: /** Utility to support listener registry and event dispatch. */
062: private final PropertyChangeSupport changeSupport;
063:
064: // Constructors
065: //-----------------------------------------------------------------------
066: /**
067: * Constructor that wraps (not copies) and takes a
068: * {@link CollectionChangeEventFactory}.
069: * <p>
070: * This should be used if the client wants to provide a user-specific
071: * CollectionChangeEventFactory implementation. Note that the
072: * same instance of the factory may not be used with multiple
073: * collection decorators.
074: *
075: * @param map the map to decorate, must not be null
076: * @param eventFactory the factory which instantiates
077: * {@link CollectionChangeEvent}s.
078: * @throws IllegalArgumentException if the map or event factory
079: * is null.
080: * @throws UnsupportedOperationException if the <code>eventFactory</code>
081: * has already been used with another decorator.
082: */
083: protected BoundMap(final Map map,
084: CollectionChangeEventFactory eventFactory) {
085:
086: // initialize parent
087: super (map);
088:
089: // Make a default event factory if necessary
090: if (eventFactory == null) {
091: eventFactory = new DefaultCollectionChangeEventFactory();
092: }
093:
094: // install the event factory
095: eventFactory.setCollection(this );
096: this .eventFactory = eventFactory;
097:
098: // initialize property change support.
099: changeSupport = new PropertyChangeSupport(this );
100: }
101:
102: /**
103: * Constructor that wraps (not copies) and uses the
104: * {@link DefaultCollectionChangeEventFactory}.
105: * <p>
106: * This should be used if the default change events are considered
107: * adequate to the task of monitoring changes to the collection.
108: *
109: * @param map the map to decorate, must not be null
110: * @throws IllegalArgumentException if the map is null
111: */
112: protected BoundMap(final Map map) {
113: this (map, null);
114: }
115:
116: // Factory Methods
117: //-----------------------------------------------------------------------
118: /**
119: * Factory method to decorate an existing Map using a user-defined
120: * {@link CollectionChangeEventFactory}.
121: * <p>
122: * This should be used if the client wants to provide a user-specific
123: * CollectionChangeEventFactory implementation. Note that the
124: * same instance of the factory may not be used with multiple
125: * collection decorators.
126: *
127: * @param map the map to decorate, must not be null
128: * @param eventFactory the factory which instantiates
129: * {@link CollectionChangeEvent}s.
130: * @throws IllegalArgumentException if the map or event factory
131: * is null.
132: * @throws UnsupportedOperationException if the <code>eventFactory</code>
133: * has already been used with another decorator.
134: */
135: public static BoundMap decorate(final Map map,
136: final CollectionChangeEventFactory eventFactory) {
137: return new BoundMap(map, eventFactory);
138: }
139:
140: /**
141: * Factory method to decorate an existing Map using the provided
142: * {@link DefaultCollectionChangeEventFactory}.
143: * <p>
144: * This should be used if the default change events are considered
145: * adequate to the task of monitoring changes to the collection.
146: *
147: * @param map the map to decorate, must not be null
148: * @throws IllegalArgumentException if the map is null
149: */
150: public static BoundMap decorate(final Map map) {
151: return new BoundMap(map);
152: }
153:
154: // Listener Management
155: //-----------------------------------------------------------------------
156: /**
157: * Registers a listener with this decorator. The Listener must
158: * implement the <code>PropertyChangeListener</code> interface.
159: * Adding a listener more than once will result in more than
160: * one notification for each change event.
161: *
162: * @param l The listener to register with this decorator.
163: */
164: public void addPropertyChangeListener(PropertyChangeListener l) {
165: changeSupport.addPropertyChangeListener(l);
166: }
167:
168: /**
169: * Unregisters a listener from this decorator. The Listener must
170: * implement the <code>PropertyChangeListener</code> interface.
171: * If the listener was registered more than once, calling this method
172: * cancels out a single registration. If the listener is not
173: * registered with this object, no action is taken.
174: *
175: * @param l The listener to unregister with this decorator.
176: */
177: public void removePropertyChangeListener(PropertyChangeListener l) {
178: changeSupport.removePropertyChangeListener(l);
179: }
180:
181: /**
182: * This is a utility method to allow subclasses to fire property change
183: * events.
184: * @param evt The pre-constructed event.
185: */
186: protected void firePropertyChange(PropertyChangeEvent evt) {
187: changeSupport.firePropertyChange(evt);
188: }
189:
190: /**
191: * Package private method to create an EventRepeater from within the
192: * context of a particular BoundCollection object. This event repeater
193: * will relay events to all property change listeners subscribed to this
194: * bound collection.
195: * @return the event repeater object.
196: */
197: EventRepeater createEventRepeater() {
198: return new EventRepeater(this );
199: }
200:
201: // Decoration of Map Methods
202: //-----------------------------------------------------------------------
203: public Object put(Object key, Object value) {
204: Object formerValue = map.put(key, value);
205: CollectionChangeEvent evt = eventFactory.createPut(key, value,
206: formerValue, true);
207: firePropertyChange(evt);
208:
209: return formerValue;
210: }
211:
212: public void putAll(Map toPut) {
213: map.putAll(toPut);
214: CollectionChangeEvent evt = eventFactory.createPutAll(toPut,
215: true);
216: firePropertyChange(evt);
217: }
218:
219: public void clear() {
220: map.clear();
221: CollectionChangeEvent evt = eventFactory.createClear(true);
222: firePropertyChange(evt);
223: }
224:
225: public Object remove(Object key) {
226: Object formerValue = map.remove(key);
227: CollectionChangeEvent evt = eventFactory.createRemove(
228: formerValue, true);
229: firePropertyChange(evt);
230:
231: return formerValue;
232: }
233:
234: /**
235: * <p>
236: * Returns a Set view of the Map entries. This set is backed by the
237: * map, so changes to either view is reflected in the other.
238: * Accordingly, our CollectionChangeEvents are sent to the
239: * Set's subscribers, and the Set's CollectionChangeEvents are sent
240: * to our subscribers.
241: * </p>
242: * <p>
243: * The interface contract for this method specifies that most
244: * methods of removal are supported, but no methods of addition.
245: * </p>
246: * @return An entry set view decorated with a BoundSet.
247: */
248: public Set entrySet() {
249: Set entries = map.entrySet();
250:
251: // make a copy of the event factory
252: CollectionChangeEventFactory factoryCopy = (CollectionChangeEventFactory) (eventFactory
253: .clone());
254:
255: // bind the entry set.
256: BoundSet boundEntries = BoundSet.decorate(entries, factoryCopy);
257:
258: // relay "boundEntries" changes to our listeners
259: boundEntries.addPropertyChangeListener(createEventRepeater());
260:
261: // relay our changes to "boundEntries" listeners
262: addPropertyChangeListener(boundEntries.createEventRepeater());
263:
264: return boundEntries;
265: }
266:
267: /**
268: * <p>
269: * Returns a Set view of the Map's keys. This set is backed by the
270: * map, so changes to either view is reflected in the other.
271: * Accordingly, our CollectionChangeEvents are sent to the
272: * Set's subscribers, and the Set's CollectionChangeEvents are sent
273: * to our subscribers.
274: * </p>
275: * <p>
276: * The interface contract for this method specifies that most
277: * methods of removal are supported, but no methods of addition.
278: * </p>
279: * @return A set view of the keys decorated with a BoundSet.
280: */
281: public Set keySet() {
282: Set keys = map.keySet();
283:
284: // make a copy of the event factory
285: CollectionChangeEventFactory factoryCopy = (CollectionChangeEventFactory) (eventFactory
286: .clone());
287:
288: // bind the set of keys.
289: BoundSet boundKeys = BoundSet.decorate(keys, factoryCopy);
290:
291: // relay "boundKeys" changes to our listeners
292: boundKeys.addPropertyChangeListener(createEventRepeater());
293:
294: // relay our changes to "boundKeys" listeners
295: addPropertyChangeListener(boundKeys.createEventRepeater());
296:
297: return boundKeys;
298:
299: }
300:
301: /**
302: * <p>
303: * Returns a Collection view of the Map's values. This collection
304: * is backed by the
305: * map, so changes to either view is reflected in the other.
306: * Accordingly, our CollectionChangeEvents are sent to the
307: * Collection's subscribers, and the Collection's CollectionChangeEvents
308: * are sent to our subscribers.
309: * </p>
310: * <p>
311: * The interface contract for this method specifies that most
312: * methods of removal are supported, but no methods of addition.
313: * </p>
314: * @return A collection view of the values decorated with a BoundCollection.
315: */
316: public Collection values() {
317: Collection values = map.values();
318:
319: // make a copy of the event factory
320: CollectionChangeEventFactory factoryCopy = (CollectionChangeEventFactory) (eventFactory
321: .clone());
322:
323: // bind the collection of values
324: BoundCollection boundValues = BoundCollection.decorate(values,
325: factoryCopy);
326:
327: // relay "boundValues" changes to our listeners
328: boundValues.addPropertyChangeListener(createEventRepeater());
329:
330: // relay our changes to "boundValues" listeners
331: addPropertyChangeListener(boundValues.createEventRepeater());
332:
333: return boundValues;
334: }
335:
336: // Utility classes
337: //-----------------------------------------------------------------------
338: /**
339: * This class subscribes to events from another collection and fires them
340: * to all the subscribers of this collection.
341: */
342: private class EventRepeater implements PropertyChangeListener {
343: private Object myself;
344:
345: public EventRepeater(Object me) {
346: myself = me;
347: }
348:
349: /**
350: * Relay events which did not originate with my list. This
351: * prevents an infinite loop.
352: */
353: public void propertyChange(PropertyChangeEvent evt) {
354: if ((evt != null) && (evt.getSource() != myself)) {
355: firePropertyChange(evt);
356: }
357: }
358: }
359: }
|