/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-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.xml.gml;
import java.io.IOException;
import java.net.URI;
import java.util.Calendar;
import java.util.Date;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.data.FeatureReader;
import org.geotools.xml.DocumentFactory;
import org.geotools.xml.XMLHandlerHints;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.xml.sax.SAXException;
/**
* <p>
* Feature Buffer ... acts as a FeatureReader<SimpleFeatureType, SimpleFeature> by making itself as a seperate
* thread prior starting execution with the SAX Parser.
* </p>
*
* @author dzwiers
* @source $URL$
*/
public class FCBuffer extends Thread implements FeatureReader<SimpleFeatureType, SimpleFeature> {
/** Last feature is in the buffer */
public static final int FINISH = -1;
/** DOCUMENT ME! */
public static final int STOP = -2;
/** DOCUMENT ME! */
protected static Logger logger = getLogger();
// positive number is the number of feature to parse before yield
/** DOCUMENT ME! */
protected int state = 0;
private SimpleFeature[] features;
private int end;
private int size;
private int head;
private int timeout = 1000;
private URI document; // for run
protected SAXException exception = null;
private FCBuffer() {
// should not be called
super("Feature Collection Buffer");
}
/**
*
* @param document
* @param capacity
* @param timeout
* @param ft Nullable
*/
protected FCBuffer(URI document, int capacity, int timeout, SimpleFeatureType ft) {
super("Feature Collection Buffer");
features = new SimpleFeature[capacity];
this.timeout = timeout;
this.document = document;
end = size = head = 0;
this.ft = ft;
}
/**
* Returns the logger to be used for this class.
*
* @todo Logger.setLevel(...) should not be invoked, because it override any user setting in
* {@code jre/lib/logging.properties}. Users should edit their properties file instead.
* If Geotools is too verbose below the warning level, then some log messages should
* probably be changed from Level.INFO to Level.FINE.
*/
private static final Logger getLogger() {
Logger l = org.geotools.util.logging.Logging.getLogger("org.geotools.xml.gml");
l.setLevel(Level.WARNING);
return l;
}
/**
* DOCUMENT ME!
*
* @return The buffer size
*/
public int getSize() {
return size;
}
/**
* DOCUMENT ME!
*
* @return The buffer capacity
*/
public int getCapacity() {
return features.length;
}
/**
* DOCUMENT ME!
*
* @return The buffer capacity
*/
public int getTimeout() {
return timeout;
}
/**
* <p>
* Adds a feature to the buffer
* </p>
*
* @param f Feature to add
*
* @return false if unable to add the feature
*/
protected boolean addFeature(SimpleFeature f) {
if (ft == null) {
ft = f.getFeatureType();
}
if (size >= features.length) {
return false;
}
synchronized (this) {
notify();
features[end] = f;
end++;
if (end == features.length) {
end = 0;
}
size++;
}
return true;
}
/**
* <p>
* The prefered method of using this class, this will return the Feature
* Reader for the document specified, using the specified buffer capacity.
* </p>
*
* @param document URL to parse
* @param capacity
*
*
* @throws SAXException
*/
public static FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(URI document, int capacity)
throws SAXException {
return getFeatureReader(document,capacity,1000,null);
}
public static FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(URI document, int capacity, SimpleFeatureType ft)
throws SAXException {
return getFeatureReader(document,capacity,1000,ft);
}
public static FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(URI document, int capacity,
int timeout) throws SAXException {
return getFeatureReader(document,capacity,timeout,null);
}
public static FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(URI document, int capacity,
int timeout, SimpleFeatureType ft) throws SAXException {
FCBuffer fc = new FCBuffer(document, capacity, timeout,ft);
fc.start(); // calls run
if (fc.exception != null) {
throw fc.exception;
}
if(fc.getFeatureType()!=null && fc.getFeatureType().getGeometryDescriptor()!=null && fc.getFeatureType().getGeometryDescriptor().getCoordinateReferenceSystem() == null){
// load crs
// Feature f = fc.peek();
// TODO set crs here.
}
return fc;
}
protected SimpleFeatureType ft = null;
private volatile Date lastUpdate;
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @see org.geotools.data.FeatureReader#getFeatureType()
*/
public SimpleFeatureType getFeatureType() {
if(ft != null)
return ft;
Date d = new Date(Calendar.getInstance().getTimeInMillis() + timeout);
while ((ft == null) && ((state != FINISH) && (state != STOP))) {
yield(); // let the parser run ... this is being called from
if (d.before(Calendar.getInstance().getTime())) {
exception = new SAXException("Timeout");
state = STOP;
}
}
// the original thread
if ((state == FINISH) || (state == STOP)) {
return ft;
}
return ft;
}
/**
* @see org.geotools.data.FeatureReader#next()
*/
public SimpleFeature next()
throws IOException, NoSuchElementException {
if (exception != null) {
state = STOP;
IOException e = new IOException(exception.toString());
e.initCause(exception);
throw e;
}
SimpleFeature f = null;
synchronized (this) {
size--;
f = features[head++];
notify();
if (head == features.length) {
head = 0;
}
}
return f;
}
/**
* @see org.geotools.data.FeatureReader#next()
*/
public SimpleFeature peek()
throws IOException, NoSuchElementException {
if (exception != null) {
state = STOP;
IOException e = new IOException(exception.toString());
e.initCause(exception);
throw e;
}
SimpleFeature f = features[head];
return f;
}
/**
* @see org.geotools.data.FeatureReader#hasNext()
*/
public boolean hasNext() throws IOException {
if (exception instanceof StopException) {
return false;
}
if (exception != null) {
IOException e = new IOException(exception.toString());
e.initCause(exception);
throw e;
}
logger.finest("hasNext " + size);
resetTimer();
while ((size <= 1) && (state != FINISH) && (state != STOP)) { //&& t>0) {
if (exception != null) {
state = STOP;
IOException e = new IOException(exception.toString());
e.initCause(exception);
throw e;
}
logger.finest("waiting for parser");
try {
synchronized (this) {
wait(200);
}
} catch (InterruptedException e) {
//just continue;
}
if (lastUpdate.before(new Date(Calendar.getInstance().getTimeInMillis() - timeout))) {
exception = new SAXException("Timeout");
state = STOP;
}
}
if (state == STOP) {
if (exception != null) {
IOException e = new IOException(exception.toString());
e.initCause(exception);
throw e;
}
return false;
}
if (state == FINISH) {
return !(size == 0);
}
if (size == 0) {
state = STOP;
if (exception != null) {
throw new IOException(exception.toString());
}
throw new IOException("There was an error");
}
return true;
}
/**
* @see org.geotools.data.FeatureReader#close()
*/
public void close(){
state = STOP; // note for the sax parser
interrupt();
}
/**
* @see java.lang.Runnable#run()
*/
public void run() {
XMLHandlerHints hints = new XMLHandlerHints();
initHints(hints);
try {
DocumentFactory.getInstance(document, hints);
// start parsing until buffer part full, then yield();
} catch (StopException e) {
exception = e;
state = STOP;
yield();
} catch (SAXException e) {
exception = e;
state = STOP;
yield();
}
}
/**
* Called before parsing the FeatureCollection. Subclasses may override to set their custom hints.
*/
protected void initHints(XMLHandlerHints hints) {
hints.put(XMLHandlerHints.STREAM_HINT, this);
hints.put(XMLHandlerHints.FLOW_HANDLER_HINT, new FCFlowHandler());
if( this.ft!=null ){
hints.put("DEBUG_INFO_FEATURE_TYPE_NAME", ft.getTypeName());
}
}
/**
* DOCUMENT ME!
*
* @author $author$
* @version $Revision: 1.9 $
*/
public static class StopException extends SAXException {
public StopException() {
super("Stopping");
}
}
public int getInternalState() {
return state;
}
public void resetTimer() {
this.lastUpdate = Calendar.getInstance().getTime();
}
}