001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.data;
017:
018: import java.util.HashMap;
019: import java.util.Iterator;
020: import java.util.Map;
021:
022: import javax.swing.event.EventListenerList;
023:
024: import com.vividsolutions.jts.geom.Envelope;
025:
026: /**
027: * This class is used by DataStore implementations to provide FeatureListener
028: * support for the FeatureSources they create.
029: *
030: * <p>
031: * FeatureWriters created by the DataStore will need to make use of this class
032: * to provide the required FeatureEvents.
033: * </p>
034: *
035: * @author Jody Garnett, Refractions Research
036: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/data/FeatureListenerManager.java $
037: */
038: public class FeatureListenerManager {
039: /** EvenListenerLists by FeatureSource */
040: Map listenerMap = new HashMap();
041:
042: /**
043: * Used by FeaureSource implementations to provide listener support.
044: *
045: * @param featureSource
046: * @param featureListener
047: */
048: public void addFeatureListener(FeatureSource featureSource,
049: FeatureListener featureListener) {
050: eventListenerList(featureSource).add(FeatureListener.class,
051: featureListener);
052: }
053:
054: /**
055: * Used by FeatureSource implementations to provide listener support.
056: *
057: * @param featureSource
058: * @param featureListener
059: */
060: public void removeFeatureListener(FeatureSource featureSource,
061: FeatureListener featureListener) {
062: EventListenerList list = eventListenerList(featureSource);
063: list.remove(FeatureListener.class, featureListener);
064: // don't keep references to feature sources if we have no
065: // more any listener. Since there's no way to know a feature source
066: // has ceased its existance, better remove references as soon as possible
067: if (list.getListenerCount() == 0)
068: cleanListenerList(featureSource);
069: }
070:
071: public EventListenerList eventListenerList(
072: FeatureSource featureSource) {
073: synchronized (listenerMap) {
074: if (listenerMap.containsKey(featureSource)) {
075: return (EventListenerList) listenerMap
076: .get(featureSource);
077: } else {
078: EventListenerList listenerList = new EventListenerList();
079: listenerMap.put(featureSource, listenerList);
080:
081: return listenerList;
082: }
083: }
084: }
085:
086: public void cleanListenerList(FeatureSource featureSource) {
087: synchronized (listenerMap) {
088: listenerMap.remove(featureSource);
089: }
090: }
091:
092: /**
093: * Returns a Map of FeatureListener[] by FeatureSource for all matches with
094: * featureType and transaction.
095: *
096: * <p>
097: * A FeatureSource is considered a match when typeName and Transaction
098: * agree. Transaction.AUTO_COMMIT will match with any change.
099: * </p>
100: *
101: * @param typeName typeName to match against
102: * @param transaction Transaction to match against (may be AUTO_COMMIT)
103: *
104: */
105: Map getListeners(String typeName, Transaction transaction) {
106: Map map = new HashMap();
107: Map.Entry entry;
108: FeatureSource featureSource;
109: EventListenerList listenerList;
110: FeatureListener[] listeners;
111:
112: synchronized (listenerMap) {
113: for (Iterator i = listenerMap.entrySet().iterator(); i
114: .hasNext();) {
115: entry = (Map.Entry) i.next();
116: featureSource = (FeatureSource) entry.getKey();
117:
118: if (!featureSource.getSchema().getTypeName().equals(
119: typeName)) {
120: continue; // skip as typeName does not match
121: }
122:
123: if ((transaction != Transaction.AUTO_COMMIT)
124: && hasTransaction(featureSource)) {
125: // need to ensure Transactions match
126: if (transaction != getTransaction(featureSource)) {
127: continue; // skip as transactions do not match
128: }
129: }
130:
131: listenerList = (EventListenerList) entry.getValue();
132: listeners = (FeatureListener[]) listenerList
133: .getListeners(FeatureListener.class);
134:
135: if (listeners.length != 0) {
136: map.put(featureSource, listeners);
137: }
138: }
139: }
140:
141: return map;
142: }
143:
144: private static boolean hasTransaction(FeatureSource featureSource) {
145: return featureSource instanceof FeatureStore
146: && (((FeatureStore) featureSource).getTransaction() != null);
147: }
148:
149: private static Transaction getTransaction(
150: FeatureSource featureSource) {
151: if (hasTransaction(featureSource)) {
152: return ((FeatureStore) featureSource).getTransaction();
153: }
154:
155: return Transaction.AUTO_COMMIT;
156: }
157:
158: /**
159: * Notify all listeners that have registered interest for notification on
160: * this event type.
161: *
162: * <p>
163: * This method is called by:
164: * </p>
165: *
166: * <ul>
167: * <li>
168: * FeatureWriter.next() with FeatureWriter.hasNext() == false<br>
169: * - when an existing Feature is removed with Tranasaction.AUTO_COMMIT all
170: * listeners registered with FeatureSource of typeName will be notified.
171: * </li>
172: * <li>
173: * FeatureWriter.next()with FeatureWriter.hasNext() == false<br>
174: * - when an existing Feature is removed with a Transaction all listeners
175: * registered with FeatureSource of typeName and with the same Transaction
176: * will be notified.
177: * </li>
178: * </ul>
179: *
180: *
181: * @param typeName typeName being modified
182: * @param transaction Transaction used for change
183: * @param bounds BoundingBox of changes (may be <code>null</code> if
184: * unknown)
185: */
186: public void fireFeaturesAdded(String typeName,
187: Transaction transaction, Envelope bounds, boolean commit) {
188: if (commit) {
189: fireCommit(typeName, transaction,
190: FeatureEvent.FEATURES_ADDED, bounds);
191: } else {
192: fireEvent(typeName, transaction,
193: FeatureEvent.FEATURES_ADDED, bounds);
194: }
195: }
196:
197: /**
198: * Notify all listeners that have registered interest for notification on
199: * this event type.
200: *
201: * <p>
202: * This method is called by:
203: * </p>
204: *
205: * <ul>
206: * <li>
207: * FeatureWriter.next() with FeatureWriter.hasNext() == true <br>
208: * - when an existing Feature is modified with Tranasaction.AUTO_COMMIT
209: * all listeners registered with FeatureSource of typeName will be
210: * notified.
211: * </li>
212: * <li>
213: * FeatureWriter.next()with FeatureWriter.hasNext() == true <br>
214: * - when an existing Feature is modified, with a Transaction all
215: * listeners registered with FeatureSource of typeName and with the same
216: * Transaction will be notified.
217: * </li>
218: * </ul>
219: *
220: *
221: * @param typeName typeName being modified
222: * @param transaction Transaction used for change
223: * @param bounds BoundingBox of changes (may be <code>null</code> if
224: * unknown)
225: */
226: public void fireFeaturesChanged(String typeName,
227: Transaction transaction, Envelope bounds, boolean commit) {
228: if (commit) {
229: fireCommit(typeName, transaction,
230: FeatureEvent.FEATURES_CHANGED, bounds);
231: } else {
232: fireEvent(typeName, transaction,
233: FeatureEvent.FEATURES_CHANGED, bounds);
234: }
235:
236: }
237:
238: /**
239: * Notify all listeners that have registered interest for notification on
240: * this event type.
241: *
242: * <p>
243: * This method is called by:
244: * </p>
245: *
246: * <ul>
247: * <li>
248: * Transaction.commit()<br> - when changes have occured on a Transaction
249: * all listeners registered with FeatureSource of typeName will be
250: * notified except those with the Same Transaction
251: * </li>
252: * <li>
253: * Transaction.rollback()<br> - when changes have been reverted only those
254: * listeners registered with FeatureSource of typeName and with the same
255: * Transaction will be notified.
256: * </li>
257: * </ul>
258: *
259: *
260: * @param typeName typeName being modified
261: * @param transaction Transaction used for change
262: * @param commit <code>true</code> for <code>commit</code>,
263: * <code>false</code> for <code>rollback</code>
264: */
265: public void fireChanged(String typeName, Transaction transaction,
266: boolean commit) {
267: Map.Entry entry;
268: FeatureSource featureSource;
269: FeatureListener[] listeners;
270: FeatureEvent event;
271: if (commit) {
272: fireCommit(typeName, transaction,
273: FeatureEvent.FEATURES_CHANGED, null);
274: } else {
275: fireEvent(typeName, transaction,
276: FeatureEvent.FEATURES_CHANGED, null);
277: }
278: }
279:
280: /**
281: * TODO summary sentence for fireEvent ...
282: *
283: * @param typeName
284: * @param transaction
285: */
286: private void fireCommit(String typeName, Transaction transaction,
287: int type, Envelope bounds) {
288: Map.Entry entry;
289: FeatureSource featureSource;
290: FeatureListener[] listeners;
291: FeatureEvent event;
292: Map map = getListeners(typeName, Transaction.AUTO_COMMIT);
293:
294: for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
295: entry = (Map.Entry) i.next();
296: featureSource = (FeatureSource) entry.getKey();
297: listeners = (FeatureListener[]) entry.getValue();
298:
299: if (hasTransaction(featureSource)
300: && (getTransaction(featureSource) == transaction)) {
301: continue; // skip notify members of the same transaction
302: }
303:
304: event = new FeatureEvent(featureSource, type, bounds);
305:
306: for (int l = 0; l < listeners.length; l++) {
307: listeners[l].changed(event);
308: }
309: }
310: }
311:
312: private void fireEvent(String typeName, Transaction transaction,
313: int type, Envelope bounds) {
314: Map.Entry entry;
315: FeatureSource featureSource;
316: FeatureListener[] listeners;
317: FeatureEvent event;
318: Map map = getListeners(typeName, transaction);
319:
320: for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
321: entry = (Map.Entry) i.next();
322: featureSource = (FeatureSource) entry.getKey();
323: listeners = (FeatureListener[]) entry.getValue();
324:
325: event = new FeatureEvent(featureSource, type, bounds);
326:
327: for (int l = 0; l < listeners.length; l++) {
328: listeners[l].changed(event);
329: }
330: }
331: }
332:
333: /**
334: * Notify all listeners that have registered interest for notification on
335: * this event type.
336: *
337: * <p>
338: * This method is called by:
339: * </p>
340: *
341: * <ul>
342: * <li>
343: * FeatureWrtier.remove() - when an existing Feature is removed with
344: * Tranasaction.AUTO_COMMIT all listeners registered with FeatureSource of
345: * typeName will be notified.
346: * </li>
347: * <li>
348: * FeatureWrtier.remove() - when an existing Feature is removed with a
349: * Transaction all listeners registered with FeatureSource of typeName and
350: * with the same Transaction will be notified.
351: * </li>
352: * </ul>
353: *
354: *
355: * @param typeName typeName being modified
356: * @param transaction Transaction used for change
357: * @param bounds BoundingBox of changes (may be <code>null</code> if
358: * unknown)
359: */
360: public void fireFeaturesRemoved(String typeName,
361: Transaction transaction, Envelope bounds, boolean commit) {
362: if (commit) {
363: fireCommit(typeName, transaction,
364: FeatureEvent.FEATURES_REMOVED, bounds);
365: } else {
366: fireEvent(typeName, transaction,
367: FeatureEvent.FEATURES_REMOVED, bounds);
368: }
369: }
370: }
|