package net.refractions.linecleaner.cleansing; import java.io.IOException; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; import net.refractions.udig.project.internal.Map; import org.geotools.data.DefaultQuery; import org.geotools.data.FeatureSource; import org.geotools.data.FeatureStore; import org.geotools.data.Query; import org.geotools.feature.Feature; import org.geotools.feature.FeatureIterator; import org.geotools.filter.FidFilter; import org.geotools.filter.Filter; import org.geotools.filter.FilterFactoryFinder; public class MemoryFeatureIterator { // TODO: make the threshold proportional to the size of input and total heap memory private static final long DEFAULT_LOW_MEMORY_THRESHOLD = 150000000; // 150MB FeatureIterator delegate; FeatureSource source; Query query; long minimum; private Map map; private Set<String> visitedFids; public MemoryFeatureIterator(FeatureSource source, Map map, Query query, long minimum) throws IOException { this.source = source; this.map = map; this.query = query; this.minimum = minimum; this.delegate = source.getFeatures(query).features(); this.visitedFids = new TreeSet<String>(); } public MemoryFeatureIterator(FeatureSource source, Map map, Query query) throws IOException { this(source, map, query, DEFAULT_LOW_MEMORY_THRESHOLD); } public void close() { delegate.close(); } public boolean hasNext() throws IOException { if (isMemoryLow()) { delegate.close(); map.getEditManagerInternal().commitTransaction(); System.gc(); System.runFinalization(); DefaultQuery newQuery = new DefaultQuery(query); FidFilter newFilter = FilterFactoryFinder.createFilterFactory().createFidFilter(); newFilter.addAllFids(visitedFids); newQuery.setFilter(query.getFilter().and(newFilter.not())); delegate = source.getFeatures(newQuery).features(); } return delegate.hasNext(); } public Feature next() throws NoSuchElementException, IOException { if (!hasNext()) { throw new NoSuchElementException(); } Feature current = delegate.next(); visitedFids.add(current.getID()); return current; } private boolean isMemoryLow() { // freeMemory (on windows at least) is actually the amount of currently // allocated heap memory - memory used. Committing when this is low // causes more commits than is needed. UnmallocedMemory is when we // hit the real upper bound of maximum memory available for the heap. Runtime r = Runtime.getRuntime(); long unmallocedMemory = r.maxMemory() - r.totalMemory(); long freeMemory = r.freeMemory(); if (unmallocedMemory <= minimum && freeMemory <= minimum) { return true; } return false; } /** * Creates a new MemoryFeatureIterator with a query that uses Filter.NONE and only the geometry name. * @param store * @param map * @return * @throws IOException */ public static MemoryFeatureIterator createDefault(FeatureStore store, Map map) throws IOException { return new MemoryFeatureIterator(store, map, new DefaultQuery(store.getSchema().getTypeName(), Filter.NONE, new String[] { store.getSchema().getDefaultGeometry().getName() })); } }