diff --git a/pom.xml b/pom.xml index ef5d00b..6471512 100644 --- a/pom.xml +++ b/pom.xml @@ -101,7 +101,7 @@ - + diff --git a/src/main/java/com/intel/pmem/llpl/AnyHeap.java b/src/main/java/com/intel/pmem/llpl/AnyHeap.java index 243e029..ae47ecb 100644 --- a/src/main/java/com/intel/pmem/llpl/AnyHeap.java +++ b/src/main/java/com/intel/pmem/llpl/AnyHeap.java @@ -10,7 +10,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.SortedMap; -import java.util.TreeMap; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.Arrays; import java.util.MissingResourceException; import java.io.File; @@ -97,7 +97,7 @@ public abstract class AnyHeap { AnyHeap(String path, long requestedSize) { this.path = path; - userSizes = new TreeMap(); + userSizes = new ConcurrentSkipListMap(); allocationClasses = new long[TOTAL_ALLOCATION_CLASSES]; poolHandle = nativeCreateHeap(path, requestedSize, allocationClasses, this.getHeapLayoutID()); if (poolHandle == 0) throw new HeapException("Failed to create heap."); @@ -109,7 +109,7 @@ public abstract class AnyHeap { AnyHeap(String path) { this.path = path; - userSizes = new TreeMap(); + userSizes = new ConcurrentSkipListMap(); allocationClasses = new long[TOTAL_ALLOCATION_CLASSES]; poolHandle = nativeOpenHeap(path, allocationClasses, this.getHeapLayoutID()); if (poolHandle == 0) throw new HeapException("Failed to open heap."); @@ -191,17 +191,19 @@ void close() { * @return true if size was successfully registered * @throws HeapException if the allocation size could not be registered */ - public synchronized boolean registerAllocationSize(long size, boolean compact) { + public boolean registerAllocationSize(long size, boolean compact) { if (userSizes.size() == MAX_USER_CLASSES) throw new HeapException("Max number of allocation sizes reached."); if (size <= 0) throw new IllegalArgumentException(); long effectiveSize; if (!userSizes.containsKey(effectiveSize = (size + (compact ? 0L : Long.BYTES)))) { int id = nativeRegisterAllocationClass(poolHandle, effectiveSize); if (id != -1) { - int i = USER_CLASS_INDEX + (userSizes.size() * 2); userSizes.put(effectiveSize, id); - allocationClasses[i++] = effectiveSize; - allocationClasses[i] = id; + return true; + } + int index = (int)((effectiveSize / 8) - 1); + if (effectiveSize % 8 == 0 && effectiveSize < 128 && allocationClasses[index] > 0) { + userSizes.put(effectiveSize, (int)allocationClasses[index]); return true; } return false; diff --git a/src/main/java/com/intel/pmem/llpl/util/LongART.java b/src/main/java/com/intel/pmem/llpl/util/LongART.java index 593623f..4e19b25 100755 --- a/src/main/java/com/intel/pmem/llpl/util/LongART.java +++ b/src/main/java/com/intel/pmem/llpl/util/LongART.java @@ -158,6 +158,7 @@ private void setMaxKeyLength(int length) { public byte[] firstKey() { Node n = root.getChild(); if (n == null) throw new NoSuchElementException(); + if (n.isLeaf()) return n.getPrefix(); ByteBuffer firstKey = ByteBuffer.allocate(maxKeyLen); SearchHelper h = (Node parent, Node child, Byte radix, Consumer cleanerFunction) -> { byte[] ba = parent.getPrefix(); @@ -177,6 +178,7 @@ public byte[] firstKey() { public byte[] lastKey() { Node n = root.getChild(); if (n == null) throw new NoSuchElementException(); + if (n.isLeaf()) return n.getPrefix(); ByteBuffer lastKey = ByteBuffer.allocate(maxKeyLen); SearchHelper h = (Node parent, Node child, Byte radix, Consumer cleanerFunction) -> { byte[] ba = parent.getPrefix(); @@ -467,7 +469,6 @@ void mergeTree(LongART tree) { } } } - } @SuppressWarnings("unchecked") @@ -477,7 +478,7 @@ private long search2(Node node, byte[] key, int depth, SearchHelper helper, Cons } Node next; int matchedLength = (key.length == 0) ? node.getPrefixLength() : node.checkPrefix(key, depth); - if (matchedLength != node.getPrefixLength()) { + if (matchedLength != node.getPrefixLength() && !node.isLeaf()) { return -1; } if (node.isLeaf()) @@ -569,6 +570,7 @@ public void clear(Consumer cleanerFunction) { if (cleanerFunction == null) throw new IllegalArgumentException("cleaner function cannot be null"); heap.execute(() -> { root.destroy(cleanerFunction); + root.setCount(0); count = 0; }); } @@ -607,7 +609,20 @@ else if (((InternalNode)child).getChildrenCount() == 0) { public long remove(byte[] key, Consumer cleanerFunction) { if (cleanerFunction == null) throw new NullPointerException("cleaner function cannot be null"); if (key.length == 0) throw new IllegalArgumentException("Invalid key"); - return search(root.getChild(), key, 0, this::deleteNodes , cleanerFunction); + Node n = root.getChild(); + if (n != null && n.isLeaf() && Arrays.equals(key, n.getPrefix())) { + heap.execute(() -> { + root.destroy(cleanerFunction); + root.setCount(0); + count = 0; + }); + return ((SimpleLeaf)n).getValue(); + } + else { + long ret = search(n, key, 0, this::deleteNodes , cleanerFunction); + if (n != null && !n.isLeaf() && ((InternalNode)n).getChildrenCount() == 0) root.deleteChild(); + return ret; + } } /** @@ -660,7 +675,21 @@ public Iterator getReverseEntryIterator(byte[] firstKey, boolean */ public Iterator getHeadEntryIterator(byte[] lastKey, boolean lastInclusive) { if (lastKey == null || lastKey.length == 0) throw new IllegalArgumentException(); - return new EntryIterator(lastKey, lastInclusive); + return new EntryIterator(lastKey, lastInclusive, false); + } + + /** + * Returns a descending-order iterator over entries in this radix tree; + * the iterator will include entries whose keys are lower than (or equal to, + * if {@code firstInclusive} is true) {@code firstKey}. + * @param firstKey high endpoint of the keys in the returned iterator + * @param firstInclusive true if the highest key is to be included in the returned iterator + * @return the iterator + * @throws IllegalArgumentException if lastKey has zero length + */ + public Iterator getReverseHeadEntryIterator(byte[] firstKey, boolean firstInclusive) { + if (firstKey == null || firstKey.length == 0) throw new IllegalArgumentException(); + return new EntryIterator(firstKey, firstInclusive, null, false, true); } /** @@ -670,13 +699,27 @@ public Iterator getHeadEntryIterator(byte[] lastKey, boolean lastInclusiv * @param firstKey low endpoint of the keys in the returned iterator * @param firstInclusive true if the lowest key is to be included in the returned iterator * @return the iterator - * @throws IllegalArgumentException if lastKey has zero length + * @throws IllegalArgumentException if firstKey has zero length */ public Iterator getTailEntryIterator(byte[] firstKey, boolean firstInclusive) { if (firstKey == null || firstKey.length == 0) throw new IllegalArgumentException(); return new EntryIterator(firstKey, firstInclusive, null, false, false); } + /** + * Returns a descending-order iterator over entries in this radix tree; + * the iterator will include entries whose keys are higher than (or equal to, + * if {@code lastInclusive} is true) {@code lastKey}. + * @param lastKey low endpoint of the keys in the returned iterator + * @param lastInclusive true if the lowest key is to be included in the returned iterator + * @return the iterator + * @throws IllegalArgumentException if lastKey has zero length + */ + public Iterator getReverseTailEntryIterator(byte[] lastKey, boolean lastInclusive) { + if (lastKey == null || lastKey.length == 0) throw new IllegalArgumentException(); + return new EntryIterator(lastKey, lastInclusive, true); + } + /** * Returns an ascending-order iterator over entries in this radix tree; * the iterator will include entries whose keys range from {@code firstKey} to {@code lastKey}. @@ -713,7 +756,7 @@ public StackItem(NodeEntry[] entries, int prefixLen, Byte radix, boolean hasBlan this.hasBlank=hasBlank; this.reversed = reversed; if (radix != entries[0].radix) this.index = calcIndex(radix); - if (reversed && index == entries.length) index = entries.length - 1; + if (index == entries.length) index = entries.length - 1; } int calcIndex(byte radix) { @@ -812,7 +855,6 @@ public EntryIterator(boolean reversed) { void buildCache(Node parent, Node child, Byte radix, Consumer cleanerFunction) { StackItem item; - if (reversed && child == null) return; NodeEntry[] entries = ((InternalNode)parent).getEntries(); if (radix == null && child != null) item = new StackItem(entries, parent.getPrefixLength(), reversed); else if (child == null) { @@ -827,37 +869,39 @@ else if (child == null) { if (radix != null && child != null && !child.isLeaf()) keyBuf.put(radix); } - public EntryIterator(byte[] lastKey, boolean lastInclusive) { - this(); + public EntryIterator(byte[] lastKey, boolean lastInclusive, boolean reversed) { + this(reversed); this.lastKey = lastKey; this.lastInclusive = lastInclusive; } public EntryIterator(byte[] firstKey, boolean firstInclusive, byte[] lastKey, boolean lastInclusive, boolean reversed) { cache = new ArrayDeque<>(); - this.reversed = reversed; Node first = root.getChild(); // lastKey can be null, firstkey is never null as it is checked before calling this if (lastKey != null) { int comp = keyCompare(firstKey, lastKey); if (comp == 0) { - long val = get(firstKey); - next = (val == 0) ? null : new Entry(firstKey, val); + if (firstInclusive && lastInclusive) { + long val = get(firstKey); + next = (val == 0) ? null : new Entry(firstKey, val); + } return; } - if (!reversed && comp > 0) throw new IllegalArgumentException(); - if (reversed && comp < 0) throw new IllegalArgumentException(); - } - if (reversed) { - this.lastKey = firstKey; - firstKey = lastKey; - this.lastInclusive = firstInclusive; - firstInclusive = lastInclusive; - } - else { - this.lastKey = lastKey; - this.lastInclusive = lastInclusive; + if (comp > 0) throw new IllegalArgumentException(); + if (reversed) { + this.lastKey = firstKey; + firstKey = lastKey; + this.lastInclusive = firstInclusive; + firstInclusive = lastInclusive; + } + else { + this.lastKey = lastKey; + this.lastInclusive = lastInclusive; + } } + this.reversed = reversed; + if (first != null) { if (first.isLeaf()) { SimpleLeaf leaf = (SimpleLeaf)first; @@ -947,7 +991,10 @@ void advanceTo(byte[] firstKey, boolean inclusive) { if ((inclusive && x == 0) || x < 0) break; while (cursor.isDone() && cache.size() > 0) { pop(); - if (cursor == null) break; + if (cursor == null) { + if (keyBuf.position() == 0) next = null; + break; + } } } } diff --git a/src/test/java/com/intel/pmem/llpl/LongARTTests.java b/src/test/java/com/intel/pmem/llpl/LongARTTests.java index 6be8f46..a60347d 100644 --- a/src/test/java/com/intel/pmem/llpl/LongARTTests.java +++ b/src/test/java/com/intel/pmem/llpl/LongARTTests.java @@ -16,6 +16,7 @@ import org.testng.Assert; import java.util.NoSuchElementException; import java.util.Arrays; +import java.util.ArrayList; import java.util.Iterator; import java.util.Random; import java.util.Map; @@ -28,6 +29,9 @@ public class LongARTTests { static byte[] firstKey; static byte[] lastKey; static byte[] prefixKey; + static byte[] fixedLengthFirstKey; + static byte[] fixedLengthLastKey; + static byte[] fixedLengthPrefixKey; static long firstValue; static long lastValue; static long prefixValue; @@ -41,6 +45,11 @@ public void initialize() { firstKey = new byte[8]; Arrays.fill(firstKey, (byte)0); lastKey = new byte[20]; Arrays.fill(lastKey, (byte)0xff); prefixKey = Arrays.copyOfRange(lastKey, 0, 12); + fixedLengthPrefixKey = new byte[10]; rnd.nextBytes(fixedLengthPrefixKey); + fixedLengthFirstKey = new byte[10]; Arrays.fill(fixedLengthFirstKey, (byte)0); + System.arraycopy(fixedLengthPrefixKey, 0, fixedLengthFirstKey, 0, 6); + fixedLengthLastKey = new byte[10]; Arrays.fill(fixedLengthLastKey, (byte)0xff); + System.arraycopy(prefixKey, 0, fixedLengthLastKey, 0, 6); firstValue = 0x1234L << 8 | (byte)1; lastValue = 0x1234L << 8 | (byte)2; prefixValue = 0x1234L << 8 | (byte)3; @@ -92,19 +101,33 @@ static void fill (LongART tree, ConcurrentSkipListMap control) { tree.put(firstKey, firstValue); tree.put(lastKey, lastValue); tree.put(prefixKey, prefixValue); + tree.put(fixedLengthFirstKey, firstValue); + tree.put(fixedLengthLastKey, lastValue); + tree.put(fixedLengthPrefixKey, prefixValue); control.put(new KeyBytes(firstKey), firstValue); control.put(new KeyBytes(lastKey), lastValue); control.put(new KeyBytes(prefixKey), prefixValue); + control.put(new KeyBytes(fixedLengthFirstKey), firstValue); + control.put(new KeyBytes(fixedLengthLastKey), lastValue); + control.put(new KeyBytes(fixedLengthPrefixKey), prefixValue); byte[] key; long val = 0; rnd.setSeed(SEED); - for (int i = 3; i < TREESIZE; i++) { + for (int i = 3; i < TREESIZE - 20; i++) { key = new byte[8 + rnd.nextInt(12)]; rnd.nextBytes(key); val = 0x1234L << 8 | (byte)i; tree.put(key, val); control.put(new KeyBytes(key), val); } + for (int i = 3; i < 20; i++) { + key = new byte[10]; + rnd.nextBytes(key); + System.arraycopy(fixedLengthPrefixKey, 0, key, 0, 6); + val = 0x1234L << 8 | (byte)i; + tree.put(key, val); + control.put(new KeyBytes(key), val); + } } static void fill (LongART tree) { @@ -315,7 +338,43 @@ public void testLastkeyRecreatedEmptyART() { Assert.assertTrue(true); } } + + @Test + public void testFirstkeySingleEntryShortKey() { + LongART art = new LongART(heap); + byte[] shortKey = Arrays.copyOf(prefixKey, 8); + art.put(shortKey, prefixValue); + Assert.assertEquals(art.firstKey(), shortKey); + } + @Test + public void testLastkeySingleEntryART() { + LongART art = new LongART(heap); + byte[] shortKey = Arrays.copyOf(lastKey, 8); + art.put(shortKey, lastValue); + Assert.assertEquals(art.lastKey(), shortKey); + } + + @Test + public void testFirstkeyRecreatedSingleEntryART() { + LongART oldArt = new LongART(heap); + byte[] shortKey = Arrays.copyOf(prefixKey, 8); + oldArt.put(shortKey, prefixValue); + long artHandle = (oldArt.handle()); + LongART art = LongART.fromHandle(heap, artHandle); + Assert.assertEquals(art.firstKey(), shortKey); + } + + @Test + public void testLastkeyRecreatedSingleEntryART() { + LongART oldArt = new LongART(heap); + byte[] shortKey = Arrays.copyOf(prefixKey, 8); + oldArt.put(shortKey, prefixValue); + long artHandle = (oldArt.handle()); + LongART art = LongART.fromHandle(heap, artHandle); + Assert.assertEquals(art.lastKey(), shortKey); + } + @Test public void testFirstkeyFilledART() { LongART art = new LongART(heap); @@ -851,6 +910,50 @@ public void testRangeReverseEntryIteratorZeroLengthToKey() { } } + @Test + public void testRangeReverseHeadEntryIteratorNullKey() { + LongART art = new LongART(heap); + try { + Iterator it = art.getReverseHeadEntryIterator(null, true); + Assert.fail("IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + } + + @Test + public void testRangeReverseHeadEntryIteratorZeroLengthKey() { + LongART art = new LongART(heap); + try { + Iterator it = art.getReverseHeadEntryIterator(new byte[]{}, true); + Assert.fail("IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + } + + @Test + public void testRangeReverseTailEntryIteratorNullKey() { + LongART art = new LongART(heap); + try { + Iterator it = art.getReverseTailEntryIterator(null, true); + Assert.fail("IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + } + + @Test + public void testRangeReverseTailEntryIteratorZeroLengthKey() { + LongART art = new LongART(heap); + try { + Iterator it = art.getReverseTailEntryIterator(new byte[]{}, true); + Assert.fail("IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + } + @Test public void testHasNextHeadEntryIteratorNewEmptyART() { LongART art = new LongART(heap); @@ -1128,6 +1231,24 @@ public void testIterateRangeSingleEntryIteratorEq() { } } + @Test + public void testIterateRangeSingleEntryShortKeyIteratorEq() { + LongART art = new LongART(heap); + ConcurrentSkipListMap control = new ConcurrentSkipListMap(); + byte[] key = getKey(7); + art.put(key, firstValue); + control.put(new KeyBytes(key), firstValue); + + Iterator it = art.getEntryIterator(key, true, lastKey, true); + LongART.Entry entry = null; + + for (Map.Entry e : control.subMap(new KeyBytes(firstKey), true, new KeyBytes(lastKey), true).entrySet()) { + entry = it.next(); + Assert.assertEquals(entry.getKey(), e.getKey().get()); + Assert.assertEquals(entry.getValue(), e.getValue().longValue()); + } + } + @Test public void testIterateRangeSingleEntryIteratorLT() { LongART art = new LongART(heap); @@ -1146,6 +1267,25 @@ public void testIterateRangeSingleEntryIteratorLT() { } } + @Test + public void testIterateRangeSingleEntryShortKeyIteratorLT() { + LongART art = new LongART(heap); + ConcurrentSkipListMap control = new ConcurrentSkipListMap(); + byte[] key = getKey(7); + art.put(key, firstValue); + control.put(new KeyBytes(key), firstValue); + byte[] fromKey = Arrays.copyOf(firstKey, 4); + + Iterator it = art.getEntryIterator(fromKey, true, lastKey, true); + LongART.Entry entry = null; + + for (Map.Entry e : control.subMap(new KeyBytes(fromKey), true, new KeyBytes(lastKey), true).entrySet()) { + entry = it.next(); + Assert.assertEquals(entry.getKey(), e.getKey().get()); + Assert.assertEquals(entry.getValue(), e.getValue().longValue()); + } + } + @Test public void testIterateRangeSingleEntryIteratorPrefix() { LongART art = new LongART(heap); @@ -1163,6 +1303,24 @@ public void testIterateRangeSingleEntryIteratorPrefix() { } } + @Test + public void testIterateRangeSingleEntryShortKeyIteratorPrefix() { + LongART art = new LongART(heap); + ConcurrentSkipListMap control = new ConcurrentSkipListMap(); + byte[] key = getKey(7); + art.put(key, firstValue); + control.put(new KeyBytes(key), firstValue); + + Iterator it = art.getEntryIterator(prefixKey, true, lastKey, true); + LongART.Entry entry = null; + + for (Map.Entry e : control.subMap(new KeyBytes(prefixKey), true, new KeyBytes(lastKey), true).entrySet()) { + entry = it.next(); + Assert.assertEquals(entry.getKey(), e.getKey().get()); + Assert.assertEquals(entry.getValue(), e.getValue().longValue()); + } + } + @Test public void testIterateRangeEntryIterator() { LongART art = new LongART(heap); @@ -1268,11 +1426,60 @@ public void testIterateRangeEntryIteratorFromKeyAbsentInRange() { } @Test - public void testIterateRangeEntryIteratorFromKeyAbsentInRangePartialPrefixMatch() { + public void testIterateRangeReverseHeadEntryIteratorFromKeyAbsentInRange() { + LongART art = new LongART(heap); + ConcurrentSkipListMap control = new ConcurrentSkipListMap(); + fill(art, control); + byte[] fromKey = getKey(20); + Iterator it = art.getReverseHeadEntryIterator(fromKey, true); + + LongART.Entry entry = null; + for (Map.Entry e : control.headMap(new KeyBytes(fromKey), true).descendingMap().entrySet()) { + entry = it.next(); + Assert.assertEquals(entry.getKey(), e.getKey().get()); + Assert.assertEquals(entry.getValue(), e.getValue().longValue()); + } + } + + + @Test + public void testIterateRangeEntryIteratorFromKeyAbsentInRangePartialPrefixMatch1() { + LongART art = new LongART(heap); + ConcurrentSkipListMap control = new ConcurrentSkipListMap(); + fill(art, control); + byte[] fromKey = Arrays.copyOfRange(lastKey, 0, 8); + Iterator it = art.getEntryIterator(fromKey, true, lastKey, true); + + LongART.Entry entry = null; + for (Map.Entry e : control.subMap(new KeyBytes(fromKey), true, new KeyBytes(lastKey), true).entrySet()) { + entry = it.next(); + Assert.assertEquals(entry.getKey(), e.getKey().get()); + Assert.assertEquals(entry.getValue(), e.getValue().longValue()); + } + } + + @Test + public void testIterateRangeReverseHeadEntryIteratorFromKeyAbsentInRangePartialPrefixMatch1() { LongART art = new LongART(heap); ConcurrentSkipListMap control = new ConcurrentSkipListMap(); fill(art, control); byte[] fromKey = Arrays.copyOfRange(lastKey, 0, 8); + Iterator it = art.getReverseHeadEntryIterator(fromKey, true); + + LongART.Entry entry = null; + for (Map.Entry e : control.headMap(new KeyBytes(fromKey), true).descendingMap().entrySet()) { + entry = it.next(); + Assert.assertEquals(entry.getKey(), e.getKey().get()); + Assert.assertEquals(entry.getValue(), e.getValue().longValue()); + } + } + // Tests for searchkey with common prefix that differs in the leaf prefix + @Test + public void testIterateRangeEntryIteratorFromKeyAbsentInRangePartialPrefixMatch2() { + LongART art = new LongART(heap); + ConcurrentSkipListMap control = new ConcurrentSkipListMap(); + fill(art, control); + byte[] fromKey = Arrays.copyOfRange(lastKey, 0, lastKey.length - 1); Iterator it = art.getEntryIterator(fromKey, true, lastKey, true); LongART.Entry entry = null; @@ -1283,6 +1490,40 @@ public void testIterateRangeEntryIteratorFromKeyAbsentInRangePartialPrefixMatch( } } + // Tests for searchkey with common prefix that differs in the leaf prefix + @Test + public void testIterateRangeReverseHeadEntryIteratorFromKeyAbsentInRangePartialPrefixMatch2() { + LongART art = new LongART(heap); + ConcurrentSkipListMap control = new ConcurrentSkipListMap(); + fill(art, control); + byte[] fromKey = Arrays.copyOfRange(lastKey, 0, lastKey.length - 1); + Iterator it = art.getReverseHeadEntryIterator(fromKey, true); + + LongART.Entry entry = null; + for (Map.Entry e : control.headMap(new KeyBytes(fromKey), true).descendingMap().entrySet()) { + entry = it.next(); + Assert.assertEquals(entry.getKey(), e.getKey().get()); + Assert.assertEquals(entry.getValue(), e.getValue().longValue()); + } + } + + // Tests for searchkey with common prefix that differs in the leaf prefix + @Test + public void testIterateRangeReverseHeadEntryIteratorFromKeyAbsentInRangePartialPrefixMatch3() { + LongART art = new LongART(heap); + ConcurrentSkipListMap control = new ConcurrentSkipListMap(); + fill(art, control); + byte[] fromKey = Arrays.copyOfRange(lastKey, 0, lastKey.length - 9); + Iterator it = art.getReverseHeadEntryIterator(fromKey, true); + + LongART.Entry entry = null; + for (Map.Entry e : control.headMap(new KeyBytes(fromKey), true).descendingMap().entrySet()) { + entry = it.next(); + Assert.assertEquals(entry.getKey(), e.getKey().get()); + Assert.assertEquals(entry.getValue(), e.getValue().longValue()); + } + } + @Test public void testIterateRangeTailEntryIteratorFromKeyAbsentPostRange() { LongART art = new LongART(heap); @@ -1427,7 +1668,158 @@ public void testIterateRangeReverseEntryIteratorPrefix() { Assert.assertEquals(entry.getValue(), e.getValue().longValue()); } } - + + @Test + public void testIterateReverseHeadEntryIterator() { + LongART art = new LongART(heap); + ConcurrentSkipListMap control = new ConcurrentSkipListMap(); + fill(art, control); + + Iterator it = art.getReverseHeadEntryIterator(prefixKey, true); + LongART.Entry entry = null; + + for (Map.Entry e : control.headMap(new KeyBytes(prefixKey), true).descendingMap().entrySet()) { + entry = it.next(); + Assert.assertEquals(entry.getKey(), e.getKey().get()); + Assert.assertEquals(entry.getValue(), e.getValue().longValue()); + } + } + + @Test + public void testIterateReverseTailEntryIterator() { + LongART art = new LongART(heap); + ConcurrentSkipListMap control = new ConcurrentSkipListMap(); + fill(art, control); + + Iterator it = art.getReverseTailEntryIterator(prefixKey, true); + LongART.Entry entry = null; + + for (Map.Entry e : control.tailMap(new KeyBytes(prefixKey), true).descendingMap().entrySet()) { + entry = it.next(); + Assert.assertEquals(entry.getKey(), e.getKey().get()); + Assert.assertEquals(entry.getValue(), e.getValue().longValue()); + } + } + + //EmptyRange tests + public void testEmptyRangeIterationPost() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getEntryIterator(lastKey, false, lastKey, false); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyRangeIterationPre() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getEntryIterator(Arrays.copyOf(firstKey, 1), true, Arrays.copyOf(firstKey, 2), true); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyReverseRangeIterationPost() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getReverseEntryIterator(lastKey, false, lastKey, false); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyReverseRangeIterationPre() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getReverseEntryIterator(Arrays.copyOf(firstKey, 1), true, Arrays.copyOf(firstKey, 2), true); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyHeadIteration() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getHeadEntryIterator(Arrays.copyOf(firstKey, 1), true); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyReverseHeadIteration() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getReverseHeadEntryIterator(Arrays.copyOf(firstKey, 1), true); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyTailIteration() { + LongART art = new LongART(heap); + fill(art); + byte[] key = new byte[22]; Arrays.fill(key, (byte)0xff); + Iterator it = art.getTailEntryIterator(key, true); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyReverseTailIteration() { + LongART art = new LongART(heap); + fill(art); + byte[] key = new byte[22]; Arrays.fill(key, (byte)0xff); + Iterator it = art.getReverseTailEntryIterator(key, true); + Assert.assertFalse(it.hasNext()); + } + +/* + //SingleEntryShortKey tests + public void testSingleEntryIterationPost() { + LongART art = new LongART(heap); + art.put(Arrays.copy + Iterator it = art.getEntryIterator(lastKey, false, lastKey, false); + Assert.assertFalse(it.hasNext()); + } + public void testEmptyRangeIterationPre() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getEntryIterator(Arrays.copyOf(firstKey, 1), true, Arrays.copyOf(firstKey, 2), true); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyReverseRangeIterationPost() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getReverseEntryIterator(lastKey, false, lastKey, false); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyReverseRangeIterationPre() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getReverseEntryIterator(Arrays.copyOf(firstKey, 1), true, Arrays.copyOf(firstKey, 2), true); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyHeadIteration() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getHeadEntryIterator(Arrays.copyOf(firstKey, 1), true); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyReverseHeadIteration() { + LongART art = new LongART(heap); + fill(art); + Iterator it = art.getReverseHeadEntryIterator(Arrays.copyOf(firstKey, 1), true); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyTailIteration() { + LongART art = new LongART(heap); + fill(art); + byte[] key = new byte[22]; Arrays.fill(key, (byte)0xff); + Iterator it = art.getTailEntryIterator(key, true); + Assert.assertFalse(it.hasNext()); + } + + public void testEmptyReverseTailIteration() { + LongART art = new LongART(heap); + fill(art); + byte[] key = new byte[22]; Arrays.fill(key, (byte)0xff); + Iterator it = art.getReverseTailEntryIterator(key, true); + Assert.assertFalse(it.hasNext()); + } +*/ + // Delete tests public void testDeleteNullKey() { LongART art = new LongART(heap); @@ -1493,6 +1885,43 @@ public void testDeleteAfterFree() { Assert.assertTrue(true); } } + + public void testDeleteSingleEntry() { + LongART art = new LongART(heap); + art.put(firstKey, firstValue); + art.remove(firstKey, c -> {}); + art.remove(firstKey, (Long l) -> { Assert.assertTrue(l == 0L); }); + Iterator it = art.getEntryIterator(); + Assert.assertFalse(it.hasNext()); + } + + public void testSizeAfterDelete() { + LongART art = new LongART(heap); + fill(art); + long size = art.size(); + for (long i = 0; i < size; i++) { + art.remove(art.firstKey(), c -> {}); + Assert.assertEquals(art.size(), --size); + } + } + + public void testIterateAfterDelete() { + LongART art = new LongART(heap); + fill(art); + ArrayList list = new ArrayList((int)art.size()); + Iterator it = art.getEntryIterator(); + while (it.hasNext()) list.add(it.next().getKey()); + list.forEach((byte[] arr) -> { art.remove(arr, c -> {}); }); + it = art.getEntryIterator(); + Assert.assertFalse(it.hasNext()); + } + + public void testSizeAfterDeleteSingleEntry() { + LongART art = new LongART(heap); + art.put(firstKey, firstValue); + art.remove(firstKey, c -> {}); + Assert.assertEquals(art.size(), 0L); + } // Split tests public void testSplitEmptyART() {