├── .gitignore ├── .travis.yml ├── src ├── main │ ├── java │ │ └── uk │ │ │ └── co │ │ │ └── omegaprime │ │ │ └── btreemap │ │ │ ├── AbstractNode.java │ │ │ ├── Sets.java │ │ │ ├── Bound.java │ │ │ ├── NavigableMap2.java │ │ │ ├── SortedMaps.java │ │ │ ├── Iterators.java │ │ │ ├── Iterables.java │ │ │ ├── MapEntrySet.java │ │ │ ├── MapValueCollection.java │ │ │ ├── DescendingNavigableMap.java │ │ │ └── NavigableMapKeySet.java │ └── templates │ │ └── uk.co.omegaprime.btreemap │ │ ├── {{K_}}Comparator.java │ │ ├── {{KV_}}Node.java │ │ ├── {{KV_}}RestrictedBTreeMap.java │ │ └── {{KV_}}BTreeMap.java └── test │ └── java │ └── uk │ └── co │ └── omegaprime │ └── btreemap │ ├── BTreeMapBenchmark.java │ └── BTreeMapTest.java ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | out/ 4 | .gradle/ 5 | build/ 6 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | before_cache: 3 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 4 | cache: 5 | directories: 6 | - $HOME/.gradle/caches/ 7 | - $HOME/.gradle/wrapper/ -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/AbstractNode.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | abstract class AbstractNode { 4 | public int size; 5 | 6 | @Override 7 | public abstract AbstractNode clone(); 8 | public abstract AbstractNode clone(int depth); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/Sets.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.Set; 4 | 5 | class Sets { 6 | private Sets() {} 7 | 8 | public static boolean equals(Set xs, Object ys) { 9 | return ys instanceof Set && equals(xs, (Set)ys); 10 | } 11 | 12 | public static boolean equals(Set xs, Set ys) { 13 | if (xs.size() != ys.size()) return false; 14 | 15 | for (K k : xs) { 16 | if (!ys.contains(k)) { 17 | return false; 18 | } 19 | } 20 | 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/Bound.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.Comparator; 4 | 5 | enum Bound { 6 | MISSING, INCLUSIVE, EXCLUSIVE; 7 | 8 | public static Bound inclusive(boolean flag) { 9 | return flag ? INCLUSIVE : EXCLUSIVE; 10 | } 11 | 12 | public static int cmp(Object l, Object r, Comparator c) { 13 | return c == null ? ((Comparable)l).compareTo(r) : c.compare(l, r); 14 | } 15 | 16 | public boolean lt(Object x, Object y, Comparator c) { 17 | if (this == MISSING) return true; 18 | 19 | final int r = cmp(x, y, c); 20 | return r < 0 || (r == 0 && this == INCLUSIVE); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/NavigableMap2.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.Map; 4 | import java.util.NavigableMap; 5 | import java.util.Set; 6 | 7 | /** 8 | * Why not just have this extend NavigableMap2 and implement the interface on BTreeMap and friends? 9 | * Because then this interface would be visible in the public interface of the library. 10 | */ 11 | interface NavigableMap2 { 12 | NavigableMap asNavigableMap(); 13 | 14 | Set> descendingEntrySet(); 15 | 16 | NavigableMap2 subMap(K fromKey, boolean fromInclusive, 17 | K toKey, boolean toInclusive); 18 | NavigableMap2 headMap(K toKey, boolean inclusive); 19 | NavigableMap2 tailMap(K fromKey, boolean inclusive); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/SortedMaps.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.Map; 4 | import java.util.Objects; 5 | import java.util.SortedMap; 6 | 7 | class SortedMaps { 8 | private SortedMaps() {} 9 | 10 | public static boolean equals(SortedMap xs, Object ys) { 11 | return ys instanceof Map && equals(xs, (Map)ys); 12 | } 13 | 14 | private static boolean equals(SortedMap xs, Map ys) { 15 | if (xs.size() != ys.size()) return false; 16 | 17 | if (ys instanceof SortedMap && Objects.equals(xs.comparator(), ((SortedMap)ys).comparator())) { 18 | return Iterables.equals(xs.entrySet(), ys.entrySet()); 19 | } else { 20 | for (Map.Entry e : xs.entrySet()) { 21 | if (!Objects.equals(ys.get(e.getKey()), e.getValue())) { 22 | return false; 23 | } 24 | } 25 | 26 | return true; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # btreemap 2 | 3 | This is a simple and high-performance Java B-tree that is intended to be used as a drop-in replacement for java.util.TreeMap 4 | in situations where the vanilla binary trees used by the JDK are just too slow. 5 | 6 | For maps with 100k keys, performance is like this on my machine: 7 | 8 | ``` 9 | Benchmark Mode Cnt Score Error Units 10 | BTreeMapBenchmark.get thrpt 20 5500161.400 ± 70850.009 ops/s 11 | BTreeMapBenchmark.lowerKey thrpt 20 4720565.667 ± 259185.836 ops/s 12 | BTreeMapBenchmark.put thrpt 20 3757808.623 ± 37792.128 ops/s 13 | ``` 14 | 15 | With `TreeMap` the numbers are about 50%-60% worse: 16 | 17 | ``` 18 | Benchmark Mode Cnt Score Error Units 19 | BTreeMapBenchmark.get thrpt 20 3404585.749 ± 47180.578 ops/s 20 | BTreeMapBenchmark.lowerKey thrpt 20 3255788.782 ± 54390.949 ops/s 21 | BTreeMapBenchmark.put thrpt 20 2542375.846 ± 38924.217 ops/s 22 | ``` 23 | 24 | The source code lives at [GitHub](https://github.com/batterseapower/btreemap/). If you just want some JARs, check out [Maven Central](http://mvnrepository.com/artifact/uk.co.omega-prime/btreemap/). 25 | 26 | [![Build Status](https://travis-ci.org/batterseapower/btreemap.svg?branch=master)](https://travis-ci.org/batterseapower/btreemap) 27 | -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/Iterators.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.Iterator; 4 | import java.util.NoSuchElementException; 5 | import java.util.function.Predicate; 6 | 7 | class Iterators { 8 | private Iterators() {} 9 | 10 | public static Iterator takeWhile(Iterator it, Predicate p) { 11 | return new Iterator() { 12 | private Boolean hasNext; 13 | private T next; 14 | 15 | private boolean ensureHasNext() { 16 | if (hasNext != null) { 17 | return hasNext; 18 | } 19 | 20 | if (!it.hasNext()) { 21 | return hasNext = false; 22 | } 23 | 24 | next = it.next(); 25 | return hasNext = p.test(next); 26 | } 27 | 28 | @Override 29 | public boolean hasNext() { 30 | return ensureHasNext(); 31 | } 32 | 33 | @Override 34 | public T next() { 35 | if (!ensureHasNext()) { 36 | throw new NoSuchElementException("Iterator exhausted"); 37 | } 38 | 39 | final T result = next; 40 | 41 | hasNext = null; 42 | next = null; // Just to free up memory 43 | return result; 44 | } 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/templates/uk.co.omegaprime.btreemap/{{K_}}Comparator.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.Comparator; 4 | 5 | /** 6 | * An equivalent to {@link java.util.Comparator} for primitive {@code {{K.unboxed}}} values. 7 | *

8 | * You may optionally have your custom comparators implement this interface, which may improve 9 | * the ability of the JVM to optimize the code by removing unnecessary boxing. 10 | *

11 | * The default implementation of the {@code Comparator} interface simply throws a {@code NullPointerException} 12 | * if a {@code null} boxed value is supplied. 13 | */ 14 | public interface Comparator<$K$> extends Comparator<{{K.boxed}}> { 15 | int compare{{K.name}}($K$ x, $K$ y); 16 | 17 | default int compare({{K.boxed}} x, {{K.boxed}} y) { 18 | return compare{{K.name}}(x, y); 19 | } 20 | 21 | /** Construct an unboxed, specialized comparator using a boxed one */ 22 | static Comparator<$K$> unbox(Comparator that) { 23 | if (that instanceof Comparator<$K$>) { 24 | return (Comparator<$K$>)that; 25 | } else { 26 | return new Comparator<$K$>() { 27 | @Override 28 | public int compare{{K.name}}($K$ x, $K$ y) { 29 | return that.compare(x, y); 30 | } 31 | 32 | @Override 33 | public int compare({{K.boxed}} x, {{K.boxed}} y) { 34 | return that.compare(x, y); 35 | } 36 | }; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/Iterables.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.Iterator; 4 | import java.util.Map; 5 | import java.util.Objects; 6 | 7 | class Iterables { 8 | private Iterables() {} 9 | 10 | public static String toString(Iterable xs) { 11 | final StringBuilder sb = new StringBuilder(); 12 | sb.append('['); 13 | 14 | final Iterator it = xs.iterator(); 15 | if (it.hasNext()) { 16 | sb.append(it.next()); 17 | 18 | while (it.hasNext()) { 19 | sb.append(',').append(' '); 20 | sb.append(it.next()); 21 | } 22 | } 23 | 24 | sb.append(']'); 25 | return sb.toString(); 26 | } 27 | 28 | public static String toMapString(Iterable> xs) { 29 | final StringBuilder sb = new StringBuilder(); 30 | sb.append('{'); 31 | 32 | final Iterator> it = xs.iterator(); 33 | if (it.hasNext()) { 34 | { 35 | final Map.Entry e = it.next(); 36 | sb.append(e.getKey()); 37 | sb.append('='); 38 | sb.append(e.getValue()); 39 | } 40 | 41 | while (it.hasNext()) { 42 | sb.append(',').append(' '); 43 | { 44 | final Map.Entry e = it.next(); 45 | sb.append(e.getKey()); 46 | sb.append('='); 47 | sb.append(e.getValue()); 48 | } 49 | } 50 | } 51 | 52 | sb.append('}'); 53 | return sb.toString(); 54 | } 55 | 56 | public static boolean equals(Iterable xs, Iterable ys) { 57 | final Iterator thisIt = xs.iterator(); 58 | final Iterator thatIt = ys.iterator(); 59 | 60 | while (thisIt.hasNext() && thatIt.hasNext()) { 61 | final Object thisK = thisIt.next(); 62 | final Object thatK = thatIt.next(); 63 | 64 | if (!Objects.equals(thisK, thatK)) { 65 | return false; 66 | } 67 | } 68 | 69 | if (thisIt.hasNext() || thatIt.hasNext()) { 70 | return false; 71 | } 72 | 73 | return true; 74 | } 75 | 76 | public static int hashCode(Iterable xs) { 77 | int h = 0; 78 | for (Object k : xs) { 79 | h += Objects.hashCode(k); 80 | } 81 | 82 | return h; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/uk/co/omegaprime/btreemap/BTreeMapBenchmark.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import org.openjdk.jmh.Main; 4 | import org.openjdk.jmh.annotations.*; 5 | 6 | import java.util.*; 7 | 8 | public class BTreeMapBenchmark { 9 | // -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*BTreeMap.get 10 | 11 | public static final int KEYS = 1_000_000; 12 | 13 | @State(Scope.Thread) 14 | public static class MyState { 15 | private static NavigableMap createMap() { 16 | return 17 | //new it.unimi.dsi.fastutil.ints.Int2IntAVLTreeMap(); 18 | //IntIntBTreeMap.create(); 19 | //BTreeMap.create(); 20 | new TreeMap<>(); 21 | //DBMaker.newMemoryDB().make().createTreeMap("foo").make(); 22 | } 23 | 24 | public final Random random = new Random(); 25 | public final List> warmedMaps = new ArrayList<>(); 26 | public final NavigableMap warmedMap = createMap(); 27 | public Integer key; 28 | private int next = 0; 29 | 30 | private int nextKey() { 31 | return random.nextInt(KEYS); 32 | } 33 | 34 | @Setup(Level.Trial) 35 | public void trialSetup() { 36 | // TODO: not the best because of key collisions 37 | for (int i = 0; i < KEYS; i++) { 38 | warmedMap.put(nextKey(), i); 39 | } 40 | 41 | for (int i = 0; i < KEYS; i += 100) { 42 | final NavigableMap map = createMap(); 43 | for (int j = 0; j < 100; j++) { 44 | map.put(nextKey(), j); 45 | } 46 | 47 | warmedMaps.add(map); 48 | } 49 | } 50 | 51 | @Setup(Level.Invocation) 52 | public void invocationSetup() { 53 | key = nextKey(); 54 | } 55 | 56 | public NavigableMap smallWarmedMap() { 57 | final NavigableMap result = warmedMaps.get(next); 58 | next = (next + 1) % warmedMaps.size(); 59 | return result; 60 | } 61 | } 62 | 63 | @Benchmark 64 | public void put(MyState state) { 65 | state.warmedMap.put(state.key, 1337); 66 | } 67 | 68 | @Benchmark 69 | public void get(MyState state) { 70 | state.warmedMap.get(state.key); 71 | } 72 | 73 | @Benchmark 74 | public void lowerKey(MyState state) { 75 | state.warmedMap.lowerKey(state.key); 76 | } 77 | 78 | @Benchmark 79 | public void putSmall(MyState state) { 80 | state.smallWarmedMap().put(state.key, 1337); 81 | } 82 | 83 | @Benchmark 84 | public void getSmall(MyState state) { 85 | state.smallWarmedMap().get(state.key); 86 | } 87 | 88 | @Benchmark 89 | public void lowerKeySmall(MyState state) { 90 | state.smallWarmedMap().lowerKey(state.key); 91 | } 92 | 93 | public static void main(String[] args) throws Exception { 94 | Main.main(args); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/MapEntrySet.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.*; 4 | 5 | class MapEntrySet implements Set> { 6 | private final Map that; 7 | private final Iterable> iterable; 8 | 9 | public MapEntrySet(Map that, Iterable> iterable) { 10 | this.that = that; 11 | this.iterable = iterable; 12 | } 13 | 14 | @Override 15 | public String toString() { 16 | return Iterables.toString(this); 17 | } 18 | 19 | @Override 20 | public boolean equals(Object that) { 21 | return Sets.equals(this, that); 22 | } 23 | 24 | @Override 25 | public int hashCode() { 26 | return Iterables.hashCode(this); 27 | } 28 | 29 | @Override 30 | public int size() { 31 | return that.size(); 32 | } 33 | 34 | @Override 35 | public boolean isEmpty() { 36 | return that.isEmpty(); 37 | } 38 | 39 | @Override 40 | public boolean contains(Object o) { 41 | if (!(o instanceof Map.Entry)) { 42 | return false; 43 | } 44 | 45 | return containsEntry((Map.Entry)o); 46 | } 47 | 48 | private boolean containsEntry(Map.Entry e) { 49 | return Objects.equals(e.getValue(), that.get(e.getKey())) && 50 | (e.getValue() != null || that.containsKey(e.getKey())); 51 | } 52 | 53 | @Override 54 | public Object[] toArray() { 55 | final Object[] result = new Object[that.size()]; 56 | int i = 0; 57 | for (Map.Entry e : this) { 58 | result[i++] = e; 59 | } 60 | 61 | assert i == result.length; 62 | return result; 63 | } 64 | 65 | @Override 66 | public T[] toArray(T[] a) { 67 | final int size = size(); 68 | if (a.length < size) { 69 | a = Arrays.copyOf(a, size); 70 | } 71 | 72 | int i = 0; 73 | for (Map.Entry e : this) { 74 | //noinspection unchecked 75 | a[i++] = (T)e; 76 | } 77 | 78 | if (i < a.length) { 79 | a[i] = null; 80 | } 81 | 82 | return a; 83 | } 84 | 85 | @Override 86 | public boolean add(Map.Entry kvEntry) { 87 | throw new UnsupportedOperationException(); 88 | } 89 | 90 | @Override 91 | public boolean remove(Object o) { 92 | if (!(o instanceof Map.Entry)) { 93 | return false; 94 | } 95 | 96 | final Map.Entry e = (Map.Entry)o; 97 | if (containsEntry(e)) { 98 | that.remove(e.getKey()); 99 | return true; 100 | } else { 101 | return false; 102 | } 103 | } 104 | 105 | @Override 106 | public boolean containsAll(Collection c) { 107 | return c.stream().allMatch(this::contains); 108 | } 109 | 110 | @Override 111 | public boolean addAll(Collection> c) { 112 | throw new UnsupportedOperationException(); 113 | } 114 | 115 | @Override 116 | public boolean removeAll(Collection c) { 117 | return removeRetainAll(c, true); 118 | } 119 | 120 | @Override 121 | public boolean retainAll(Collection c) { 122 | return removeRetainAll(c, false); 123 | } 124 | 125 | private boolean removeRetainAll(Collection c, boolean removeIfMentioned) { 126 | final Map> cMap = new HashMap<>(); 127 | for (Object x : c) { 128 | if (!(x instanceof Map.Entry)) { 129 | continue; 130 | } 131 | 132 | final Map.Entry e = (Map.Entry)x; 133 | cMap.computeIfAbsent(e.getKey(), _key -> new HashSet<>()).add(e.getValue()); 134 | } 135 | 136 | boolean changed = false; 137 | final Iterator> it = iterator(); 138 | while (it.hasNext()) { 139 | final Map.Entry e = it.next(); 140 | if (removeIfMentioned == cMap.getOrDefault(e.getKey(), Collections.emptySet()).contains(e.getValue())) { 141 | it.remove(); 142 | changed = true; 143 | } 144 | } 145 | 146 | return changed; 147 | } 148 | 149 | @Override 150 | public void clear() { 151 | that.clear(); 152 | } 153 | 154 | @Override 155 | public Iterator> iterator() { 156 | return iterable.iterator(); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/MapValueCollection.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.*; 4 | 5 | class MapValueCollection implements Collection { 6 | private final Map that; 7 | 8 | public MapValueCollection(Map that) { 9 | this.that = that; 10 | } 11 | 12 | @Override 13 | public String toString() { 14 | return Iterables.toString(this); 15 | } 16 | 17 | @Override 18 | public boolean equals(Object that) { 19 | return that instanceof Collection && equals((Collection)that); 20 | } 21 | 22 | private boolean equals(Collection that) { 23 | if (this.size() != that.size()) return false; 24 | 25 | // Seems dumb but suggested by the Javadoc for Collection.equals 26 | if (that instanceof Set || that instanceof List) return false; 27 | 28 | final Iterator thisIt = this.iterator(); 29 | final Iterator thatIt = that.iterator(); 30 | 31 | while (thisIt.hasNext() && thatIt.hasNext()) { 32 | if (!Objects.equals(thisIt.next(), thatIt.next())) { 33 | return false; 34 | } 35 | } 36 | 37 | if (thisIt.hasNext() || thatIt.hasNext()) { 38 | return false; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | int h = 0; 47 | for (V v : this) { 48 | h += Objects.hashCode(v); 49 | } 50 | 51 | return h; 52 | } 53 | 54 | @Override 55 | public int size() { 56 | return that.size(); 57 | } 58 | 59 | @Override 60 | public boolean isEmpty() { 61 | return that.isEmpty(); 62 | } 63 | 64 | @Override 65 | public boolean contains(Object o) { 66 | for (V x : this) { 67 | if (Objects.equals(x, o)) { 68 | return true; 69 | } 70 | } 71 | 72 | return false; 73 | } 74 | 75 | @Override 76 | public Iterator iterator() { 77 | final Iterator> it = that.entrySet().iterator(); 78 | return new Iterator() { 79 | @Override 80 | public boolean hasNext() { 81 | return it.hasNext(); 82 | } 83 | 84 | @Override 85 | public V next() { 86 | return it.next().getValue(); 87 | } 88 | 89 | @Override 90 | public void remove() { 91 | it.remove(); 92 | } 93 | }; 94 | } 95 | 96 | @Override 97 | public Object[] toArray() { 98 | final Object[] result = new Object[size()]; 99 | int i = 0; 100 | for (V value : this) { 101 | result[i++] = value; 102 | } 103 | 104 | assert i == result.length; 105 | return result; 106 | } 107 | 108 | @Override 109 | public T[] toArray(T[] a) { 110 | final int size = size(); 111 | if (a.length < size) { 112 | a = Arrays.copyOf(a, size); 113 | } 114 | 115 | int i = 0; 116 | for (V value : this) { 117 | //noinspection unchecked 118 | a[i++] = (T)value; 119 | } 120 | 121 | if (i < a.length) { 122 | a[i] = null; 123 | } 124 | 125 | return a; 126 | } 127 | 128 | @Override 129 | public boolean add(V v) { 130 | throw new UnsupportedOperationException(); 131 | } 132 | 133 | @Override 134 | public boolean remove(Object o) { 135 | return that.entrySet().removeIf(e -> Objects.equals(o, e.getValue())); 136 | } 137 | 138 | @Override 139 | public boolean containsAll(Collection c) { 140 | final Set all = new LinkedHashSet<>(c); 141 | for (V x : this) { 142 | all.remove(x); 143 | if (all.isEmpty()) { 144 | return true; 145 | } 146 | } 147 | 148 | return false; 149 | } 150 | 151 | @Override 152 | public boolean addAll(Collection c) { 153 | throw new UnsupportedOperationException(); 154 | } 155 | 156 | @Override 157 | public boolean removeAll(Collection c) { 158 | return removeRetainAll(c, true); 159 | } 160 | 161 | @Override 162 | public boolean retainAll(Collection c) { 163 | return removeRetainAll(c, false); 164 | } 165 | 166 | private boolean removeRetainAll(Collection c, boolean removeIfMentioned) { 167 | final Set all = new LinkedHashSet<>(c); 168 | 169 | boolean changed = false; 170 | final Iterator it = this.iterator(); 171 | while (it.hasNext()) { 172 | if (removeIfMentioned == all.contains(it.next())) { 173 | it.remove(); 174 | changed = true; 175 | } 176 | } 177 | 178 | return changed; 179 | } 180 | 181 | @Override 182 | public void clear() { 183 | that.clear(); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/DescendingNavigableMap.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.*; 4 | 5 | class DescendingNavigableMap implements NavigableMap { 6 | private final NavigableMap2 that; 7 | 8 | public DescendingNavigableMap(NavigableMap2 that) { 9 | this.that = that; 10 | } 11 | 12 | @Override 13 | public String toString() { 14 | return Iterables.toMapString(this.entrySet()); 15 | } 16 | 17 | @Override 18 | public boolean equals(Object that) { 19 | return SortedMaps.equals(this, that); 20 | } 21 | 22 | @Override 23 | public int hashCode() { 24 | return Iterables.hashCode(entrySet()); 25 | } 26 | 27 | @Override 28 | public Entry lowerEntry(K key) { 29 | return that.asNavigableMap().higherEntry(key); 30 | } 31 | 32 | @Override 33 | public K lowerKey(K key) { 34 | return that.asNavigableMap().higherKey(key); 35 | } 36 | 37 | @Override 38 | public Entry floorEntry(K key) { 39 | return that.asNavigableMap().ceilingEntry(key); 40 | } 41 | 42 | @Override 43 | public K floorKey(K key) { 44 | return that.asNavigableMap().ceilingKey(key); 45 | } 46 | 47 | @Override 48 | public Entry ceilingEntry(K key) { 49 | return that.asNavigableMap().floorEntry(key); 50 | } 51 | 52 | @Override 53 | public K ceilingKey(K key) { 54 | return that.asNavigableMap().floorKey(key); 55 | } 56 | 57 | @Override 58 | public Entry higherEntry(K key) { 59 | return that.asNavigableMap().lowerEntry(key); 60 | } 61 | 62 | @Override 63 | public K higherKey(K key) { 64 | return that.asNavigableMap().lowerKey(key); 65 | } 66 | 67 | @Override 68 | public Entry firstEntry() { 69 | return that.asNavigableMap().lastEntry(); 70 | } 71 | 72 | @Override 73 | public Entry lastEntry() { 74 | return that.asNavigableMap().firstEntry(); 75 | } 76 | 77 | @Override 78 | public Entry pollFirstEntry() { 79 | return that.asNavigableMap().pollLastEntry(); 80 | } 81 | 82 | @Override 83 | public Entry pollLastEntry() { 84 | return that.asNavigableMap().pollFirstEntry(); 85 | } 86 | 87 | @Override 88 | public NavigableMap descendingMap() { 89 | return that.asNavigableMap(); 90 | } 91 | 92 | @Override 93 | public NavigableSet navigableKeySet() { 94 | return new NavigableMapKeySet(this); 95 | } 96 | 97 | @Override 98 | public NavigableSet descendingKeySet() { 99 | return that.asNavigableMap().navigableKeySet(); 100 | } 101 | 102 | @Override 103 | public NavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { 104 | return new DescendingNavigableMap(that.subMap(toKey, toInclusive, fromKey, fromInclusive)); 105 | } 106 | 107 | @Override 108 | public NavigableMap headMap(K toKey, boolean inclusive) { 109 | return new DescendingNavigableMap(that.tailMap(toKey, inclusive)); 110 | } 111 | 112 | @Override 113 | public NavigableMap tailMap(K fromKey, boolean inclusive) { 114 | return new DescendingNavigableMap(that.headMap(fromKey, inclusive)); 115 | } 116 | 117 | @Override 118 | public Comparator comparator() { 119 | return Collections.reverseOrder(that.asNavigableMap().comparator()); 120 | } 121 | 122 | @Override 123 | public SortedMap subMap(K fromKey, K toKey) { 124 | return subMap(fromKey, true, toKey, false); 125 | } 126 | 127 | @Override 128 | public SortedMap headMap(K toKey) { 129 | return headMap(toKey, false); 130 | } 131 | 132 | @Override 133 | public SortedMap tailMap(K fromKey) { 134 | return tailMap(fromKey, true); 135 | } 136 | 137 | @Override 138 | public K firstKey() { 139 | return that.asNavigableMap().lastKey(); 140 | } 141 | 142 | @Override 143 | public K lastKey() { 144 | return that.asNavigableMap().firstKey(); 145 | } 146 | 147 | @Override 148 | public int size() { 149 | return that.asNavigableMap().size(); 150 | } 151 | 152 | @Override 153 | public boolean isEmpty() { 154 | return that.asNavigableMap().isEmpty(); 155 | } 156 | 157 | @Override 158 | public boolean containsKey(Object key) { 159 | return that.asNavigableMap().containsKey(key); 160 | } 161 | 162 | @Override 163 | public boolean containsValue(Object value) { 164 | return that.asNavigableMap().containsValue(value); 165 | } 166 | 167 | @Override 168 | public V get(Object key) { 169 | return that.asNavigableMap().get(key); 170 | } 171 | 172 | @Override 173 | public V put(K key, V value) { 174 | return that.asNavigableMap().put(key, value); 175 | } 176 | 177 | @Override 178 | public V remove(Object key) { 179 | return that.asNavigableMap().remove(key); 180 | } 181 | 182 | @Override 183 | public void putAll(Map m) { 184 | that.asNavigableMap().putAll(m); 185 | } 186 | 187 | @Override 188 | public void clear() { 189 | that.asNavigableMap().clear(); 190 | } 191 | 192 | @Override 193 | public Set keySet() { 194 | return navigableKeySet(); 195 | } 196 | 197 | @Override 198 | public Collection values() { 199 | return new MapValueCollection(this); 200 | } 201 | 202 | @Override 203 | public Set> entrySet() { 204 | return that.descendingEntrySet(); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/uk/co/omegaprime/btreemap/NavigableMapKeySet.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.*; 4 | 5 | class NavigableMapKeySet implements NavigableSet { 6 | private final NavigableMap that; 7 | 8 | NavigableMapKeySet(NavigableMap that) { 9 | this.that = that; 10 | } 11 | 12 | @Override 13 | public String toString() { 14 | return Iterables.toString(this); 15 | } 16 | 17 | @Override 18 | public boolean equals(Object that) { 19 | return that instanceof Set && equals((Set)that); 20 | } 21 | 22 | private boolean equals(Set that) { 23 | if (that instanceof SortedSet && Objects.equals(comparator(), ((SortedSet)that).comparator())) { 24 | return this.size() == that.size() && Iterables.equals(this, that); 25 | } else { 26 | return Sets.equals(this, that); 27 | } 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | return Iterables.hashCode(this); 33 | } 34 | 35 | @Override 36 | public K lower(K k) { 37 | return that.lowerKey(k); 38 | } 39 | 40 | @Override 41 | public K floor(K k) { 42 | return that.floorKey(k); 43 | } 44 | 45 | @Override 46 | public K ceiling(K k) { 47 | return that.ceilingKey(k); 48 | } 49 | 50 | @Override 51 | public K higher(K k) { 52 | return that.higherKey(k); 53 | } 54 | 55 | @Override 56 | public K pollFirst() { 57 | return BTreeMap.getEntryKey(that.pollFirstEntry()); 58 | } 59 | 60 | @Override 61 | public K pollLast() { 62 | return BTreeMap.getEntryKey(that.pollLastEntry()); 63 | } 64 | 65 | @Override 66 | public int size() { 67 | return that.size(); 68 | } 69 | 70 | @Override 71 | public boolean isEmpty() { 72 | return that.isEmpty(); 73 | } 74 | 75 | @Override 76 | public boolean contains(Object o) { 77 | //noinspection SuspiciousMethodCalls 78 | return that.containsKey(o); 79 | } 80 | 81 | @Override 82 | public Iterator iterator() { 83 | final Iterator> it = that.entrySet().iterator(); 84 | return new Iterator() { 85 | @Override 86 | public boolean hasNext() { 87 | return it.hasNext(); 88 | } 89 | 90 | @Override 91 | public K next() { 92 | return it.next().getKey(); 93 | } 94 | 95 | @Override 96 | public void remove() { 97 | it.remove(); 98 | } 99 | }; 100 | } 101 | 102 | @Override 103 | public Object[] toArray() { 104 | final Object[] result = new Object[size()]; 105 | int i = 0; 106 | for (K key : this) { 107 | result[i++] = key; 108 | } 109 | 110 | assert i == result.length; 111 | return result; 112 | } 113 | 114 | @Override 115 | public T[] toArray(T[] a) { 116 | final int size = size(); 117 | if (a.length < size) { 118 | a = Arrays.copyOf(a, size); 119 | } 120 | 121 | int i = 0; 122 | for (K key : this) { 123 | //noinspection unchecked 124 | a[i++] = (T)key; 125 | } 126 | 127 | if (i < a.length) { 128 | a[i] = null; 129 | } 130 | 131 | return a; 132 | } 133 | 134 | @Override 135 | public boolean add(K k) { 136 | throw new UnsupportedOperationException(); 137 | } 138 | 139 | @Override 140 | public boolean addAll(Collection c) { 141 | throw new UnsupportedOperationException(); 142 | } 143 | 144 | @Override 145 | public boolean remove(Object o) { 146 | //noinspection SuspiciousMethodCalls 147 | if (that.containsKey(o)) { 148 | that.remove(o); 149 | return true; 150 | } else { 151 | return false; 152 | } 153 | } 154 | 155 | @Override 156 | public boolean containsAll(Collection c) { 157 | return c.stream().allMatch(this::contains); 158 | } 159 | 160 | @Override 161 | public boolean retainAll(Collection c) { 162 | // TODO: fast path? 163 | final Set cSet = c instanceof Set ? (Set)c : new HashSet<>(c); 164 | 165 | boolean changed = false; 166 | final Iterator it = iterator(); 167 | while (it.hasNext()) { 168 | final K k = it.next(); 169 | if (!cSet.contains(k)) { 170 | changed = true; 171 | it.remove(); 172 | } 173 | } 174 | 175 | return changed; 176 | } 177 | 178 | @Override 179 | public boolean removeAll(Collection c) { 180 | boolean changed = false; 181 | for (Object x : c) { 182 | changed |= remove(x); 183 | } 184 | 185 | return changed; 186 | } 187 | 188 | @Override 189 | public void clear() { 190 | that.clear(); 191 | } 192 | 193 | @Override 194 | public NavigableSet descendingSet() { 195 | return new NavigableMapKeySet<>(that.descendingMap()); 196 | } 197 | 198 | @Override 199 | public Iterator descendingIterator() { 200 | return new NavigableMapKeySet<>(that.descendingMap()).iterator(); 201 | } 202 | 203 | @Override 204 | public Comparator comparator() { 205 | return that.comparator(); 206 | } 207 | 208 | @Override 209 | public SortedSet subSet(K fromElement, K toElement) { 210 | return subSet(fromElement, true, toElement, false); 211 | } 212 | 213 | @Override 214 | public NavigableSet subSet(K fromElement, boolean fromInclusive, K toElement, boolean toInclusive) { 215 | return new NavigableMapKeySet<>(that.subMap(fromElement, fromInclusive, toElement, toInclusive)); 216 | } 217 | 218 | @Override 219 | public SortedSet headSet(K toElement) { 220 | return headSet(toElement, false); 221 | } 222 | 223 | @Override 224 | public NavigableSet headSet(K toElement, boolean inclusive) { 225 | return new NavigableMapKeySet<>(that.headMap(toElement, inclusive)); 226 | } 227 | 228 | @Override 229 | public SortedSet tailSet(K fromElement) { 230 | return tailSet(fromElement, true); 231 | } 232 | 233 | @Override 234 | public NavigableSet tailSet(K fromElement, boolean inclusive) { 235 | return new NavigableMapKeySet<>(that.tailMap(fromElement, inclusive)); 236 | } 237 | 238 | @Override 239 | public K first() { 240 | return that.firstKey(); 241 | } 242 | 243 | @Override 244 | public K last() { 245 | return that.lastKey(); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/main/templates/uk.co.omegaprime.btreemap/{{KV_}}Node.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import sun.misc.Unsafe; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Modifier; 9 | import java.util.regex.Pattern; 10 | import java.util.*; 11 | 12 | // This is basically a fixed size Object[] with a "int" size field 13 | final class Node<$K$,$V$> extends AbstractNode { 14 | // We're going to do a generalized (2, 3) tree i.e. each internal node will have between m and (2m - 1) children inclusive, for m >= 2 15 | // 16 | // What's a sensible value for m? It would be good if each array we allocate fits within one cache line. On Skylake, 17 | // cache lines are 64 bytes, and with compressed OOPS (default for heap sizes < 32GB) object pointers are only 4 bytes long, 18 | // implying that MAX_FANOUT = 16 would be a good choice, i.e. MIN_FANOUT = 8. 19 | // 20 | // With MIN_FANOUT=2: 21 | // Benchmark Mode Cnt Score Error Units 22 | // BTreeMapBenchmark.get thrpt 40 1900386.806 ± 115791.569 ops/s 23 | // BTreeMapBenchmark.put thrpt 40 1617089.096 ± 32891.292 ops/s 24 | // 25 | // With MIN_FANOUT=8: 26 | // Benchmark Mode Cnt Score Error Units 27 | // BTreeMapBenchmark.get thrpt 40 4021130.733 ± 31473.315 ops/s 28 | // BTreeMapBenchmark.put thrpt 40 2821784.716 ± 141837.270 ops/s 29 | // 30 | // java.util.TreeMap: 31 | // Benchmark Mode Cnt Score Error Units 32 | // BTreeMapBenchmark.get thrpt 40 3226633.131 ± 195725.464 ops/s 33 | // BTreeMapBenchmark.put thrpt 40 2561772.533 ± 31611.667 ops/s 34 | // 35 | // After some benchmarking I found that MIN_FANOUT = 16 is ~10% faster than MIN_FANOUT = 8: 36 | // 37 | // Min Fanout Get Throughput (ops/sec) 99.9% CI 38 | // 4 4216265.491 43520.265 39 | // 5 4409875.947 91176.223 40 | // 6 4573909.740 59522.347 41 | // 7 4833759.229 102045.678 42 | // 8 4919145.622 55779.310 43 | // 9 4983996.875 116098.733 44 | // 10 4727779.650 100560.027 45 | // 11 4903133.847 180600.028 46 | // 12 5245384.941 48753.377 47 | // 13 5248156.906 83901.512 48 | // 14 4953773.754 77839.875 49 | // 15 5204504.977 174990.439 50 | // 16 5305792.167 155854.297 51 | // 17 5347054.930 47659.022 52 | // 18 5415975.463 53697.353 53 | // 19 5398902.319 47398.619 54 | // 20 5374494.509 73927.102 55 | // 21 5132999.986 40521.262 56 | // 22 5161704.152 70647.085 57 | public static final int MIN_FANOUT = 16; 58 | public static final int MAX_FANOUT = 2 * MIN_FANOUT - 1; 59 | 60 | // Linear search seems about ~20% faster than binary (for MIN_FANOUT = 8 at least). 61 | // It's 45% (!) faster for MIN_FANOUT = 16. Crazy. 62 | public static final boolean BINARY_SEARCH = false; 63 | 64 | private static final Unsafe UNSAFE; 65 | private static final long KEY_OFFSET0, VALUE_OFFSET0; 66 | private static final int KEY_SIZE, VALUE_SIZE; 67 | 68 | private static long verifyInstanceFieldsContiguous(String prefix, long stride) { 69 | final TreeMap fieldByOffset = new TreeMap<>(); 70 | for (Field f : Node<$K$,$V$>.class.getDeclaredFields()) { 71 | if (f.getName().matches(Pattern.quote(prefix) + "[0-9]+") && (f.getModifiers() & Modifier.STATIC) == 0) { 72 | final long offset = UNSAFE.objectFieldOffset(f); 73 | if (fieldByOffset.put(offset, f) != null) { 74 | throw new IllegalStateException("Multiple fields seem to share a single offset " + offset); 75 | } 76 | } 77 | } 78 | 79 | if (fieldByOffset.size() != MAX_FANOUT) { 80 | throw new IllegalStateException("Expected " + MAX_FANOUT + " " + prefix + " fields, got " + fieldByOffset.size()); 81 | } 82 | 83 | final Iterator> it = fieldByOffset.entrySet().iterator(); 84 | final long firstOffset = it.next().getKey(); 85 | long lastOffset = firstOffset; 86 | while (it.hasNext()) { 87 | final Map.Entry e = it.next(); 88 | final long offset = e.getKey(); 89 | if (offset != lastOffset + stride) { 90 | throw new IllegalStateException("Expected fields to be contiguous in memory but " + e.getValue() + " is at " + offset + " and the last one was at " + lastOffset); 91 | } 92 | 93 | lastOffset = offset; 94 | } 95 | 96 | return firstOffset; 97 | } 98 | 99 | static { 100 | try { 101 | Constructor unsafeConstructor = Unsafe.class.getDeclaredConstructor(); 102 | unsafeConstructor.setAccessible(true); 103 | UNSAFE = unsafeConstructor.newInstance(); 104 | } catch (NoSuchElementException | IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException e) { 105 | throw new RuntimeException(e); 106 | } 107 | 108 | // Object pointer size might differ from Unsafe.ADDRESS_SIZE if compressed OOPs are in use 109 | KEY_SIZE = UNSAFE.arrayIndexScale(@Erased $K$[].class); 110 | VALUE_SIZE = UNSAFE.arrayIndexScale(@Erased $V$[].class); 111 | 112 | KEY_OFFSET0 = verifyInstanceFieldsContiguous("k", KEY_SIZE); 113 | VALUE_OFFSET0 = verifyInstanceFieldsContiguous("v", VALUE_SIZE); 114 | } 115 | 116 | private $K$ 117 | k00, k01, k02, k03, k04, k05, k06, k07, 118 | k08, k09, k10, k11, k12, k13, k14, k15, 119 | k16, k17, k18, k19, k20, k21, k22, k23, 120 | k24, k25, k26, k27, k28, k29, k30; 121 | private $V$ 122 | v00, v01, v02, v03, v04, v05, v06, v07, 123 | v08, v09, v10, v11, v12, v13, v14, v15, 124 | v16, v17, v18, v19, v20, v21, v22, v23, 125 | v24, v25, v26, v27, v28, v29, v30; 126 | 127 | @Override 128 | public Node<$K$, $V$> clone() { 129 | final Node<$K$, $V$> result = new Node<$K$, $V$>(); 130 | result.size = this.size; 131 | for (int i = 0; i < size; i++) { 132 | result.setKey (i, this.getKey(i)); 133 | result.setValue(i, this.getValue(i)); 134 | } 135 | return result; 136 | } 137 | 138 | public Node<$K$, $V$> clone(int depth) { 139 | assert depth >= 0; 140 | if (depth > 0) { 141 | {% if V.isPrimitive() %} 142 | throw new IllegalArgumentException("Can't use depth > 1 since this is a primitive node"); 143 | {% else %} 144 | final Node<$K$, $V$> result = new Node<$K$, $V$>(); 145 | result.size = this.size; 146 | for (int i = 0; i < size; i++) { 147 | final $V$ value = this.getValue(i); 148 | final $V$ newValue = ($V$)AbstractNode.class.cast(value).clone(depth - 1); 149 | 150 | result.setKey (i, this.getKey(i)); 151 | result.setValue(i, newValue); 152 | } 153 | return result; 154 | {% endif %} 155 | } else { 156 | return this.clone(); 157 | } 158 | } 159 | 160 | public $K$ getKey (int i) { 161 | return ($K$)UNSAFE.get{{K.name}}(this, KEY_OFFSET0 + i * KEY_SIZE); 162 | } 163 | public $V$ getValue(int i) { 164 | return ($V$)UNSAFE.get{{V.name}}(this, VALUE_OFFSET0 + i * VALUE_SIZE); 165 | } 166 | 167 | public void setKey (int i, $K$ x) { 168 | UNSAFE.put{{K.name}}(this, KEY_OFFSET0 + i * KEY_SIZE, x); 169 | } 170 | public void setValue(int i, $V$ x) { 171 | UNSAFE.put{{V.name}}(this, VALUE_OFFSET0 + i * VALUE_SIZE, x); 172 | } 173 | 174 | public int binarySearch(int fromIndex, int toIndex, @Erased $K$ key, {{K_}}Comparator c) { 175 | if (c == null) { 176 | return binarySearch(fromIndex, toIndex, key); 177 | } 178 | 179 | int low = fromIndex; 180 | int high = toIndex - 1; 181 | 182 | while (low <= high) { 183 | int mid = (low + high) >>> 1; 184 | $K$ midVal = getKey(mid); 185 | int cmp = c.compare{{K_}}(midVal, key); 186 | if (cmp < 0) 187 | low = mid + 1; 188 | else if (cmp > 0) 189 | high = mid - 1; 190 | else 191 | return mid; // key found 192 | } 193 | return -(low + 1); // key not found. 194 | } 195 | 196 | public int binarySearch(int fromIndex, int toIndex, @Erased $K$ key) { 197 | int low = fromIndex; 198 | int high = toIndex - 1; 199 | 200 | while (low <= high) { 201 | int mid = (low + high) >>> 1; 202 | @SuppressWarnings("rawtypes") 203 | Comparable midVal = (Comparable)this.getKey(mid); 204 | @SuppressWarnings("unchecked") 205 | int cmp = midVal.compareTo(key); 206 | 207 | if (cmp < 0) 208 | low = mid + 1; 209 | else if (cmp > 0) 210 | high = mid - 1; 211 | else 212 | return mid; // key found 213 | } 214 | return -(low + 1); // key not found. 215 | } 216 | 217 | public static <$K$,$V$> void arraycopyKey(Node<$K$,$V$> src, int srcIndex, Node dst, int dstIndex, int size) { 218 | if (size < 0 || srcIndex + size > MAX_FANOUT || dstIndex + size > MAX_FANOUT) { 219 | throw new ArrayIndexOutOfBoundsException(); 220 | } 221 | 222 | if (dst == src && srcIndex < dstIndex) { 223 | for (int i = size - 1; i >= 0; i--) { 224 | dst.setKey(dstIndex + i, src.getKey(srcIndex + i)); 225 | } 226 | } else { 227 | for (int i = 0; i < size; i++) { 228 | dst.setKey(dstIndex + i, src.getKey(srcIndex + i)); 229 | } 230 | } 231 | } 232 | 233 | public static <$K$,$V$> void arraycopyValue(Node<$K$,$V$> src, int srcIndex, Node dst, int dstIndex, int size) { 234 | if (size < 0 || srcIndex + size > MAX_FANOUT || dstIndex + size > MAX_FANOUT) { 235 | throw new ArrayIndexOutOfBoundsException(); 236 | } 237 | 238 | if (dst == src && srcIndex < dstIndex) { 239 | for (int i = size - 1; i >= 0; i--) { 240 | dst.setValue(dstIndex + i, src.getValue(srcIndex + i)); 241 | } 242 | } else { 243 | for (int i = 0; i < size; i++) { 244 | dst.setValue(dstIndex + i, src.getValue(srcIndex + i)); 245 | } 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /src/main/templates/uk.co.omegaprime.btreemap/{{KV_}}RestrictedBTreeMap.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.*; 4 | 5 | class RestrictedBTreeMap<$K$, $V$> implements NavigableMap<@Boxed $K$, @Boxed $V$> { 6 | 7 | private final BTreeMap<$K$, $V$> that; 8 | private final @Boxed $K$ min, max; 9 | private final Bound minBound, maxBound; 10 | 11 | public {{KV_}}RestrictedBTreeMap(BTreeMap<$K$, $V$> that, @Boxed $K$ min, @Boxed $K$ max, Bound minBound, Bound maxBound) { 12 | // Map should still work fine if this invariant is violated, but: 13 | // 1. It might be less efficient than using "that" directly 14 | // 2. It's impossible for a user to construct such an instance right now 15 | assert minBound != Bound.MISSING || maxBound != Bound.MISSING; 16 | 17 | this.that = that; 18 | this.min = min; 19 | this.max = max; 20 | this.minBound = minBound; 21 | this.maxBound = maxBound; 22 | } 23 | 24 | private boolean inRange(Object that) { 25 | return minBound.lt(min, that, comparator()) && maxBound.lt(that, max, comparator()); 26 | } 27 | 28 | @Override 29 | public Entry<@Boxed $K$, @Boxed $V$> lowerEntry(@Boxed $K$ key) { 30 | final Entry<@Boxed $K$, @Boxed $V$> e; 31 | if (maxBound == Bound.MISSING || Objects.compare(key, max, comparator()) <= 0) { 32 | e = that.lowerEntry(key); 33 | } else if (maxBound == Bound.INCLUSIVE) { 34 | e = that.floorEntry(max); 35 | } else { 36 | e = that.lowerEntry(max); 37 | } 38 | 39 | return e != null && minBound.lt(min, e.getKey(), comparator()) ? e : null; 40 | } 41 | 42 | @Override 43 | public @Boxed $K$ lowerKey(@Boxed $K$ key) { 44 | return BTreeMap.getEntryKey(lowerEntry(key)); 45 | } 46 | 47 | @Override 48 | public Entry<@Boxed $K$, @Boxed $V$> floorEntry(@Boxed $K$ key) { 49 | final Entry<@Boxed $K$, @Boxed $V$> e; 50 | if (maxBound == Bound.MISSING || Objects.compare(key, max, comparator()) < 0) { 51 | e = that.floorEntry(key); 52 | } else if (maxBound == Bound.INCLUSIVE) { 53 | e = that.floorEntry(max); 54 | } else { 55 | e = that.lowerEntry(max); 56 | } 57 | 58 | return e != null && minBound.lt(min, e.getKey(), comparator()) ? e : null; 59 | } 60 | 61 | @Override 62 | public @Boxed $K$ floorKey(@Boxed $K$ key) { 63 | return BTreeMap.getEntryKey(floorEntry(key)); 64 | } 65 | 66 | @Override 67 | public Entry<@Boxed $K$, @Boxed $V$> ceilingEntry(@Boxed $K$ key) { 68 | final Entry<@Boxed $K$, @Boxed $V$> e; 69 | if (minBound == Bound.MISSING || Objects.compare(min, key, comparator()) > 0) { 70 | e = that.ceilingEntry(key); 71 | } else if (minBound == Bound.INCLUSIVE) { 72 | e = that.ceilingEntry(min); 73 | } else { 74 | e = that.higherEntry(min); 75 | } 76 | 77 | return e != null && maxBound.lt(e.getKey(), max, comparator()) ? e : null; 78 | } 79 | 80 | @Override 81 | public @Boxed $K$ ceilingKey(@Boxed $K$ key) { 82 | return BTreeMap.getEntryKey(ceilingEntry(key)); 83 | } 84 | 85 | @Override 86 | public Entry<@Boxed $K$, @Boxed $V$> higherEntry(@Boxed $K$ key) { 87 | final Entry<@Boxed $K$, @Boxed $V$> e; 88 | if (minBound == Bound.MISSING || Objects.compare(min, key, comparator()) >= 0) { 89 | e = that.higherEntry(key); 90 | } else if (minBound == Bound.INCLUSIVE) { 91 | e = that.ceilingEntry(min); 92 | } else { 93 | e = that.higherEntry(min); 94 | } 95 | 96 | return e != null && maxBound.lt(e.getKey(), max, comparator()) ? e : null; 97 | } 98 | 99 | @Override 100 | public @Boxed $K$ higherKey(@Boxed $K$ key) { 101 | return BTreeMap.getEntryKey(higherEntry(key)); 102 | } 103 | 104 | @Override 105 | public Entry<@Boxed $K$, @Boxed $V$> firstEntry() { 106 | switch (minBound) { 107 | case MISSING: return that.firstEntry(); 108 | case INCLUSIVE: return that.ceilingEntry(min); 109 | case EXCLUSIVE: return that.higherEntry(min); 110 | default: throw new IllegalStateException(); 111 | } 112 | } 113 | 114 | @Override 115 | public Entry<@Boxed $K$, @Boxed $V$> lastEntry() { 116 | switch (minBound) { 117 | case MISSING: return that.lastEntry(); 118 | case INCLUSIVE: return that.floorEntry(max); 119 | case EXCLUSIVE: return that.lowerEntry(max); 120 | default: throw new IllegalStateException(); 121 | } 122 | } 123 | 124 | @Override 125 | public Entry<@Boxed $K$, @Boxed $V$> pollFirstEntry() { 126 | // TODO: fast path? 127 | final Entry<@Boxed $K$, @Boxed $V$> e = firstEntry(); 128 | if (e != null) { 129 | remove(e.getKey()); 130 | } 131 | 132 | return e; 133 | } 134 | 135 | @Override 136 | public Entry<@Boxed $K$, @Boxed $V$> pollLastEntry() { 137 | // TODO: fast path? 138 | final Entry<@Boxed $K$, @Boxed $V$> e = lastEntry(); 139 | if (e != null) { 140 | remove(e.getKey()); 141 | } 142 | 143 | return e; 144 | } 145 | 146 | @Override 147 | public NavigableMap<@Boxed $K$, @Boxed $V$> descendingMap() { 148 | return new DescendingNavigableMap<>(this.asNavigableMap2()); 149 | } 150 | 151 | @Override 152 | public NavigableSet<@Boxed $K$> navigableKeySet() { 153 | return new NavigableMapKeySet<>(this); 154 | } 155 | 156 | @Override 157 | public NavigableSet<@Boxed $K$> descendingKeySet() { 158 | return descendingMap().descendingKeySet(); 159 | } 160 | 161 | @Override 162 | public NavigableMap<@Boxed $K$, @Boxed $V$> subMap(@Boxed $K$ fromKey, boolean fromInclusive, @Boxed $K$ toKey, boolean toInclusive) { 163 | return asNavigableMap2().subMap(fromKey, fromInclusive, toKey, toInclusive).asNavigableMap(); 164 | } 165 | 166 | @Override 167 | public NavigableMap<@Boxed $K$, @Boxed $V$> headMap(@Boxed $K$ toKey, boolean inclusive) { 168 | return asNavigableMap2().headMap(toKey, inclusive).asNavigableMap(); 169 | } 170 | 171 | @Override 172 | public NavigableMap<@Boxed $K$, @Boxed $V$> tailMap(@Boxed $K$ fromKey, boolean inclusive) { 173 | return asNavigableMap2().tailMap(fromKey, inclusive).asNavigableMap(); 174 | } 175 | 176 | @Override 177 | public Comparator comparator() { 178 | return that.comparator(); 179 | } 180 | 181 | @Override 182 | public SortedMap<@Boxed $K$, @Boxed $V$> subMap(@Boxed $K$ fromKey, @Boxed $K$ toKey) { 183 | return subMap(fromKey, true, toKey, false); 184 | } 185 | 186 | @Override 187 | public SortedMap<@Boxed $K$, @Boxed $V$> headMap(@Boxed $K$ toKey) { 188 | return headMap(toKey, false); 189 | } 190 | 191 | @Override 192 | public SortedMap<@Boxed $K$, @Boxed $V$> tailMap(@Boxed $K$ fromKey) { 193 | return tailMap(fromKey, true); 194 | } 195 | 196 | @Override 197 | public @Boxed $K$ firstKey() { 198 | return BTreeMap.getEntryKey(firstEntry()); 199 | } 200 | 201 | @Override 202 | public @Boxed $K$ lastKey() { 203 | return BTreeMap.getEntryKey(lastEntry()); 204 | } 205 | 206 | @Override 207 | public int size() { 208 | int i = 0; 209 | for (Map.Entry<@Boxed $K$, @Boxed $V$> _e : this.entrySet()) { 210 | i++; 211 | } 212 | 213 | return i; 214 | } 215 | 216 | @Override 217 | public boolean isEmpty() { 218 | return !entrySet().iterator().hasNext(); 219 | } 220 | 221 | @Override 222 | public boolean containsKey(Object key) { 223 | return inRange(key) && that.containsKey(key); 224 | } 225 | 226 | @Override 227 | public boolean containsValue(Object value) { 228 | return values().contains(value); 229 | } 230 | 231 | @Override 232 | public @Boxed $V$ get(Object key) { 233 | return inRange(key) ? that.get(key) : null; 234 | } 235 | 236 | @Override 237 | public @Boxed $V$ put(@Boxed $K$ key, @Boxed $V$ value) { 238 | if (!inRange(key)) { 239 | throw new IllegalArgumentException("key out of range"); 240 | } 241 | return that.put(key, value); 242 | } 243 | 244 | @Override 245 | public @Boxed $V$ remove(Object key) { 246 | return inRange(key) ? that.remove(key) : null; 247 | } 248 | 249 | @Override 250 | public void putAll(Map m) { 251 | for (Map.Entry e : m.entrySet()) { 252 | put(e.getKey(), e.getValue()); 253 | } 254 | } 255 | 256 | @Override 257 | public void clear() { 258 | // TODO: fast path? 259 | final Iterator> it = entrySet().iterator(); 260 | while (it.hasNext()) { 261 | it.next(); 262 | it.remove(); 263 | } 264 | } 265 | 266 | @Override 267 | public Set<@Boxed $K$> keySet() { 268 | return navigableKeySet(); 269 | } 270 | 271 | @Override 272 | public Collection<@Boxed $V$> values() { 273 | return new MapValueCollection<@Boxed $V$>(this); 274 | } 275 | 276 | @Override 277 | public Set> entrySet() { 278 | return new MapEntrySet<@Boxed $K$, @Boxed $V$>(this, () -> { 279 | final Iterator> it; 280 | switch (minBound) { 281 | case MISSING: it = that.firstIterator(); break; 282 | case INCLUSIVE: it = that.ceilingIterator(min); break; 283 | case EXCLUSIVE: it = that.higherIterator(min); break; 284 | default: throw new IllegalStateException(); 285 | } 286 | 287 | switch (maxBound) { 288 | case MISSING: return it; 289 | case INCLUSIVE: return Iterators.takeWhile(it, e -> Bound.cmp(e.getKey(), max, comparator()) <= 0); 290 | case EXCLUSIVE: return Iterators.takeWhile(it, e -> Bound.cmp(e.getKey(), max, comparator()) < 0); 291 | default: throw new IllegalStateException(); 292 | } 293 | }); 294 | } 295 | 296 | NavigableMap2<@Boxed $K$, @Boxed $V$> asNavigableMap2() { 297 | return new NavigableMap2<@Boxed $K$, @Boxed $V$>() { 298 | @Override 299 | public NavigableMap<@Boxed $K$, @Boxed $V$> asNavigableMap() { 300 | return {{KV_}}RestrictedBTreeMap.this; 301 | } 302 | 303 | @Override 304 | public Set> descendingEntrySet() { 305 | return new MapEntrySet<@Boxed $K$, @Boxed $V$>({{KV_}}RestrictedBTreeMap.this, () -> { 306 | final Iterator> it; 307 | switch (maxBound) { 308 | case MISSING: it = that.lastIterator(); break; 309 | case INCLUSIVE: it = that.floorIterator(max); break; 310 | case EXCLUSIVE: it = that.lowerIterator(max); break; 311 | default: throw new IllegalStateException(); 312 | } 313 | 314 | switch (minBound) { 315 | case MISSING: return it; 316 | case INCLUSIVE: return Iterators.takeWhile(it, e -> Bound.cmp(e.getKey(), min, comparator()) >= 0); 317 | case EXCLUSIVE: return Iterators.takeWhile(it, e -> Bound.cmp(e.getKey(), min, comparator()) > 0); 318 | default: throw new IllegalStateException(); 319 | } 320 | }); 321 | 322 | } 323 | 324 | @Override 325 | public NavigableMap2<@Boxed $K$, @Boxed $V$> subMap(@Boxed $K$ fromKey, boolean fromInclusive, @Boxed $K$ toKey, boolean toInclusive) { 326 | // FIXME: javadoc specifies several sanity checks that should generate a IllegalArgumentException. May need some of these on BTreeMap too. (or in our constructor) 327 | return headMap(toKey, toInclusive).tailMap(fromKey, fromInclusive); 328 | } 329 | 330 | @Override 331 | public NavigableMap2<@Boxed $K$, @Boxed $V$> headMap(@Boxed $K$ toKey, boolean inclusive) { 332 | if (maxBound.lt(toKey, max, comparator())) { 333 | return new RestrictedBTreeMap<$K$, $V$>(that, min, toKey, minBound, Bound.inclusive(inclusive)).asNavigableMap2(); 334 | } else { 335 | return this; 336 | } 337 | } 338 | 339 | @Override 340 | public NavigableMap2<@Boxed $K$, @Boxed $V$> tailMap(@Boxed $K$ fromKey, boolean inclusive) { 341 | if (minBound.lt(min, fromKey, comparator())) { 342 | return new RestrictedBTreeMap<$K$, $V$>(that, fromKey, max, Bound.inclusive(inclusive), maxBound).asNavigableMap2(); 343 | } else { 344 | return this; 345 | } 346 | } 347 | }; 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/test/java/uk/co/omegaprime/btreemap/BTreeMapTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import com.pholser.junit.quickcheck.From; 4 | import com.pholser.junit.quickcheck.Property; 5 | import com.pholser.junit.quickcheck.generator.GenerationStatus; 6 | import com.pholser.junit.quickcheck.generator.Generator; 7 | import com.pholser.junit.quickcheck.random.SourceOfRandomness; 8 | import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | 13 | import java.util.*; 14 | import java.util.function.BiFunction; 15 | import java.util.function.Function; 16 | 17 | import static org.junit.Assert.*; 18 | 19 | @RunWith(JUnitQuickcheck.class) 20 | public class BTreeMapTest { 21 | private interface Operation { 22 | void apply(NavigableMap expected, NavigableMap actual); 23 | } 24 | 25 | public abstract static class KeyedOperation implements Operation { 26 | private final String name; 27 | private final int key; 28 | private final BiFunction, Integer, T> f; 29 | 30 | public KeyedOperation(String name, Integer key, BiFunction, Integer, T> f) { 31 | this.name = name; 32 | this.key = key; 33 | this.f = f; 34 | } 35 | 36 | @Override 37 | public void apply(NavigableMap expected, NavigableMap actual) { 38 | Assert.assertEquals(f.apply(expected, key), f.apply(actual, key)); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return String.format("%s(%s)", name, key); 44 | } 45 | } 46 | 47 | public static class Get extends KeyedOperation { 48 | public Get(Integer key) { super("Get", key, Map::get); } 49 | } 50 | 51 | public static class Remove extends KeyedOperation { 52 | public Remove(Integer key) { super("Remove", key, Map::remove); } 53 | } 54 | 55 | public static class LowerEntry extends KeyedOperation> { 56 | public LowerEntry(Integer key) { super("LowerEntry", key, NavigableMap::lowerEntry); } 57 | } 58 | 59 | public static class FloorEntry extends KeyedOperation> { 60 | public FloorEntry(Integer key) { super("FloorEntry", key, NavigableMap::floorEntry); } 61 | } 62 | 63 | public static class HigherEntry extends KeyedOperation> { 64 | public HigherEntry(Integer key) { super("HigherEntry", key, NavigableMap::higherEntry); } 65 | } 66 | 67 | public static class CeilingEntry extends KeyedOperation> { 68 | public CeilingEntry(Integer key) { super("CeilingEntry", key, NavigableMap::ceilingEntry); } 69 | } 70 | 71 | // Actually a test of our ceilingIterator 72 | public static class TailMap extends KeyedOperation> { 73 | public TailMap(Integer key) { super("TailMap", key, (m, k) -> new ArrayList<>(m.tailMap(k, true).values())); } 74 | } 75 | 76 | // Actually a test of our higherIterator 77 | public static class TailMapExclusive extends KeyedOperation> { 78 | public TailMapExclusive(Integer key) { super("TailMapExclusive", key, (m, k) -> new ArrayList<>(m.tailMap(k, false).values())); } 79 | } 80 | 81 | // Actually a test of our floorIterator 82 | public static class DescendingTailMap extends KeyedOperation> { 83 | public DescendingTailMap(Integer key) { super("DescendingTailMap", key, (m, k) -> new ArrayList<>(m.descendingMap().tailMap(k, true).values())); } 84 | } 85 | 86 | // Actually a test of our lowerIterator 87 | public static class DescendingTailMapExclusive extends KeyedOperation> { 88 | public DescendingTailMapExclusive(Integer key) { super("DescendingTailMapExclusive", key, (m, k) -> new ArrayList<>(m.descendingMap().tailMap(k, false).values())); } 89 | } 90 | 91 | public static class HeadMap extends KeyedOperation> { 92 | public HeadMap(Integer key) { super("HeadMap", key, (m, k) -> new ArrayList<>(m.headMap(k, true).values())); } 93 | } 94 | 95 | public static class HeadMapExclusive extends KeyedOperation> { 96 | public HeadMapExclusive(Integer key) { super("HeadMapExclusive", key, (m, k) -> new ArrayList<>(m.headMap(k, false).values())); } 97 | } 98 | 99 | public static class UnkeyedOperation implements Operation { 100 | private final String name; 101 | private final Function, T> f; 102 | 103 | public UnkeyedOperation(String name, Function, T> f) { 104 | this.name = name; 105 | this.f = f; 106 | } 107 | 108 | @Override 109 | public void apply(NavigableMap expected, NavigableMap actual) { 110 | Assert.assertEquals(f.apply(expected), f.apply(actual)); 111 | } 112 | 113 | @Override 114 | public String toString() { return name; } 115 | } 116 | 117 | public static class Size extends UnkeyedOperation { 118 | public Size() { super("Size", NavigableMap::size); } 119 | } 120 | 121 | // Actually a test of our ascending iterator 122 | public static class Values extends UnkeyedOperation> { 123 | public Values() { super("Values", m -> new ArrayList<>(m.values())); } 124 | } 125 | 126 | // Actually a test of our descending iterator 127 | public static class DescendingValues extends UnkeyedOperation> { 128 | public DescendingValues() { super("DescendingValues", m -> new ArrayList<>(m.descendingMap().values())); } 129 | } 130 | 131 | public static class FirstEntry extends UnkeyedOperation> { 132 | public FirstEntry() { super("FirstEntry", NavigableMap::firstEntry); } 133 | } 134 | 135 | public static class LastEntry extends UnkeyedOperation> { 136 | public LastEntry() { super("LastEntry", NavigableMap::lastEntry); } 137 | } 138 | 139 | public static class Put implements Operation { 140 | public final int key; 141 | public final int value; 142 | public Put(int key, int value) { 143 | this.key = key; 144 | this.value = value; 145 | } 146 | 147 | @Override 148 | public void apply(NavigableMap expected, NavigableMap actual) { 149 | Assert.assertEquals(expected.put(key, value), actual.put(key, value)); 150 | } 151 | 152 | @Override 153 | public String toString() { 154 | return String.format("Put(%s, %s)", key, value); 155 | } 156 | } 157 | 158 | private static int randomKey(SourceOfRandomness sor) { 159 | // Use a small keyspace so that we'll randomly get some collisions. Tests more interesting that way! 160 | return sor.nextInt(0, 10000); 161 | } 162 | 163 | public static class OperationGenerator extends Generator { 164 | public OperationGenerator() { 165 | super(Operation.class); 166 | } 167 | 168 | @Override 169 | public Operation generate(SourceOfRandomness sourceOfRandomness, GenerationStatus generationStatus) { 170 | switch (sourceOfRandomness.nextInt(18)) { 171 | case 0: return new Put(randomKey(sourceOfRandomness), sourceOfRandomness.nextInt()); 172 | case 1: return new Get(randomKey(sourceOfRandomness)); 173 | case 2: return new LowerEntry(randomKey(sourceOfRandomness)); 174 | case 3: return new FloorEntry(randomKey(sourceOfRandomness)); 175 | case 4: return new HigherEntry(randomKey(sourceOfRandomness)); 176 | case 5: return new CeilingEntry(randomKey(sourceOfRandomness)); 177 | case 6: return new Size(); 178 | case 7: return new FirstEntry(); 179 | case 8: return new LastEntry(); 180 | case 9: return new Values(); 181 | case 10: return new DescendingValues(); 182 | case 11: return new TailMap(randomKey(sourceOfRandomness)); 183 | case 12: return new TailMapExclusive(randomKey(sourceOfRandomness)); 184 | case 13: return new HeadMap(randomKey(sourceOfRandomness)); 185 | case 14: return new HeadMapExclusive(randomKey(sourceOfRandomness)); 186 | case 15: return new DescendingTailMap(randomKey(sourceOfRandomness)); 187 | case 16: return new DescendingTailMapExclusive(randomKey(sourceOfRandomness)); 188 | case 17: return new Remove(randomKey(sourceOfRandomness)); 189 | default: throw new IllegalStateException(); 190 | } 191 | } 192 | } 193 | 194 | public static class KeyGenerator extends Generator { 195 | public KeyGenerator() { super(Integer.class); } 196 | 197 | @Override 198 | public Integer generate(SourceOfRandomness sourceOfRandomness, GenerationStatus generationStatus) { 199 | return randomKey(sourceOfRandomness); 200 | } 201 | } 202 | 203 | private static void checkMapInvariants(NavigableMap mp) { 204 | if (mp instanceof IntIntBTreeMap) { 205 | ((IntIntBTreeMap)mp).checkAssumingKeysNonNull(); 206 | } else { 207 | ((BTreeMap)mp).checkAssumingKeysNonNull(); 208 | } 209 | } 210 | 211 | @Property 212 | public void randomOperationSequenceOnEmptyMap(boolean unbox, @com.pholser.junit.quickcheck.generator.Size(min=4, max=100) List<@From(OperationGenerator.class) Operation> ops) { 213 | final TreeMap expected = new TreeMap<>(); 214 | final NavigableMap actual = unbox ? IntIntBTreeMap.create() : BTreeMap.create(); 215 | 216 | for (Operation op : ops) { 217 | op.apply(expected, actual); 218 | checkMapInvariants(actual); 219 | } 220 | } 221 | 222 | @Property(trials = 1000) 223 | public void randomOperationSequenceOnBigMap(boolean unbox, @com.pholser.junit.quickcheck.generator.Size(min=4, max=100) List<@From(OperationGenerator.class) Operation> ops) { 224 | final TreeMap expected = new TreeMap<>(); 225 | final NavigableMap actual = unbox ? IntIntBTreeMap.create() : BTreeMap.create(); 226 | 227 | // Try to make sure we have at least one internal node 228 | createMaps(expected, actual, 128); 229 | 230 | for (Operation op : ops) { 231 | op.apply(expected, actual); 232 | checkMapInvariants(actual); 233 | } 234 | } 235 | 236 | @Property(trials = 1000) 237 | public void randomOperationSequenceOnGiantMap(boolean unbox, @com.pholser.junit.quickcheck.generator.Size(min=4, max=100) List<@From(OperationGenerator.class) Operation> ops) { 238 | final TreeMap expected = new TreeMap<>(); 239 | final NavigableMap actual = unbox ? IntIntBTreeMap.create() : BTreeMap.create(); 240 | 241 | // Try to make sure we have at least 2 levels of internal nodes 242 | createMaps(expected, actual, 1024); 243 | 244 | for (Operation op : ops) { 245 | op.apply(expected, actual); 246 | checkMapInvariants(actual); 247 | } 248 | } 249 | 250 | @Property 251 | public void randomDeletionSequenceOnGiantMap(boolean unbox, List<@From(KeyGenerator.class) Integer> keys) { 252 | final TreeMap expected = new TreeMap<>(); 253 | final NavigableMap actual = unbox ? IntIntBTreeMap.create() : BTreeMap.create(); 254 | 255 | // Try to make sure we have at least 2 levels of internal nodes 256 | createMaps(expected, actual, 1024); 257 | checkMapInvariants(actual); 258 | 259 | for (Integer key : keys) { 260 | assertEquals(expected.remove(key), actual.remove(key)); 261 | checkMapInvariants(actual); 262 | } 263 | } 264 | 265 | private static void createMaps(TreeMap expected, NavigableMap actual, int maxSize) { 266 | final SourceOfRandomness sor = new SourceOfRandomness(new Random(1337)); 267 | for (int i = 0; i < maxSize; i++) { 268 | final int key = randomKey(sor); 269 | expected.put(key, i); 270 | actual .put(key, i); 271 | } 272 | } 273 | 274 | @Test 275 | public void lowerEntry() { 276 | final BTreeMap map = BTreeMap.create(); 277 | map.put(1, "One"); 278 | map.put(3, "Three"); 279 | 280 | assertEquals(null, map.lowerEntry(0)); 281 | assertEquals(null, map.lowerEntry(1)); 282 | assertEquals(Integer.valueOf(1), map.lowerEntry(2).getKey()); 283 | assertEquals("One", map.lowerEntry(2).getValue()); 284 | assertEquals(Integer.valueOf(3), map.lowerEntry(4).getKey()); 285 | assertEquals("Three", map.lowerEntry(4).getValue()); 286 | } 287 | 288 | @Test 289 | public void putManyCheckingWithGet() { 290 | final TreeMap expected = new TreeMap<>(); 291 | final BTreeMap actual = BTreeMap.create(); 292 | 293 | final int[] keys = new Random(1337).ints(0, 100).limit(100).toArray(); 294 | int i = 0; 295 | for (int key : keys) { 296 | expected.put(key, i); 297 | actual.put(key, i); 298 | i++; 299 | 300 | for (Map.Entry e : expected.entrySet()) { 301 | Assert.assertEquals(e.getValue(), actual.get(e.getKey())); 302 | } 303 | } 304 | } 305 | 306 | // Mostly useful for finding out whether this crashes or not 307 | @Test 308 | public void putLinear() { 309 | final int KEYS = 100_000; 310 | 311 | final Random random = new Random(1337); 312 | final BTreeMap map = BTreeMap.create(); 313 | for (int i = 0; i < KEYS; i++) { 314 | map.put(random.nextInt(KEYS), i); 315 | } 316 | 317 | assertEquals(map.get(-1), null); 318 | } 319 | 320 | @Test 321 | public void descendingIterator1Item() { 322 | final BTreeMap map = BTreeMap.create(); 323 | map.put("Hello", 123); 324 | 325 | final Iterator> it = map.asNavigableMap2().descendingEntrySet().iterator(); 326 | 327 | { 328 | assertTrue(it.hasNext()); 329 | final Map.Entry e = it.next(); 330 | assertEquals("Hello", e.getKey()); 331 | assertEquals(Integer.valueOf(123), e.getValue()); 332 | } 333 | 334 | assertFalse(it.hasNext()); 335 | } 336 | 337 | @Test 338 | public void descendingIterator2Items() { 339 | final BTreeMap map = BTreeMap.create(); 340 | map.put("Hello", 123); 341 | map.put("World", 321); 342 | 343 | final Iterator> it = map.asNavigableMap2().descendingEntrySet().iterator(); 344 | 345 | { 346 | assertTrue(it.hasNext()); 347 | final Map.Entry e = it.next(); 348 | assertEquals("World", e.getKey()); 349 | assertEquals(Integer.valueOf(321), e.getValue()); 350 | } 351 | 352 | { 353 | assertTrue(it.hasNext()); 354 | final Map.Entry e = it.next(); 355 | assertEquals("Hello", e.getKey()); 356 | assertEquals(Integer.valueOf(123), e.getValue()); 357 | } 358 | 359 | assertFalse(it.hasNext()); 360 | } 361 | 362 | @Test 363 | public void tailMap() { 364 | final BTreeMap map = BTreeMap.create(); 365 | for (int i = 11; i < 99; i += 2) { 366 | map.put(Integer.toString(i), i); 367 | } 368 | 369 | for (int i = 10; i < 100; i++) { 370 | int j = i % 2 == 1 ? i : i + 1; 371 | for (Integer x : map.tailMap(Integer.toString(i), true).values()) { 372 | assertEquals(j, x.intValue()); 373 | j += 2; 374 | } 375 | assertEquals(99, j); 376 | } 377 | } 378 | 379 | @Test 380 | public void tailMapExclusive() { 381 | final BTreeMap map = BTreeMap.create(); 382 | for (int i = 11; i < 99; i += 2) { 383 | map.put(Integer.toString(i), i); 384 | } 385 | 386 | for (int i = 10; i < 100; i++) { 387 | int j = Math.min(99, i % 2 == 1 ? i + 2 : i + 1); 388 | for (Integer x : map.tailMap(Integer.toString(i), false).values()) { 389 | assertEquals(j, x.intValue()); 390 | j += 2; 391 | } 392 | assertEquals(99, j); 393 | } 394 | } 395 | 396 | @Test 397 | public void descendingTailMap() { 398 | final BTreeMap map = BTreeMap.create(); 399 | for (int i = 11; i < 99; i += 2) { 400 | map.put(Integer.toString(i), i); 401 | } 402 | 403 | for (int i = 10; i < 100; i++) { 404 | int j = Math.min(97, i % 2 == 1 ? i : i - 1); 405 | for (Integer x : map.descendingMap().tailMap(Integer.toString(i), true).values()) { 406 | assertEquals(j, x.intValue()); 407 | j -= 2; 408 | } 409 | assertEquals(9, j); 410 | } 411 | } 412 | 413 | @Test 414 | public void descendingTailMapExclusive() { 415 | final BTreeMap map = BTreeMap.create(); 416 | for (int i = 11; i < 99; i += 2) { 417 | map.put(Integer.toString(i), i); 418 | } 419 | 420 | for (int i = 10; i < 100; i++) { 421 | int j = i % 2 == 1 ? i - 2 : i - 1; 422 | for (Integer x : map.descendingMap().tailMap(Integer.toString(i), false).values()) { 423 | assertEquals(j, x.intValue()); 424 | j -= 2; 425 | } 426 | assertEquals(9, j); 427 | } 428 | } 429 | 430 | @Test 431 | public void removeSmallMap() { 432 | final BTreeMap map = BTreeMap.create(); 433 | 434 | assertNull(map.remove("hello")); 435 | 436 | map.put("hello", 1); 437 | 438 | assertEquals(Integer.valueOf(1), map.remove("hello")); 439 | assertNull(map.get("hello")); 440 | assertEquals(0, map.size()); 441 | } 442 | 443 | @Test 444 | public void putRemoveForward() { 445 | final BTreeMap map = BTreeMap.create(); 446 | for (int i = 11; i < 99; i++) { 447 | map.put(Integer.toString(i), i); 448 | } 449 | 450 | for (int i = 11; i < 99; i++) { 451 | assertEquals(Integer.valueOf(i), map.remove(Integer.toString(i))); 452 | } 453 | } 454 | 455 | @Test 456 | public void putRemoveBackward() { 457 | final BTreeMap map = BTreeMap.create(); 458 | for (int i = 11; i < 99; i++) { 459 | map.put(Integer.toString(i), i); 460 | } 461 | 462 | for (int i = 98; i >= 11; i--) { 463 | assertEquals(Integer.valueOf(i), map.remove(Integer.toString(i))); 464 | } 465 | } 466 | 467 | @Test 468 | public void cloneWorks() { 469 | final BTreeMap oldMap = BTreeMap.create(); 470 | 471 | for (int i = 0; i < 500; i++) { 472 | oldMap.put(Integer.toString(2*i), i); 473 | } 474 | 475 | assertEquals(oldMap.size(), 500); 476 | assertTrue(oldMap.containsKey("500")); 477 | assertFalse(oldMap.containsKey("501")); 478 | 479 | 480 | final BTreeMap newMap = oldMap.clone(); 481 | 482 | for (int i = 0; i < 500; i++) { 483 | newMap.put(Integer.toString(2*i + 1), i); 484 | } 485 | 486 | assertEquals(oldMap.size(), 500); 487 | assertEquals(newMap.size(), 1000); 488 | 489 | assertTrue(oldMap.containsKey("500")); 490 | assertTrue(newMap.containsKey("500")); 491 | 492 | assertFalse(oldMap.containsKey("501")); 493 | assertTrue (newMap.containsKey("501")); 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /src/main/templates/uk.co.omegaprime.btreemap/{{KV_}}BTreeMap.java: -------------------------------------------------------------------------------- 1 | package uk.co.omegaprime.btreemap; 2 | 3 | import java.util.*; 4 | 5 | import static uk.co.omegaprime.btreemap.Node.BINARY_SEARCH; 6 | import static uk.co.omegaprime.btreemap.Node.MAX_FANOUT; 7 | import static uk.co.omegaprime.btreemap.Node.MIN_FANOUT; 8 | 9 | /** 10 | * A B-tree based {@link NavigableMap} implementation for {{K.erased}} keys and {{V.erased}} values. 11 | *

12 | * To get started, call {@link #create()}. 13 | *

14 | * All values in the map are stored at leaf nodes, and all leaf nodes are at the same depth. This ensures that accesses 15 | * to the map via e.g. {@code get}, {@code put} and {@code remove} all take log(n) time. In contrast to a balanced binary 16 | * tree (like that used by {@link java.util.TreeMap}), each node may holds more than one key. This makes the datastructure 17 | * more cache-friendly: you can expect it to be around 50% faster than {@code TreeMap} for workloads that do not fit in cache. 18 | *

19 | * The map is sorted either according to the Comparable method of the key type, or via a user-supplied {@code Comparator}. 20 | * This ordering should be consistent with {@code equals}. 21 | *

22 | * The implementation is unsynchronized, and there are no guarantees as to what will happen if you make use of iterator 23 | * that was created before some operation that modified the map. 24 | *

25 | * {@code Entry} instances returned by this class are immutable and hence do not support the {@link Entry#setValue(Object)} method. 26 | */ 27 | public class BTreeMap<$K$, $V$> implements NavigableMap<@Boxed $K$, @Boxed $V$> { 28 | /** Create as empty {@code BTreeMap} that uses the natural order of the keys */ 29 | public static <$K$ extends Comparable, $V$> BTreeMap<$K$, $V$> create() { 30 | return new BTreeMap<$K$, $V$>(null); 31 | } 32 | 33 | /** 34 | * Create an empty {@code BTreeMap} that uses a custom comparator on the keys. 35 | * 36 | * @param comparator If null, the natural order of the keys will be used 37 | */ 38 | public static <$K$, $V$> BTreeMap<$K$, $V$> create(Comparator/* wait for it.. */ comparator) { 39 | return new BTreeMap<$K$, $V$>({% if K.isObject() %}comparator{% else %}{{K_}}Comparator.unbox(comparator){% endif %}); 40 | } 41 | 42 | {% if K.isPrimitive %} 43 | /** 44 | * Create an empty {@code BTreeMap} that uses a custom comparator on the keys. 45 | * 46 | * @param comparator If null, the natural order of the keys will be used 47 | */ 48 | public static <$K$, $V$> BTreeMap<$K$, $V$> create(Comparator<$K$> comparator) { 49 | return new BTreeMap<$K$, $V$>(comparator); 50 | } 51 | {% endif %} 52 | 53 | /** Create a new map that contains the same entries as the specified {@code SortedMap}, and also shares a key ordering with it */ 54 | public static <$K$, $V$> BTreeMap<$K$, $V$> create(SortedMap<@Boxed $K$, ? extends @Boxed $V$> that) { 55 | final BTreeMap<$K$, $V$> result = create(that.comparator()); 56 | result.putAll(that); 57 | return result; 58 | } 59 | 60 | /** Create a new map that contains the same entries as the specified {@code Map}, and uses the natural ordering on the keys */ 61 | public static <$K$, $V$> BTreeMap<$K$, $V$> create(Map that) { 62 | final BTreeMap<$K$, $V$> result = new BTreeMap<$K$, $V$>(null); 63 | result.putAll(that); 64 | return result; 65 | } 66 | 67 | // Internal nodes and leaf nodes are both represented by an instance of the Node class. A Node is essentially 68 | // an Object[MAX_FANOUT * 2] with a size. For an internal node: 69 | // - The first MAX_FANOUT - 1 elements of this will refer to keys 70 | // - The next MAX_FANOUT elements will hold references to the Object[] of child nodes 71 | // - The final element will hold a reference to a int[] of child node sizes 72 | // 73 | // For a leaf node: 74 | // - The first MAX_FANOUT elements will refer to keys 75 | // - The next MAX_FANOUT elements will refer to values 76 | // 77 | // Instead of Node, I used to just use a Object[] instead, with the size of all sibling nodes stored contiguously in another int[]. 78 | // This was sort of fine but incurred more indirections & was fiddly to program against. Replacing it with this scheme 79 | // (such that the size and Objects are stored contiguously) sped up my get benchmark from 4.8M ops/sec to 5.3M ops/sec. 80 | // 81 | // FIXME: the single element at the end of each internal node is unused. What to do about this? Use for parent link -- might help us avoid allocation in iterator? 82 | private static class BubbledInsertion<$K$> { 83 | private final AbstractNode leftObjects, rightObjects; 84 | private final $K$ separator; // The seperator key is <= all keys in the right and > all keys in the left 85 | 86 | private {{K_}}BubbledInsertion(AbstractNode leftObjects, AbstractNode rightObjects, $K$ separator) { 87 | this.leftObjects = leftObjects; 88 | this.rightObjects = rightObjects; 89 | this.separator = separator; 90 | } 91 | } 92 | 93 | private static class Leaf { 94 | private Leaf() {} 95 | 96 | public static <$K$, $V$> int find(Node<$K$, $V$> repr, @Erased $K$ key, {{K_}}Comparator comparator) { 97 | final int size = repr.size; 98 | if (BINARY_SEARCH) { 99 | return repr.binarySearch(0, size, key, comparator); 100 | } else if (comparator == null) { 101 | // Tried not exiting early (improves branch prediction) but significantly worse. 102 | int i; 103 | for (i = 0; i < size; i++) { 104 | final $K$ checkKey = repr.getKey(i); 105 | {% if K.isPrimitive %} 106 | // It's very much worth avoiding casting a primitive to Comparable (50% speedup) 107 | if (checkKey == key) { 108 | return i; 109 | } else if (checkKey > key) { 110 | return -i - 1; 111 | } 112 | {% else %} 113 | final int cmp = ((Comparable)checkKey).compareTo(key); 114 | if (cmp == 0) { 115 | return i; 116 | } else if (cmp > 0) { 117 | return -i - 1; 118 | } 119 | {% endif %} 120 | } 121 | 122 | return -size - 1; 123 | } else { 124 | // Tried not exiting early (improves branch prediction) but significantly worse. 125 | int i; 126 | for (i = 0; i < size; i++) { 127 | final $K$ checkKey = repr.getKey(i); 128 | final int cmp = comparator.compare{% if K.isPrimitive %}{{K.name}}{% endif %}(checkKey, key); 129 | if (cmp == 0) { 130 | return i; 131 | } else if (cmp > 0) { 132 | return -i - 1; 133 | } 134 | } 135 | 136 | return -size - 1; 137 | } 138 | } 139 | 140 | public static <$K$, $V$> $K$ getKey(Node<$K$, $V$> keysValues, int index) { 141 | return keysValues.getKey(index); 142 | } 143 | 144 | public static <$K$, $V$> $V$ getValue(Node<$K$, $V$> keysValues, int index) { 145 | return keysValues.getValue(index); 146 | } 147 | 148 | public static boolean canPutAtIndex(int size, int index) { 149 | return index >= 0 || size < MAX_FANOUT; 150 | } 151 | 152 | /** @param index must be the index of existing key in the leaf */ 153 | public static <$K$, $V$> $V$ putOverwriteIndex(Node<$K$, $V$> keysValues, int index, $K$ key, $V$ value) { 154 | assert index >= 0; 155 | 156 | final $V$ result = keysValues.getValue(index); 157 | keysValues.setValue(index, value); 158 | return result; 159 | } 160 | 161 | /** @param index must be the insertion point in the leaf, using same convention as Arrays.binarySearch */ 162 | public static <$K$, $V$> void putInsertIndex(Node<$K$, $V$> keysValues, int index, $K$ key, $V$ value) { 163 | assert index < 0 && keysValues.size < MAX_FANOUT; 164 | 165 | final int size = keysValues.size; 166 | final int insertionPoint = -(index + 1); 167 | assert size < MAX_FANOUT && insertionPoint <= size; 168 | 169 | {{KV_}}Node.arraycopyKey (keysValues, insertionPoint, keysValues, insertionPoint + 1, size - insertionPoint); 170 | {{KV_}}Node.arraycopyValue(keysValues, insertionPoint, keysValues, insertionPoint + 1, size - insertionPoint); 171 | keysValues.size = size + 1; 172 | 173 | keysValues.setKey(insertionPoint, key); 174 | keysValues.setValue(insertionPoint, value); 175 | } 176 | 177 | private static <$K$, $V$> void copy(Node<$K$, $V$> srcKeysValues, int srcIndex, Node dstKeysValues, int dstIndex, int size) { 178 | {{KV_}}Node.arraycopyKey (srcKeysValues, srcIndex, dstKeysValues, dstIndex, size); 179 | {{KV_}}Node.arraycopyValue(srcKeysValues, srcIndex, dstKeysValues, dstIndex, size); 180 | } 181 | 182 | // This splits the leaf (of size MAX_FANOUT == 2 * MIN_FANOUT - 1) plus one extra item into two new 183 | // leaves, each of size MIN_FANOUT. 184 | public static <$K$, $V$> BubbledInsertion<$K$> bubblePutAtIndex(Node<$K$, $V$> keysValues, int index, $K$ key, $V$ value) { 185 | assert !canPutAtIndex(keysValues.size, index); 186 | assert keysValues.size == MAX_FANOUT; // i.e. implies index < 0 187 | 188 | int insertionPoint = -(index + 1); 189 | final Node<$K$, $V$> l = new Node<$K$, $V$>(), r = new Node<$K$, $V$>(); 190 | l.size = r.size = MIN_FANOUT; 191 | 192 | if (insertionPoint < MIN_FANOUT) { 193 | copy(keysValues, 0, l, 0, insertionPoint); 194 | copy(keysValues, insertionPoint, l, insertionPoint + 1, MIN_FANOUT - insertionPoint - 1); 195 | copy(keysValues, MIN_FANOUT - 1, r, 0, MIN_FANOUT); 196 | 197 | l.setKey (insertionPoint, key); 198 | l.setValue(insertionPoint, value); 199 | } else { 200 | insertionPoint -= MIN_FANOUT; 201 | 202 | copy(keysValues, 0, l, 0, MIN_FANOUT); 203 | copy(keysValues, MIN_FANOUT, r, 0, insertionPoint); 204 | copy(keysValues, MIN_FANOUT + insertionPoint, r, insertionPoint + 1, MIN_FANOUT - insertionPoint - 1); 205 | 206 | r.setKey (insertionPoint, key); 207 | r.setValue(insertionPoint, value); 208 | } 209 | 210 | return new BubbledInsertion<$K$>(l, r, r.getKey(0)); 211 | } 212 | } 213 | 214 | private static class Internal { 215 | private Internal() {} 216 | 217 | private static <$K$> $K$ getKey(Node<$K$, AbstractNode> repr, int index) { 218 | return repr.getKey(index); 219 | } 220 | 221 | private static <$K$> AbstractNode getNode(Node<$K$, AbstractNode> repr, int index) { 222 | return (AbstractNode)repr.getValue(index); 223 | } 224 | 225 | /** Always returns a valid index into the nodes array. Keys in the node indicated in the index will be >= key */ 226 | public static <$K$> int find(Node<$K$, AbstractNode> repr, @Erased $K$ key, {{K_}}Comparator comparator) { 227 | final int size = repr.size; 228 | if (BINARY_SEARCH) { 229 | final int index = repr.binarySearch(0, size - 1, key, comparator); 230 | return index < 0 ? -(index + 1) : index + 1; 231 | } else if (comparator == null) { 232 | // Tried not exiting early (improves branch prediction) but significantly worse. 233 | int i; 234 | for (i = 0; i < size - 1; i++) { 235 | final $K$ checkKey = repr.getKey(i); 236 | {% if K.isPrimitive %} 237 | // Avoiding the cast to Comparable is EXTREMELY IMPORTANT for speed (worth 10x) 238 | if (checkKey > key) { 239 | {% else %} 240 | if (((Comparable)checkKey).compareTo(key) > 0) { 241 | {% endif %} 242 | return i; 243 | } 244 | } 245 | 246 | return i; 247 | } else { 248 | // Tried not exiting early (improves branch prediction) but significantly worse. 249 | int i; 250 | for (i = 0; i < size - 1; i++) { 251 | final $K$ checkKey = repr.getKey(i); 252 | if (comparator.compare{% if K.isPrimitive %}{{K.name}}{% endif %}(checkKey, key) > 0) { 253 | return i; 254 | } 255 | } 256 | 257 | return i; 258 | } 259 | } 260 | 261 | public static boolean canPutAtIndex(int size) { 262 | return size < MAX_FANOUT; 263 | } 264 | 265 | public static <$K$> void putAtIndex(Node<$K$, AbstractNode> repr, int index, BubbledInsertion<$K$> toBubble) { 266 | assert canPutAtIndex(repr.size); 267 | 268 | // Tree: Bubbled input: 269 | // Key 0 1 Key S 270 | // Node 0 1 2 Node 1A 1B 271 | // If inserting at nodeIndex 1, shuffle things to: 272 | // Key 0 S 1 273 | // Node 0 1A 1B 2 274 | 275 | final int size = repr.size++; 276 | {{KObject_}}Node.arraycopyKey (repr, index, repr, index + 1, size - index - 1); 277 | {{KObject_}}Node.arraycopyValue(repr, index + 1, repr, index + 2, size - index - 1); 278 | 279 | repr.setKey (index , toBubble.separator); 280 | repr.setValue(index , toBubble.leftObjects); 281 | repr.setValue(index + 1, toBubble.rightObjects); 282 | } 283 | 284 | private static <$K$> void deleteAtIndex(Node<$K$, AbstractNode> node, int index) { 285 | final int size = --node.size; 286 | {{KObject_}}Node.arraycopyKey (node, index, node, index - 1, size - index); 287 | {{KObject_}}Node.arraycopyValue(node, index + 1, node, index, size - index); 288 | 289 | // Avoid memory leaks 290 | {% if K.isObject %} 291 | node.setKey (size - 1, null); 292 | {% endif %} 293 | node.setValue(size, null); 294 | } 295 | 296 | public static <$K$> BubbledInsertion<$K$> bubblePutAtIndex(Node<$K$, AbstractNode> repr, int nodeIndex, BubbledInsertion<$K$> toBubble) { 297 | assert !canPutAtIndex(repr.size); // i.e. size == MAX_FANOUT 298 | 299 | // Tree: Bubbled input: 300 | // Key 0 1 Key S 301 | // Node 0 1 2 Node 1A 1B 302 | // 303 | // If inserting at nodeIndex 1, split things as: 304 | // 305 | // Separator: S 306 | // Left bubbled: Right bubbled: 307 | // Key 0 Key 1 308 | // Node 0 1A Node 1B 2 309 | 310 | final Node<$K$, AbstractNode> l = new Node<$K$, AbstractNode>(), r = new Node<$K$, AbstractNode>(); 311 | l.size = r.size = MIN_FANOUT; 312 | 313 | final $K$ separator; 314 | if (nodeIndex == MIN_FANOUT - 1) { 315 | separator = toBubble.separator; 316 | 317 | {{KObject_}}Node.arraycopyKey(repr, 0, l, 0, MIN_FANOUT - 1); 318 | {{KObject_}}Node.arraycopyKey(repr, MIN_FANOUT - 1, r, 0, MIN_FANOUT - 1); 319 | 320 | {{KObject_}}Node.arraycopyValue(repr, 0, l, 0, MIN_FANOUT - 1); 321 | {{KObject_}}Node.arraycopyValue(repr, MIN_FANOUT, r, 1, MIN_FANOUT - 1); 322 | 323 | l.setValue(MIN_FANOUT - 1, toBubble.leftObjects); 324 | r.setValue(0, toBubble.rightObjects); 325 | } else if (nodeIndex < MIN_FANOUT) { 326 | separator = getKey(repr, MIN_FANOUT - 2); 327 | 328 | {{KObject_}}Node.arraycopyKey(repr, 0, l, 0, nodeIndex); 329 | {{KObject_}}Node.arraycopyKey(repr, nodeIndex, l, nodeIndex + 1, MIN_FANOUT - nodeIndex - 2); 330 | {{KObject_}}Node.arraycopyKey(repr, MIN_FANOUT - 1, r, 0, MIN_FANOUT - 1); 331 | 332 | {{KObject_}}Node.arraycopyValue(repr, 0, l, 0, nodeIndex); 333 | {{KObject_}}Node.arraycopyValue(repr, nodeIndex + 1, l, nodeIndex + 2, MIN_FANOUT - nodeIndex - 2); 334 | {{KObject_}}Node.arraycopyValue(repr, MIN_FANOUT - 1, r, 0, MIN_FANOUT); 335 | 336 | l.setKey (nodeIndex, toBubble.separator); 337 | l.setValue(nodeIndex, toBubble.leftObjects); 338 | l.setValue(nodeIndex + 1, toBubble.rightObjects); 339 | } else { 340 | nodeIndex -= MIN_FANOUT; 341 | // i.e. 0 <= nodeIndex < MIN_FANOUT - 1 342 | 343 | separator = getKey(repr, MIN_FANOUT - 1); 344 | 345 | {{KObject_}}Node.arraycopyKey(repr, 0, l, 0, MIN_FANOUT - 1); 346 | {{KObject_}}Node.arraycopyKey(repr, MIN_FANOUT, r, 0, nodeIndex); 347 | {{KObject_}}Node.arraycopyKey(repr, MIN_FANOUT + nodeIndex, r, nodeIndex + 1, MIN_FANOUT - nodeIndex - 2); 348 | 349 | {{KObject_}}Node.arraycopyValue(repr, 0, l, 0, MIN_FANOUT); 350 | {{KObject_}}Node.arraycopyValue(repr, MIN_FANOUT, r, 0, nodeIndex); 351 | {{KObject_}}Node.arraycopyValue(repr, MIN_FANOUT + nodeIndex + 1, r, nodeIndex + 2, MIN_FANOUT - nodeIndex - 2); 352 | 353 | r.setKey (nodeIndex, toBubble.separator); 354 | r.setValue(nodeIndex, toBubble.leftObjects); 355 | r.setValue(nodeIndex + 1, toBubble.rightObjects); 356 | } 357 | 358 | return new BubbledInsertion<$K$>(l, r, separator); 359 | } 360 | } 361 | 362 | private final Comparator comparator; 363 | 364 | // Allocate these lazily to optimize allocation of lots of empty BTreeMaps 365 | private AbstractNode rootObjects; 366 | 367 | private int depth; // Number of levels of internal nodes in the tree 368 | private int size; 369 | 370 | private {{KV_}}BTreeMap(Comparator comparator) { 371 | this.comparator = comparator; 372 | } 373 | 374 | @Override 375 | public {{KV_}}BTreeMap clone() { 376 | final {{KV_}}BTreeMap result = new {{KV_}}BTreeMap(this.comparator); 377 | result.depth = this.depth; 378 | result.size = this.size; 379 | result.rootObjects = this.rootObjects == null ? null : this.rootObjects.clone(this.depth); 380 | return result; 381 | } 382 | 383 | void checkAssumingKeysNonNull() { 384 | if (rootObjects != null) { 385 | checkCore(rootObjects, depth, null, null, Bound.MISSING, Bound.MISSING); 386 | } 387 | } 388 | 389 | private void checkInRange(@Boxed $K$ k, @Boxed $K$ min, @Boxed $K$ max, Bound minBound, Bound maxBound) { 390 | assert minBound.lt(min, k, comparator) && maxBound.lt(k, max, comparator); 391 | } 392 | 393 | private void checkCore(AbstractNode repr, int depth, @Boxed $K$ min, @Boxed $K$ max, Bound minBound, Bound maxBound) { 394 | final int size = repr.size; // FIXME: decide what to do here -- base class, or push into branches? 395 | assert size <= Node.MAX_FANOUT; 396 | if (depth == this.depth) { 397 | // The root node may be smaller than others 398 | if (depth > 0) { 399 | assert size >= 2; 400 | } 401 | } else { 402 | assert size >= Node.MIN_FANOUT; 403 | } 404 | 405 | if (depth == 0) { 406 | final Node<$K$, $V$> leaf = (Node<$K$, $V$>)repr; 407 | 408 | int i; 409 | for (i = 0; i < size; i++) { 410 | $K$ k = Leaf.getKey(leaf, i); 411 | checkInRange(k, min, max, minBound, maxBound); 412 | {% if K.isObject %} 413 | assert k != null; 414 | {% endif %} 415 | } 416 | 417 | // To avoid memory leaks 418 | for (; i < Node.MAX_FANOUT; i++) { 419 | {% if K.isObject %} 420 | assert Leaf.getKey(leaf, i) == null; 421 | {% endif %} 422 | {% if V.isObject %} 423 | assert Leaf.getValue(leaf, i) == null; 424 | {% endif %} 425 | } 426 | } else { 427 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 428 | 429 | { 430 | int i; 431 | for (i = 0; i < size - 1; i++) { 432 | $K$ k = Internal.getKey(internal, i); 433 | checkInRange(k, min, max, minBound, maxBound); 434 | {% if K.isObject %} 435 | assert k != null; 436 | {% endif %} 437 | } 438 | 439 | {% if K.isObject %} 440 | // To avoid memory leaks 441 | for (; i < Node.MAX_FANOUT - 1; i++) { 442 | assert Internal.getKey(internal, i) == null; 443 | } 444 | {% endif %} 445 | } 446 | 447 | { 448 | int i; 449 | checkCore (Internal.getNode(internal, 0), depth - 1, min, Internal.getKey(internal, 0), minBound, Bound.EXCLUSIVE); 450 | for (i = 1; i < size - 1; i++) { 451 | checkCore(Internal.getNode(internal, i), depth - 1, Internal.getKey(internal, i - 1), Internal.getKey(internal, i), Bound.INCLUSIVE, Bound.EXCLUSIVE); 452 | } 453 | checkCore (Internal.getNode(internal, size - 1), depth - 1, Internal.getKey(internal, size - 2), max, Bound.INCLUSIVE, maxBound); 454 | 455 | // To avoid memory leaks 456 | for (i = size; i < Node.MAX_FANOUT; i++) { 457 | assert Internal.getNode(internal, i) == null; 458 | } 459 | } 460 | } 461 | } 462 | 463 | @Override 464 | public void clear() { 465 | rootObjects = null; 466 | depth = 0; 467 | size = 0; 468 | } 469 | 470 | @Override 471 | public Comparator comparator() { 472 | return comparator; 473 | } 474 | 475 | @Override 476 | public void putAll(Map that) { 477 | if (that instanceof SortedMap && Objects.equals(this.comparator(), ((SortedMap)that).comparator())) { 478 | // TODO: fastpath? (Even faster if that instanceof {{KV_}}BTreeMap) 479 | } 480 | 481 | for (Map.Entry e : that.entrySet()) { 482 | put(e.getKey(), e.getValue()); 483 | } 484 | } 485 | 486 | 487 | @Override 488 | public @Boxed $V$ get(Object key) { 489 | return getOrDefault(key, null); 490 | } 491 | 492 | {% if V.isPrimitive() %} 493 | 494 | /** Gets the value at the given key. If no such value was found, returns the most negative {@code {{V}}} value. */ 495 | public $V$ get{{V.name}}(Object key) { 496 | return getOrDefault{{V.name}}(key, {{V.dfault}}); 497 | } 498 | 499 | {% endif %} 500 | 501 | {% if K.isPrimitive() %} 502 | 503 | /** Gets the value at the given key. If no such value was found, returns null. */ 504 | public @Boxed $V$ get($K$ key) { 505 | return getOrDefault(key, null); 506 | } 507 | 508 | {% endif %} 509 | 510 | {% if K.isPrimitive() and V.isPrimitive() %} 511 | 512 | /** Gets the value at the given key. If no such value was found, returns the most negative {@code {{V}}} value. */ 513 | public $V$ get{{V.name}}($K$ key) { 514 | return getOrDefault{{V.name}}(key, {{V.dfault}}); 515 | } 516 | 517 | {% endif %} 518 | 519 | 520 | private Node<$K$, $V$> findLeaf(@Erased $K$ key) { 521 | AbstractNode nextObjects = rootObjects; 522 | int depth = this.depth; 523 | 524 | if (nextObjects == null) { 525 | return null; 526 | } 527 | 528 | final Comparator comparator = this.comparator; 529 | while (depth-- > 0) { 530 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)nextObjects; 531 | final int ix = Internal.find(internal, key, comparator); 532 | nextObjects = Internal.getNode(internal, ix); 533 | } 534 | 535 | return (Node<$K$, $V$>)nextObjects; 536 | } 537 | 538 | @Override 539 | public @Boxed $V$ getOrDefault(Object key, @Boxed $V$ dflt) { 540 | {% if K.isPrimitive() %} 541 | if (!(key instanceof @Boxed $K$)) return dflt; 542 | return getOrDefault(($K$)key, dflt); 543 | {% else %} 544 | final Node<$K$, $V$> leaf = findLeaf(key); 545 | 546 | if (leaf == null) return dflt; 547 | 548 | final int ix = Leaf.find(leaf, ({{K.erased}})key, this.comparator); 549 | if (ix < 0) { 550 | return dflt; 551 | } else { 552 | return Leaf.getValue(leaf, ix); 553 | } 554 | {% endif %} 555 | } 556 | 557 | {% if K.isPrimitive() %} 558 | 559 | /** Gets the value at the given key. If no such value was found, returns the specified default. */ 560 | public @Boxed $V$ getOrDefault($K$ key, @Boxed $V$ dflt) { 561 | final Node<$K$, $V$> leaf = findLeaf(key); 562 | 563 | if (leaf == null) return dflt; 564 | 565 | final int ix = Leaf.find(leaf, key, this.comparator); 566 | if (ix < 0) { 567 | return dflt; 568 | } else { 569 | return Leaf.getValue(leaf, ix); 570 | } 571 | } 572 | 573 | {% endif %} 574 | 575 | {% if V.isPrimitive() %} 576 | 577 | /** Gets the value at the given key. If no such value was found, returns the most negative {@code {{V}}} value. */ 578 | public $V$ getOrDefault{{V.name}}(Object key, $V$ dflt) { 579 | {% if K.isPrimitive() %} 580 | if (!(key instanceof @Boxed $K$)) return dflt; 581 | return getOrDefault{{V.name}}(($K$)key, dflt); 582 | {% else %} 583 | final Node<$K$, $V$> leaf = findLeaf(key); 584 | 585 | if (leaf == null) return dflt; 586 | 587 | final int ix = Leaf.find(leaf, key, this.comparator); 588 | if (ix < 0) { 589 | return dflt; 590 | } else { 591 | return Leaf.getValue(leaf, ix); 592 | } 593 | {% endif %} 594 | } 595 | 596 | {% endif %} 597 | 598 | {% if K.isPrimitive() and V.isPrimitive() %} 599 | 600 | /** Gets the value at the given key. If no such value was found, returns the specified default. */ 601 | public $V$ getOrDefault{{V.name}}($K$ key, $V$ dflt) { 602 | final Node<$K$, $V$> leaf = findLeaf(key); 603 | 604 | if (leaf == null) return dflt; 605 | 606 | final int ix = Leaf.find(leaf, key, this.comparator); 607 | if (ix < 0) { 608 | return dflt; 609 | } else { 610 | return Leaf.getValue(leaf, ix); 611 | } 612 | } 613 | 614 | {% endif %} 615 | 616 | 617 | @Override 618 | public boolean containsKey(Object key) { 619 | {% if K.isPrimitive() %} 620 | if (!(key instanceof @Boxed $K$)) return false; 621 | return containsKey(($K$)key); 622 | {% else %} 623 | final Node<$K$, $V$> leaf = findLeaf(key); 624 | if (leaf == null) return false; 625 | 626 | final int ix = Leaf.find(leaf, key, comparator); 627 | return ix >= 0; 628 | {% endif %} 629 | } 630 | 631 | {% if K.isPrimitive() %} 632 | 633 | /** Returns true iff an entry exists in the map with the supplied key. */ 634 | public boolean containsKey($K$ key) { 635 | final Node<$K$, $V$> leaf = findLeaf(key); 636 | if (leaf == null) return false; 637 | 638 | final int ix = Leaf.find(leaf, key, comparator); 639 | return ix >= 0; 640 | } 641 | 642 | {% endif %} 643 | 644 | 645 | {% if K.isObject() and V.isObject() %} 646 | @Override 647 | public @Boxed $V$ put(@Boxed $K$ key, @Boxed $V$ value) { 648 | {% else %} 649 | /** Adds a new entry to the map, and returns the old value associated with this key. If no prior entry existed, returns null. */ 650 | public @Boxed $V$ put($K$ key, $V$ value) { 651 | {% endif %} 652 | if (tryPutIntoEmptyMap(key, value)) { 653 | return null; 654 | } 655 | 656 | final @Erased @Boxed $V$[] resultBox = new @Erased @Boxed $V$[1]; 657 | final BubbledInsertion<$K$> toBubble = putInternal(key, value, rootObjects, this.depth, resultBox); 658 | if (toBubble == null) { 659 | return (@Boxed $V$)resultBox[0]; 660 | } else { 661 | finishBubbling(toBubble); 662 | return null; 663 | } 664 | } 665 | 666 | {% if V.isPrimitive() %} 667 | /** Adds a new entry to the map, and returns the old value associated with this key. If no prior entry existed, returns the most negative {@code {{V}}} value. */ 668 | public $V$ put{{V.name}}($K$ key, $V$ value) { 669 | if (tryPutIntoEmptyMap(key, value)) { 670 | return {{V.dfault}}; 671 | } 672 | 673 | final $V$[] resultBox = new $V$[1]; 674 | resultBox[0] = {{V.dfault}}; 675 | final BubbledInsertion<$K$> toBubble = putInternal{{V.name}}(key, value, rootObjects, this.depth, resultBox); 676 | if (toBubble == null) { 677 | return resultBox[0]; 678 | } else { 679 | finishBubbling(toBubble); 680 | return {{V.dfault}}; 681 | } 682 | } 683 | {% endif %} 684 | 685 | {% if K.isPrimitive or V.isPrimitive %} 686 | /** 687 | * {@inheritDoc} 688 | * 689 | {% if K.isPrimitive and V.isPrimitive %} 690 | * @throws NullPointerException if either the key or value are null 691 | {% elseif K.isPrimitive %} 692 | * @throws NullPointerException if the key is null 693 | {% else %} 694 | * @throws NullPointerException if the value is null 695 | {% endif %} 696 | */ 697 | @Override 698 | public @Boxed $V$ put(@Boxed $K$ key, @Boxed $V$ value) { 699 | return put(($K$)key, ($V$)value); 700 | } 701 | {% endif %} 702 | 703 | private boolean tryPutIntoEmptyMap($K$ key, $V$ value) { 704 | if (rootObjects != null) { 705 | return false; 706 | } else { 707 | final Node<$K$, $V$> leaf = new Node<$K$, $V$>(); 708 | leaf.setKey(0, key); 709 | leaf.setValue(0, value); 710 | leaf.size = 1; 711 | 712 | rootObjects = leaf; 713 | this.size = 1; 714 | return true; 715 | } 716 | } 717 | 718 | private void finishBubbling(BubbledInsertion<$K$> toBubble) { 719 | final Node<$K$, AbstractNode> internal = new Node<$K$, AbstractNode>(); 720 | internal.size = 2; 721 | internal.setKey (0, toBubble.separator); 722 | internal.setValue(0, toBubble.leftObjects); 723 | internal.setValue(1, toBubble.rightObjects); 724 | 725 | this.rootObjects = internal; 726 | this.depth++; 727 | } 728 | 729 | private BubbledInsertion<$K$> putInternal($K$ key, $V$ value, AbstractNode nextObjects, int depth, @Erased @Boxed $V$[] resultBox) { 730 | if (depth == 0) { 731 | final Node<$K$, $V$> leaf = (Node<$K$, $V$>)nextObjects; 732 | final int nodeIndex = Leaf.find(leaf, key, comparator); 733 | if (nodeIndex < 0) this.size++; 734 | 735 | if (Leaf.canPutAtIndex(leaf.size, nodeIndex)) { 736 | if (nodeIndex >= 0) { 737 | resultBox[0] = Leaf.putOverwriteIndex(leaf, nodeIndex, key, value); 738 | } else { 739 | Leaf.putInsertIndex(leaf, nodeIndex, key, value); 740 | } 741 | 742 | return null; 743 | } 744 | 745 | return Leaf.bubblePutAtIndex(leaf, nodeIndex, key, value); 746 | } else { 747 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)nextObjects; 748 | final int nodeIndex = Internal.find(internal, key, comparator); 749 | 750 | final BubbledInsertion<$K$> toBubble = putInternal(key, value, Internal.getNode(internal, nodeIndex), depth - 1, resultBox); 751 | return putInternalFinishInternal(internal, nodeIndex, toBubble); 752 | } 753 | } 754 | 755 | {% if V.isPrimitive() %} 756 | private BubbledInsertion<$K$> putInternal{{V.name}}($K$ key, $V$ value, AbstractNode nextObjects, int depth, $V$[] resultBox) { 757 | if (depth == 0) { 758 | final Node<$K$, $V$> leaf = (Node<$K$, $V$>)nextObjects; 759 | final int nodeIndex = Leaf.find(leaf, key, comparator); 760 | if (nodeIndex < 0) this.size++; 761 | 762 | if (Leaf.canPutAtIndex(leaf.size, nodeIndex)) { 763 | if (nodeIndex >= 0) { 764 | resultBox[0] = Leaf.putOverwriteIndex(leaf, nodeIndex, key, value); 765 | } else { 766 | Leaf.putInsertIndex(leaf, nodeIndex, key, value); 767 | } 768 | 769 | return null; 770 | } 771 | 772 | return Leaf.bubblePutAtIndex(leaf, nodeIndex, key, value); 773 | } else { 774 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)nextObjects; 775 | final int nodeIndex = Internal.find(internal, key, comparator); 776 | 777 | final BubbledInsertion<$K$> toBubble = putInternal{{V.name}}(key, value, Internal.getNode(internal, nodeIndex), depth - 1, resultBox); 778 | return putInternalFinishInternal(internal, nodeIndex, toBubble); 779 | } 780 | } 781 | {% endif %} 782 | 783 | private BubbledInsertion<$K$> putInternalFinishInternal(Node<$K$, AbstractNode> internal, int nodeIndex, BubbledInsertion<$K$> toBubble) { 784 | if (toBubble == null) { 785 | return null; 786 | } 787 | 788 | if (Internal.canPutAtIndex(internal.size)) { 789 | Internal.putAtIndex(internal, nodeIndex, toBubble); 790 | return null; 791 | } 792 | 793 | return Internal.bubblePutAtIndex(internal, nodeIndex, toBubble); 794 | } 795 | 796 | @Override 797 | public int size() { 798 | return size; 799 | } 800 | 801 | @Override 802 | public boolean isEmpty() { 803 | return size == 0; 804 | } 805 | 806 | public boolean containsValue(Object value) { 807 | return values().stream().anyMatch(v -> Objects.equals(v, value)); 808 | } 809 | 810 | @Override 811 | public String toString() { 812 | if (false) { 813 | return rootObjects == null ? "{}" : toStringInternal(rootObjects, depth); 814 | } else { 815 | return Iterables.toMapString(this.entrySet()); 816 | } 817 | } 818 | 819 | @Override 820 | public boolean equals(Object that) { 821 | return SortedMaps.equals(this, that); 822 | } 823 | 824 | @Override 825 | public int hashCode() { 826 | return Iterables.hashCode(entrySet()); 827 | } 828 | 829 | private static <$K$, $V$> String toStringInternal(AbstractNode repr, int depth) { 830 | if (depth == 0) { 831 | final Node<$K$, $V$> leaf = (Node<$K$, $V$>)repr; 832 | final StringBuilder sb = new StringBuilder(); 833 | for (int i = 0; i < leaf.size; i++) { 834 | if (sb.length() != 0) sb.append(", "); 835 | sb.append(Leaf.getKey(leaf, i)).append(": ").append(Leaf.getValue(leaf, i)); 836 | } 837 | 838 | return sb.toString(); 839 | } else { 840 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 841 | final StringBuilder sb = new StringBuilder(); 842 | for (int i = 0; i < internal.size; i++) { 843 | if (sb.length() != 0) { 844 | sb.append(" |").append(Internal.getKey(internal, i - 1)).append("| "); 845 | } 846 | final AbstractNode node = Internal.getNode(internal, i); 847 | sb.append("{").append(toStringInternal(node, depth - 1)).append("}"); 848 | } 849 | 850 | return sb.toString(); 851 | } 852 | } 853 | 854 | 855 | {% if K.isPrimitive() %} 856 | static <$K$, $V$> $K$ getEntryKey{{K.name}}(Entry<@Boxed $K$, @Boxed $V$> e) { 857 | return e == null ? {{K.dfault}} : ($K$)e.getKey(); 858 | } 859 | {% endif %} 860 | 861 | static K getEntryKey(Entry e) { 862 | return e == null ? null : e.getKey(); 863 | } 864 | 865 | 866 | {% for keyMethod in ["lower", "higher", "floor", "ceiling"] %} 867 | 868 | {% if K.isPrimitive() %} 869 | /** 870 | * {@inheritDoc} 871 | * 872 | * @throws NullPointerException if the key is null 873 | */ 874 | @Override 875 | public @Boxed $K$ {{keyMethod}}Key(@Boxed $K$ key) { 876 | return {{keyMethod}}Key(($K$)key); 877 | } 878 | 879 | /** Returns the key of the entry returned by {@link #{{keyMethod}}Entry}, or the most negative {@code {{K}}} value if no such entry exists */ 880 | public $K$ {{keyMethod}}Key{{K.name}}($K$ key) { 881 | return getEntryKey{{K.name}}({{keyMethod}}Entry(key)); 882 | } 883 | 884 | /** Returns the key of the entry returned by {@link #{{keyMethod}}Entry}, or null if no such entry exists */ 885 | {% else %} 886 | @Override 887 | {% endif %} 888 | public @Boxed $K$ {{keyMethod}}Key($K$ key) { 889 | return getEntryKey({{keyMethod}}Entry(key)); 890 | } 891 | 892 | {% endfor %} 893 | 894 | 895 | {% if K.isPrimitive() %} 896 | /** 897 | * {@inheritDoc} 898 | * 899 | * @throws NullPointerException if key is null 900 | */ 901 | @Override 902 | public Entry<@Boxed $K$, @Boxed $V$> lowerEntry(@Boxed $K$ key) { 903 | return lowerEntry(($K$)key); 904 | } 905 | 906 | /** Returns the entry with largest key strictly less than {@code key}, or null if no such entry exists */ 907 | public Entry<@Boxed $K$, @Boxed $V$> lowerEntry($K$ key) { 908 | {% else %} 909 | @Override 910 | public Entry<@Boxed $K$, @Boxed $V$> lowerEntry($K$ key) { 911 | {% endif %} 912 | if (rootObjects == null) { 913 | return null; 914 | } 915 | 916 | final int depth = this.depth; 917 | 918 | Node<$K$, AbstractNode> backtrackParent = null; // Deepest internal node on the path to "key" which has a child prior to the one we descended into 919 | int backtrackIndex = -1; // Index of that prior child 920 | int backtrackDepth = -1; // Depth of that internal node 921 | 922 | AbstractNode repr = rootObjects; 923 | for (int i = 0; i < depth; i++) { 924 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 925 | final int index = Internal.find(internal, key, comparator); 926 | if (index > 0) { 927 | backtrackParent = internal; 928 | backtrackIndex = index - 1; 929 | backtrackDepth = i; 930 | } 931 | repr = Internal.getNode(internal, index); 932 | } 933 | 934 | Node<$K$, $V$> leaf = (Node<$K$, $V$>)repr; 935 | final int leafIndex = Leaf.find(leaf, key, comparator); 936 | final int insertionPoint = leafIndex >= 0 ? leafIndex : -(leafIndex + 1); 937 | final int returnIndex; 938 | if (insertionPoint > 0) { 939 | returnIndex = insertionPoint - 1; 940 | } else { 941 | // insertionPoint == 0: we need to find the last item in the prior leaf node. 942 | if (backtrackParent == null) { 943 | // Oh -- that was the first leaf node 944 | return null; 945 | } 946 | 947 | repr = backtrackParent; 948 | int index = backtrackIndex; 949 | for (int i = backtrackDepth; i < depth; i++) { 950 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 951 | repr = Internal.getNode(internal, index); 952 | index = repr.size - 1; 953 | } 954 | 955 | returnIndex = index; 956 | leaf = (Node<$K$, $V$>)repr; 957 | } 958 | 959 | return new AbstractMap.SimpleImmutableEntry<>( 960 | Leaf.getKey (leaf, returnIndex), 961 | Leaf.getValue(leaf, returnIndex) 962 | ); 963 | } 964 | 965 | {% if K.isPrimitive() %} 966 | /** 967 | * {@inheritDoc} 968 | * 969 | * @throws NullPointerException if key is null 970 | */ 971 | @Override 972 | public Entry<@Boxed $K$, @Boxed $V$> floorEntry(@Boxed $K$ key) { 973 | return floorEntry(($K$)key); 974 | } 975 | 976 | /** Returns entry with largest key less than or equal to {@code key}, or null if no such entry exists */ 977 | public Entry<@Boxed $K$, @Boxed $V$> floorEntry($K$ key) { 978 | {% else %} 979 | @Override 980 | public Entry<@Boxed $K$, @Boxed $V$> floorEntry($K$ key) { 981 | {% endif %} 982 | if (rootObjects == null) { 983 | return null; 984 | } 985 | 986 | final int depth = this.depth; 987 | 988 | Node<$K$, AbstractNode> backtrackParent = null; // Deepest internal node on the path to "key" which has a child prior to the one we descended into 989 | int backtrackIndex = -1; // Index of that prior child 990 | int backtrackDepth = -1; // Depth of that internal node 991 | 992 | AbstractNode repr = rootObjects; 993 | for (int i = 0; i < depth; i++) { 994 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 995 | final int index = Internal.find(internal, key, comparator); 996 | if (index > 0) { 997 | backtrackParent = internal; 998 | backtrackIndex = index - 1; 999 | backtrackDepth = i; 1000 | } 1001 | repr = Internal.getNode(internal, index); 1002 | } 1003 | 1004 | Node<$K$, $V$> leaf = (Node<$K$, $V$>)repr; 1005 | final int leafIndex = Leaf.find(leaf, key, comparator); 1006 | final int returnIndex; 1007 | if (leafIndex >= 0) { 1008 | returnIndex = leafIndex; 1009 | } else { 1010 | final int insertionPoint = -(leafIndex + 1); 1011 | if (insertionPoint > 0) { 1012 | returnIndex = insertionPoint - 1; 1013 | } else { 1014 | // insertionPoint == 0: we need to find the last item in the prior leaf node. 1015 | if (backtrackParent == null) { 1016 | // Oh -- that was the first leaf node 1017 | return null; 1018 | } 1019 | 1020 | repr = backtrackParent; 1021 | int index = backtrackIndex; 1022 | for (int i = backtrackDepth; i < depth; i++) { 1023 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 1024 | repr = Internal.getNode(internal, index); 1025 | index = repr.size - 1; 1026 | } 1027 | 1028 | leaf = (Node<$K$, $V$>)repr; 1029 | returnIndex = index; 1030 | } 1031 | } 1032 | 1033 | return new AbstractMap.SimpleImmutableEntry<>( 1034 | Leaf.getKey (leaf, returnIndex), 1035 | Leaf.getValue(leaf, returnIndex) 1036 | ); 1037 | } 1038 | 1039 | {% if K.isPrimitive() %} 1040 | /** 1041 | * {@inheritDoc} 1042 | * 1043 | * @throws NullPointerException if key is null 1044 | */ 1045 | @Override 1046 | public Entry<@Boxed $K$, @Boxed $V$> ceilingEntry(@Boxed $K$ key) { 1047 | return ceilingEntry(($K$)key); 1048 | } 1049 | 1050 | /** Returns the entry with smallest key greater than or equal to {@code key}, or null if no such entry exists */ 1051 | public Entry<@Boxed $K$, @Boxed $V$> ceilingEntry($K$ key) { 1052 | {% else %} 1053 | @Override 1054 | public Entry<@Boxed $K$, @Boxed $V$> ceilingEntry($K$ key) { 1055 | {% endif %} 1056 | if (rootObjects == null) { 1057 | return null; 1058 | } 1059 | 1060 | final int depth = this.depth; 1061 | 1062 | Node<$K$, AbstractNode> backtrackParent = null; // Deepest internal node on the path to "key" which has a child next to the one we descended into 1063 | int backtrackIndex = -1; // Index of that next child 1064 | int backtrackDepth = -1; // Depth of that internal node 1065 | 1066 | AbstractNode repr = rootObjects; 1067 | for (int i = 0; i < depth; i++) { 1068 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 1069 | final int index = Internal.find(internal, key, comparator); 1070 | if (index < internal.size - 1) { 1071 | backtrackParent = internal; 1072 | backtrackIndex = index + 1; 1073 | backtrackDepth = i; 1074 | } 1075 | repr = Internal.getNode(internal, index); 1076 | } 1077 | 1078 | Node<$K$, $V$> leaf = (Node<$K$, $V$>)repr; 1079 | final int leafIndex = Leaf.find(leaf, key, comparator); 1080 | final int returnIndex; 1081 | if (leafIndex >= 0) { 1082 | returnIndex = leafIndex; 1083 | } else { 1084 | final int insertionPoint = -(leafIndex + 1); 1085 | if (insertionPoint < leaf.size) { 1086 | returnIndex = insertionPoint; 1087 | } else { 1088 | // insertionPoint == repr.size: we need to find the first item in the next leaf node. 1089 | if (backtrackParent == null) { 1090 | // Oh -- that was the last leaf node 1091 | return null; 1092 | } 1093 | 1094 | repr = backtrackParent; 1095 | int index = backtrackIndex; 1096 | for (int i = backtrackDepth; i < depth; i++) { 1097 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 1098 | repr = Internal.getNode(internal, index); 1099 | index = 0; 1100 | } 1101 | 1102 | leaf = (Node<$K$, $V$>)repr; 1103 | returnIndex = index; 1104 | } 1105 | } 1106 | 1107 | return new AbstractMap.SimpleImmutableEntry<>( 1108 | Leaf.getKey (leaf, returnIndex), 1109 | Leaf.getValue(leaf, returnIndex) 1110 | ); 1111 | } 1112 | 1113 | {% if K.isPrimitive() %} 1114 | /** 1115 | * {@inheritDoc} 1116 | * 1117 | * @throws NullPointerException if key is null 1118 | */ 1119 | @Override 1120 | public Entry<@Boxed $K$, @Boxed $V$> higherEntry(@Boxed $K$ key) { 1121 | return higherEntry(($K$)key); 1122 | } 1123 | 1124 | /** Returns the entry with smallest key strictly greater than {@code key}, or null if no such entry exists */ 1125 | public Entry<@Boxed $K$, @Boxed $V$> higherEntry($K$ key) { 1126 | {% else %} 1127 | @Override 1128 | public Entry<@Boxed $K$, @Boxed $V$> higherEntry($K$ key) { 1129 | {% endif %} 1130 | if (rootObjects == null) { 1131 | return null; 1132 | } 1133 | 1134 | final int depth = this.depth; 1135 | 1136 | Node<$K$, AbstractNode> backtrackParent = null; // Deepest internal node on the path to "key" which has a child next to the one we descended into 1137 | int backtrackIndex = -1; // Index of that next child 1138 | int backtrackDepth = -1; // Depth of that internal node 1139 | 1140 | AbstractNode repr = rootObjects; 1141 | for (int i = 0; i < depth; i++) { 1142 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 1143 | final int index = Internal.find(internal, key, comparator); 1144 | if (index < internal.size - 1) { 1145 | backtrackParent = internal; 1146 | backtrackIndex = index + 1; 1147 | backtrackDepth = i; 1148 | } 1149 | repr = Internal.getNode(internal, index); 1150 | } 1151 | 1152 | Node<$K$, $V$> leaf = (Node<$K$, $V$>)repr; 1153 | final int leafIndex = Leaf.find(leaf, key, comparator); 1154 | final int insertionPoint = leafIndex >= 0 ? leafIndex + 1 : -(leafIndex + 1); 1155 | final int returnIndex; 1156 | if (insertionPoint < leaf.size) { 1157 | returnIndex = insertionPoint; 1158 | } else { 1159 | // insertionPoint == repr.size: we need to find the first item in the next leaf node. 1160 | if (backtrackParent == null) { 1161 | // Oh -- that was the last leaf node 1162 | return null; 1163 | } 1164 | 1165 | repr = backtrackParent; 1166 | int index = backtrackIndex; 1167 | for (int i = backtrackDepth; i < depth; i++) { 1168 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 1169 | repr = Internal.getNode(internal, index); 1170 | index = 0; 1171 | } 1172 | 1173 | leaf = (Node<$K$, $V$>)repr; 1174 | returnIndex = index; 1175 | } 1176 | 1177 | return new AbstractMap.SimpleImmutableEntry<>( 1178 | Leaf.getKey (leaf, returnIndex), 1179 | Leaf.getValue(leaf, returnIndex) 1180 | ); 1181 | } 1182 | 1183 | @Override 1184 | public Entry<@Boxed $K$, @Boxed $V$> firstEntry() { 1185 | if (rootObjects == null) { 1186 | return null; 1187 | } 1188 | 1189 | AbstractNode repr = rootObjects; 1190 | int depth = this.depth; 1191 | 1192 | while (depth-- > 0) { 1193 | final int index = 0; 1194 | repr = Internal.getNode((Node<$K$, AbstractNode>)repr, index); 1195 | } 1196 | 1197 | final Node<$K$, $V$> leaf = (Node<$K$, $V$>)repr; 1198 | final int size = leaf.size; 1199 | if (size == 0) { 1200 | return null; 1201 | } else { 1202 | final int index = 0; 1203 | return new AbstractMap.SimpleImmutableEntry<>( 1204 | Leaf.getKey (leaf, index), 1205 | Leaf.getValue(leaf, index) 1206 | ); 1207 | } 1208 | } 1209 | 1210 | @Override 1211 | public Entry<@Boxed $K$, @Boxed $V$> lastEntry() { 1212 | if (rootObjects == null) { 1213 | return null; 1214 | } 1215 | 1216 | AbstractNode repr = rootObjects; 1217 | int depth = this.depth; 1218 | 1219 | while (depth-- > 0) { 1220 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 1221 | final int index = internal.size - 1; 1222 | repr = Internal.getNode(internal, index); 1223 | } 1224 | 1225 | final Node<$K$, $V$> leaf = (Node<$K$, $V$>)repr; 1226 | final int size = leaf.size; 1227 | if (size == 0) { 1228 | return null; 1229 | } else { 1230 | final int index = size - 1; 1231 | return new AbstractMap.SimpleImmutableEntry<>( 1232 | Leaf.getKey (leaf, index), 1233 | Leaf.getValue(leaf, index) 1234 | ); 1235 | } 1236 | } 1237 | 1238 | @Override 1239 | public Entry<@Boxed $K$, @Boxed $V$> pollFirstEntry() { 1240 | // TODO: fast path? 1241 | final Entry<@Boxed $K$, @Boxed $V$> e = firstEntry(); 1242 | if (e != null) { 1243 | remove(e.getKey()); 1244 | } 1245 | 1246 | return e; 1247 | } 1248 | 1249 | @Override 1250 | public Entry<@Boxed $K$, @Boxed $V$> pollLastEntry() { 1251 | // TODO: fast path? 1252 | final Entry<@Boxed $K$, @Boxed $V$> e = lastEntry(); 1253 | if (e != null) { 1254 | remove(e.getKey()); 1255 | } 1256 | 1257 | return e; 1258 | } 1259 | 1260 | @Override 1261 | public NavigableMap<@Boxed $K$, @Boxed $V$> descendingMap() { 1262 | return new DescendingNavigableMap<@Boxed $K$, @Boxed $V$>(this.asNavigableMap2()); 1263 | } 1264 | 1265 | @Override 1266 | public NavigableSet<@Boxed $K$> navigableKeySet() { 1267 | return new NavigableMapKeySet<@Boxed $K$>(this); 1268 | } 1269 | 1270 | @Override 1271 | public NavigableSet<@Boxed $K$> descendingKeySet() { 1272 | return descendingMap().navigableKeySet(); 1273 | } 1274 | 1275 | @Override 1276 | public NavigableMap<@Boxed $K$, @Boxed $V$> subMap(@Boxed $K$ fromKey, boolean fromInclusive, @Boxed $K$ toKey, boolean toInclusive) { 1277 | return asNavigableMap2().subMap(fromKey, fromInclusive, toKey, toInclusive).asNavigableMap(); 1278 | } 1279 | 1280 | @Override 1281 | public NavigableMap<@Boxed $K$, @Boxed $V$> headMap(@Boxed $K$ toKey, boolean inclusive) { 1282 | return asNavigableMap2().headMap(toKey, inclusive).asNavigableMap(); 1283 | } 1284 | 1285 | @Override 1286 | public NavigableMap<@Boxed $K$, @Boxed $V$> tailMap(@Boxed $K$ fromKey, boolean inclusive) { 1287 | return asNavigableMap2().tailMap(fromKey, inclusive).asNavigableMap(); 1288 | } 1289 | 1290 | @Override 1291 | public SortedMap<@Boxed $K$, @Boxed $V$> subMap(@Boxed $K$ fromKey, @Boxed $K$ toKey) { 1292 | return subMap(fromKey, true, toKey, false); 1293 | } 1294 | 1295 | @Override 1296 | public SortedMap<@Boxed $K$, @Boxed $V$> headMap(@Boxed $K$ toKey) { 1297 | return headMap(toKey, false); 1298 | } 1299 | 1300 | @Override 1301 | public SortedMap<@Boxed $K$, @Boxed $V$> tailMap(@Boxed $K$ fromKey) { 1302 | return tailMap(fromKey, true); 1303 | } 1304 | 1305 | {% for fl in ["first", "last"] %} 1306 | 1307 | @Override 1308 | public @Boxed $K$ {{fl}}Key() { 1309 | final Entry<@Boxed $K$, @Boxed $V$> e = {{fl}}Entry(); 1310 | if (e == null) throw new NoSuchElementException(); 1311 | 1312 | return e.getKey(); 1313 | } 1314 | 1315 | /** 1316 | * Returns the key of the entry that would be returned by {@link #{{fl}}Entry} 1317 | * 1318 | * @throws NoSuchElementException if the map is empty 1319 | */ 1320 | public $K$ {{fl}}Key{{K.name}}() { 1321 | final Entry<@Boxed $K$, @Boxed $V$> e = {{fl}}Entry(); 1322 | if (e == null) throw new NoSuchElementException(); 1323 | 1324 | // TODO: fast path avoiding allocation of Entry? 1325 | return ($K$)e.getKey(); 1326 | } 1327 | 1328 | {% endfor %} 1329 | 1330 | @Override 1331 | public NavigableSet<@Boxed $K$> keySet() { 1332 | return navigableKeySet(); 1333 | } 1334 | 1335 | @Override 1336 | public Collection<@Boxed $V$> values() { 1337 | return new MapValueCollection<>(this); 1338 | } 1339 | 1340 | private class EntryIterator implements Iterator> { 1341 | // indexes[0] is an index into rootObjects. 1342 | // indexes[i] is an index into nodes[i - 1] (for i >= 1) 1343 | private final int[] indexes = new int[depth + 1]; 1344 | private final AbstractNode[] nodes = new AbstractNode[depth]; 1345 | // If nextLevel >= 0: 1346 | // 1. indexes[nextLevel] < size - 1 1347 | // 2. There is no level l > nextLevel such that indexes[l] < size - 1 1348 | private int nextLevel; 1349 | private boolean hasNext; 1350 | 1351 | public void positionAtFirst() { 1352 | nextLevel = -1; 1353 | hasNext = false; 1354 | if (rootObjects != null) { 1355 | AbstractNode node = rootObjects; 1356 | for (int i = 0;; i++) { 1357 | final int index = indexes[i] = 0; 1358 | if (index < node.size - 1) { 1359 | nextLevel = i; 1360 | } 1361 | 1362 | if (i >= nodes.length) { 1363 | break; 1364 | } 1365 | 1366 | node = nodes[i] = Internal.getNode((Node<$K$, AbstractNode>)node, index); 1367 | } 1368 | 1369 | hasNext = node.size > 0; 1370 | } 1371 | } 1372 | 1373 | private Node<$K$, $V$> findLeaf($K$ key) { 1374 | if (rootObjects == null) { 1375 | return null; 1376 | } 1377 | 1378 | AbstractNode repr = rootObjects; 1379 | for (int i = 0; i < nodes.length; i++) { 1380 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 1381 | final int index = indexes[i] = Internal.find(internal, key, comparator); 1382 | if (index < internal.size - 1) { 1383 | nextLevel = i; 1384 | // backtrackParent == nextLevel == 0 ? rootObjects : nodes[nextLevel - 1] 1385 | // backtrackIndex == indexes[nextLevel] + 1 1386 | } 1387 | repr = nodes[i] = Internal.getNode(internal, index); 1388 | } 1389 | 1390 | return (Node<$K$, $V$>)repr; 1391 | } 1392 | 1393 | private void findNextLevel() { 1394 | for (int i = indexes.length - 1; i >= 0; i--) { 1395 | final AbstractNode node = i == 0 ? rootObjects : nodes[i - 1]; 1396 | if (indexes[i] < node.size - 1) { 1397 | nextLevel = i; 1398 | break; 1399 | } 1400 | } 1401 | } 1402 | 1403 | private void positionAtIndex(AbstractNode repr, int returnIndex) { 1404 | if (returnIndex >= repr.size) { 1405 | // We need to find the first item in the next leaf node. 1406 | int i = nextLevel; 1407 | if (i < 0) { 1408 | // Oh -- that was the last leaf node 1409 | return; 1410 | } 1411 | 1412 | repr = i == 0 ? rootObjects : nodes[i - 1]; 1413 | int index = indexes[i] + 1; 1414 | for (; i < nodes.length; i++) { 1415 | indexes[i] = index; 1416 | repr = nodes[i] = Internal.getNode((Node<$K$, AbstractNode>)repr, index); 1417 | index = 0; 1418 | } 1419 | 1420 | returnIndex = index; 1421 | nextLevel = Integer.MIN_VALUE; 1422 | } 1423 | 1424 | hasNext = true; 1425 | indexes[nodes.length] = returnIndex; 1426 | 1427 | // Restore the nextLevel invariant 1428 | if (returnIndex < repr.size - 1) { 1429 | nextLevel = nodes.length; 1430 | } else if (nextLevel == Integer.MIN_VALUE) { 1431 | // We already "used up" backtrackDepth 1432 | findNextLevel(); 1433 | } 1434 | } 1435 | 1436 | public void positionAtCeiling($K$ key) { 1437 | nextLevel = -1; 1438 | hasNext = false; 1439 | 1440 | final Node<$K$, $V$> leaf = findLeaf(key); 1441 | if (leaf == null) { 1442 | return; 1443 | } 1444 | 1445 | final int leafIndex = Leaf.find(leaf, key, comparator); 1446 | positionAtIndex(leaf, leafIndex >= 0 ? leafIndex : -(leafIndex + 1)); 1447 | } 1448 | 1449 | public void positionAtHigher($K$ key) { 1450 | nextLevel = -1; 1451 | hasNext = false; 1452 | 1453 | final Node<$K$, $V$> leaf = findLeaf(key); 1454 | if (leaf == null) { 1455 | return; 1456 | } 1457 | 1458 | final int leafIndex = Leaf.find(leaf, key, comparator); 1459 | positionAtIndex(leaf, leafIndex >= 0 ? leafIndex + 1 : -(leafIndex + 1)); 1460 | } 1461 | 1462 | @Override 1463 | public boolean hasNext() { 1464 | return hasNext; 1465 | } 1466 | 1467 | @Override 1468 | public Entry<@Boxed $K$, @Boxed $V$> next() { 1469 | if (!hasNext) { 1470 | throw new NoSuchElementException(); 1471 | } 1472 | 1473 | final Entry<@Boxed $K$, @Boxed $V$> result; 1474 | { 1475 | final Node<$K$, $V$> leafNode = (Node<$K$, $V$>)(nodes.length == 0 ? rootObjects : nodes[nodes.length - 1]); 1476 | final int ix = indexes[indexes.length - 1]; 1477 | result = new AbstractMap.SimpleImmutableEntry<@Boxed $K$, @Boxed $V$>( 1478 | Leaf.getKey(leafNode, ix), 1479 | Leaf.getValue(leafNode, ix) 1480 | ); 1481 | } 1482 | 1483 | if (nextLevel < 0) { 1484 | hasNext = false; 1485 | } else { 1486 | int index = ++indexes[nextLevel]; 1487 | AbstractNode node = nextLevel == 0 ? rootObjects : nodes[nextLevel - 1]; 1488 | assert index < node.size; 1489 | if (nextLevel < nodes.length) { 1490 | // We stepped forward to a later item in an internal node: update all children 1491 | for (int i = nextLevel; i < nodes.length;) { 1492 | node = nodes[i++] = Internal.getNode((Node<$K$, AbstractNode>)node, index); 1493 | index = indexes[i] = 0; 1494 | } 1495 | 1496 | nextLevel = nodes.length; 1497 | } else if (index == node.size - 1) { 1498 | // We stepped forward to the last item in a leaf node: find parent we should step forward next 1499 | assert nextLevel == nodes.length; 1500 | nextLevel = -1; 1501 | for (int i = nodes.length - 1; i >= 0; i--) { 1502 | node = i == 0 ? rootObjects : nodes[i - 1]; 1503 | index = indexes[i]; 1504 | if (index < node.size - 1) { 1505 | nextLevel = i; 1506 | break; 1507 | } 1508 | } 1509 | } 1510 | } 1511 | 1512 | return result; 1513 | } 1514 | 1515 | @Override 1516 | public void remove() { 1517 | // FIXME 1518 | throw new UnsupportedOperationException("Iterator.remove() isn't supported yet, but I wouldn't be averse to adding it."); 1519 | } 1520 | } 1521 | 1522 | Iterator> firstIterator() { 1523 | final EntryIterator it = new EntryIterator(); 1524 | it.positionAtFirst(); 1525 | return it; 1526 | } 1527 | 1528 | Iterator> ceilingIterator($K$ k) { 1529 | final EntryIterator it = new EntryIterator(); 1530 | it.positionAtCeiling(k); 1531 | return it; 1532 | } 1533 | 1534 | Iterator> higherIterator($K$ k) { 1535 | final EntryIterator it = new EntryIterator(); 1536 | it.positionAtHigher(k); 1537 | return it; 1538 | } 1539 | 1540 | private class DescendingEntryIterator implements Iterator> { 1541 | // indexes[0] is an index into rootObjects. 1542 | // indexes[i] is an index into nodes[i - 1] (for i >= 1) 1543 | private final int[] indexes = new int[depth + 1]; 1544 | private final AbstractNode[] nodes = new AbstractNode[depth]; 1545 | // If nextLevel >= 0: 1546 | // 1. indexes[nextLevel] > 0 1547 | // 2. There is no level l > nextLevel such that indexes[l] > 0 1548 | private int nextLevel; 1549 | private boolean hasNext; 1550 | 1551 | public void positionAtLast() { 1552 | nextLevel = -1; 1553 | hasNext = false; 1554 | if (rootObjects != null) { 1555 | AbstractNode node = rootObjects; 1556 | for (int i = 0;; i++) { 1557 | final int index = indexes[i] = node.size - 1; 1558 | if (index > 0) { 1559 | nextLevel = i; 1560 | } 1561 | 1562 | if (i >= nodes.length) { 1563 | break; 1564 | } 1565 | 1566 | node = nodes[i] = Internal.getNode((Node<$K$, AbstractNode>)node, index); 1567 | } 1568 | 1569 | hasNext = node.size > 0; 1570 | } 1571 | } 1572 | 1573 | private Node<$K$, $V$> findLeaf($K$ key) { 1574 | if (rootObjects == null) { 1575 | return null; 1576 | } 1577 | 1578 | AbstractNode repr = rootObjects; 1579 | for (int i = 0; i < nodes.length; i++) { 1580 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 1581 | final int index = indexes[i] = Internal.find(internal, key, comparator); 1582 | if (index > 0) { 1583 | nextLevel = i; 1584 | } 1585 | repr = nodes[i] = Internal.getNode(internal, index); 1586 | } 1587 | 1588 | return (Node<$K$, $V$>)repr; 1589 | } 1590 | 1591 | private void positionAtIndex(int returnIndex) { 1592 | if (returnIndex < 0) { 1593 | // We need to find the last item in the prior leaf node. 1594 | int i = nextLevel; 1595 | if (i < 0) { 1596 | // Oh -- that was the first leaf node 1597 | return; 1598 | } 1599 | 1600 | AbstractNode repr = i == 0 ? rootObjects : nodes[i - 1]; 1601 | int index = indexes[i] - 1; 1602 | for (; i < nodes.length; i++) { 1603 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)repr; 1604 | indexes[i] = index; 1605 | repr = nodes[i] = Internal.getNode(internal, index); 1606 | index = repr.size - 1; 1607 | } 1608 | 1609 | returnIndex = index; 1610 | nextLevel = Integer.MIN_VALUE; 1611 | } 1612 | 1613 | hasNext = true; 1614 | indexes[nodes.length] = returnIndex; 1615 | 1616 | // Restore the nextLevel invariant 1617 | if (returnIndex > 0) { 1618 | nextLevel = nodes.length; 1619 | } else if (nextLevel == Integer.MIN_VALUE) { 1620 | // We already "used up" backtrackDepth 1621 | for (int i = indexes.length - 1; i >= 0; i--) { 1622 | if (indexes[i] > 0) { 1623 | nextLevel = i; 1624 | break; 1625 | } 1626 | } 1627 | } 1628 | } 1629 | 1630 | public void positionAtFloor($K$ key) { 1631 | nextLevel = -1; 1632 | hasNext = false; 1633 | 1634 | final Node<$K$, $V$> leaf = findLeaf(key); 1635 | if (leaf == null) { 1636 | return; 1637 | } 1638 | 1639 | final int leafIndex = Leaf.find(leaf, key, comparator); 1640 | positionAtIndex(leafIndex >= 0 ? leafIndex : -(leafIndex + 1) - 1); 1641 | } 1642 | 1643 | public void positionAtLower($K$ key) { 1644 | nextLevel = -1; 1645 | hasNext = false; 1646 | 1647 | final Node<$K$, $V$> leaf = findLeaf(key); 1648 | if (leaf == null) { 1649 | return; 1650 | } 1651 | 1652 | final int leafIndex = Leaf.find(leaf, key, comparator); 1653 | positionAtIndex(leafIndex >= 0 ? leafIndex - 1 : -(leafIndex + 1) - 1); 1654 | } 1655 | 1656 | @Override 1657 | public boolean hasNext() { 1658 | return hasNext; 1659 | } 1660 | 1661 | @Override 1662 | public Entry<@Boxed $K$, @Boxed $V$> next() { 1663 | if (!hasNext) { 1664 | throw new NoSuchElementException(); 1665 | } 1666 | 1667 | final Entry<@Boxed $K$, @Boxed $V$> result; 1668 | { 1669 | final Node<$K$, $V$> leafNode = (Node<$K$, $V$>)(nodes.length == 0 ? rootObjects : nodes[nodes.length - 1]); 1670 | final int ix = indexes[indexes.length - 1]; 1671 | result = new AbstractMap.SimpleImmutableEntry<@Boxed $K$, @Boxed $V$>( 1672 | Leaf.getKey(leafNode, ix), 1673 | Leaf.getValue(leafNode, ix) 1674 | ); 1675 | } 1676 | 1677 | if (nextLevel < 0) { 1678 | hasNext = false; 1679 | } else { 1680 | int index = --indexes[nextLevel]; 1681 | assert index >= 0; 1682 | if (nextLevel < nodes.length) { 1683 | // We stepped back to an earlier item in an internal node: update all children 1684 | AbstractNode node = nextLevel == 0 ? rootObjects : nodes[nextLevel - 1]; 1685 | for (int i = nextLevel; i < nodes.length;) { 1686 | node = nodes[i++] = Internal.getNode((Node<$K$, AbstractNode>)node, index); 1687 | index = indexes[i] = node.size - 1; 1688 | assert index > 0; 1689 | } 1690 | 1691 | nextLevel = nodes.length; 1692 | } else if (index == 0) { 1693 | // We stepped back to the first item in a leaf node: find parent we should step back next 1694 | assert nextLevel == nodes.length; 1695 | nextLevel = -1; 1696 | for (int i = nodes.length - 1; i >= 0; i--) { 1697 | //Node node = i == 0 ? rootObjects : nodes[i - 1]; 1698 | index = indexes[i]; 1699 | if (index > 0) { 1700 | nextLevel = i; 1701 | break; 1702 | } 1703 | } 1704 | } 1705 | } 1706 | 1707 | return result; 1708 | } 1709 | 1710 | @Override 1711 | public void remove() { 1712 | // FIXME 1713 | throw new UnsupportedOperationException("Iterator.remove() isn't supported yet, but I wouldn't be averse to adding it."); 1714 | } 1715 | } 1716 | 1717 | Iterator> lastIterator() { 1718 | final DescendingEntryIterator it = new DescendingEntryIterator(); 1719 | it.positionAtLast();; 1720 | return it; 1721 | } 1722 | 1723 | Iterator> lowerIterator($K$ key) { 1724 | final DescendingEntryIterator it = new DescendingEntryIterator(); 1725 | it.positionAtLower(key); 1726 | return it; 1727 | } 1728 | 1729 | Iterator> floorIterator($K$ key) { 1730 | final DescendingEntryIterator it = new DescendingEntryIterator(); 1731 | it.positionAtFloor(key); 1732 | return it; 1733 | } 1734 | 1735 | @Override 1736 | public Set> entrySet() { 1737 | return new MapEntrySet<>(this, this::firstIterator); 1738 | } 1739 | 1740 | NavigableMap2<@Boxed $K$, @Boxed $V$> asNavigableMap2() { 1741 | return new NavigableMap2<@Boxed $K$, @Boxed $V$>() { 1742 | @Override 1743 | public NavigableMap<@Boxed $K$, @Boxed $V$> asNavigableMap() { 1744 | return {{KV_}}BTreeMap.this; 1745 | } 1746 | 1747 | @Override 1748 | public Set> descendingEntrySet() { 1749 | return new MapEntrySet<>({{KV_}}BTreeMap.this, {{KV_}}BTreeMap.this::lastIterator); 1750 | } 1751 | 1752 | @Override 1753 | public NavigableMap2<@Boxed $K$, @Boxed $V$> subMap(@Boxed $K$ fromKey, boolean fromInclusive, @Boxed $K$ toKey, boolean toInclusive) { 1754 | return new RestrictedBTreeMap<$K$, $V$>( 1755 | {{KV_}}BTreeMap.this, fromKey, toKey, 1756 | Bound.inclusive(fromInclusive), 1757 | Bound.inclusive(toInclusive)).asNavigableMap2(); 1758 | } 1759 | 1760 | @Override 1761 | public NavigableMap2<@Boxed $K$, @Boxed $V$> headMap(@Boxed $K$ toKey, boolean inclusive) { 1762 | return new RestrictedBTreeMap<$K$, $V$>( 1763 | {{KV_}}BTreeMap.this, null, toKey, 1764 | Bound.MISSING, 1765 | Bound.inclusive(inclusive)).asNavigableMap2(); 1766 | } 1767 | 1768 | @Override 1769 | public NavigableMap2<@Boxed $K$, @Boxed $V$> tailMap(@Boxed $K$ fromKey, boolean inclusive) { 1770 | return new RestrictedBTreeMap<$K$, $V$>( 1771 | {{KV_}}BTreeMap.this, fromKey, null, 1772 | Bound.inclusive(inclusive), 1773 | Bound.MISSING).asNavigableMap2(); 1774 | } 1775 | }; 1776 | } 1777 | 1778 | {% if V.isPrimitive() %} 1779 | /** Remove the entry with the specified key, returning the value of the removed entry. If no such entry existed, returns the most negative {@code {{V}}} value. */ 1780 | public $V$ remove{{V.name}}(Object key) { 1781 | // TODO: fast path avoiding allocation of result? 1782 | final @Boxed $V$ result = remove(key); 1783 | return result == null ? {{V.dfault}} : ($V$)result; 1784 | } 1785 | 1786 | {% if K.isPrimitive() %} 1787 | /** Remove the entry with the specified key, returning the value of the removed entry. If no such entry existed, returns the most negative {@code {{V}}} value. */ 1788 | public $V$ remove{{V.name}}($K$ key) { 1789 | // TODO: fast path avoiding allocation of result? 1790 | final @Boxed $V$ result = remove(key); 1791 | return result == null ? {{V.dfault}} : ($V$)result; 1792 | } 1793 | {% endif %} 1794 | {% endif %} 1795 | 1796 | {% if K.isPrimitive() %} 1797 | @Override 1798 | public @Boxed $V$ remove(Object key) { 1799 | if (!(key instanceof @Boxed $K$)) { 1800 | return null; 1801 | } else { 1802 | return remove(($K$)key); 1803 | } 1804 | } 1805 | 1806 | /** Remove the entry with the specified key, returning the value of the removed entry. If no such entry existed, returns null. */ 1807 | public @Boxed $V$ remove($K$ key) { 1808 | {% else %} 1809 | @Override 1810 | public @Boxed $V$ remove(Object key) { 1811 | {% endif %} 1812 | if (rootObjects == null) { 1813 | return null; 1814 | } 1815 | 1816 | final @Boxed $V$ result = removeCore(rootObjects, depth, key); 1817 | if (rootObjects.size == 1 && depth > 0) { 1818 | rootObjects = Internal.getNode((Node<$K$, AbstractNode>)rootObjects, 0); 1819 | depth--; 1820 | } 1821 | 1822 | return result; 1823 | } 1824 | 1825 | private @Boxed $V$ removeCore(Object node, int depth, @Erased $K$ key) { 1826 | if (depth == 0) { 1827 | final Node<$K$, $V$> leaf = (Node<$K$, $V$>)node; 1828 | final int index = Leaf.find(leaf, key, comparator); 1829 | if (index < 0) { 1830 | return null; 1831 | } else { 1832 | final $V$ result = ($V$)Leaf.getValue(leaf, index); 1833 | 1834 | size--; 1835 | leaf.size--; 1836 | {{KV_}}Node.arraycopyKey (leaf, index + 1, leaf, index, leaf.size - index); 1837 | {{KV_}}Node.arraycopyValue(leaf, index + 1, leaf, index, leaf.size - index); 1838 | 1839 | // Avoid memory leaks 1840 | {% if K.isObject() %}leaf.setKey (leaf.size, null);{% endif %} 1841 | {% if V.isObject() %}leaf.setValue(leaf.size, null);{% endif %} 1842 | 1843 | return result; 1844 | } 1845 | } else { 1846 | final Node<$K$, AbstractNode> internal = (Node<$K$, AbstractNode>)node; 1847 | final int index = Internal.find(internal, key, comparator); 1848 | final AbstractNode child = Internal.getNode(internal, index); 1849 | final @Boxed $V$ result = removeCore(child, depth - 1, key); 1850 | 1851 | if (child.size < Node.MIN_FANOUT) { 1852 | assert child.size == Node.MIN_FANOUT - 1; 1853 | 1854 | if (index > 0) { 1855 | // Take key from or merge with predecessor 1856 | final AbstractNode pred = Internal.getNode(internal, index - 1); 1857 | if (pred.size > Node.MIN_FANOUT) { 1858 | // Can take key from predecessor 1859 | final int predSize = --pred.size; 1860 | final int childSize = child.size++; 1861 | 1862 | final $K$ predLtKey; 1863 | if (depth == 1) { 1864 | // Children are leaves 1865 | final Node<$K$, $V$> childLeaf = (Node<$K$, $V$>)child; 1866 | final Node<$K$, $V$> predLeaf = (Node<$K$, $V$>)pred; 1867 | predLtKey = predLeaf.getKey(predSize); 1868 | final $V$ predValue = predLeaf.getValue(predSize); 1869 | 1870 | // Avoid memory leaks 1871 | {% if K.isObject() %}predLeaf.setKey (predSize, null);{% endif %} 1872 | {% if V.isObject() %}predLeaf.setValue(predSize, null);{% endif %} 1873 | 1874 | {{KV_}}Node.arraycopyKey (childLeaf, 0, childLeaf, 1, childSize); 1875 | {{KV_}}Node.arraycopyValue(childLeaf, 0, childLeaf, 1, childSize); 1876 | childLeaf.setKey (0, predLtKey); 1877 | childLeaf.setValue(0, predValue); 1878 | } else { 1879 | // Children are internal nodes 1880 | final Node<$K$, AbstractNode> childInternal = (Node<$K$, AbstractNode>)child; 1881 | final Node<$K$, AbstractNode> predInternal = (Node<$K$, AbstractNode>)pred; 1882 | predLtKey = Internal.getKey(predInternal, predSize - 1); 1883 | final $K$ predKey = Internal.getKey(internal, index - 1); 1884 | final AbstractNode predNode = Internal.getNode(predInternal, predSize); 1885 | 1886 | // Avoid memory leaks 1887 | {% if K.isObject %} 1888 | predInternal.setKey (predSize - 1, null); 1889 | {% endif %} 1890 | predInternal.setValue(predSize, null); 1891 | 1892 | {{KObject_}}Node.arraycopyKey (childInternal, 0, childInternal, 1, childSize - 1); 1893 | {{KObject_}}Node.arraycopyValue(childInternal, 0, childInternal, 1, childSize); 1894 | childInternal.setKey (0, predKey); 1895 | childInternal.setValue(0, predNode); 1896 | } 1897 | 1898 | internal.setKey(index - 1, predLtKey); 1899 | } else { 1900 | // Can merge with predecessor 1901 | final $K$ middleKey = Internal.getKey(internal, index - 1); 1902 | Internal.deleteAtIndex(internal, index); 1903 | appendToPred(pred, middleKey, child, depth - 1); 1904 | } 1905 | } else { 1906 | // Take key from or merge with successor (there must be one because all nodes except the root must have at least 1 sibling) 1907 | final AbstractNode succ = Internal.getNode(internal, index + 1); 1908 | if (succ.size > Node.MIN_FANOUT) { 1909 | // Can take key from successor 1910 | final int succSize = --succ.size; 1911 | final int childSize = child.size++; 1912 | 1913 | final $K$ succGteKey; 1914 | if (depth == 1) { 1915 | // Children are leaves 1916 | final Node<$K$, $V$> childLeaf = (Node<$K$, $V$>)child; 1917 | final Node<$K$, $V$> succLeaf = (Node<$K$, $V$>)succ; 1918 | succGteKey = Leaf.getKey(succLeaf, 1); 1919 | final $K$ succKey = succLeaf.getKey (0); 1920 | final $V$ succValue = succLeaf.getValue(0); 1921 | 1922 | {{KV_}}Node.arraycopyKey (succLeaf, 1, succLeaf, 0, succSize); 1923 | {{KV_}}Node.arraycopyValue(succLeaf, 1, succLeaf, 0, succSize); 1924 | 1925 | // Avoid memory leaks 1926 | {% if K.isObject %}succLeaf.setKey (succSize, null);{% endif %} 1927 | {% if V.isObject %}succLeaf.setValue(succSize, null);{% endif %} 1928 | 1929 | childLeaf.setKey (childSize, succKey); 1930 | childLeaf.setValue(childSize, succValue); 1931 | } else { 1932 | // Children are internal nodes 1933 | final Node<$K$, AbstractNode> childInternal = (Node<$K$, AbstractNode>)child; 1934 | final Node<$K$, AbstractNode> succInternal = (Node<$K$, AbstractNode>)succ; 1935 | succGteKey = Internal.getKey(succInternal, 0); 1936 | final $K$ succKey = Internal.getKey(internal, index); 1937 | final AbstractNode succNode = Internal.getNode(succInternal, 0); 1938 | 1939 | {{KObject_}}Node.arraycopyKey (succInternal, 1, succInternal, 0, succSize - 1); 1940 | {{KObject_}}Node.arraycopyValue(succInternal, 1, succInternal, 0, succSize); 1941 | 1942 | // Avoid memory leaks 1943 | {% if K.isObject %} 1944 | succInternal.setKey (succSize - 1, null); 1945 | {% endif %} 1946 | succInternal.setValue(succSize, null); 1947 | 1948 | childInternal.setKey (childSize, succKey); 1949 | childInternal.setValue(childSize, succNode); 1950 | } 1951 | 1952 | internal.setKey(index, succGteKey); 1953 | } else { 1954 | // Can merge with successor 1955 | final $K$ middleKey = Internal.getKey(internal, index); 1956 | Internal.deleteAtIndex(internal, index + 1); 1957 | appendToPred(child, middleKey, succ, depth - 1); 1958 | } 1959 | } 1960 | } 1961 | 1962 | return result; 1963 | } 1964 | } 1965 | 1966 | private void appendToPred(AbstractNode pred, $K$ middleKey, AbstractNode succ, int depth) { 1967 | final int succSize = succ.size; 1968 | final int predSize = pred.size; 1969 | 1970 | pred.size = predSize + succSize; 1971 | assert pred.size == MAX_FANOUT; 1972 | 1973 | if (depth == 0) { 1974 | // Children are leaves 1975 | final Node<$K$, $V$> succLeaf = (Node<$K$, $V$>)succ, 1976 | predLeaf = (Node<$K$, $V$>)pred; 1977 | {{KV_}}Node.arraycopyKey (succLeaf, 0, predLeaf, predSize, succSize); 1978 | {{KV_}}Node.arraycopyValue(succLeaf, 0, predLeaf, predSize, succSize); 1979 | } else { 1980 | // Children are internal nodes 1981 | final Node<$K$, AbstractNode> succInternal = (Node<$K$, AbstractNode>)succ, 1982 | predInternal = (Node<$K$, AbstractNode>)pred; 1983 | predInternal.setKey(predSize - 1, middleKey); 1984 | {{KObject_}}Node.arraycopyKey (succInternal, 0, predInternal, predSize, succSize - 1); 1985 | {{KObject_}}Node.arraycopyValue(succInternal, 0, predInternal, predSize, succSize); 1986 | } 1987 | } 1988 | } 1989 | --------------------------------------------------------------------------------