/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2015, 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.renderer.lite;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.logging.Logger;
import org.geotools.data.sort.SimpleFeatureIO;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.util.DefaultProgressListener;
import org.geotools.util.logging.Logging;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.util.ProgressListener;
/**
* A FeatureIterator that can have a position marked, and can be reset to it
*
* @author Andrea Aime - GeoSolutions
*/
abstract class MarkFeatureIterator implements FeatureIterator<Feature> {
static final Logger LOGGER = Logging.getLogger(MarkFeatureIterator.class);
/**
* Builds a new {@link MarkFeatureIterator} making sure no too many features are kept in memory.
* The listener won't receive any notification, but will be used to check if the data loading
* should be stopped using {@link ProgressListener#isCanceled()}
*
* @param fc
* @param maxFeaturesInMemory
* @return
* @throws IOException
*/
public static MarkFeatureIterator create(FeatureCollection fc, int maxFeaturesInMemory,
ProgressListener listener) throws IOException {
List<Feature> features = new ArrayList<>();
int count = 0;
if (listener == null) {
listener = new DefaultProgressListener();
}
try (FeatureIterator fi = fc.features()) {
while (fi.hasNext()) {
if (listener.isCanceled()) {
return null;
}
Feature f = fi.next();
features.add(f);
count++;
if (count >= maxFeaturesInMemory) {
if (fc.getSchema() instanceof SimpleFeatureType) {
return new DiskMarkFeatureIterator(features, fi,
(SimpleFeatureType) fc.getSchema(), listener);
} else {
throw new IllegalArgumentException(
"Cannot offload to disk complex features "
+ "and reached the max number of feature in memory: "
+ maxFeaturesInMemory);
}
}
}
return new MemoryMarkFeatureIterator(features);
}
}
/**
* Marks the current position of the feature iterator
*
* @throws IOException
*/
public abstract void mark() throws IOException;
public abstract void reset() throws IOException;
/**
* Simple in memory implementation of the mark iterator
*/
static class MemoryMarkFeatureIterator extends MarkFeatureIterator {
List<Feature> features;
int curr;
int mark;
public MemoryMarkFeatureIterator(List<Feature> features) {
this.features = features;
curr = mark = 0;
}
@Override
public boolean hasNext() {
return curr < features.size();
}
@Override
public Feature next() throws NoSuchElementException {
return features.get(curr++);
}
@Override
public void close() {
// nothing to do
}
@Override
public void mark() throws IOException {
mark = curr;
}
@Override
public void reset() throws IOException {
curr = mark;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "MemoryMarkFeatureIterator [curr=" + curr + ", mark=" + mark + "]";
}
}
/**
* Implementation offloading on disk the features
*
* @author Andrea Aime - GeoSolutions
*/
static class DiskMarkFeatureIterator extends MarkFeatureIterator {
int mark;
long markOffset;
SimpleFeatureIO io;
int curr;
int featureCount;
public DiskMarkFeatureIterator(List<Feature> features, FeatureIterator fi,
SimpleFeatureType schema, ProgressListener listener) throws IOException {
File file = File.createTempFile("z-ordered-", ".features");
this.io = new SimpleFeatureIO(file, schema);
// dump all the features in memory
for (Feature feature : features) {
if (listener.isCanceled()) {
break;
}
io.write((SimpleFeature) feature);
featureCount++;
}
// dump all the subsequent ones
while (fi.hasNext() && !listener.isCanceled()) {
Feature feature = fi.next();
io.write((SimpleFeature) feature);
featureCount++;
}
// do not close the iterator, the caller does that
// reset to the beginning of the file
io.seek(0);
}
@Override
public boolean hasNext() {
return curr < featureCount;
}
@Override
public Feature next() throws NoSuchElementException {
try {
curr++;
return io.read();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void close() {
if (io != null) {
try {
io.close(true);
io = null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void mark() throws IOException {
mark = curr;
markOffset = io.getOffset();
}
@Override
public void reset() throws IOException {
curr = mark;
io.seek(markOffset);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "DiskMarkFeatureIterator [mark=" + mark + ", markOffset=" + markOffset + ", io="
+ io + ", curr=" + curr + ", featureCount=" + featureCount + "]";
}
@Override
protected void finalize() throws Throwable {
if (io != null) {
LOGGER.warning("There is code leaving DiskMarkFeatureIterator open, "
+ "this is leaking temporary files!");
close();
}
}
}
}