/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-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.geotoolkit.data.shapefile.fix;
import java.net.URI;
import java.net.URL;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.logging.Level;
import org.geotoolkit.data.shapefile.lock.AccessManager;
import org.geotoolkit.data.shapefile.lock.ShpFiles;
import org.geotoolkit.data.shapefile.lock.StorageFile;
import org.geotoolkit.data.shapefile.shx.ShxReader;
import static org.geotoolkit.data.shapefile.lock.ShpFileType.*;
import static org.geotoolkit.data.shapefile.ShapefileFeatureStoreFactory.*;
import org.geotoolkit.data.dbf.Closeable;
/**
* The Writer writes out the fid and record number of features to the fid index file.
*
* @author Jesse
* @module
*/
public class IndexedFidWriter implements Closeable {
public static final int HEADER_SIZE = 13;
public static final int RECORD_SIZE = 12;
private FileChannel channel;
private ByteBuffer writeBuffer;
private IndexedFidReader reader;
long fidIndex;
private int recordIndex;
private boolean closed;
private long current;
private long position;
private int removes;
/**
* Create a new instance<br>
* Note: {@link StorageFile#replaceOriginal()} is NOT called. Call {@link #IndexedFidWriter(ShpFiles)} for that
* behaviour.
* @param shpFiles The shapefiles to used
* @param storageFile the storage file that will be written to. It will NOT be closed.
* @throws IOException
*/
public IndexedFidWriter(final URI fixUrl, final ReadableByteChannel readfixChannel,
final FileChannel writeChannel ) throws IOException {
// Note do NOT assign storageFile so that it is closed because this method method requires that
// the caller close the storage file.
// Call the single argument constructor instead
reader = new IndexedFidReader(fixUrl,readfixChannel,null);
this.channel = writeChannel;
allocateBuffers();
removes = reader.getRemoves();
writeBuffer.position(HEADER_SIZE);
closed = false;
position = 0;
current = -1;
recordIndex = 0;
fidIndex = 0;
}
private IndexedFidWriter() {
}
/**
* Allocate some buffers for writing.
*/
private void allocateBuffers() {
writeBuffer = ByteBuffer.allocateDirect(HEADER_SIZE + RECORD_SIZE * 1024);
}
/**
* Drain internal buffers into underlying channels.
*
* @throws IOException DOCUMENT ME!
*/
private void drain() throws IOException {
writeBuffer.flip();
int written = 0;
while( writeBuffer.remaining() > 0 )
written += channel.write(writeBuffer, position);
position += written;
writeBuffer.flip().limit(writeBuffer.capacity());
}
private void writeHeader() throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE);
buffer.put((byte) 1);
buffer.putLong(recordIndex);
buffer.putInt(removes);
buffer.flip();
channel.write(buffer, 0);
}
public boolean hasNext() throws IOException {
return reader.hasNext();
}
public long next() throws IOException {
if (current != -1)
write();
if (reader.hasNext()) {
reader.next();
fidIndex = reader.getCurrentFIDIndex();
} else {
fidIndex++;
}
current = fidIndex;
return fidIndex;
}
@Override
public void close() throws IOException {
if (closed) {
return;
}
try {
finishLastWrite();
} finally {
try {
reader.close();
} finally {
closeWriterChannels();
}
}
closed = true;
}
private void closeWriterChannels() throws IOException {
if (channel.isOpen()){
channel.close();
}
}
private void finishLastWrite() throws IOException {
while( hasNext() )
next();
if (current != -1)
write();
drain();
writeHeader();
}
/**
* Increments the fidIndex by 1. Indicates that a feature was removed from the location. This is
* intended to ensure that FIDs stay constant over time. Consider the following case of 5
* features. feature 1 has fid typename.0 feature 2 has fid typename.1 feature 3 has fid
* typename.2 feature 4 has fid typename.3 feature 5 has fid typename.4 when feature 3 is
* removed/deleted the following usage of the write should take place: next(); (move to feature
* 1) next(); (move to feature 2) next(); (move to feature 3) remove();(delete feature 3)
* next(); (move to feature 4) // optional write(); (write feature 4) next(); (move to feature
* 5) write(); (write(feature 5)
*
* @throws IOException
*/
public void remove() throws IOException {
if (current == -1)
throw new IOException("Current fid index is null, next must be called before remove");
if (hasNext()) {
removes++;
current = -1;
// reader.next();
}
}
/**
* Writes the current fidIndex. Writes to the same place in the file each time. Only
* {@link #next()} moves forward in the file.
*
* @throws IOException
* @see #next()
* @see #remove()
*/
public void write() throws IOException {
if (current == -1)
throw new IOException("Current fid index is null, next must be called before write()");
if (writeBuffer == null) {
allocateBuffers();
}
if (writeBuffer.remaining() < RECORD_SIZE) {
drain();
}
writeBuffer.putLong(current);
writeBuffer.putInt(recordIndex);
recordIndex++;
current = -1;
}
@Override
public boolean isClosed() {
return closed;
}
/**
* Generates the FID index file for the shpFile
*/
public static synchronized void generate(final URL shpURL) throws IOException {
generate(new ShpFiles(shpURL));
}
/**
* Generates the FID index file for the shpFiles
*/
public static void generate(final ShpFiles shpFiles) throws IOException {
LOGGER.log(Level.FINE, "Generating fids for {0}", shpFiles.get(SHP));
final AccessManager locker = shpFiles.createLocker();
ShxReader indexFile = null;
StorageFile file = locker.getStorageFile(FIX);
IndexedFidWriter writer = null;
try {
indexFile = locker.getSHXReader(false);
// writer closes channel for you.
writer = locker.getFIXWriter(file);
for (int i = 0, j = indexFile.getRecordCount(); i < j; i++) {
writer.next();
}
} finally {
try {
if (writer != null) {
writer.close();
}
locker.disposeReaderAndWriters();
locker.replaceStorageFiles();
} finally {
if (indexFile != null) {
indexFile.close();
}
}
}
}
public static final IndexedFidWriter EMPTY_WRITER = new IndexedFidWriter(){
@Override
public void close() throws IOException {
}
@Override
public boolean hasNext() throws IOException {
return false;
}
@Override
public boolean isClosed() {
return false;
}
@Override
public void write() throws IOException {
}
@Override
public long next() throws IOException {
return 0;
}
@Override
public void remove() throws IOException {
}
};
}