// This software is released into the Public Domain. See copying.txt for details.
package org.openstreetmap.osmosis.core.store;
import java.io.File;
import java.util.Comparator;
import java.util.Iterator;
import org.openstreetmap.osmosis.core.OsmosisRuntimeException;
import org.openstreetmap.osmosis.core.lifecycle.Completable;
import org.openstreetmap.osmosis.core.lifecycle.ReleasableIterator;
import org.openstreetmap.osmosis.core.sort.common.FileBasedSort;
/**
* Writes data into an index file and sorts it if input data is unordered. The
* data must be fixed width to allow index values to be randomly accessed later.
*
* @param <K>
* The index key type.
* @param <T>
* The index element type to be stored.
* @author Brett Henderson
*/
public class IndexStore<K, T extends IndexElement<K>> implements Completable {
private ObjectSerializationFactory serializationFactory;
private RandomAccessObjectStore<T> indexStore;
private Comparator<K> ordering;
private String tempFilePrefix;
private File indexFile;
private K previousKey;
private boolean sorted;
private long elementCount;
private long elementSize;
private boolean complete;
/**
* Creates a new instance.
*
* @param elementType
* The type of index element to be stored in the index.
* @param ordering
* A comparator that sorts index elements desired index key
* ordering.
* @param indexFile
* The file to use for storing the index.
*/
public IndexStore(Class<T> elementType, Comparator<K> ordering, File indexFile) {
this.ordering = ordering;
this.indexFile = indexFile;
serializationFactory = new SingleClassObjectSerializationFactory(elementType);
indexStore = new RandomAccessObjectStore<T>(serializationFactory, indexFile);
sorted = true;
elementCount = 0;
elementSize = -1;
complete = false;
}
/**
* Creates a new instance.
*
*
* @param elementType
* The type of index element to be stored in the index.
* @param ordering
* A comparator that sorts index elements desired index key
* ordering.
* @param tempFilePrefix
* The prefix of the temporary file.
*/
public IndexStore(Class<T> elementType, Comparator<K> ordering, String tempFilePrefix) {
this.ordering = ordering;
this.tempFilePrefix = tempFilePrefix;
serializationFactory = new SingleClassObjectSerializationFactory(elementType);
indexStore = new RandomAccessObjectStore<T>(serializationFactory, tempFilePrefix);
sorted = true;
elementCount = 0;
elementSize = -1;
complete = false;
}
/**
* Writes the specified element to the index.
*
* @param element
* The index element which includes the identifier when stored.
*/
public void write(T element) {
K key;
long fileOffset;
if (complete) {
throw new OsmosisRuntimeException("Cannot write new data once reading has begun.");
}
fileOffset = indexStore.add(element);
key = element.getKey();
// If the new element contains a key that is not sequential, we need to
// mark the index as unsorted so we can perform a sort prior to reading.
if (previousKey != null) {
if (ordering.compare(previousKey, key) > 0) {
sorted = false;
}
}
previousKey = key;
elementCount++;
// Calculate and verify the element size. This index requires all keys to be the same length
// to allow sorting and searching to occur. The first element has a file offset of 0 so we
// ignore that one. The second element offset will tell us the size of the first element.
// From that point on we verify that all elements have the same size.
if (elementCount == 2) {
elementSize = fileOffset;
} else if (elementCount > 2) {
long expectedOffset;
expectedOffset = (elementCount - 1) * elementSize;
if (expectedOffset != fileOffset) {
throw new OsmosisRuntimeException(
"Inconsistent element sizes, new file offset=" + fileOffset
+ ", expected offset=" + expectedOffset
+ ", element size=" + elementSize
+ ", element count=" + elementCount
);
}
}
}
/**
* Creates a new reader capable of accessing the contents of this store. The
* reader must be explicitly released when no longer required. Readers must
* be released prior to this store.
*
* @return A store reader.
*/
public IndexStoreReader<K, T> createReader() {
return new IndexStoreReader<K, T>(indexStore.createReader(), ordering);
}
/**
* {@inheritDoc}
*/
public void complete() {
if (!complete) {
indexStore.complete();
if (!sorted) {
final Comparator<K> keyOrdering = ordering;
// Create a new file based sort instance ordering elements by their
// identifiers.
try (FileBasedSort<T> fileSort = new FileBasedSort<T>(
serializationFactory,
new Comparator<T>() {
private Comparator<K> elementKeyOrdering = keyOrdering;
@Override
public int compare(T o1, T o2) {
return elementKeyOrdering.compare(o1.getKey(), o2.getKey());
}
},
true
)) {
// Read all data from the index store into the sorting store.
try (RandomAccessObjectStoreReader<T> indexStoreReader = indexStore.createReader()) {
Iterator<T> indexIterator;
indexIterator = indexStoreReader.iterate();
while (indexIterator.hasNext()) {
fileSort.add(indexIterator.next());
}
}
// Release the existing index store and create a new one.
indexStore.close();
if (indexFile != null) {
indexStore = new RandomAccessObjectStore<T>(serializationFactory, indexFile);
} else {
indexStore = new RandomAccessObjectStore<T>(serializationFactory, tempFilePrefix);
}
// Read all data from the sorting store back into the index store.
try (ReleasableIterator<T> sortIterator = fileSort.iterate()) {
while (sortIterator.hasNext()) {
indexStore.add(sortIterator.next());
}
}
}
}
complete = true;
}
}
/**
* {@inheritDoc}
*/
public void close() {
indexStore.close();
}
}