/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2005-2008, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2011, 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.fix; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.util.NoSuchElementException; import java.util.logging.Level; import org.geotoolkit.data.FeatureStoreRuntimeException; import org.geotoolkit.data.shapefile.FeatureIDReader; import org.geotoolkit.data.shapefile.lock.ShpFileType; import org.geotoolkit.data.shapefile.indexed.RecordNumberTracker; import org.geotoolkit.data.shapefile.shp.ShapefileReader; import org.apache.sis.internal.storage.IOUtilities; import org.geotoolkit.data.dbf.Closeable; import static org.geotoolkit.data.shapefile.ShapefileFeatureStoreFactory.*; /** * This object reads from a file the fid of a feature in a shapefile. * * @author Jesse * @author Johann Sorel (Geomatys) * @module */ public class IndexedFidReader implements FeatureIDReader, Closeable { private ReadableByteChannel readChannel; private ByteBuffer buffer; private long count; private String typeName; private boolean done; private int removes; private int currentShxIndex = -1; private RecordNumberTracker reader; private long currentId; /** * move the reader to the recno-th entry in the file. * * @param recno * @throws IOException */ private long bufferStart = Long.MIN_VALUE; public IndexedFidReader(final URI fixUrl, final ReadableByteChannel fixChannel, final RecordNumberTracker reader) throws IOException { this.reader = reader; final String path = ShpFileType.FIX.toBase(fixUrl); final int slash = Math.max(0, path.lastIndexOf('/') + 1); int dot = path.indexOf('.', slash); if (dot < 0) { dot = path.length(); } this.typeName = path.substring(slash, dot) + "."; this.readChannel = fixChannel; getHeader(fixUrl); buffer = ByteBuffer.allocateDirect(IndexedFidWriter.RECORD_SIZE * 1024); buffer.position(buffer.limit()); } private void getHeader(final URI fixUrl) throws IOException { final ByteBuffer buffer = ByteBuffer.allocate(IndexedFidWriter.HEADER_SIZE); ShapefileReader.fill(buffer, readChannel); if (buffer.position() == 0) { done = true; count = 0; return; } buffer.position(0); byte version = buffer.get(); if (version != 1) { throw new IOException( "File is not of a compatible version for this reader or file is corrupt."); } this.count = buffer.getLong(); this.removes = buffer.getInt(); if (removes > count/2 ) { try { IOUtilities.toFile(fixUrl.toURL(), ENCODING).deleteOnExit(); } finally { } } } /** * Returns the number of Fid Entries in the file. * * @return Returns the number of Fid Entries in the file. */ public long getCount() { return count; } /** * Returns the number of features that have been removed since the fid index * was regenerated. * * @return Returns the number of features that have been removed since the * fid index was regenerated. */ public int getRemoves() { return removes; } /** * Returns the offset to the location in the SHX file that the fid * identifies. This search take logN time. * * @param fid * the fid to find. * * @return Returns the record number of the record in the SHX file that the * fid identifies. Will return -1 if the fid was not found. * * @throws IOException * @throws IllegalArgumentException * DOCUMENT ME! */ public long findFid(final String fid) throws IOException { try { final long desired; if(fid.startsWith(typeName)){ try{ desired = Long.parseLong(fid.substring(typeName.length()), 10); }catch(NumberFormatException e){ return -1; } }else{ return -1; } if (desired < 0) { return -1; } if (desired < count) { return search(desired, -1, this.count, desired - 1); } else { return search(desired, -1, this.count, count - 1); } } catch (NumberFormatException e) { LOGGER.log(Level.WARNING, "Fid is not recognized as a fid for this shapefile: {0}", typeName); return -1; } } /** * Searches for the desired record. * * @param desired * the id of the desired record. * @param minRec * the last record that is known to be <em>before</em> the * desired record. * @param maxRec * the first record that is known to be <em>after</em> the * desired record. * @param predictedRec * the record that is predicted to be the desired record. * * @return returns the record number of the feature in the shx file. * * @throws IOException */ long search(final long desired, final long minRec, final long maxRec, final long predictedRec) throws IOException { if (minRec == maxRec) { return -1; } goTo(predictedRec); hasNext(); // force data reading next(); buffer.limit(buffer.capacity()); if (currentId == desired) { return currentShxIndex; } if (maxRec - minRec < 10) { return search(desired, minRec + 1, maxRec, minRec + 1); } else { long newOffset = desired - currentId; long newPrediction = predictedRec + newOffset; if (newPrediction <= minRec) { newPrediction = minRec + ((predictedRec - minRec) / 2); } if (newPrediction >= maxRec) { newPrediction = predictedRec + ((maxRec - predictedRec) / 2); } if (newPrediction == predictedRec) { return -1; } if (newPrediction < predictedRec) { return search(desired, minRec, predictedRec, newPrediction); } else { return search(desired, predictedRec, maxRec, newPrediction); } } } public void goTo(final long recno) throws IOException { assert recno<count; if (readChannel instanceof FileChannel) { long newPosition = IndexedFidWriter.HEADER_SIZE + (recno * IndexedFidWriter.RECORD_SIZE); if (newPosition >= bufferStart + buffer.limit() || newPosition < bufferStart) { FileChannel fc = (FileChannel) readChannel; fc.position(newPosition); buffer.limit(buffer.capacity()); buffer.position(buffer.limit()); } else { buffer.position((int) (newPosition - bufferStart)); } } else { throw new IOException( "Read Channel is not a File Channel so this is not possible."); } } @Override public void close() throws FeatureStoreRuntimeException { try { if (reader != null){ try { reader.close(); } catch (IOException ex) { throw new FeatureStoreRuntimeException(ex); } } } finally { try { readChannel.close(); } catch (IOException ex) { throw new FeatureStoreRuntimeException(ex); } } } @Override public boolean isClosed() { return !readChannel.isOpen(); } @Override public boolean hasNext() throws FeatureStoreRuntimeException { if (done) { return false; } if (buffer.position() == buffer.limit()) { buffer.position(0); final FileChannel fc = (FileChannel) readChannel; final int read; try { bufferStart = fc.position(); read = ShapefileReader.fill(buffer, readChannel); } catch (IOException ex) { throw new FeatureStoreRuntimeException(ex); } if (read != 0) { buffer.position(0); } } return buffer.remaining() != 0; } @Override public String next() throws FeatureStoreRuntimeException { if (reader != null) { try { goTo(reader.getRecordNumber() - 1); } catch (IOException ex) { throw new FeatureStoreRuntimeException(ex); } } if (!hasNext()) { throw new NoSuchElementException("Feature could not be read; a the index may be invalid"); } currentId = buffer.getLong(); currentShxIndex = buffer.getInt(); return typeName + currentId; } /** * Returns the record number of the feature in the shx or shp that is * identified by the the last fid returned by next(). * * @return Returns the record number of the feature in the shx or shp that * is identified by the the last fid returned by next(). * * @throws NoSuchElementException DOCUMENT ME! */ public int currentSHXIndex() { if (currentShxIndex == -1) { throw new NoSuchElementException("Next must be called before there exists a current element."); } return currentShxIndex; } /** * Returns the index that is appended to the typename to construct the fid. * * @return the index that is appended to the typename to construct the fid. */ public long getCurrentFIDIndex(){ return currentId; } }