blob: 1448f15a462c719908a3bc4737f48bef9ed54a9a [file] [log] [blame]
/* Copyright (c) 2001-2010, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb.lib;
import java.util.NoSuchElementException;
/**
* Maintains an ordered integer->integer lookup table, consisting of two
* columns, one for keys, the other for values.
*
* The table is sorted on either the key or value column, depending on the calls to
* setKeysSearchTarget() or setValuesSearchTarget(). By default, the table is
* sorted on values.<p>
*
* findXXX() methods return the array index into the list
* pair containing a matching key or value, or or -1 if not found.<p>
*
* Sorting methods originally contributed by Tony Lai.
*
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 1.8.0
* @since 1.8.0
*/
public class DoubleIntIndex implements IntLookup {
private int count = 0;
private int capacity;
private boolean sorted = true;
private boolean sortOnValues = true;
private boolean hasChanged;
private final boolean fixedSize;
private int[] keys;
private int[] values;
//
private int targetSearchValue;
public DoubleIntIndex(int capacity, boolean fixedSize) {
this.capacity = capacity;
keys = new int[capacity];
values = new int[capacity];
this.fixedSize = fixedSize;
hasChanged = true;
}
public synchronized int getKey(int i) {
if (i < 0 || i >= count) {
throw new IndexOutOfBoundsException();
}
return keys[i];
}
public synchronized int getValue(int i) {
if (i < 0 || i >= count) {
throw new IndexOutOfBoundsException();
}
return values[i];
}
/**
* Modifies an existing pair.
* @param i the index
* @param key the key
*/
public synchronized void setKey(int i, int key) {
if (i < 0 || i >= count) {
throw new IndexOutOfBoundsException();
}
if (!sortOnValues) {
sorted = false;
}
keys[i] = key;
}
/**
* Modifies an existing pair.
* @param i the index
* @param value the value
*/
public synchronized void setValue(int i, int value) {
if (i < 0 || i >= count) {
throw new IndexOutOfBoundsException();
}
if (sortOnValues) {
sorted = false;
}
values[i] = value;
}
public synchronized int size() {
return count;
}
public synchronized int capacity() {
return capacity;
}
/**
* Adds a pair into the table.
*
* @param key the key
* @param value the value
* @return true or false depending on success
*/
public synchronized boolean addUnsorted(int key, int value) {
if (count == capacity) {
if (fixedSize) {
return false;
} else {
doubleCapacity();
}
}
if (sorted && count != 0) {
if (sortOnValues) {
if (value < values[count - 1]) {
sorted = false;
}
} else {
if (key < keys[count - 1]) {
sorted = false;
}
}
}
hasChanged = true;
keys[count] = key;
values[count] = value;
count++;
return true;
}
/**
* Adds a key, value pair into the table with the guarantee that the key
* is equal or larger than the largest existing key. This prevents a sort
* from taking place on next call to find()
*
* @param key the key
* @param value the value
* @return true or false depending on success
*/
public synchronized boolean addSorted(int key, int value) {
if (count == capacity) {
if (fixedSize) {
return false;
} else {
doubleCapacity();
}
}
if (count != 0) {
if (sortOnValues) {
if (value < values[count - 1]) {
return false;
}
} else {
if (key < keys[count - 1]) {
return false;
}
}
}
hasChanged = true;
keys[count] = key;
values[count] = value;
count++;
return true;
}
/**
* Adds a pair, ensuring no duplicate key xor value already exists in the
* current search target column.
* @param key the key
* @param value the value
* @return true or false depending on success
*/
public synchronized boolean addUnique(int key, int value) {
if (count == capacity) {
if (fixedSize) {
return false;
} else {
doubleCapacity();
}
}
if (!sorted) {
fastQuickSort();
}
targetSearchValue = sortOnValues ? value
: key;
int i = binaryEmptySlotSearch();
if (i == -1) {
return false;
}
hasChanged = true;
if (count != i) {
moveRows(i, i + 1, count - i);
}
keys[i] = key;
values[i] = value;
count++;
return true;
}
/**
* Adds a pair, maintaining sorted order
* current search target column.
* @param key the key
* @param value the value
* @return true or false depending on success
*/
public synchronized int add(int key, int value) {
if (count == capacity) {
if (fixedSize) {
return -1;
} else {
doubleCapacity();
}
}
if (!sorted) {
fastQuickSort();
}
targetSearchValue = sortOnValues ? value
: key;
int i = binarySlotSearch();
if (i == -1) {
return i;
}
hasChanged = true;
if (count != i) {
moveRows(i, i + 1, count - i);
}
keys[i] = key;
values[i] = value;
count++;
return i;
}
public int lookupFirstEqual(int key) throws NoSuchElementException {
if (sortOnValues) {
sorted = false;
sortOnValues = false;
}
int i = findFirstEqualKeyIndex(key);
if (i == -1) {
throw new NoSuchElementException();
}
return getValue(i);
}
public int lookupFirstGreaterEqual(int key) throws NoSuchElementException {
if (sortOnValues) {
sorted = false;
sortOnValues = false;
}
int i = findFirstGreaterEqualKeyIndex(key);
if (i == -1) {
throw new NoSuchElementException();
}
return getValue(i);
}
public synchronized void setValuesSearchTarget() {
if (!sortOnValues) {
sorted = false;
}
sortOnValues = true;
}
public synchronized void setKeysSearchTarget() {
if (sortOnValues) {
sorted = false;
}
sortOnValues = false;
}
/**
* @param value the value
* @return the index
*/
public synchronized int findFirstGreaterEqualKeyIndex(int value) {
int index = findFirstGreaterEqualSlotIndex(value);
return index == count ? -1
: index;
}
/**
* @param value the value
* @return the index
*/
public synchronized int findFirstEqualKeyIndex(int value) {
if (!sorted) {
fastQuickSort();
}
targetSearchValue = value;
return binaryFirstSearch();
}
/**
* This method is similar to findFirstGreaterEqualKeyIndex(int) but
* returns the index of the empty row past the end of the array if
* the search value is larger than all the values / keys in the searched
* column.
* @param value the value
* @return the index
*/
public synchronized int findFirstGreaterEqualSlotIndex(int value) {
if (!sorted) {
fastQuickSort();
}
targetSearchValue = value;
return binarySlotSearch();
}
/**
* Returns the index of the lowest element == the given search target,
* or -1
* @return index or -1 if not found
*/
private int binaryFirstSearch() {
int low = 0;
int high = count;
int mid = 0;
int compare = 0;
int found = count;
while (low < high) {
mid = (low + high) / 2;
compare = compare(mid);
if (compare < 0) {
high = mid;
} else if (compare > 0) {
low = mid + 1;
} else {
high = mid;
found = mid;
}
}
return found == count ? -1
: found;
}
/**
* Returns the index of the lowest element > the given search target
* @return the index
*/
private int binaryGreaterSearch() {
int low = 0;
int high = count;
int mid = 0;
int compare = 0;
while (low < high) {
mid = (low + high) / 2;
compare = compare(mid);
if (compare < 0) {
high = mid;
} else {
low = mid + 1;
}
}
return low == count ? -1
: low;
}
/**
* Returns the index of the lowest element >= the given search target,
* or count
* @return the index
*/
private int binarySlotSearch() {
int low = 0;
int high = count;
int mid = 0;
int compare = 0;
while (low < high) {
mid = (low + high) / 2;
compare = compare(mid);
if (compare <= 0) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
/**
* Returns the index of the lowest element > the given search target
* or count or -1 if target is found
* @return the index
*/
private int binaryEmptySlotSearch() {
int low = 0;
int high = count;
int mid = 0;
int compare = 0;
while (low < high) {
mid = (low + high) / 2;
compare = compare(mid);
if (compare < 0) {
high = mid;
} else if (compare > 0) {
low = mid + 1;
} else {
return -1;
}
}
return low;
}
private synchronized void fastQuickSort() {
quickSort(0, count - 1);
insertionSort(0, count - 1);
sorted = true;
}
private void quickSort(int l, int r) {
int M = 16;
int i;
int j;
int v;
if ((r - l) > M) {
i = (r + l) / 2;
if (lessThan(i, l)) {
swap(l, i); // Tri-Median Methode!
}
if (lessThan(r, l)) {
swap(l, r);
}
if (lessThan(r, i)) {
swap(i, r);
}
j = r - 1;
swap(i, j);
i = l;
v = j;
for (;;) {
while (lessThan(++i, v)) {}
while (lessThan(v, --j)) {}
if (j < i) {
break;
}
swap(i, j);
}
swap(i, r - 1);
quickSort(l, j);
quickSort(i + 1, r);
}
}
private void insertionSort(int lo0, int hi0) {
int i;
int j;
for (i = lo0 + 1; i <= hi0; i++) {
j = i;
while ((j > lo0) && lessThan(i, j - 1)) {
j--;
}
if (i != j) {
moveAndInsertRow(i, j);
}
}
}
protected void moveAndInsertRow(int i, int j) {
int col1 = keys[i];
int col2 = values[i];
moveRows(j, j + 1, i - j);
keys[j] = col1;
values[j] = col2;
}
protected void swap(int i1, int i2) {
int col1 = keys[i1];
int col2 = values[i1];
keys[i1] = keys[i2];
values[i1] = values[i2];
keys[i2] = col1;
values[i2] = col2;
}
/**
* Check if targeted column value in the row indexed i is less than the
* search target object.
* @param i the index
* @return -1, 0 or +1
*/
protected int compare(int i) {
if (sortOnValues) {
if (targetSearchValue > values[i]) {
return 1;
} else if (targetSearchValue < values[i]) {
return -1;
}
} else {
if (targetSearchValue > keys[i]) {
return 1;
} else if (targetSearchValue < keys[i]) {
return -1;
}
}
return 0;
}
/**
* Check if row indexed i is less than row indexed j
* @param i the first index
* @param j the second index
* @return true or false
*/
protected boolean lessThan(int i, int j) {
if (sortOnValues) {
if (values[i] < values[j]) {
return true;
}
} else {
if (keys[i] < keys[j]) {
return true;
}
}
return false;
}
protected void moveRows(int fromIndex, int toIndex, int rows) {
System.arraycopy(keys, fromIndex, keys, toIndex, rows);
System.arraycopy(values, fromIndex, values, toIndex, rows);
}
protected void doubleCapacity() {
keys = (int[]) ArrayUtil.resizeArray(keys, capacity * 2);
values = (int[]) ArrayUtil.resizeArray(values, capacity * 2);
capacity *= 2;
}
public void removeRange(int start, int limit) {
moveRows(limit, start, count - limit);
count -= (limit - start);
}
public void removeAll() {
hasChanged = true;
ArrayUtil.clearArray(ArrayUtil.CLASS_CODE_INT, keys, 0, count);
ArrayUtil.clearArray(ArrayUtil.CLASS_CODE_INT, values, 0, count);
count = 0;
}
public final synchronized void remove(int position) {
hasChanged = true;
moveRows(position + 1, position, count - position - 1);
count--;
keys[count] = 0;
values[count] = 0;
}
}