package com.vividsolutions.jump.workbench.model.cache; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.swing.SwingUtilities; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.feature.FeatureCollection; import com.vividsolutions.jump.feature.FeatureCollectionWrapper; import com.vividsolutions.jump.feature.FeatureDataset; import com.vividsolutions.jump.feature.FeatureSchema; import com.vividsolutions.jump.util.Block; import com.vividsolutions.jump.util.LazyList; import com.vividsolutions.jump.util.ListWrapper; import com.vividsolutions.jump.workbench.ui.plugin.AddNewLayerPlugIn; /** * Caches features to prevent unnecessary queries. Useful for wrapping * database-backed FeatureCollections. All calls are delegated to the cache, * except for calls to query(envelope).iterator() where (1) the envelope is not * within the cache envelope, and (2) the call is made in a non-GUI thread. */ // The cache is a ThreadSafeFeatureCollection. [Jon Aquino 2005-03-04] public class CachingFeatureCollection extends FeatureCollectionWrapper { private Envelope envelopeOfCompletedCache = new Envelope(); private FeatureCollection featureCollection; private boolean cachingByEnvelope = true; // private static final FeatureCollection DUMMY_CACHED_FEATURE_COLLECTION = AddNewLayerPlugIn.createBlankFeatureCollection(); public CachingFeatureCollection(final FeatureCollection featureCollection) { // Note that this implementation assumes that the feature collection is // being viewed by a single LayerViewPanel. This is the common case; // however, it is possibile that there could be multiple LayerViewPanels // viewing the feature collection i.e. if the user clicks Clone Window. // This is not used very often though. [Jon Aquino 2005-03-03] super(AddNewLayerPlugIn.createBlankFeatureCollection()); this.featureCollection = featureCollection; } /** * @see com.vividsolutions.jump.feature.FeatureCollectionWrapper#getEnvelope() */ public Envelope getEnvelope() { try{ Envelope e = featureCollection.getEnvelope(); if(e!=null) return e; }catch(Throwable t){t.printStackTrace();} return super.getEnvelope(); } /** * @see com.vividsolutions.jump.feature.FeatureCollectionWrapper#getFeatureSchema() */ public FeatureSchema getFeatureSchema() { return featureCollection.getFeatureSchema(); } public List query(final Envelope envelope) { // This code achieves its simplicity using two wrappers: // LazyList and ListWrapper. [Jon Aquino 2005-03-22] // To prevent an unnecessary query of the cached feature collection, use // a LazyList to defer the query, as we don't yet know whether the // methods are called on or off the GUI thread. In the former case, use // the cached feature collection; in the latter case (and only for the // #iterator method, as this is the only method used in the // LayerRenderer thread), use the live feature collection. // [Jon Aquino 2005-03-03] final LazyList cachedFeatureCollectionQueryResults = new LazyList( new Block() { public Object yield() { return getCachedFeatureCollection().query(envelope); } }); // Use a ListWrapper to delegate all calls to the cached feature // collection, except for calls to #iterator, which may or may not use // the cache, depending on whether we are in the GUI thread. // [Jon Aquino 2005-03-03] return new ListWrapper() { public Collection getCollection() { return cachedFeatureCollectionQueryResults; } public Iterator iterator() { // Caching criterion 1: envelope check [Jon Aquino 2005-03-22] if (cachingByEnvelope && envelopeOfCompletedCache.contains(envelope)) { return super.iterator(); } // Caching criterion 2: GUI-thread check [Jon Aquino 2005-03-22] if (SwingUtilities.isEventDispatchThread()) { // Don't do database queries on the GUI thread, as we don't // want the GUI thread to be held up by long operations. // Instead, delegate to the cached feature collection. // [Jon Aquino 2005-03-03] return super.iterator(); } final Iterator iterator = featureCollection.query(envelope) .iterator(); initializeCacheIfNecessary(); emptyCache(); envelopeOfCompletedCache = new Envelope(); return new Iterator() { public void remove() { iterator.remove(); } public boolean hasNext() { return iterator.hasNext(); } public Object next() { Feature nextFeature = (Feature) iterator.next(); getCachedFeatureCollection().add(nextFeature); if (!hasNext()) { // Set the cache envelope only when the cache is // complete. [Jon Aquino 2005-03-03] envelopeOfCompletedCache = new Envelope(envelope); } return nextFeature; } }; } }; } private boolean initialized = false; private void initializeCacheIfNecessary() { // The FeatureSchema might not defined until the last minute // i.e. until FeatureCollection#query is called [Jon Aquino // 2005-03-04] // Example: DynamicFeatureCollection [Jon Aquino 2005-03-22] if (initialized) { return; } setCachedFeatureCollection(new ThreadSafeFeatureCollectionWrapper( new FeatureDataset(featureCollection.getFeatureSchema()))); initialized = true; } private FeatureCollection getCachedFeatureCollection() { return getFeatureCollection(); } private void setCachedFeatureCollection( FeatureCollection cachedFeatureCollection) { setFeatureCollection(cachedFeatureCollection); } /** * This setting is ignored if the call to query(envelope).iterator() is made * on the GUI thread, because long queries would make the GUI unresponsive. * * @param cachingByEnvelope * whether query(envelope).iterator() delegates to the cache if * envelope is within the cache envelope */ public CachingFeatureCollection setCachingByEnvelope( boolean cachingByEnvelope) { this.cachingByEnvelope = cachingByEnvelope; return this; } public void emptyCache() { getCachedFeatureCollection().clear(); envelopeOfCompletedCache = new Envelope(); } }