// This software is released into the Public Domain. See copying.txt for details.
package org.openstreetmap.osmosis.core.filter.common;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.openstreetmap.osmosis.core.util.LongAsInt;
/**
* Implements the IdTracker interface using a list of ids. The current
* implementation only supports 31 bit numbers, but will be enhanced if and when
* required.
*
* @author Brett Henderson
*/
public class ListIdTracker implements IdTracker {
/**
* The internal list size is multiplied by this factor when more space is
* required.
*/
private static final double LIST_SIZE_EXTENSION_FACTOR = 1.5;
/**
* This is the main list of id values. It is allocated in chunks and the
* maximum valid offset is defined by idOffset.
*/
/* package */ int[] idList;
/**
* Flags where the maximum written id offset occurs in the list. If new
* values are added and the list is full, new space must be allocated.
*/
/* package */ int idOffset;
private int maxIdAdded;
private boolean sorted;
/**
* Creates a new instance.
*/
public ListIdTracker() {
// This is being initialised with a very small array size because this class is now also
// used within the DynamicIdTracker which relies on the ListIdTracker being efficient for
// small amounts of data.
// When storing large amounts of data, the overhead of extending the array more often in the
// early stages is relatively small compared to the overall processing. For small amounts of
// data, the size efficiency is most important.
idList = new int[1];
idOffset = 0;
maxIdAdded = Integer.MIN_VALUE;
sorted = true;
}
/**
* Increases the size of the id list to make space for new ids.
*/
private void extendIdList() {
int[] newIdList;
int newListLength;
newListLength = (int) (idList.length * LIST_SIZE_EXTENSION_FACTOR);
if (newListLength == idList.length) {
newListLength++;
}
newIdList = new int[newListLength];
System.arraycopy(idList, 0, newIdList, 0, idList.length);
idList = newIdList;
}
/**
* If the list is unsorted, this method will re-order the contents.
*/
private void ensureListIsSorted() {
if (!sorted) {
List<Integer> tmpList;
int newIdOffset;
tmpList = new ArrayList<Integer>(idOffset);
for (int i = 0; i < idOffset; i++) {
tmpList.add(Integer.valueOf(idList[i]));
}
Collections.sort(tmpList);
newIdOffset = 0;
for (int i = 0; i < idOffset; i++) {
int nextValue;
nextValue = tmpList.get(i).intValue();
if (newIdOffset <= 0 || nextValue > idList[newIdOffset - 1]) {
idList[newIdOffset++] = nextValue;
}
}
idOffset = newIdOffset;
sorted = true;
}
}
/**
* {@inheritDoc}
*/
public void set(long id) {
int integerId;
integerId = LongAsInt.longToInt(id);
// Increase the id list size if it is full.
if (idOffset >= idList.length) {
extendIdList();
}
idList[idOffset++] = integerId;
// If ids are added out of order, the list will have to be sorted before
// it can be searched using a binary search algorithm.
if (integerId < maxIdAdded) {
sorted = false;
} else {
maxIdAdded = integerId;
}
}
/**
* {@inheritDoc}
*/
public boolean get(long id) {
int integerId;
int intervalBegin;
int intervalEnd;
boolean idFound;
integerId = LongAsInt.longToInt(id);
// If the list is not sorted, it must be sorted prior to a search being
// performed.
ensureListIsSorted();
// Perform a binary search splitting the list in half each time until
// the requested id is confirmed as existing or not.
intervalBegin = 0;
intervalEnd = idOffset;
idFound = false;
for (boolean searchComplete = false; !searchComplete;) {
int intervalSize;
// Calculate the interval size.
intervalSize = intervalEnd - intervalBegin;
// Divide and conquer if the size is large, otherwise commence
// linear search.
if (intervalSize >= 2) {
int intervalMid;
int currentId;
// Split the interval in two.
intervalMid = intervalSize / 2 + intervalBegin;
// Check whether the midpoint id is above or below the id
// required.
currentId = idList[intervalMid];
if (currentId == integerId) {
idFound = true;
searchComplete = true;
} else if (currentId < integerId) {
intervalBegin = intervalMid + 1;
} else {
intervalEnd = intervalMid;
}
} else {
// Iterate through the entire interval.
for (int currentOffset = intervalBegin; currentOffset < intervalEnd; currentOffset++) {
int currentId;
// Check if the current offset contains the id required.
currentId = idList[currentOffset];
if (currentId == integerId) {
idFound = true;
break;
}
}
searchComplete = true;
}
}
return idFound;
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<Long> iterator() {
// If the list is not sorted, it must be sorted prior to data being
// returned.
ensureListIsSorted();
return new IdIterator();
}
/**
* {@inheritDoc}
*/
@Override
public void setAll(IdTracker idTracker) {
for (Long id : idTracker) {
set(id);
}
}
/**
* The iterator implementation for providing access to the list of ids.
*
* @author Brett Henderson
*/
private class IdIterator implements Iterator<Long> {
private int iteratorOffset;
/**
* Creates a new instance.
*/
public IdIterator() {
iteratorOffset = 0;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasNext() {
return (iteratorOffset < idOffset);
}
/**
* {@inheritDoc}
*/
@Override
public Long next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return (long) idList[iteratorOffset++];
}
/**
* {@inheritDoc}
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}