/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* KIND, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
*/ | |
package org.apache.uima.internal.util; | |
import java.lang.reflect.Array; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import java.util.Iterator; | |
import java.util.NavigableSet; | |
import java.util.NoSuchElementException; | |
import java.util.Set; | |
import java.util.SortedSet; | |
import java.util.function.Supplier; | |
import org.apache.uima.jcas.cas.TOP; | |
import org.apache.uima.util.impl.Constants; | |
/** | |
* A set of FSs, ordered using a comparator | |
* Not thread-safe, use on single thread only | |
* | |
* Use: set-sorted indexes in UIMA | |
* | |
* Entries kept in order in 1 big ArrayList | |
* | |
* Adds optimized: | |
* - maintain high mark, if >, add to end | |
* - batch adds other than above | |
* -- do when reference needed | |
* -- sort the to be added | |
* - to add to pos p, shift elements in p to higher, insert | |
* | |
* shifting optimization: | |
* removes replace element with null | |
* shift until hit null | |
* | |
* bitset: 1 for avail slot | |
* used to compute move for array copy | |
* | |
* | |
*/ | |
public class OrderedFsSet_array implements NavigableSet<TOP> { | |
// public boolean specialDebug = false; | |
final private static boolean TRACE = false; | |
final private static boolean MEASURE = false; | |
final private static int DEFAULT_MIN_SIZE = 8; // power of 2 please | |
final private static int MAX_DOUBLE_SIZE = 1024 * 1024 * 4; // 4 million, power of 2 please | |
final private static int MIN_SIZE = 8; | |
// final private static MethodHandle getActualArray; | |
// | |
// static { | |
// Field f; | |
// try { | |
// f = ArrayList.class.getDeclaredField("array"); | |
// } catch (NoSuchFieldException e) { | |
// try { | |
// f = ArrayList.class.getDeclaredField("elementData"); | |
// } catch (NoSuchFieldException e2) { | |
// throw new RuntimeException(e2); | |
// } | |
// } | |
// | |
// f.setAccessible(true); | |
// try { | |
// getActualArray = Misc.UIMAlookup.unreflectGetter(f); | |
// } catch (IllegalAccessException e) { | |
// throw new RuntimeException(e); | |
// } | |
// } | |
TOP[] a = new TOP[DEFAULT_MIN_SIZE]; | |
/** | |
* index of slot at the end which is free, all following slots are free too | |
*/ | |
int a_nextFreeslot = 0; | |
int a_firstUsedslot = 0; | |
private final ArrayList<TOP> batch = new ArrayList<>(); | |
final private Comparator<TOP> comparatorWithID; | |
final public Comparator<TOP> comparatorWithoutID; | |
private int size = 0; | |
private int maxSize = 0; | |
private TOP highest = null; | |
private int nullBlockStart = -1; // inclusive | |
private int nullBlockEnd = -1 ; // exclusive | |
private boolean doingBatchAdds = false; | |
private int modificationCount = 0; | |
private int lastRemovedPos = -1; | |
private StringBuilder tr = TRACE ? new StringBuilder() : null; | |
public OrderedFsSet_array(Comparator<TOP> comparatorWithID, Comparator<TOP> comparatorWithoutID) { | |
this.comparatorWithID = comparatorWithID; | |
this.comparatorWithoutID = comparatorWithoutID; | |
} | |
/** | |
* copy constructor | |
* @param set the original to be copied | |
*/ | |
public OrderedFsSet_array(OrderedFsSet_array set) { | |
set.processBatch(); | |
this.a = set.a.clone(); | |
this.a_nextFreeslot = set.a_nextFreeslot; | |
this.a_firstUsedslot = set.a_firstUsedslot; | |
this.comparatorWithID = set.comparatorWithID; | |
this.comparatorWithoutID = set.comparatorWithoutID; | |
this.size = set.size; | |
this.maxSize = set.maxSize; | |
this.highest = set.highest; | |
this.nullBlockStart = set.nullBlockStart; | |
this.nullBlockEnd = set.nullBlockEnd; | |
this.modificationCount = set.modificationCount; | |
this.lastRemovedPos = set.lastRemovedPos; | |
} | |
public OrderedFsSet_array(OrderedFsSet_array set, boolean isReadOnly) { | |
if (!isReadOnly) Misc.internalError(); | |
set.processBatch(); | |
this.size = set.size; | |
this.a = (size == 0) ? Constants.EMPTY_TOP_ARRAY : set.a.clone(); | |
this.a_nextFreeslot = set.a_nextFreeslot; | |
this.a_firstUsedslot = set.a_firstUsedslot; | |
this.comparatorWithID = set.comparatorWithID; | |
this.comparatorWithoutID = set.comparatorWithoutID; | |
this.maxSize = set.maxSize; | |
this.highest = set.highest; | |
this.nullBlockStart = set.nullBlockStart; | |
this.nullBlockEnd = set.nullBlockEnd; | |
this.modificationCount = set.modificationCount; | |
this.lastRemovedPos = set.lastRemovedPos; | |
} | |
/** | |
* @see SortedSet#comparator() | |
*/ | |
@Override | |
public Comparator<? super TOP> comparator() { | |
return comparatorWithID; | |
} | |
/** | |
* @see SortedSet#first() | |
*/ | |
@Override | |
public TOP first() { | |
processBatch(); | |
if (size == 0) { | |
throw new NoSuchElementException(); | |
} | |
for (int i = a_firstUsedslot; i < a_nextFreeslot; i++) { | |
TOP item = a[i]; | |
if (null != item) { | |
if (i > a_firstUsedslot) { | |
a_firstUsedslot = i; | |
} | |
return item; | |
} | |
} | |
Misc.internalError(); | |
return null; | |
} | |
/** | |
* @see SortedSet#last() | |
*/ | |
@Override | |
public TOP last() { | |
processBatch(); | |
if (size == 0) { | |
throw new NoSuchElementException(); | |
} | |
for (int i = a_nextFreeslot - 1; i >= a_firstUsedslot; i--) { | |
TOP item = a[i]; | |
if (item != null) { | |
if (i < a_nextFreeslot - 1) { | |
a_nextFreeslot = i + 1; | |
} | |
return item; | |
} | |
} | |
Misc.internalError(); | |
return null; | |
} | |
/** | |
* @see Set#size() | |
*/ | |
@Override | |
public int size() { | |
processBatch(); | |
return size; | |
} | |
/** | |
* @see Set#isEmpty() | |
*/ | |
@Override | |
public boolean isEmpty() { | |
return size == 0 && batch.size() == 0; | |
} | |
/** | |
* @see Set#contains(Object) | |
*/ | |
@Override | |
public boolean contains(Object o) { | |
if (o == null) { | |
throw new IllegalArgumentException(); | |
} | |
if (isEmpty()) { | |
return false; | |
} | |
TOP fs = (TOP) o; | |
processBatch(); | |
return find(fs) >= 0; | |
} | |
/** | |
* @see Set#toArray() | |
*/ | |
@Override | |
public Object[] toArray() { | |
Object [] r = new Object[size()]; | |
int i = 0; | |
for (TOP item : a) { | |
if (item != null) { | |
r[i++] = item; | |
} | |
} | |
// try { // debug | |
assert r.length == i; | |
// } catch (AssertionError e) { // debug | |
// System.err.format("size: %,d, final index: %,d, array length: %,d%n", size(), i, a.length ); | |
// for (int di = 0; di < a.length; di++) { | |
// System.err.format("a[%,d] = %s%n", di, a[di]); | |
// } | |
// System.err.format("first used slot: %,d, next free slot: %,d batch size: %,d," | |
// + " nullblockstart: %,d nullBlockEnd: %d, lastRemovedPos: %,d", | |
// a_firstUsedslot, a_nextFreeslot, batch.size(), nullBlockStart, nullBlockEnd, | |
// lastRemovedPos); | |
// throw e; | |
// } | |
return r; | |
} | |
/** | |
* @see Set#toArray(Object[]) | |
*/ | |
@SuppressWarnings("unchecked") | |
@Override | |
public <T> T[] toArray(T[] a1) { | |
if (a1.length < size()) { | |
a1 = (T[]) Array.newInstance(a.getClass(), size()); | |
} | |
int i = 0; | |
for (TOP item : a) { | |
if (item != null) { | |
a1[i++] = (T) item; | |
} | |
} | |
if (i < a1.length) { | |
a1[i] = null; // contract for toArray, when array bigger than items | |
} | |
return a1; | |
} | |
/** | |
* Note: doesn't implement the return value; always returns true; | |
* @see Set#add(Object) | |
*/ | |
@Override | |
public boolean add(TOP fs) { | |
if (highest == null) { | |
addNewHighest(fs); | |
return true; | |
} | |
int c = comparatorWithID.compare(fs, highest); | |
if (c > 0) { | |
addNewHighest(fs); | |
return true; | |
} | |
if (c == 0) { | |
return false; | |
} | |
batch.add(fs); | |
if (MEASURE) { | |
addNotToEndCount ++; | |
} | |
return true; | |
} | |
private void addNewHighest(TOP fs) { | |
highest = fs; | |
ensureCapacity(1); | |
a[a_nextFreeslot++] = fs; | |
incrSize(); | |
if (MEASURE) { | |
addToEndCount++; | |
} | |
return; | |
} | |
private void incrSize() { | |
size++; | |
maxSize = Math.max(maxSize, size); | |
modificationCount++; | |
} | |
// /** validate array | |
// * number of non-null elements == size | |
// */ | |
// // debug | |
// private void validateA() { | |
// synchronized (batch) { | |
// try { | |
// if (nullBlockStart != -1) { | |
// assert a[nullBlockStart] == null; | |
// if (nullBlockStart > 0) { | |
// assert a[nullBlockStart - 1] != null; | |
// } | |
// } | |
// int sz = 0; | |
// for (TOP item : a) { | |
// if (item != null) { | |
// sz++; | |
// } | |
// } | |
// // if (sz != size) { | |
// // System.out.format("debug error OrderedFsSet_array size(): %,d array non-null element count: %,d%n", | |
// // size, sz); | |
// // } | |
// assert sz == size; | |
// for (int i = 0; i < a_firstUsedslot; i++) { | |
// assert a[i] == null; | |
// } | |
// for (int i = a_nextFreeslot; i < a.length; i++) { | |
// assert a[i] == null; | |
// } | |
// assert a_firstUsedslot < a_nextFreeslot; | |
// TOP prev = a[a_firstUsedslot]; | |
// for (int i = a_firstUsedslot + 1; i < a_nextFreeslot; i++) { | |
// TOP fs = a[i]; | |
// if (fs != null) { | |
// assert comparatorWithID.compare(fs, prev) > 0; | |
// prev = fs; | |
// } | |
// } | |
// } catch (AssertionError e) { | |
// e.printStackTrace(); | |
// } | |
// } | |
// } | |
private void ensureCapacity(int incr) { | |
int szNeeded = a_nextFreeslot + incr; | |
if (szNeeded <= a.length) { | |
return; | |
} | |
int sz = a.length; | |
do { | |
sz = (sz < MAX_DOUBLE_SIZE) ? (sz << 1) : (sz + MAX_DOUBLE_SIZE); | |
} while (sz < szNeeded); | |
TOP[] aa = new TOP[sz]; | |
System.arraycopy(a, 0, aa, 0, a_nextFreeslot); | |
a = aa; | |
} | |
private boolean shrinkCapacity() { | |
int nextSmallerSize = getNextSmallerSize(2); | |
if (nextSmallerSize == MIN_SIZE) { | |
return false; | |
} | |
if (maxSize < nextSmallerSize) { | |
a = new TOP[getNextSmallerSize(1)]; | |
maxSize = 0; | |
return true; | |
} | |
maxSize = 0; | |
return false; | |
} | |
/** | |
* get next smaller size | |
* @param n number of increments | |
* @return the size | |
*/ | |
private int getNextSmallerSize(int n) { | |
int sz = a.length; | |
if (sz <= MIN_SIZE) { | |
return MIN_SIZE; | |
} | |
for (int i = 0; i < n; i ++) { | |
sz = (sz > MAX_DOUBLE_SIZE) ? (sz - MAX_DOUBLE_SIZE) : sz >> 1; | |
} | |
return sz; | |
} | |
void processBatch() { | |
if (batch.size() != 0) { | |
// validateA(); | |
doProcessBatch(); | |
// validateA(); | |
} | |
} | |
/** | |
* Because multiple threads can be "reading" the CAS and using iterators, | |
* the sync must insure that the setting of batch.size() to 0 occurs after | |
* all the adding is done. | |
* | |
* This keeps other threads blocked until the batch is completely processed. | |
*/ | |
private void doProcessBatch() { | |
synchronized (batch) { | |
int batchSize = batch.size(); | |
if (batchSize == 0) { | |
return; // another thread did this | |
} | |
if (doingBatchAdds == true) { | |
return; // bypass recursive calls from Eclipse IDE on same thread | |
} | |
try { | |
// validateA(); | |
doingBatchAdds = true; | |
if (MEASURE) { | |
batchAddCount ++; | |
batchAddTotal += batchSize; | |
batchCountHistogram[31 - Integer.numberOfLeadingZeros(batchSize)] ++; | |
} | |
/* the number of new empty slots created, | |
* may end up being larger than actually used because some of the items | |
* being inserted may already be in the array | |
* - decreases as each item is actually inserted into the array | |
*/ | |
int nbrNewSlots = 1; // start at one, may increase | |
if (batchSize > 1) { | |
// Sort the items to add | |
Collections.sort(batch, comparatorWithID); | |
TOP prev = batch.get(batchSize - 1); | |
// nbrNewSlots = batch.size(); | |
// count dups (to reduce excess allocations) | |
// deDups done using the comparatorWithID | |
final boolean useEq = comparatorWithID != comparatorWithoutID; // true for Sorted, false for set | |
for (int i = batchSize - 2; i >= 0; i--) { | |
TOP item = batch.get(i); | |
if (useEq ? (item == prev) : (comparatorWithID.compare(item, prev) == 0)) { | |
batch.set(i + 1, null); // need to do this way so the order of adding is the same as v2 | |
if (i + 1 == batchSize - 1) { | |
batchSize --; // start with non-null when done | |
} | |
} else { | |
prev = item; | |
nbrNewSlots++; // count of items that will actually be added; skips the duplicates | |
} | |
} | |
} | |
int i_batch = batchSize - 1; | |
int insertPosOfAddedSpace = 0; | |
TOP itemToAdd; | |
// skip entries already found | |
itemToAdd = batch.get(i_batch); | |
while (itemToAdd == null || (insertPosOfAddedSpace = find(itemToAdd)) >= 0) { | |
// skip any entries at end of list if they're already in the set | |
i_batch--; | |
nbrNewSlots --; | |
if (i_batch < 0) { | |
batch.clear(); | |
return; // all were already in the index | |
} | |
itemToAdd = batch.get(i_batch); | |
} | |
assert nbrNewSlots > 0; // otherwise batch would be empty and would have returned before | |
// insertPosOfAddedSpace is index to non-null item that is > itemToAdd | |
// or points to 1 beyond current size | |
insertPosOfAddedSpace = (- insertPosOfAddedSpace) - 1; | |
// insertPos is insert point, i_batch is index of first batch element to insert | |
// there may be other elements in batch that duplicate; these won't be inserted, but | |
// there will be space lost in this case | |
int indexOfNewItem = insertSpace(insertPosOfAddedSpace, nbrNewSlots) // returns index of a non-null item | |
// the new item goes one spot to the left of this | |
- 1; // inserts nulls at the insert point, shifting other cells down | |
int nbrNewSlotsRemaining = nbrNewSlots; // will be decremented as slots are used | |
// process first item | |
insertItem(indexOfNewItem, itemToAdd); | |
// TOP prevItem = itemToAdd; | |
if (indexOfNewItem + 1 == a_nextFreeslot) { | |
highest = itemToAdd; | |
} | |
nbrNewSlotsRemaining --; | |
int bPos = i_batch - 1; // next after first one from end | |
for (; bPos >= 0; bPos --) { | |
itemToAdd = batch.get(bPos); | |
if (null == itemToAdd) { | |
continue; // skipping a duplicate | |
} | |
int pos = findRemaining(itemToAdd); | |
if (pos >= 0) { | |
continue; // already in the list | |
} | |
pos = (-pos) - 1; // pos is the insert point, new item goes 1 to left of this | |
indexOfNewItem = pos - 1; // where the new item goes, 1 to left of insert point | |
if (nullBlockStart == 0) { | |
// this and all the rest of the elements are lower, insert in bulk | |
// because all are lower, none are in the array, so don't need the compare check | |
insertItem(indexOfNewItem--, itemToAdd); | |
nbrNewSlotsRemaining --; | |
bPos--; | |
for (;bPos >= 0; bPos--) { | |
itemToAdd = batch.get(bPos); | |
if (itemToAdd == null) { | |
continue; | |
} | |
insertItem(indexOfNewItem--, itemToAdd); | |
nbrNewSlotsRemaining --; // do this way to respect skipped items due to == to prev | |
} | |
break; | |
} | |
// validateA(); | |
if (indexOfNewItem == -1 || null != a[indexOfNewItem]) { | |
indexOfNewItem = shiftFreespaceDown(pos, nbrNewSlotsRemaining) - 1; // results in null being available at pos - 1 | |
} | |
insertItem(indexOfNewItem, itemToAdd); | |
nbrNewSlotsRemaining --; | |
} | |
if (nbrNewSlotsRemaining > 0) { | |
// have extra space left over due to dups not being added | |
// If this space is not at beginning, move space to beginning or end (whichever is closer) | |
// if (indexOfNewItem - nbrNewSlotsRemaining > 0) { | |
if (nullBlockEnd != a_firstUsedslot) { | |
// space is not at beginning | |
int mid = (a_nextFreeslot + a_firstUsedslot) >>> 1; // overflow aware | |
if (indexOfNewItem < mid) { | |
shiftFreespaceDown(a_firstUsedslot, nbrNewSlotsRemaining); | |
// System.arraycopy(a, indexOfNewItem - nbrNewSlots, a, 0, nbrNewSlots); | |
// a_firstUsedslot += nbrNewSlots; | |
// validateA(); | |
} else { | |
shiftFreespaceUp(a_nextFreeslot, nbrNewSlotsRemaining); | |
a_nextFreeslot -= nbrNewSlotsRemaining; | |
// // move to end | |
// System.arraycopy(a, indexOfNewItem, a, indexOfNewItem - nbrNewSlots, a_nextFreeslot - indexOfNewItem); | |
// Arrays.fill(a, a_nextFreeslot - nbrNewSlots, a_nextFreeslot, null); | |
// a_nextFreeslot -= nbrNewSlots; | |
// validateA(); | |
} | |
} | |
} | |
nullBlockStart = nullBlockEnd = -1; | |
// validateA(); | |
batch.clear(); | |
} finally { | |
doingBatchAdds = false; | |
} | |
} | |
} | |
/** | |
* side effects: | |
* increment size | |
* reset a_firstUsedslot if adding in front | |
* ( a_nextFreeslot not updated, because this method only called to inserting before end ) | |
* nullBlockEnd reduced conditionally | |
* @param indexToUpdate - the index in the array to update with the item to add | |
* @param itemToAdd - | |
*/ | |
private void insertItem(int indexToUpdate, TOP itemToAdd) { | |
// validateA(); | |
try { | |
assert indexToUpdate >= 0; | |
assert null == a[indexToUpdate]; | |
} catch (AssertionError e) { | |
if (TRACE) { | |
System.err.println("OrderedFsSet_array caught assert. array values around indexToUpdate: "); | |
for (int i = indexToUpdate - 2; i < indexToUpdate + 3; i++) { | |
if (i >= 0 && i < a.length) { | |
System.err.format("a[%,d]: %s%n", i, a[i].toString(2)); | |
} else { | |
System.err.format("a[%,d}: out-of-range%n", i); | |
} | |
} | |
System.err.format("trace info: %n%s", tr); | |
} | |
throw e; | |
} | |
a[indexToUpdate] = itemToAdd; | |
incrSize(); | |
if (indexToUpdate < a_firstUsedslot) { | |
a_firstUsedslot = indexToUpdate; | |
} | |
if (nullBlockEnd == indexToUpdate + 1) { | |
nullBlockEnd --; | |
} | |
if (nullBlockStart == indexToUpdate) { | |
nullBlockStart = -1; | |
} | |
// validateA(); | |
} | |
/** | |
* Attempt to move a small amount; make use of both beginning and end free space. | |
* | |
* @param positionToInsert position containing a value, to free up by moving the current free block | |
* so that the last free element is at that (adjusted up) position. | |
* @param nbrNewSlots | |
* @return adjusted positionToInsert, the free spot is just to the left of this position | |
*/ | |
private int insertSpace(final int positionToInsert, int nbrNewSlots) { | |
if (TRACE) { | |
tr.setLength(0); | |
tr.append("Tracing OrderedFsSet_array\n"); | |
tr.append(String.format("insertSpace called with positionToInsert: %,d nbrNewSlots: %,d%n", positionToInsert, nbrNewSlots)); | |
} | |
// while the positionToInsert (a ref to non-null or 1 past end) | |
// is > 0 && the pos to the left is null, | |
// reduce the nbrNewSlots | |
int i = positionToInsert; | |
int nullsBelowInsertMin = -1; | |
while (i > 0 && a[i - 1] == null && nbrNewSlots > 0) { | |
i--; | |
nbrNewSlots--; | |
nullsBelowInsertMin = i; | |
} | |
if (nbrNewSlots == 0) { | |
return positionToInsert; // because previous exists and is empty | |
} | |
// nbrNewSlots, if at front, reduced by | |
if (nbrNewSlots == 1) { | |
int distanceFromLastRemoved = (lastRemovedPos == -1) | |
? Integer.MAX_VALUE | |
: (positionToInsert - lastRemovedPos); | |
int distanceFromEnd = a_nextFreeslot - positionToInsert; | |
int distanceFromFront = (0 == a_firstUsedslot) | |
? Integer.MAX_VALUE | |
: positionToInsert - a_firstUsedslot; | |
boolean useFront = | |
// make sure size of front free space is not included in previous nulls already counted | |
a_firstUsedslot > positionToInsert && | |
distanceFromFront < distanceFromEnd; | |
boolean useLastRemoved = | |
// make sure lastRemovedPos is outside range already counted | |
(lastRemovedPos > positionToInsert && | |
lastRemovedPos < nullsBelowInsertMin) | |
? (Math.abs(distanceFromLastRemoved) < (useFront | |
? distanceFromFront | |
: distanceFromEnd)) | |
: false; | |
if (TRACE) | |
tr.append(String.format("distances: %d %d %d, useFront: %s useLastRemoved: %s%n", | |
distanceFromLastRemoved, distanceFromEnd, distanceFromFront, useFront, useLastRemoved)); | |
if (useLastRemoved) { // due to find skipping over nulls, the distanceFromLastRemoved is never 0 | |
if (distanceFromLastRemoved > 0) { | |
if (distanceFromLastRemoved != 1) { | |
nullBlockStart = lastRemovedPos; | |
nullBlockEnd = lastRemovedPos + 1; | |
shiftFreespaceUp(lastRemovedPos, nbrNewSlots); // move one slot (since nullblockstart/end set above | |
} | |
lastRemovedPos = -1; | |
return positionToInsert; | |
} else { | |
nullBlockStart = lastRemovedPos; | |
lastRemovedPos = -1; | |
int r = shiftFreespaceDown(positionToInsert, nbrNewSlots); | |
if (TRACE) | |
tr.append(String.format("shiftFreespaceDown result was %,d%n", r)); | |
return r; | |
} | |
} | |
if (useFront) { | |
nullBlockStart = a_firstUsedslot - 1; | |
// if (null != a[nullBlockStart]) { | |
if (a_firstUsedslot != positionToInsert) { | |
// need to move the free slot if not already next to the insert position | |
nullBlockEnd = a_firstUsedslot; | |
shiftFreespaceUp(positionToInsert, nbrNewSlots); | |
} | |
// a_firstUsedslot --; // not done here, done in insert routine | |
return positionToInsert; | |
} | |
} | |
// using space at end | |
ensureCapacity(nbrNewSlots); | |
nullBlockStart = a_nextFreeslot; | |
nullBlockEnd = nullBlockStart + nbrNewSlots; | |
a_nextFreeslot += nbrNewSlots; | |
int r = shiftFreespaceDown(positionToInsert, nbrNewSlots); | |
if (TRACE) { | |
tr.append(String.format("shiftFreespaceDown2 result was %,d, nullBlockStart: %,d nullBlockEnd: %,d a_nextFreeslot: %,d%n", | |
r, nullBlockStart, nullBlockEnd, a_nextFreeslot)); | |
} | |
return r; | |
} | |
/** | |
* Shift a block of free space lower in the array. | |
* This is done by shifting the space at the insert point | |
* for length = start of free block - insert point | |
* to the right by the nbrNewSlots | |
* and then resetting (filling) the freed up space with null | |
* | |
* Example: u = used, f = free space | |
* | |
* before |--| | |
* uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuffffffffuuuuuuu | |
* ^ insert point | |
* after |--| | |
* uuuuuuuuuuuuuuuuuuuuuuuuuuuuffffffffuuuuuuuuuuu | |
* ^ insert point | |
* | |
* before | |
* |------------------------------| | |
* uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuffffffffuuuuuuu | |
* ^ insert point | |
* after |------------------------------| | |
* ffffffffuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu | |
* ^ insert point | |
* before | |
* |------------------------------| | |
* ffffuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuffffffffuuuuuuu | |
* ^ insert point | |
* after |------------------------------| | |
* ffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu | |
* ^ insert point | |
* | |
* move up by nbrNewSlots | |
* length to move = nullBlockStart - insert point | |
* new insert point is nbrOfFreeSlots higher (this points to a filled spot, prev spot is free) | |
* | |
* fill goes from original newInsertPoint, for min(nbrNewSlots, length of move) | |
* | |
* hidden param: nullBlockStart | |
* @param insertPoint index of slot array, currently occupied, where an item is to be set into | |
* @param nbrNewSlots - the size of the inserted space | |
* @return the updated insert point, now moved up | |
*/ | |
private int shiftFreespaceDown(final int insertPoint, final int nbrNewSlots) { | |
assert insertPoint >= 0; | |
assert nbrNewSlots >= 0; | |
int lengthToMove = nullBlockStart - insertPoint; | |
System.arraycopy(a, insertPoint, a, insertPoint + nbrNewSlots, lengthToMove); | |
int lengthToClear = Math.min(nbrNewSlots, lengthToMove); | |
Arrays.fill(a, insertPoint, insertPoint + lengthToClear, null); | |
nullBlockStart = insertPoint; | |
nullBlockEnd = nullBlockStart + nbrNewSlots; | |
// adjust nullBlockStart to account for nulls in front | |
int i = insertPoint - 1; | |
for (; i >= 0; i--) { | |
if (a[i] != null) { | |
break; | |
} | |
} | |
nullBlockStart = i + 1; | |
if (MEASURE) { | |
moveSizeHistogram[32 - Integer.numberOfLeadingZeros(lengthToMove)] ++; | |
movePctHistogram[lengthToMove* 10 / (a_nextFreeslot - a_firstUsedslot)] ++; | |
fillHistogram[32 - Integer.numberOfLeadingZeros(lengthToClear)] ++; | |
} | |
if (insertPoint == a_firstUsedslot) { | |
a_firstUsedslot = insertPoint + nbrNewSlots; | |
} | |
return insertPoint + nbrNewSlots; | |
} | |
/** | |
* Shift a block of free space higher in the array. | |
* This is done by shifting the space at the insert point | |
* of length = insert point - (end+1) of free block | |
* to the left by the nbrNewSlots | |
* and then resetting (filling) the freed up space with null | |
* | |
* Example: u = used, f = free space | |
* | |
* before |-| << block shifted | |
* uuuuuuuuuuuuuuufffffuuuuuuuuuuuuuuuuuuuuuuuuuuu | |
* ^ insert point | |
* after |-| << block shifted | |
* uuuuuuuuuuuuuuuuuufffffuuuuuuuuuuuuuuuuuuu | |
* ^ insert point | |
* | |
* before |----| | |
* uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuffffuuuuuuu | |
* ^ insert point | |
* note: insert point is never beyond last because | |
* those are added immediately | |
* after |----| | |
* uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuffffu | |
* ^ insert point | |
* | |
* before |--| | |
* uuuuuuuuuuuufuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu | |
* ^ insert point | |
* after |--| | |
* uuuuuuuuuuuuuuuufuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu | |
* ^ insert point | |
* | |
* |--------| before | |
* ffffuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu | |
* ^ insert point | |
* |--------| | |
* uuuuuuuuuuffffuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu | |
* ^ insert point | |
* | |
* | |
* move down by nbrNewSlots | |
* length to move = insert point - null block end (which is 1 plus index of last free) | |
* new insert point is the same as the old one (this points to a filled spot, prev spot is free) | |
* | |
* fill goes from original null block end, for min(nbrNewSlots, length of move) | |
* | |
* hidden param: nullBlockEnd = 1 past end of last free slot | |
* @param newInsertPoint index of slot array, currently occupied, where an item is to be set into | |
* @param nbrNewSlots - the size of the inserted space | |
* @return the updated insert point, now moved up | |
*/ | |
private int shiftFreespaceUp(int newInsertPoint, int nbrNewSlots) { | |
boolean need2setFirstUsedslot = nullBlockEnd == a_firstUsedslot; | |
int lengthToMove = newInsertPoint - nullBlockEnd; | |
System.arraycopy(a, nullBlockEnd, a, nullBlockStart, lengthToMove); | |
int lengthToClear = Math.min(nbrNewSlots, lengthToMove); | |
Arrays.fill(a, newInsertPoint - lengthToClear, newInsertPoint, null); | |
nullBlockStart = newInsertPoint - nbrNewSlots; | |
nullBlockEnd = newInsertPoint; | |
if (need2setFirstUsedslot) { | |
a_firstUsedslot = 0; | |
} | |
return newInsertPoint; | |
} | |
// /** | |
// * @param from start of items to shift, inclusive | |
// * @param to end of items to shift, exclusive | |
// */ | |
// private void shiftBy2(int from, int to) { | |
// if (to == -1) { | |
// to = theArray.size(); | |
// theArray.add(null); | |
// theArray.add(null); | |
// } | |
// try { | |
// Object[] aa = (Object[]) getActualArray.invokeExact(theArray); | |
// System.arraycopy(aa, from, aa, from + 2, to - from); | |
// } catch (Throwable e) { | |
// throw new RuntimeException(e); | |
// } | |
// } | |
/** | |
* Never returns an index to a "null" (deleted) item. | |
* If all items are LT key, returns - size - 1 | |
* @param fs the key | |
* @param pos first position to compare | |
* @return the lowest position whose item is equal to or greater than fs; | |
* if not equal, the item's position is returned as -insertionPoint - 1. | |
* If the key is greater than all elements, return -size - 1). | |
*/ | |
private int find(TOP fs) { | |
if (size == 0) { | |
return -1; | |
} | |
return binarySearch(fs); | |
} | |
private int findRemaining(TOP fs) { | |
int pos = binarySearch(fs, a_firstUsedslot, nullBlockStart); | |
return pos < 0 && ((-pos) - 1 == nullBlockStart) | |
? ( -(nullBlockEnd) - 1) | |
: pos; | |
} | |
/** | |
* Special version of binary search that ignores null values | |
* @param fs the value to look for | |
* @return the position whose non-null value is equal to fs, or is gt fs (in which case, return (-pos) - 1) | |
*/ | |
private int binarySearch(final TOP fs) { | |
return binarySearch(fs, a_firstUsedslot, a_nextFreeslot); | |
} | |
private int binarySearch(final TOP fs, int start, int end) { | |
if (start < 0 || end - start <= 0) { | |
return (start < 0) ? -1 : ( (-start) - 1); // means not found, insert at position start | |
} | |
int lower = start, upper = end; | |
int prevUpperPos = -1; | |
for (;;) { | |
int mid = (lower + upper) >>> 1; // overflow aware | |
TOP item = a[mid]; | |
int delta = 0; | |
int midup = mid; | |
int middwn = mid; | |
int pos = mid; | |
while (null == item) { // skip over nulls | |
if (nullBlockStart != -1 && | |
middwn >= nullBlockStart && | |
midup < nullBlockEnd) { | |
// in the null block | |
// move to edges | |
midup = nullBlockEnd; // midup inclusive, nullBlockEnd exclusive | |
middwn = nullBlockStart - 1; // middwn and nullBlockStart inclusive | |
} else { | |
delta ++; | |
} | |
boolean belowUpper = (pos = midup + delta) < upper; | |
if (belowUpper && null != (item = a[pos])) { | |
break; | |
} | |
boolean belowLower = (pos = middwn - delta) < lower; | |
if (!belowLower && null != (item = a[pos])) { | |
break; | |
} | |
if (! belowUpper && belowLower) { | |
return (-prevUpperPos) - 1; // return previous | |
} | |
} | |
int c = comparatorWithID.compare(fs, item); | |
if (c == 0) { | |
return pos; | |
} | |
if (c < 0) { // fs is smaller than item at pos in array; search downwards | |
upper = prevUpperPos = pos; | |
if (upper == lower) { | |
return (-lower) - 1; | |
} | |
} else { // fs is larger than item at pos in array; search upwards | |
lower = pos + 1; | |
if (lower == upper) { | |
return (-upper) - 1; | |
} | |
} | |
} | |
} | |
/** | |
* @see Set#remove(Object) | |
*/ | |
@Override | |
public boolean remove(Object o) { | |
processBatch(); | |
TOP fs = (TOP) o; | |
int pos = find(fs); | |
if (pos < 0) { | |
return false; | |
} | |
// at this point, pos points to a spot that compares "equal" using the comparator | |
// for sets, this is the single item that is in the index | |
// for sorted, because find uses the compareWithID comparator, this is the unique equal element | |
assert a[pos] != null; | |
a[pos] = null; | |
size --; | |
modificationCount ++; | |
if (size == 0) { | |
clearResets(); // also clears last removed pos | |
} else { | |
// size is > 0 | |
if (pos == a_firstUsedslot) { | |
do { // removed the first used slot | |
a_firstUsedslot ++; | |
} while (a[a_firstUsedslot] == null); | |
} else if (pos == a_nextFreeslot - 1) { | |
do { | |
a_nextFreeslot --; | |
} while (a[a_nextFreeslot - 1] == null); | |
highest = a[a_nextFreeslot - 1]; | |
} | |
if (size < ((a_nextFreeslot - a_firstUsedslot) >> 1) && | |
size > 8) { | |
compressOutRemoves(); // also clears lastRemovedPos | |
} else { | |
// update lastRemovedPos | |
lastRemovedPos = (pos > a_firstUsedslot && pos < (a_nextFreeslot - 1)) | |
? pos // is a valid position | |
: -1; // is not a valid position | |
} | |
} | |
return true; | |
} | |
/** | |
* When the main array between the first used slot and the next free slot has too many nulls | |
* representing removed items, scan and gc them. | |
* assumes: first used slot is not null, nextFreeslot - 1 is not null | |
*/ | |
private void compressOutRemoves() { | |
int j = a_firstUsedslot + 1; | |
for (int i = a_firstUsedslot + 1; i < a_nextFreeslot; i++, j++) { | |
while (a[i] == null) { | |
i ++; | |
} | |
if (i > j) { | |
a[j] = a[i]; | |
} | |
} | |
Arrays.fill(a, j, a_nextFreeslot, null); | |
a_nextFreeslot = j; | |
lastRemovedPos = -1; | |
} | |
/** | |
* @see Set#containsAll(Collection) | |
*/ | |
@Override | |
public boolean containsAll(Collection<?> c) { | |
throw new UnsupportedOperationException(); | |
} | |
/** | |
* @see Set#addAll(Collection) | |
*/ | |
@Override | |
public boolean addAll(Collection<? extends TOP> c) { | |
boolean changed = false; | |
for (TOP item : c) { | |
changed |= add(item); | |
} | |
return changed; | |
} | |
/** | |
* @see Set#retainAll(Collection) | |
*/ | |
@Override | |
public boolean retainAll(Collection<?> c) { | |
throw new UnsupportedOperationException(); | |
} | |
/** | |
* @see Set#removeAll(Collection) | |
*/ | |
@Override | |
public boolean removeAll(Collection<?> c) { | |
throw new UnsupportedOperationException(); | |
} | |
/** | |
* @see Set#clear() | |
*/ | |
@Override | |
public void clear() { | |
if (isEmpty()) { | |
return; | |
} | |
if (!shrinkCapacity()) { | |
// //debug | |
// if (a_firstUsedslot == -1) { | |
// System.out.println("a_firstUsedslot was -1"); | |
// } | |
// if (a_nextFreeslot == -1) { | |
// System.out.println("a_nextFreeslot was -1"); | |
// } | |
Arrays.fill(a, a_firstUsedslot, a_nextFreeslot, null); | |
} | |
clearResets(); | |
} | |
private void clearResets() { | |
a_firstUsedslot = 0; | |
a_nextFreeslot = 0; | |
batch.clear(); | |
size = 0; | |
maxSize = 0; | |
nullBlockStart = -1; | |
nullBlockEnd = -1; | |
doingBatchAdds = false; // just for safety, not logically needed I think. | |
highest = null; | |
modificationCount ++; | |
lastRemovedPos = -1; | |
} | |
/** | |
* @see NavigableSet#lower(Object) | |
*/ | |
@Override | |
public TOP lower(TOP fs) { | |
int pos = lowerPos(fs); | |
return (pos < 0) ? null : a[pos]; | |
} | |
/** | |
* @param fs element to test | |
* @return pos of greatest element less that fs or -1 if no such | |
*/ | |
public int lowerPos(TOP fs) { | |
processBatch(); | |
int pos = find(fs); // position of lowest item GE fs | |
pos = (pos < 0) ? ((-pos) - 2) : (pos - 1); | |
// above line subtracts 1 from LE pos; pos is now lt, may be -1 | |
while (pos >= a_firstUsedslot) { | |
if (a[pos] != null) { | |
return pos; | |
} | |
pos --; | |
} | |
return -1; | |
} | |
/** | |
* @see NavigableSet#floor(Object) | |
*/ | |
@Override | |
public TOP floor(TOP fs) { | |
int pos = floorPos(fs); | |
return (pos < 0) ? null : a[pos]; | |
} | |
/** | |
* @param fs - | |
* @return - | |
*/ | |
public int floorPos(TOP fs) { | |
processBatch(); | |
int pos = find(fs); // position of lowest item GE fs | |
if (pos < 0){ | |
pos = (-pos) - 2; | |
} | |
// pos is = or lt, may be -1 | |
while (pos >= a_firstUsedslot) { | |
if (a[pos] != null) { | |
return pos; | |
} | |
pos --; | |
} | |
return -1; | |
} | |
/** | |
* @see NavigableSet#ceiling(Object) | |
*/ | |
@Override | |
public TOP ceiling(TOP fs) { | |
int pos = ceilingPos(fs); | |
return (pos < a_nextFreeslot) ? a[pos] : null; | |
} | |
/** | |
* @param fs - | |
* @return - | |
*/ | |
public int ceilingPos(TOP fs) { | |
processBatch(); | |
int pos = find(fs); // position of lowest item GE fs | |
if (pos < 0){ | |
pos = (-pos) -1; | |
} else { | |
return pos; | |
} | |
while (pos < a_nextFreeslot) { | |
if (a[pos] != null) { | |
return pos; | |
} | |
pos ++; | |
} | |
return pos; | |
} | |
/** | |
* @see NavigableSet#higher(Object) | |
*/ | |
@Override | |
public TOP higher(TOP fs) { | |
int pos = higherPos(fs); | |
return (pos < a_nextFreeslot) ? a[pos] : null; | |
} | |
/** | |
* @param fs the Feature Structure to use for positioning | |
* @return the position that's higher | |
*/ | |
public int higherPos(TOP fs) { | |
processBatch(); | |
int pos = find(fs); // position of lowest item GE fs | |
pos = (pos < 0) ? ((-pos) -1) : (pos + 1); | |
while (pos < a_nextFreeslot) { | |
if (a[pos] != null) { | |
return pos; | |
} | |
pos ++; | |
} | |
return pos; | |
} | |
/** | |
* @see NavigableSet#pollFirst() | |
*/ | |
@Override | |
public TOP pollFirst() { | |
throw new UnsupportedOperationException(); | |
} | |
/** | |
* @see NavigableSet#pollLast() | |
*/ | |
@Override | |
public TOP pollLast() { | |
throw new UnsupportedOperationException(); | |
} | |
/** | |
* @see Iterable#iterator() | |
*/ | |
@Override | |
public Iterator<TOP> iterator() { | |
processBatch(); | |
if (a_nextFreeslot == 0) { | |
return Collections.emptyIterator(); | |
} | |
return new Iterator<TOP>() { | |
private int pos = a_firstUsedslot; | |
{ incrToNext(); | |
if (MEASURE) { | |
int s = a_nextFreeslot - a_firstUsedslot; | |
iterPctEmptySkip[(s - size()) * 10 / s] ++; | |
} | |
} | |
@Override | |
public boolean hasNext() { | |
processBatch(); | |
return pos < a_nextFreeslot; | |
} | |
@Override | |
public TOP next() { | |
if (!hasNext()) { | |
throw new NoSuchElementException(); | |
} | |
TOP r = a[pos++]; | |
incrToNext(); | |
return r; | |
} | |
private void incrToNext() { | |
while (pos < a_nextFreeslot) { | |
if (a[pos] != null) { | |
break; | |
} | |
pos ++; | |
} | |
} | |
}; | |
} | |
/** | |
* @see NavigableSet#descendingSet() | |
*/ | |
@Override | |
public NavigableSet<TOP> descendingSet() { | |
throw new UnsupportedOperationException(); | |
} | |
/** | |
* @see NavigableSet#descendingIterator() | |
*/ | |
@Override | |
public Iterator<TOP> descendingIterator() { | |
processBatch(); | |
return new Iterator<TOP>() { | |
private int pos = a_nextFreeslot - 1; // 2 slots: next free = 2, first slot = 0 | |
// 1 slot: next free = 1, first slot = 0 | |
// 0 slots: next free = 0; first slot = 0 (not -1) | |
{ if (pos >= 0) { // pos is -1 if set is empty | |
decrToNext(); | |
} | |
} | |
@Override | |
public boolean hasNext() { | |
return pos >= a_firstUsedslot; | |
} | |
@Override | |
public TOP next() { | |
if (!hasNext()) { | |
throw new NoSuchElementException(); | |
} | |
TOP r = a[pos--]; | |
decrToNext(); | |
return r; | |
} | |
private void decrToNext() { | |
while (pos >= a_firstUsedslot) { | |
if (a[pos] != null) { | |
break; | |
} | |
pos --; | |
} | |
} | |
}; | |
} | |
/** | |
* @see NavigableSet#subSet(Object, boolean, Object, boolean) | |
*/ | |
@Override | |
public NavigableSet<TOP> subSet(TOP fromElement, boolean fromInclusive, TOP toElement, boolean toInclusive) { | |
return new SubSet(() -> this, fromElement, fromInclusive, toElement, toInclusive, false, null); | |
} | |
/** | |
* @see NavigableSet#headSet(Object, boolean) | |
*/ | |
@Override | |
public NavigableSet<TOP> headSet(TOP toElement, boolean inclusive) { | |
if (isEmpty()) { | |
return this; | |
} | |
return subSet(first(), true, toElement, inclusive); | |
} | |
/** | |
* @see NavigableSet#tailSet(Object, boolean) | |
*/ | |
@Override | |
public NavigableSet<TOP> tailSet(TOP fromElement, boolean inclusive) { | |
if (isEmpty()) { | |
return this; | |
} | |
return subSet(fromElement, inclusive, last(), true); | |
} | |
/** | |
* @see NavigableSet#subSet(Object, Object) | |
*/ | |
@Override | |
public SortedSet<TOP> subSet(TOP fromElement, TOP toElement) { | |
return subSet(fromElement, true, toElement, false); | |
} | |
/** | |
* @see NavigableSet#headSet(Object) | |
*/ | |
@Override | |
public SortedSet<TOP> headSet(TOP toElement) { | |
return headSet(toElement, false); | |
} | |
/** | |
* @see NavigableSet#tailSet(Object) | |
*/ | |
@Override | |
public SortedSet<TOP> tailSet(TOP fromElement) { | |
return tailSet(fromElement, true); | |
} | |
/** | |
* This is used in a particular manner: | |
* only used to create iterators over that subset | |
* -- no insert/delete | |
*/ | |
public static class SubSet implements NavigableSet<TOP> { | |
final Supplier<OrderedFsSet_array> theSet; | |
final private TOP fromElement; | |
final private TOP toElement; | |
final private boolean fromInclusive; | |
final private boolean toInclusive; | |
final private int firstPosInRange; | |
final private int lastPosInRange; // inclusive | |
final private TOP firstElementInRange; | |
final private TOP lastElementInRange; | |
private int sizeSubSet = -1; // lazy - computed on first ref | |
private OrderedFsSet_array theSet() { | |
return theSet.get(); | |
} | |
SubSet(Supplier<OrderedFsSet_array> theSet, TOP fromElement, boolean fromInclusive, TOP toElement, boolean toInclusive) { | |
this(theSet, fromElement, fromInclusive, toElement, toInclusive, true, theSet.get().comparatorWithID); | |
} | |
SubSet(Supplier<OrderedFsSet_array> theSet, TOP fromElement, boolean fromInclusive, TOP toElement, boolean toInclusive, boolean doTest, Comparator<TOP> comparator) { | |
this.theSet = theSet; | |
this.fromElement = fromElement; | |
this.toElement = toElement; | |
this.fromInclusive = fromInclusive; | |
this.toInclusive = toInclusive; | |
if (doTest && comparator.compare(fromElement, toElement) > 0) { | |
throw new IllegalArgumentException(); | |
} | |
OrderedFsSet_array s = theSet(); | |
theSet().processBatch(); | |
firstPosInRange = fromInclusive ? s.ceilingPos(fromElement) : s.higherPos(fromElement); | |
lastPosInRange = toInclusive ? s.floorPos(toElement) : s.lowerPos(toElement); | |
// lastPosInRange can be LT firstPosition if fromInclusive is false | |
// In this case, the subset is empty | |
if (lastPosInRange < firstPosInRange) { | |
firstElementInRange = null; | |
lastElementInRange = null; | |
} else { | |
firstElementInRange = s.a[firstPosInRange]; | |
lastElementInRange = s.a[lastPosInRange]; | |
} | |
} | |
@Override | |
public Comparator<? super TOP> comparator() { | |
return theSet().comparatorWithID; | |
} | |
@Override | |
public TOP first() { | |
return firstElementInRange; | |
} | |
@Override | |
public TOP last() { | |
return lastElementInRange; | |
} | |
@Override | |
public int size() { | |
if (firstElementInRange == null) { | |
return 0; | |
} | |
if (sizeSubSet == -1) { | |
Iterator<TOP> it = iterator(); | |
int i = 0; | |
while (it.hasNext()) { | |
it.next(); | |
i++; | |
} | |
sizeSubSet = i; | |
} | |
return sizeSubSet; | |
} | |
@Override | |
public boolean isEmpty() { | |
return size() == 0; | |
} | |
@Override | |
public boolean contains(Object o) { | |
TOP fs = (TOP) o; | |
if (!isInRange(fs)) { | |
return false; | |
} | |
return theSet().contains(o); | |
} | |
@Override | |
public Object[] toArray() { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public <T> T[] toArray(T[] a1) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public boolean add(TOP e) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public boolean remove(Object o) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public boolean containsAll(Collection<?> c) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public boolean addAll(Collection<? extends TOP> c) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public boolean retainAll(Collection<?> c) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public boolean removeAll(Collection<?> c) { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public void clear() { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public TOP lower(TOP fs) { | |
if (lastElementInRange == null || isLeFirst(fs)) { | |
return null; | |
} | |
// if the key is > lastElement, | |
// return last element | |
if (isGtLast(fs)) { | |
return lastElementInRange; | |
} | |
// in range | |
return theSet().lower(fs); | |
} | |
@Override | |
public TOP floor(TOP fs) { | |
// if the key is < the first element in the range, return null | |
if (lastElementInRange == null || isLtFirst(fs)) { | |
return null; | |
} | |
// if the key is >= lastElement, | |
// return last element | |
if (isGeLast(fs)) { | |
return lastElementInRange; | |
} | |
return theSet().floor(fs); | |
} | |
@Override | |
public TOP ceiling(TOP fs) { | |
// if the key is > the last element in the range, return null | |
if (firstElementInRange == null || isGtLast(fs)) { | |
return null; | |
} | |
if (isLeFirst(fs)) { | |
return firstElementInRange; | |
} | |
return theSet().ceiling(fs); | |
} | |
@Override | |
public TOP higher(TOP fs) { | |
if (firstElementInRange == null || isGeLast(fs)) { | |
return null; | |
} | |
if (isLtFirst(fs)) { | |
return firstElementInRange; | |
} | |
return theSet().higher(fs); | |
} | |
@Override | |
public TOP pollFirst() { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public TOP pollLast() { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public Iterator<TOP> iterator() { | |
if (firstElementInRange == null) { | |
return Collections.emptyIterator(); | |
} | |
return new Iterator<TOP>() { | |
private int pos = firstPosInRange; | |
@Override | |
public boolean hasNext() { | |
return pos <= lastPosInRange; // lastPos is inclusive | |
} | |
@Override | |
public TOP next() { | |
if (!hasNext()) { | |
throw new NoSuchElementException(); | |
} | |
TOP r = theSet().a[pos++]; | |
incrToNext(); | |
return r; | |
} | |
private void incrToNext() { | |
while (pos <= lastPosInRange) { | |
if (theSet().a[pos] != null) { | |
break; | |
} | |
pos ++; | |
} | |
} | |
}; | |
} | |
@Override | |
public NavigableSet<TOP> descendingSet() { | |
throw new UnsupportedOperationException(); | |
} | |
@Override | |
public Iterator<TOP> descendingIterator() { | |
if (firstElementInRange == null) { | |
return Collections.emptyIterator(); | |
} | |
return new Iterator<TOP>() { | |
private int pos = lastPosInRange; | |
@Override | |
public boolean hasNext() { | |
return pos >= firstPosInRange; | |
} | |
@Override | |
public TOP next() { | |
if (!hasNext()) { | |
throw new NoSuchElementException(); | |
} | |
TOP r = theSet().a[pos--]; | |
decrToNext(); | |
return r; | |
} | |
private void decrToNext() { | |
while (pos >= firstPosInRange) { | |
if (theSet().a[pos] != null) { | |
break; | |
} | |
pos --; | |
} | |
} | |
}; | |
} | |
@Override | |
public NavigableSet<TOP> subSet(TOP fromElement1, boolean fromInclusive1, TOP toElement1, | |
boolean toInclusive1) { | |
if (!isInRange(fromElement1) || !isInRange(toElement1)) { | |
throw new IllegalArgumentException(); | |
} | |
return theSet().subSet(fromElement1, fromInclusive1, toElement1, toInclusive1); | |
} | |
@Override | |
public NavigableSet<TOP> headSet(TOP toElement1, boolean inclusive) { | |
return subSet(fromElement, fromInclusive, toElement1, inclusive); | |
} | |
@Override | |
public NavigableSet<TOP> tailSet(TOP fromElement1, boolean inclusive) { | |
return subSet(fromElement1, inclusive, toElement, toInclusive); | |
} | |
@Override | |
public SortedSet<TOP> subSet(TOP fromElement1, TOP toElement1) { | |
return subSet(fromElement1, true, toElement1, false); | |
} | |
@Override | |
public SortedSet<TOP> headSet(TOP toElement1) { | |
return headSet(toElement1, true); | |
} | |
@Override | |
public SortedSet<TOP> tailSet(TOP fromElement1) { | |
return tailSet(fromElement1, false); | |
} | |
private boolean isGtLast(TOP fs) { | |
return theSet().comparatorWithID.compare(fs, lastElementInRange) > 0; | |
} | |
private boolean isGeLast(TOP fs) { | |
return theSet().comparatorWithID.compare(fs, lastElementInRange) >= 0; | |
} | |
private boolean isLtFirst(TOP fs) { | |
return theSet().comparatorWithID.compare(fs, firstElementInRange) < 0; | |
} | |
private boolean isLeFirst(TOP fs) { | |
return theSet().comparatorWithID.compare(fs, firstElementInRange) <= 0; | |
} | |
private boolean isInRange(TOP fs) { | |
return isInRangeLower(fs) && isInRangeHigher(fs); | |
} | |
private boolean isInRangeLower(TOP fs) { | |
if (firstElementInRange == null) { | |
return false; | |
} | |
int r = theSet().comparatorWithID.compare(fs, firstElementInRange); | |
return fromInclusive ? (r >= 0) : (r > 0); | |
} | |
private boolean isInRangeHigher(TOP fs) { | |
if (lastElementInRange == null) { | |
return false; | |
} | |
int r = theSet().comparatorWithID.compare(fs, lastElementInRange); | |
return toInclusive ? (r <= 0) : (r < 0); | |
} | |
} | |
public int getModificationCount() { | |
return modificationCount; | |
} | |
@Override | |
public String toString() { | |
// processBatch(); | |
StringBuilder b = new StringBuilder(); | |
b.append("OrderedFsSet_array [a="); | |
if (a != null) { | |
boolean firstTime = true; | |
for (TOP i : a) { | |
if (firstTime) { | |
firstTime = false; | |
} else { | |
b.append(",\n"); | |
} | |
if (i != null) { | |
b.append(i.toShortString()); | |
} else { | |
b.append("null"); | |
} | |
// prettyPrint(0, 2, b, true); | |
} | |
} else { | |
b.append("null"); | |
} | |
b .append(", a_nextFreeslot=").append(a_nextFreeslot) | |
.append(", a_firstUsedslot=").append(a_firstUsedslot) | |
.append(", batch=").append(batch) | |
.append(", origComparator=").append(comparatorWithID) | |
.append(", size=").append(size) | |
.append(", maxSize=").append(maxSize) | |
.append(", highest=").append(highest) | |
.append(", nullBlockStart=").append(nullBlockStart) | |
.append(", nullBlockEnd=").append(nullBlockEnd).append("]"); | |
return b.toString(); | |
} | |
// these are approximate - don't take into account multi-thread access | |
static private int addToEndCount = 0; | |
static private int addNotToEndCount = 0; | |
static private int batchCountHistogram[]; | |
static private int batchAddCount = 0; | |
static private int batchAddTotal = 0; // includes things not added because of dups | |
static private int moveSizeHistogram[]; | |
static private int movePctHistogram[]; | |
static private int fillHistogram[]; | |
static private int iterPctEmptySkip[]; | |
static { | |
if (MEASURE) { | |
batchCountHistogram = new int[24]; // slot x = 2^x to (2^(x+1) - 1) counts | |
// slot 0 = 1, slot 1 = 2-3, etc | |
Arrays.fill(batchCountHistogram, 0); | |
moveSizeHistogram = new int[24]; | |
movePctHistogram = new int[10]; // slot 0 = 0-9% 1 = 10-19% 9 = 90 - 100% | |
fillHistogram = new int[24]; | |
iterPctEmptySkip = new int[10]; | |
Runtime.getRuntime().addShutdownHook(new Thread(null, () -> { | |
System.out.println("Histogram measures of Ordered Set add / remove operations"); | |
System.out.format(" - Add to end: %,d, batch add count: %,d batch add tot: %,d%n", | |
addToEndCount, batchAddCount, batchAddTotal); | |
for (int i = 0; i < batchCountHistogram.length; i++) { | |
int v = batchCountHistogram[i]; | |
if (v == 0) continue; | |
System.out.format(" batch size: %,d, count: %,d%n", 1 << i, v); | |
} | |
for (int i = 0; i < moveSizeHistogram.length; i++) { | |
int v = moveSizeHistogram[i]; | |
if (v == 0) continue; | |
System.out.format(" move size: %,d, count: %,d%n", | |
(i == 0) ? 0 : 1 << (i - 1), v); | |
} | |
for (int i = 0; i < movePctHistogram.length; i++) { | |
int v = movePctHistogram[i]; | |
if (v == 0) continue; | |
System.out.format(" move Pct: %,d - %,d, count: %,d%n", i*10, (i+1)*10, v); | |
} | |
for (int i = 0; i < fillHistogram.length; i++) { | |
int v = fillHistogram[i]; | |
if (v == 0) continue; | |
System.out.format(" fill size: %,d, count: %,d%n", | |
(i == 0) ? 0 : 1 << (i - 1), v); | |
} | |
for (int i = 0; i < iterPctEmptySkip.length; i++) { | |
int v = iterPctEmptySkip[i]; | |
if (v == 0) continue; | |
System.out.format(" iterator percent empty needing skip: %,d - %,d, count: %,d%n", i*10, (i+1)*10, v); | |
} | |
}, "dump measures OrderedFsSetSorted")); | |
} | |
} | |
} |