// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.core.store; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.openstreetmap.osmosis.core.lifecycle.Closeable; /** * Provides read-only access to an index store. Each thread accessing the object * store must create its own reader. The reader maintains all references to * heavyweight resources such as file handles used to access the store * eliminating the need for objects such as object iterators to be cleaned up * explicitly. * * @param <K> * The index key type. * @param <T> * The object type being stored. * @author Brett Henderson */ public class IndexStoreReader<K, T extends IndexElement<K>> implements Closeable { private RandomAccessObjectStoreReader<T> indexStoreReader; private Comparator<K> ordering; private boolean elementDetailsInitialized; private long elementCount; private long elementSize; private long binarySearchElementCount; private int binarySearchDepth; private List<ComparisonElement<K>> binarySearchCache; /** * Creates a new instance. * * @param indexStoreReader * Provides access to the index data. * @param ordering * A comparator that sorts index elements desired index key * ordering. */ public IndexStoreReader(RandomAccessObjectStoreReader<T> indexStoreReader, Comparator<K> ordering) { this.indexStoreReader = indexStoreReader; this.ordering = ordering; elementDetailsInitialized = false; } /** * Initialises the element count and element size required for performing * binary searches within the index. */ private void initializeElementDetails() { long dataLength; dataLength = indexStoreReader.length(); if (dataLength <= 0) { elementCount = 0; elementSize = 0; } else { indexStoreReader.get(0); elementSize = indexStoreReader.position(); elementCount = dataLength / elementSize; } // Determine how many levels of a binary tree must be traversed to reach a result. binarySearchDepth = 0; binarySearchElementCount = 1; // Must add 1 here because we search from offset -1 to elementCount while (binarySearchElementCount < (elementCount + 1)) { binarySearchDepth++; binarySearchElementCount *= 2; } // Initialise the binary search cache. binarySearchCache = new ArrayList<ComparisonElement<K>>(binarySearchDepth); for (int i = 0; i < binarySearchDepth; i++) { binarySearchCache.add(null); } elementDetailsInitialized = true; } /** * Returns the index of the first index element with a key greater than or * equal to the specified key. * * @param searchKey * The key to search for. * @return The matching index. */ private long getKeyIndex(K searchKey) { long intervalBegin; long intervalEnd; int currentSearchDepth; boolean useCache; boolean higherThanPrevious; // The element details must be initialised before searching. if (!elementDetailsInitialized) { initializeElementDetails(); } intervalBegin = -1; intervalEnd = binarySearchElementCount; currentSearchDepth = 0; useCache = true; higherThanPrevious = true; while ((intervalBegin + 1) < intervalEnd) { long intervalMid; // Calculate the mid point of the current interval. intervalMid = (intervalBegin + intervalEnd) / 2; // We can only perform a comparison if the mid point of the current // search interval is within the data set. if (intervalMid < elementCount) { K intervalMidKey = null; ComparisonElement<K> searchElement; boolean comparisonHigher; // Attempt to retrieve the key for the mid point from the search // cache. if (useCache) { searchElement = binarySearchCache.get(currentSearchDepth); if (searchElement != null && searchElement.getIndexOffset() == intervalMid) { intervalMidKey = searchElement.getKey(); } else { useCache = false; } } // If the value couldn't be retrieved from cache, load it from // the underlying dataset. if (!useCache) { intervalMidKey = indexStoreReader.get(intervalMid * elementSize).getKey(); } // Compare the current key for equality with the desired key. comparisonHigher = ordering.compare(searchKey, intervalMidKey) > 0; if (!useCache) { binarySearchCache.set(currentSearchDepth, new ComparisonElement<K>(intervalMid, intervalMidKey)); } higherThanPrevious = comparisonHigher; } else { higherThanPrevious = false; } // Update binary search attributes based on recent comparison. currentSearchDepth++; if (higherThanPrevious) { intervalBegin = intervalMid; } else { intervalEnd = intervalMid; } } return intervalEnd; } /** * Returns the index element identified by id. * * @param key * The identifier for the index element to be retrieved. * @return The requested object. */ public T get(K key) { long keyIndex; // Determine the location of the key within the index. keyIndex = getKeyIndex(key); if (keyIndex < elementCount) { T element; K locatedKey; element = indexStoreReader.get(keyIndex * elementSize); locatedKey = element.getKey(); if (ordering.compare(key, locatedKey) == 0) { return element; } } throw new NoSuchIndexElementException("Requested key " + key + " does not exist."); } /** * Returns all elements in the range specified by the begin and end keys. * All elements with keys matching and lying between the two keys will be * returned. * * @param beginKey * The key marking the beginning of the required index elements. * @param endKey * The key marking the end of the required index elements. The * identifier for the index element to be retrieved. * @return An iterator pointing to the requested range. */ public Iterator<T> getRange(K beginKey, K endKey) { long keyIndex; // Determine the location of the begin key within the index. keyIndex = getKeyIndex(beginKey); // Iterate across the range. return new IndexRangeIterator<K, T>( indexStoreReader.iterate(keyIndex * elementSize), beginKey, endKey, ordering ); } /** * {@inheritDoc} */ @Override public void close() { indexStoreReader.close(); } /** * Maintains the state associated with a single index search comparison. The * complete path to the previously searched element is maintained using a * list of these elements between searches, improving performance for * searches on keys that are closely located. */ private static class ComparisonElement<K> { private long indexOffset; private K key; /** * Creates a new instance. * * @param indexOffset * The offset of the current key within the index. * @param key * The key of the index element compared against. */ public ComparisonElement(long indexOffset, K key) { this.indexOffset = indexOffset; this.key = key; } /** * Returns the index offset this key is located at. * * @return The offset of the key within the index. */ public long getIndexOffset() { return indexOffset; } /** * Returns the key associated with this comparison. * * @return The comparison key. */ public K getKey() { return key; } } }