/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010, Johann Sorel
*
* 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.dbf;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.internal.feature.AttributeConvention;
import org.geotoolkit.data.AbstractFeatureStore;
import org.geotoolkit.data.FeatureStoreFactory;
import org.geotoolkit.data.FeatureStoreRuntimeException;
import org.geotoolkit.data.FeatureReader;
import org.geotoolkit.data.FeatureWriter;
import org.geotoolkit.data.dbf.DbaseFileReader.Row;
import org.geotoolkit.data.query.Query;
import org.geotoolkit.data.query.QueryCapabilities;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.nio.IOUtilities;
import org.geotoolkit.parameter.Parameters;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.storage.DataFileStore;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.feature.PropertyType;
import org.opengis.util.GenericName;
import org.geotoolkit.storage.DataStores;
import org.opengis.feature.AttributeType;
import org.opengis.filter.Filter;
import org.opengis.filter.identity.FeatureId;
import org.opengis.parameter.ParameterValueGroup;
/**
* DBF DataStore, holds a single feature type.
*
* @author Johann Sorel (Geomatys)
* @module
*/
public class DbaseFileFeatureStore extends AbstractFeatureStore implements DataFileStore {
private final ReadWriteLock RWLock = new ReentrantReadWriteLock();
private final ReadWriteLock TempLock = new ReentrantReadWriteLock();
private final Path file;
private String name;
private FeatureType featureType;
/**
* @deprecated use {@link #DbaseFileFeatureStore(Path, String)} instead
*/
public DbaseFileFeatureStore(final File f, final String namespace) throws MalformedURLException, DataStoreException{
this(f.toPath(), namespace);
}
public DbaseFileFeatureStore(final Path f, final String namespace) throws MalformedURLException, DataStoreException{
this(toParameters(f, namespace));
}
public DbaseFileFeatureStore(final ParameterValueGroup params) throws DataStoreException{
super(params);
final URI uri = (URI) params.parameter(DbaseFeatureStoreFactory.PATH.getName().toString()).getValue();
this.file = Paths.get(uri);
final String path = uri.toString();
final int slash = Math.max(0, path.lastIndexOf('/') + 1);
int dot = path.indexOf('.', slash);
if (dot < 0) {
dot = path.length();
}
this.name = path.substring(slash, dot);
}
private static ParameterValueGroup toParameters(final Path f,
final String namespace) throws MalformedURLException{
final ParameterValueGroup params = DbaseFeatureStoreFactory.PARAMETERS_DESCRIPTOR.createValue();
Parameters.getOrCreate(DbaseFeatureStoreFactory.PATH, params).setValue(f.toUri());
Parameters.getOrCreate(DbaseFeatureStoreFactory.NAMESPACE, params).setValue(namespace);
return params;
}
/**
* {@inheritDoc}
*/
@Override
public FeatureStoreFactory getFactory() {
return (FeatureStoreFactory) DataStores.getFactoryById(DbaseFeatureStoreFactory.NAME);
}
private synchronized void checkExist() throws DataStoreException{
if(featureType != null) return;
try{
RWLock.readLock().lock();
if(Files.exists(file)){
featureType = readType();
}
}finally{
RWLock.readLock().unlock();
}
}
private Path createWriteFile() throws MalformedURLException{
return (Path) IOUtilities.changeExtension(file, "wdbf");
}
private FeatureType readType() throws DataStoreException{
try (SeekableByteChannel sbc = Files.newByteChannel(file, StandardOpenOption.READ)){
final DbaseFileReader reader = new DbaseFileReader(sbc, true, null);
final DbaseFileHeader header = reader.getHeader();
final String defaultNs = getDefaultNamespace();
final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
ftb.setName(defaultNs, name);
ftb.addAttribute(String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY).setMinimumOccurs(1).setMaximumOccurs(1);
final List<AttributeType> fields = header.createDescriptors(defaultNs);
for(AttributeType at : fields){
ftb.addAttribute(at);
}
return ftb.build();
}catch(IOException ex){
throw new DataStoreException(ex);
}
}
/**
* {@inheritDoc}
*/
@Override
public Set<GenericName> getNames() throws DataStoreException {
checkExist();
if(featureType != null){
return Collections.singleton(featureType.getName());
}else{
return Collections.emptySet();
}
}
/**
* {@inheritDoc}
*/
@Override
public FeatureType getFeatureType(final String typeName) throws DataStoreException {
typeCheck(typeName); //raise error is type doesnt exist
return featureType;
}
/**
* {@inheritDoc}
*/
@Override
public FeatureReader getFeatureReader(final Query query) throws DataStoreException {
typeCheck(query.getTypeName()); //raise error is type doesnt exist
final FeatureReader fr = new DBFFeatureReader();
return handleRemaining(fr, query);
}
////////////////////////////////////////////////////////////////////////////
// FALLTHROUGHT OR NOT IMPLEMENTED /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/**
* Unsupported, throws a {@link DataStoreException}.
*/
@Override
public FeatureWriter getFeatureWriter(Query query) throws DataStoreException {
throw new DataStoreException("Writing not supported");
}
/**
* Unsupported, throws a {@link DataStoreException}.
*/
@Override
public void createFeatureType(final FeatureType featureType) throws DataStoreException {
throw new DataStoreException("Schema creation not supported");
}
/**
* Unsupported, throws a {@link DataStoreException}.
*/
@Override
public void deleteFeatureType(final String typeName) throws DataStoreException {
throw new DataStoreException("Schema deletion not supported");
}
/**
* Unsupported, throws a {@link DataStoreException}.
*/
@Override
public void updateFeatureType(final FeatureType featureType) throws DataStoreException {
throw new DataStoreException("Schema update not supported");
}
/**
* Unsupported, throws a {@link DataStoreException}.
*/
@Override
public QueryCapabilities getQueryCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* {@inheritDoc}
*/
@Override
public List<FeatureId> addFeatures(final String groupName, final Collection<? extends Feature> newFeatures,
final Hints hints) throws DataStoreException {
return handleAddWithFeatureWriter(groupName, newFeatures,hints);
}
/**
* {@inheritDoc}
*/
@Override
public void updateFeatures(final String groupName, final Filter filter, final Map<String, ? extends Object> values) throws DataStoreException {
handleUpdateWithFeatureWriter(groupName, filter, values);
}
/**
* {@inheritDoc}
*/
@Override
public void removeFeatures(final String groupName, final Filter filter) throws DataStoreException {
handleRemoveWithFeatureWriter(groupName, filter);
}
@Override
public Path[] getDataFiles() throws DataStoreException {
return new Path[] { this.file };
}
private class DBFFeatureReader implements FeatureReader{
protected final DbaseFileReader reader;
protected final String[] attNames;
protected Feature current = null;
protected int inc = 0;
private DBFFeatureReader() throws DataStoreException{
RWLock.readLock().lock();
try (SeekableByteChannel sbc = Files.newByteChannel(file, StandardOpenOption.READ)){
reader = new DbaseFileReader(sbc, true, null);
} catch (IOException ex) {
throw new DataStoreException(ex);
}
final Collection<? extends PropertyType> descs = featureType.getProperties(true);
attNames = new String[descs.size()-1];
int i=0;
for(PropertyType pd : descs){
//skip identifier property
if(i>0)attNames[i-1] = pd.getName().toString();
i++;
}
}
@Override
public FeatureType getFeatureType() {
return featureType;
}
@Override
public Feature next() throws FeatureStoreRuntimeException {
read();
final Feature ob = current;
current = null;
if(ob == null){
throw new FeatureStoreRuntimeException("No more records.");
}
return ob;
}
@Override
public boolean hasNext() throws FeatureStoreRuntimeException {
read();
return current != null;
}
private void read() throws FeatureStoreRuntimeException{
if(current != null) return;
if(!reader.hasNext()) return;
try{
final Row row = reader.next();
final Object[] array = row.readAll(null);
current = featureType.newInstance();
current.setPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString(), ""+inc++);
for(int i=0;i<array.length;i++){
current.setPropertyValue(attNames[i], array[i]);
}
}catch(IOException ex){
throw new FeatureStoreRuntimeException(ex);
}
}
@Override
public void close() {
RWLock.readLock().unlock();
try {
reader.close();
} catch (IOException ex) {
getLogger().log(Level.WARNING, ex.getLocalizedMessage(), ex);
}
}
@Override
public void remove() {
throw new FeatureStoreRuntimeException("Not supported on reader.");
}
}
@Override
public void refreshMetaModel() {
featureType = null;
}
}