/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.data; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.event.EventListenerList; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.geometry.jts.ReferencedEnvelope; import org.opengis.feature.Feature; import org.opengis.feature.type.FeatureType; /** * This class is used by DataStore implementations to provide FeatureListener * support for the FeatureSources they create. * * <p> * FeatureWriters created by the DataStore will need to make use of this class * to provide the required FeatureEvents. * </p> * This class has been updated to store listeners using weak references * in order to cut down on memory leaks. * * @author Jody Garnett, Refractions Research * * @source $URL$ */ public class FeatureListenerManager { private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.data"); /** * Hold on to provided FeatureListener using a weak reference. * <p> * If I was really smart I could do this with the DynamicProxy class; I am not * that smart... * <p> * @author Jody Garnett */ class WeakFeatureListener implements FeatureListener { WeakReference<FeatureListener> reference; public WeakFeatureListener( FeatureListener listener ){ reference = new WeakReference<FeatureListener>( listener ); } public void changed( FeatureEvent featureEvent ) { FeatureListener listener = reference.get(); if(listener==null){ removeFeatureListener( this ); } else { listener.changed(featureEvent); } } } /** * EvenListenerLists by FeatureSource, using a WeakHashMap to allow * listener lists to be cleaned up after their FeatureSource is no longer referenced. */ Map<FeatureSource<? extends FeatureType, ? extends Feature>, EventListenerList> listenerMap = new WeakHashMap<FeatureSource<? extends FeatureType, ? extends Feature>, EventListenerList>(); /** * Used by FeaureSource implementations to provide listener support. * * @param featureSource * @param featureListener */ public void addFeatureListener(FeatureSource<? extends FeatureType, ? extends Feature> featureSource, FeatureListener featureListener) { eventListenerList(featureSource).add(FeatureListener.class, featureListener); } /** * Used to clean up a weak reference to a feature listener after * it is no longer in use. * * @param listener */ void removeFeatureListener( WeakFeatureListener listener ){ for( EventListenerList list : listenerMap.values() ){ list.remove(FeatureListener.class, listener); } } /** * Used by SimpleFeatureSource implementations to provide listener support. * * @param featureSource * @param featureListener */ public void removeFeatureListener(FeatureSource<? extends FeatureType, ? extends Feature> featureSource, FeatureListener featureListener) { EventListenerList list = eventListenerList(featureSource); list.remove(FeatureListener.class, featureListener); // don't keep references to feature sources if we have no // more any listener. Since there's no way to know a feature source // has ceased its existance, better remove references as soon as possible if(list.getListenerCount() == 0){ cleanListenerList(featureSource); } } /** * Retrieve the EvenListenerList for the provided FeatureSource. * * @param featureSource * @return */ private EventListenerList eventListenerList(FeatureSource<? extends FeatureType, ? extends Feature> featureSource) { synchronized (listenerMap) { if (listenerMap.containsKey(featureSource)) { return listenerMap.get(featureSource); } else { EventListenerList listenerList = new EventListenerList(); listenerMap.put(featureSource, listenerList); return listenerList; } } } public void cleanListenerList(FeatureSource<? extends FeatureType, ? extends Feature> featureSource) { synchronized (listenerMap) { listenerMap.remove(featureSource); } } /** * Returns a Map of FeatureListener[] by SimpleFeatureSource for all matches with * featureType and transaction. * * <p> * A SimpleFeatureSource is considered a match when typeName and Transaction * agree. Transaction.AUTO_COMMIT will match with any change. * </p> * * @param typeName typeName to match against * @param transaction Transaction to match against (may be AUTO_COMMIT) * */ Map<SimpleFeatureSource,FeatureListener[]> getListeners(String typeName, Transaction transaction) { Map<SimpleFeatureSource,FeatureListener[]> map = new HashMap<SimpleFeatureSource,FeatureListener[]>(); //Map.Entry<SimpleFeatureSource,FeatureListener[]> entry; SimpleFeatureSource featureSource; EventListenerList listenerList; FeatureListener[] listeners; synchronized (listenerMap) { for (Map.Entry entry : listenerMap.entrySet()) { featureSource = (SimpleFeatureSource) entry.getKey(); if (!featureSource.getName().getLocalPart().equals(typeName)) { continue; // skip as typeName does not match } if ((transaction != Transaction.AUTO_COMMIT) && hasTransaction(featureSource)) { // need to ensure Transactions match if (transaction != getTransaction(featureSource)) { continue; // skip as transactions do not match } } listenerList = (EventListenerList) entry.getValue(); listeners = (FeatureListener[]) listenerList.getListeners(FeatureListener.class); if (listeners.length != 0) { map.put(featureSource, listeners); } } } return map; } private static boolean hasTransaction( FeatureSource<? extends FeatureType, ? extends Feature> featureSource) { return featureSource instanceof FeatureStore && (((FeatureStore<? extends FeatureType, ? extends Feature>) featureSource) .getTransaction() != null); } private static Transaction getTransaction( FeatureSource<? extends FeatureType, ? extends Feature> featureSource) { if (hasTransaction(featureSource)) { return ((FeatureStore<? extends FeatureType, ? extends Feature>) featureSource) .getTransaction(); } return Transaction.AUTO_COMMIT; } /** * Notify all listeners that have registered interest for notification on * this event type. * * <p> * This method is called by: * </p> * * <ul> * <li> * FeatureWriter.next() with FeatureWriter.hasNext() == false<br> * - when an existing Feature is removed with Tranasaction.AUTO_COMMIT all * listeners registered with SimpleFeatureSource of typeName will be notified. * </li> * <li> * FeatureWriter.next()with FeatureWriter.hasNext() == false<br> * - when an existing Feature is removed with a Transaction all listeners * registered with SimpleFeatureSource of typeName and with the same Transaction * will be notified. * </li> * </ul> * <p> * <b>NOTE</b> requiring to fire this event at FeatureWriter.next() is quite * a gap inherited from an old API when {@link FeatureWriter#write()} didn't * exist yet. It's a good idea though to fire the event at FeatureWriter.write() * instead of FeatureWriter.next() so there are actually changes to notify for. * </p> * * @param typeName typeName being modified * @param transaction Transaction used for change * @param bounds BoundingBox of changes (may be <code>null</code> if unknown) * @param commit true if */ public void fireFeaturesAdded(String typeName, Transaction transaction, ReferencedEnvelope bounds, boolean commit) { if (commit) { fireCommit(typeName, transaction, FeatureEvent.FEATURES_ADDED, bounds ); } else { fireEvent(typeName, transaction, FeatureEvent.FEATURES_ADDED, bounds ); } } /** * Provided event will be used as a template for notifying all FeatureSources * for the provided typeName. * * @param typeName * @param transaction * @param event */ public void fireEvent(String typeName, Transaction transaction, FeatureEvent event ){ if( event.getType() == FeatureEvent.Type.COMMIT || event.getType() == FeatureEvent.Type.ROLLBACK ){ // This is a commit event; it needs to go out to everyone // Listeners on the Transaction need to be told about any feature ids that were changed // Listeners on AUTO_COMMIT need to be told that something happened Map<SimpleFeatureSource,FeatureListener[]> map = getListeners(typeName, Transaction.AUTO_COMMIT); for (Map.Entry entry : map.entrySet()) { FeatureSource featureSource = (FeatureSource) entry.getKey(); FeatureListener[] listeners = (FeatureListener[]) entry.getValue(); event.setFeatureSource( featureSource ); for (FeatureListener listener : listeners ){ try { listener.changed(event); } catch( Throwable t ){ LOGGER.log( Level.FINE, "Could not deliver "+event+" to "+listener+":"+t.getMessage(), t ); } } } } else { // This is a commit event; it needs to go out to everyone // Listeners on the Transaction need to be told about any feature ids that were changed // Listeners on AUTO_COMMIT need to be told that something happened Map<SimpleFeatureSource,FeatureListener[]> map = getListeners(typeName, transaction); for (Map.Entry entry : map.entrySet()) { FeatureSource featureSource = (FeatureSource) entry.getKey(); FeatureListener[] listeners = (FeatureListener[]) entry.getValue(); event.setFeatureSource( featureSource ); for (FeatureListener listener : listeners ){ try { listener.changed(event); } catch( Throwable t ){ LOGGER.log( Level.FINE, "Could not deliver "+event+" to "+listener+":"+t.getMessage(), t ); } } } } } /** * Notify all listeners that have registered interest for notification on * this event type. * * <p> * This method is called by: * </p> * * <ul> * <li> * FeatureWriter.next() with FeatureWriter.hasNext() == true <br> * - when an existing Feature is modified with Tranasaction.AUTO_COMMIT * all listeners registered with SimpleFeatureSource of typeName will be * notified. * </li> * <li> * FeatureWriter.next()with FeatureWriter.hasNext() == true <br> * - when an existing Feature is modified, with a Transaction all * listeners registered with SimpleFeatureSource of typeName and with the same * Transaction will be notified. * </li> * </ul> * <p> * <b>NOTE</b> requiring to fire this event at FeatureWriter.next() is quite * a gap inherited from an old API when {@link FeatureWriter#write()} didn't * exist yet. It's a good idea though to fire the event at FeatureWriter.write() * instead of FeatureWriter.next() so there are actually changes to notify for. * </p> * * * @param typeName typeName being modified * @param transaction Transaction used for change * @param bounds BoundingBox of changes (may be <code>null</code> if * unknown) */ public void fireFeaturesChanged(String typeName, Transaction transaction, ReferencedEnvelope bounds, boolean commit) { if (commit) { fireCommit(typeName, transaction, FeatureEvent.FEATURES_CHANGED, bounds ); } else { fireEvent(typeName, transaction, FeatureEvent.FEATURES_CHANGED, bounds ); } } /** * Notify all listeners that have registered interest for notification on * this event type. * * <p> * This method is called by: * </p> * * <ul> * <li> * Transaction.commit()<br> - when changes have occured on a Transaction * all listeners registered with SimpleFeatureSource of typeName will be * notified except those with the Same Transaction * </li> * <li> * Transaction.rollback()<br> - when changes have been reverted only those * listeners registered with SimpleFeatureSource of typeName and with the same * Transaction will be notified. * </li> * </ul> * * * @param typeName typeName being modified * @param transaction Transaction used for change * @param commit <code>true</code> for <code>commit</code>, * <code>false</code> for <code>rollback</code> */ public void fireChanged(String typeName, Transaction transaction, boolean commit) { if (commit) { fireCommit(typeName, transaction, FeatureEvent.FEATURES_CHANGED, null ); } else { fireEvent(typeName, transaction, FeatureEvent.FEATURES_CHANGED, null ); } } /** * Fire notifications out to everyone. * * @param typeName * @param transaction */ private void fireCommit( String typeName, Transaction transaction, int type, ReferencedEnvelope bounds) { FeatureSource<? extends FeatureType, ? extends Feature> featureSource; FeatureListener[] listeners; FeatureEvent event; Map<SimpleFeatureSource,FeatureListener[]> map = getListeners(typeName, Transaction.AUTO_COMMIT); for (Map.Entry entry : map.entrySet()) { featureSource = (FeatureSource<? extends FeatureType, ? extends Feature>) entry.getKey(); listeners = (FeatureListener[]) entry.getValue(); if (hasTransaction(featureSource) && (getTransaction(featureSource) == transaction)) { continue; // skip notify members of the same transaction } event = new FeatureEvent(featureSource, type, bounds); for (int l = 0; l < listeners.length; l++) { listeners[l].changed(event); } } } /** * Fire notifications out to those listing on this transaction. * @param typeName * @param transaction * @param type * @param bounds */ private void fireEvent( String typeName, Transaction transaction, int type, ReferencedEnvelope bounds) { FeatureSource<? extends FeatureType, ? extends Feature> featureSource; FeatureListener[] listeners; FeatureEvent event; Map<SimpleFeatureSource,FeatureListener[]> map = getListeners(typeName, transaction); for (Map.Entry entry : map.entrySet()) { featureSource = (FeatureSource) entry.getKey(); listeners = (FeatureListener[]) entry.getValue(); event = new FeatureEvent(featureSource,type, bounds); for (int l = 0; l < listeners.length; l++) { listeners[l].changed(event); } } } /** * Notify all listeners that have registered interest for notification on * this event type. * * <p> * This method is called by: * </p> * * <ul> * <li> * FeatureWrtier.remove() - when an existing Feature is removed with * Tranasaction.AUTO_COMMIT all listeners registered with SimpleFeatureSource of * typeName will be notified. * </li> * <li> * FeatureWrtier.remove() - when an existing Feature is removed with a * Transaction all listeners registered with SimpleFeatureSource of typeName and * with the same Transaction will be notified. * </li> * </ul> * * * @param typeName typeName being modified * @param transaction Transaction used for change * @param bounds BoundingBox of changes (may be <code>null</code> if * unknown) */ public void fireFeaturesRemoved(String typeName, Transaction transaction, ReferencedEnvelope bounds, boolean commit ) { if (commit) { fireCommit(typeName, transaction, FeatureEvent.FEATURES_REMOVED, bounds ); } else { fireEvent(typeName, transaction, FeatureEvent.FEATURES_REMOVED, bounds ); } } }