/*
* Geotoolkit An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 20032008, Open Source Geospatial Foundation (OSGeo)
* (C) 20092010, Geomatys
*
* 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.geotoolkit.data.shapefile;
import com.vividsolutions.jts.geom.Geometry;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import org.apache.sis.feature.FeatureExt;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.internal.feature.AttributeConvention;
import org.geotoolkit.data.FeatureStoreRuntimeException;
import org.geotoolkit.data.FeatureReader;
import org.geotoolkit.factory.Hints;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.Classes;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.geometry.jts.JTS;
import org.opengis.feature.AttributeType;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.feature.MismatchedFeatureException;
import org.opengis.feature.PropertyNotFoundException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* Experimental FeatureReader<SimpleFeatureType, SimpleFeature> that always takes the first column of the
* attributeReader as the FeatureID. I want to get this working with postgis,
* but then will consider other options, for those who want featureIDs created
* automatically. Perhaps a constructor param or a method to say that you
* would just like to have the FeatureReader<SimpleFeatureType, SimpleFeature> increment one for each feature,
* prepending the typeName. I'm also don't really like the one argument
* constructor defaulting to the xxx typename. I feel that it should perhaps
* take a typename. If people deliberately set to null then we could use xxx
* or something. ch
*
* <p>
* This now feels sorta hacky, I'm not sure that I like it, but I'm going to
* commit as I need to go now and revisit it in a bit. I think the idea of
* passing in an FIDAttributeReader might be cleaner, and if none is provided
* then do an autoincrement one. This might then work as the
* DefaultFeatureReader.
* </p>
*
* @author Ian Schneider
* @author Chris Holmes, TOPP
* @author Johann Sorel (Geomatys)
* @module
*/
public abstract class ShapefileFeatureReader implements FeatureReader {
/**
* Stores the creation stack trace if assertion are enable.
*/
protected Throwable creationStack;
protected final ShapefileAttributeReader attributeReader;
protected final FeatureType schema;
protected final FeatureIDReader fidReader;
protected final Object[] buffer;
protected final CoordinateReferenceSystem geomCRS;
protected boolean generateId = false;
/**
* if the attributs between reader and schema are the same but not in the same order.
*/
protected final String[] attributIndexes;
/**
* Store the stat of the reader.
*/
protected boolean closed = false;
/**
* Creates a new instance of AbstractFeatureReader
*
* @param attributeReader AttributeReader
* @param fidReader FIDReader used to ID Features
* @param schema FeatureType to use, may be <code>null</code>
*
* @throws SchemaException if we could not determine the correct FeatureType
*/
private ShapefileFeatureReader(final ShapefileAttributeReader attributeReader, final FeatureIDReader fidReader,
FeatureType schema) throws MismatchedFeatureException {
this.attributeReader = attributeReader;
this.fidReader = fidReader;
if (schema == null) {
schema = createSchema(attributeReader);
}
try {
schema.getProperty(AttributeConvention.IDENTIFIER_PROPERTY.toString());
generateId = true;
} catch (PropertyNotFoundException ex){}
//check if the attributs are mixed
final AttributeType[] readerAtt = getDescriptors(attributeReader);
attributIndexes = new String[readerAtt.length];
for(int i=0; i<readerAtt.length; i++){
try{
final String name = readerAtt[i].getName().toString();
schema.getProperty(readerAtt[i].getName().toString());
attributIndexes[i] = name;
}catch(PropertyNotFoundException ex){
//property does not exist in output type
}
}
this.schema = schema;
this.buffer = new Object[attributeReader.getPropertyCount()];
geomCRS = FeatureExt.getCRS(schema);
// init the tracer if we need to debug a connection leak
assert (creationStack = new IllegalStateException().fillInStackTrace()) != null;
}
public ShapefileFeatureReader(final ShapefileAttributeReader attributeReader, final FeatureIDReader fidReader)
throws MismatchedFeatureException {
this(attributeReader, fidReader, null);
}
/**
* {@inheritDoc }
*/
@Override
public Feature next() throws FeatureStoreRuntimeException, NoSuchElementException {
try {
if (attributeReader.hasNext()) {
attributeReader.next();
try {
return readFeature();
} catch (DataStoreException ex) {
throw new FeatureStoreRuntimeException(ex);
}
} else {
throw new NoSuchElementException("There are no more Features to be read");
}
} catch (IOException ex) {
throw new FeatureStoreRuntimeException(ex);
}
}
protected abstract Feature readFeature() throws DataStoreException;
/**
* {@inheritDoc }
*/
@Override
public void close() throws FeatureStoreRuntimeException {
if(closed) return;
closed = true;
Exception ex = null;
try {
fidReader.close();
} catch (DataStoreException e) {
ex = e;
}
try {
attributeReader.close();
} catch (IOException e) {
if (ex == null) {
ex = e;
} else {
//we tryed to close both and both failed
//return the first exception
}
}
if (ex != null) {
throw new FeatureStoreRuntimeException(ex);
}
}
@Override
protected void finalize() throws Throwable {
if (!closed) {
Logging.getLogger("org.geotoolkit.data.shapefile").warning(
"UNCLOSED ITERATOR : There is code leaving simple feature reader open, "
+ "this may cause memory leaks or data integrity problems !");
if (creationStack != null) {
Logging.getLogger("org.geotoolkit.data.shapefile").log(Level.WARNING,
"The unclosed reader originated on this stack trace", creationStack);
}
close();
}
}
/**
* {@inheritDoc }
*/
@Override
public FeatureType getFeatureType() {
return schema;
}
/**
* {@inheritDoc }
*/
@Override
public boolean hasNext() throws FeatureStoreRuntimeException {
try {
return attributeReader.hasNext();
} catch (IOException ex) {
throw new FeatureStoreRuntimeException(ex);
}
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(Classes.getShortClassName(this));
sb.append('\n');
String strFidReader = "\u251C\u2500\u2500" + fidReader.toString(); //move text to the right
strFidReader = strFidReader.replaceAll("\n", "\n\u00A0\u00A0\u00A0"); //move text to the right
sb.append(strFidReader);
String strAttReader = "\u2514\u2500\u2500" + attributeReader.toString(); //move text to the right
strAttReader = strAttReader.replaceAll("\n", "\n\u00A0\u00A0\u00A0"); //move text to the right
sb.append('\n').append(strAttReader);
return sb.toString();
}
/**
* Create a FeatureType based on the attributs described in the attribut reader.
*/
private static FeatureType createSchema(final ShapefileAttributeReader attributeReader) throws MismatchedFeatureException {
final FeatureTypeBuilder b = new FeatureTypeBuilder();
b.setName("noTypeName");
b.addAttribute(String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY);
for(AttributeType at : getDescriptors(attributeReader)){
b.addAttribute(at);
}
return b.build();
}
/**
* {@inheritDoc }
*/
@Override
public void remove() throws FeatureStoreRuntimeException {
throw new FeatureStoreRuntimeException("Can not remove from a feature reader.");
}
public static ShapefileFeatureReader create(final ShapefileAttributeReader attributeReader, final FeatureIDReader fidReader,
final FeatureType schema, final Hints hints) throws MismatchedFeatureException {
return new DefaultSeparateFeatureReader(attributeReader, fidReader, schema);
}
private static class DefaultSeparateFeatureReader extends ShapefileFeatureReader {
private DefaultSeparateFeatureReader(final ShapefileAttributeReader attributeReader, final FeatureIDReader fidReader,
final FeatureType schema) throws MismatchedFeatureException {
super(attributeReader, fidReader, schema);
}
@Override
protected Feature readFeature() throws DataStoreException {
final Feature feature = schema.newInstance();
final String fid = fidReader.next();
if(generateId){
feature.setPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString(), fid);
}
try {
attributeReader.read(buffer);
} catch (IOException ex) {
throw new DataStoreException(ex);
}
//set crs on geometry
if(buffer[0] instanceof Geometry){
JTS.setCRS((Geometry)buffer[0], geomCRS);
}
for (int i = 0; i < attributIndexes.length; i++) {
if (attributIndexes[i]!=null) {
feature.setPropertyValue(attributIndexes[i], buffer[i]);
}
}
return feature;
}
}
private static AttributeType[] getDescriptors(final ShapefileAttributeReader reader) {
return reader.getPropertyDescriptors();
}
}