├── LICENSE.txt ├── README.md ├── pom.xml └── src ├── main └── java │ ├── io │ └── prelink │ │ └── critbit │ │ ├── AbstractCritBitTree.java │ │ ├── CritBitTree.java │ │ ├── MCritBitTree.java │ │ └── sharedbytearray │ │ ├── AbstractSBA.java │ │ ├── EmptySBA.java │ │ ├── JoinedSBA.java │ │ ├── PrefixSBA.java │ │ ├── SBAKeyAnalyzer.java │ │ ├── SharedByteArray.java │ │ ├── SuffixSBA.java │ │ ├── ThickSBA.java │ │ └── ThinSBA.java │ └── org │ └── ardverk │ └── collection │ ├── AbstractKeyAnalyzer.java │ ├── ByteArrayKeyAnalyzer.java │ ├── ByteKeyAnalyzer.java │ ├── CharArrayKeyAnalyzer.java │ ├── CharacterKeyAnalyzer.java │ ├── Cursor.java │ ├── DefaultKeyAnalyzer.java │ ├── IntegerKeyAnalyzer.java │ ├── Key.java │ ├── KeyAnalyzer.java │ ├── LongKeyAnalyzer.java │ ├── ShortKeyAnalyzer.java │ └── StringKeyAnalyzer.java └── test ├── java ├── io │ └── prelink │ │ └── critbit │ │ ├── CritBitTest.java │ │ ├── IterationSpeedTest.java │ │ ├── PatriciaTrieTest.java │ │ └── sharedbytearray │ │ └── SharedByteArrayTest.java └── org │ └── ardverk │ └── collection │ ├── ByteArrayKeyAnalyzerTest.java │ ├── PatriciaTrieTest.java │ └── SerializationTest.java └── resources └── org └── ardverk └── collection └── hamlet.txt /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jason Fager 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An OO/Functional Crit-bit tree in Java, inspired by 2 | [djb](http://cr.yp.to/critbit.html), [Adam Langley](https://github.com/agl/critbit), and [Okasaki](http://www.eecs.usma.edu/webs/people/okasaki/pubs.html). 3 | 4 | A primary goal for the project is space-efficiency. It accomplishes this by 5 | defining the five different kinds of nodes in a critbit tree - no 6 | nodes have unused or unnecessary references, and leaf nodes are collapsed into 7 | their parents when possible. 8 | 9 | An immutable/functional implementation is provided for use where such traits 10 | are desirable. A mutable implementation with much higher insertion throughput 11 | and lower garbage collection tolls is also available. 12 | 13 | Special thanks to [rkapsi's](https://github.com/rkapsi) 14 | [patricia-trie](https://github.com/rkapsi/patricia-trie) project for a nice 15 | set of key analyzers and tests. If you're more concerned with Java Map 16 | interface compatibility and/or performance, you should probably use that 17 | project instead. 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 5 | 4.0.0 6 | 7 | io.prelink 8 | critbit 9 | 0.0.5-SNAPSHOT 10 | jar 11 | 12 | 13 | UTF-8 14 | 15 | 16 | 17 | 18 | commons-lang 19 | commons-lang 20 | 2.6 21 | test 22 | 23 | 24 | junit 25 | junit 26 | 4.10 27 | test 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.apache.maven.plugins 35 | maven-compiler-plugin 36 | 2.3.2 37 | 38 | 1.6 39 | 1.6 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.codehaus.mojo 49 | cobertura-maven-plugin 50 | 2.5.1 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/AbstractCritBitTree.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit; 2 | 3 | import java.io.Serializable; 4 | import java.util.Map; 5 | 6 | import org.ardverk.collection.Cursor; 7 | import org.ardverk.collection.Cursor.Decision; 8 | import org.ardverk.collection.KeyAnalyzer; 9 | 10 | 11 | /** 12 | * An OO/Functional Java crit-bit tree, inspired by 13 | * djb (http://cr.yp.to/critbit.html), 14 | * Adam Langley (https://github.com/agl/critbit), 15 | * and Okasaki (http://www.eecs.usma.edu/webs/people/okasaki/pubs.html) 16 | */ 17 | abstract class AbstractCritBitTree implements Serializable { 18 | 19 | static final long serialVersionUID = 20110212L; 20 | 21 | static interface NodeFactory extends Serializable { 22 | Node mkShortBoth(int diffBit, K lk, V lv, K rk, V rv); 23 | Node mkShortRight(int diffBit, Node left, K k, V v); 24 | Node mkShortLeft(int diffBit, K k, V v, Node right); 25 | Node mkTall(int diffBit, Node left, Node right); 26 | Node mkLeaf(K key, V val); 27 | } 28 | 29 | static class Context implements Serializable { 30 | private static final long serialVersionUID = 20110212L; 31 | final KeyAnalyzer chk; 32 | final NodeFactory nf; 33 | Context(KeyAnalyzer chk, NodeFactory nf) { 34 | this.chk = chk; 35 | this.nf = nf; 36 | } 37 | } 38 | 39 | static interface Node extends Serializable { 40 | //Everybody implements these. 41 | Node insert(int diffBit, K key, V val, Context ctx); 42 | Node remove(K key, Context ctx, boolean force); 43 | boolean isInternal(); 44 | 45 | //Should only be called for internal nodes. 46 | int bit(); 47 | Direction next(K key, Context ctx); 48 | Node nextNode(K key, Context ctx); 49 | Node left(Context ctx); 50 | Node right(Context ctx); 51 | Node setLeft(int diffBit, K key, V val, Context ctx); 52 | Node setRight(int diffBit, K key, V val, Context ctx); 53 | boolean hasExternalLeft(); 54 | boolean hasExternalRight(); 55 | 56 | //Should only be called for external nodes. 57 | K getKey(); 58 | V getValue(); 59 | } 60 | 61 | static enum Direction { 62 | LEFT, RIGHT 63 | } 64 | 65 | static abstract class BaseNode implements Node { 66 | private static final long serialVersionUID = 20110212L; 67 | public Node insert(int diffBit, K key, V val, Context ctx) { 68 | throw new UnsupportedOperationException(); 69 | } 70 | public int bit() { 71 | throw new UnsupportedOperationException(); 72 | } 73 | public Direction next(K key, Context ctx) { 74 | throw new UnsupportedOperationException(); 75 | } 76 | public Node nextNode(K key, Context ctx) { 77 | throw new UnsupportedOperationException(); 78 | } 79 | public Node left(Context ctx) { 80 | throw new UnsupportedOperationException(); 81 | } 82 | public Node right(Context ctx) { 83 | throw new UnsupportedOperationException(); 84 | } 85 | public Node setLeft(int diffBit, K key, V val, Context ctx) { 86 | throw new UnsupportedOperationException(); 87 | } 88 | public Node setRight(int diffBit, K key, V val, Context ctx) { 89 | throw new UnsupportedOperationException(); 90 | } 91 | public boolean hasExternalLeft() { 92 | throw new UnsupportedOperationException(); 93 | } 94 | public boolean hasExternalRight() { 95 | throw new UnsupportedOperationException(); 96 | } 97 | public K getKey() { 98 | throw new UnsupportedOperationException(); 99 | } 100 | public V getValue() { 101 | throw new UnsupportedOperationException(); 102 | } 103 | } 104 | 105 | static abstract class AbstractInternal extends BaseNode { 106 | private static final long serialVersionUID = 20110212L; 107 | 108 | private final int bit; 109 | AbstractInternal(int bit) { 110 | this.bit = bit; 111 | } 112 | 113 | public final int bit() { return bit; } 114 | 115 | public final Node insert(int diffBit, K k, V v, Context ctx) { 116 | if(diffBit >= 0 && diffBit < bit()) { 117 | return ctx.chk.isBitSet(k, diffBit) ? ctx.nf.mkShortRight(diffBit, this, k, v) 118 | : ctx.nf.mkShortLeft(diffBit, k, v, this); 119 | } else { 120 | return ctx.chk.isBitSet(k, bit()) ? setRight(diffBit, k, v, ctx) 121 | : setLeft(diffBit, k, v, ctx); 122 | } 123 | } 124 | 125 | public Node remove(K key, Context ctx, boolean force) { 126 | switch(next(key, ctx)) { 127 | case LEFT: 128 | return removeLeft(key, ctx, force); 129 | default: 130 | return removeRight(key, ctx, force); 131 | } 132 | } 133 | 134 | public final Direction next(K key, Context ctx) { 135 | return ctx.chk.isBitSet(key, bit()) ? Direction.RIGHT 136 | : Direction.LEFT; 137 | } 138 | 139 | public final Node nextNode(K key, Context ctx) { 140 | switch(next(key, ctx)) { 141 | case LEFT: return left(ctx); 142 | default: return right(ctx); 143 | } 144 | } 145 | 146 | public final boolean isInternal() { return true; } 147 | 148 | protected abstract Node removeLeft(K key, Context ctx, boolean force); 149 | protected abstract Node removeRight(K key, Context ctx, boolean force); 150 | 151 | protected final Node mkShortBothChild(int diffBit, 152 | K newKey, V newVal, 153 | K oldKey, V oldVal, 154 | Context ctx) { 155 | boolean newGoesRight = ctx.chk.isBitSet(newKey, diffBit); 156 | K rKey = newGoesRight ? newKey : oldKey; 157 | V rVal = newGoesRight ? newVal : oldVal; 158 | K lKey = newGoesRight ? oldKey : newKey; 159 | V lVal = newGoesRight ? oldVal : newVal; 160 | return ctx.nf.mkShortBoth(diffBit, lKey, lVal, rKey, rVal); 161 | } 162 | } 163 | 164 | static final class LeafNode 165 | extends BaseNode 166 | implements Map.Entry 167 | { 168 | private static final long serialVersionUID = 20110212L; 169 | private final K key; 170 | private final V value; 171 | public LeafNode(K key, V value) { 172 | this.key = key; 173 | this.value = value; 174 | } 175 | public K getKey() { return this.key; } 176 | public V getValue() { return this.value; } 177 | public V setValue(V arg0) { 178 | throw new UnsupportedOperationException(); 179 | } 180 | public Node insert(int diffBit, K key, V val, Context ctx) { 181 | if(diffBit < 0) { 182 | return ctx.nf.mkLeaf(key, val); 183 | } 184 | return ctx.chk.isBitSet(key, diffBit) ? ctx.nf.mkShortBoth(diffBit, this.key, this.value, key, val) 185 | : ctx.nf.mkShortBoth(diffBit, key, val, this.key, this.value); 186 | } 187 | public boolean isInternal() { return false; } 188 | public Node remove(K key, Context ctx, boolean force) { 189 | return (force || ctx.chk.bitIndex(key, this.key) < 0) ? null 190 | : this; 191 | } 192 | } 193 | 194 | static final class ShortBothNode extends AbstractInternal { 195 | private static final long serialVersionUID = 20110212L; 196 | private final K leftKey; 197 | private final V leftVal; 198 | private final K rightKey; 199 | private final V rightVal; 200 | public ShortBothNode(int bit, K leftKey, V leftVal, K rightKey, V rightVal) { 201 | super(bit); 202 | this.leftKey = leftKey; 203 | this.leftVal = leftVal; 204 | this.rightKey = rightKey; 205 | this.rightVal = rightVal; 206 | } 207 | public Node left(Context ctx) { return ctx.nf.mkLeaf(leftKey, leftVal); } 208 | public Node right(Context ctx) { return ctx.nf.mkLeaf(rightKey, rightVal); } 209 | public Node setLeft(int diffBit, K key, V val, Context ctx) { 210 | if(diffBit < 0) { 211 | return ctx.nf.mkShortBoth(bit(), key, val, rightKey, rightVal); 212 | } 213 | Node newLeft = mkShortBothChild(diffBit, key, val, leftKey, leftVal, ctx); 214 | return ctx.nf.mkShortRight(bit(), newLeft, rightKey, rightVal); 215 | } 216 | public Node setRight(int diffBit, K key, V val, Context ctx) { 217 | if(diffBit < 0) { 218 | return ctx.nf.mkShortBoth(bit(), leftKey, leftVal, key, val); 219 | } 220 | Node newRight = mkShortBothChild(diffBit, key, val, rightKey, rightVal, ctx); 221 | return ctx.nf.mkShortLeft(bit(), leftKey, leftVal, newRight); 222 | } 223 | protected Node removeLeft(K key, Context ctx, boolean force) { 224 | return (force || ctx.chk.bitIndex(key, this.leftKey) < 0) ? ctx.nf.mkLeaf(rightKey, rightVal) 225 | : this; 226 | } 227 | protected Node removeRight(K key, Context ctx, boolean force) { 228 | return (force || ctx.chk.bitIndex(key, this.rightKey) < 0) ? ctx.nf.mkLeaf(leftKey, leftVal) 229 | : this; 230 | } 231 | public boolean hasExternalLeft() { return true; } 232 | public boolean hasExternalRight() { return true; } 233 | } 234 | 235 | private final Context ctx; 236 | 237 | AbstractCritBitTree(Context context) { 238 | this.ctx = context; 239 | } 240 | 241 | abstract Node root(); 242 | 243 | Context ctx() { 244 | return ctx; 245 | } 246 | 247 | static final class SearchResult { 248 | final Node parent; 249 | final Direction pDirection; 250 | final Node result; 251 | final Direction rDirection; 252 | public SearchResult(Node parent, 253 | Direction pDirection, 254 | Node result, 255 | Direction rDirection) { 256 | this.parent = parent; 257 | this.pDirection = pDirection; 258 | this.result = result; 259 | this.rDirection = rDirection; 260 | } 261 | K key(Context ctx) { 262 | switch(rDirection) { 263 | case LEFT: 264 | return result.left(ctx).getKey(); 265 | default: //case RIGHT: 266 | return result.right(ctx).getKey(); 267 | } 268 | } 269 | V value(Context ctx) { 270 | switch(rDirection) { 271 | case LEFT: 272 | return result.left(ctx).getValue(); 273 | default: //case RIGHT: 274 | return result.right(ctx).getValue(); 275 | } 276 | } 277 | } 278 | 279 | final SearchResult search(final Node start, final K key) { 280 | Node par = null; 281 | Direction parDirection = null; 282 | Node cur = start; 283 | for(;;) { 284 | switch(cur.next(key, ctx)) { 285 | case LEFT: 286 | if(cur.hasExternalLeft()) { 287 | return new SearchResult(par, parDirection, cur, Direction.LEFT); 288 | } 289 | par = cur; 290 | parDirection = Direction.LEFT; 291 | cur = cur.left(ctx); 292 | break; 293 | case RIGHT: 294 | if(cur.hasExternalRight()) { 295 | return new SearchResult(par, parDirection, cur, Direction.RIGHT); 296 | } 297 | par = cur; 298 | parDirection = Direction.RIGHT; 299 | cur = cur.right(ctx); 300 | break; 301 | } 302 | } 303 | } 304 | 305 | public final V get(Object k) { 306 | K key = AbstractCritBitTree.cast(k); 307 | if(root() == null) { 308 | return null; 309 | } 310 | if(!root().isInternal()) { 311 | return ctx().chk.bitIndex(key, root().getKey()) < 0 ? root().getValue() : null; 312 | } 313 | SearchResult sr = search(root(), key); 314 | final int diffBit = ctx().chk.bitIndex(key, sr.key(ctx())); 315 | return (diffBit < 0) ? sr.value(ctx()) : null; 316 | } 317 | 318 | public final Map.Entry min() { 319 | if(root() == null) { 320 | return null; 321 | } 322 | Node current = root(); 323 | while(current.isInternal()) { 324 | current = current.left(ctx()); 325 | } 326 | return AbstractCritBitTree.>cast(current); 327 | } 328 | 329 | public final Map.Entry max() { 330 | if(root() == null) { 331 | return null; 332 | } 333 | Node current = root(); 334 | while(current.isInternal()) { 335 | current = current.right(ctx()); 336 | } 337 | return AbstractCritBitTree.>cast(current); 338 | } 339 | 340 | public final void traverse(Cursor cursor) { 341 | if(root() == null) { 342 | return; 343 | } 344 | doTraverse(root(), cursor); 345 | } 346 | 347 | public final void traverseWithPrefix(K key, 348 | Cursor cursor) { 349 | if(root() == null) { 350 | return; 351 | } 352 | if(!root().isInternal()) { 353 | Map.Entry e = AbstractCritBitTree.>cast(root()); 354 | cursor.select(e); 355 | return; 356 | } 357 | 358 | int keyLen = ctx.chk.lengthInBits(key); 359 | Node current = root(); 360 | Node top = current; 361 | Direction topDirection = Direction.LEFT; 362 | while(current.isInternal()) { 363 | Direction nextDirection = current.next(key, ctx); 364 | if(current.bit() < keyLen) { 365 | top = current; 366 | topDirection = nextDirection; 367 | } 368 | switch(nextDirection) { 369 | case LEFT: 370 | current = current.left(ctx); 371 | break; 372 | case RIGHT: 373 | current = current.right(ctx); 374 | break; 375 | } 376 | } 377 | if(!ctx.chk.isPrefix(current.getKey(), key)) { 378 | return; 379 | } 380 | 381 | switch(topDirection) { 382 | case LEFT: 383 | doTraverse(top.left(ctx), cursor); 384 | return; 385 | default: 386 | doTraverse(top.right(ctx), cursor); 387 | return; 388 | } 389 | } 390 | 391 | protected abstract Decision doTraverse(Node top, 392 | Cursor cursor); 393 | 394 | public abstract int size(); 395 | public final boolean isEmpty() { return size() == 0; } 396 | 397 | @SuppressWarnings("unchecked") 398 | static T cast(Object obj) { 399 | return (T)obj; 400 | } 401 | 402 | public final boolean containsKey(Object k) { 403 | if(root() == null) { 404 | return false; 405 | } 406 | 407 | K key = AbstractCritBitTree.cast(k); 408 | if(!root().isInternal()) { 409 | return ctx().chk.bitIndex(key, root().getKey()) < 0; 410 | } 411 | 412 | final SearchResult sr = search(root(), key); 413 | final int diffBit = ctx().chk.bitIndex(key, sr.key(ctx())); 414 | return diffBit < 0; 415 | } 416 | 417 | private static class ContainsValueCursor implements Cursor { 418 | private final V value; 419 | private boolean outcome = false; 420 | public ContainsValueCursor(V value) { 421 | this.value = value; 422 | } 423 | public Decision select(Map.Entry entry) { 424 | if(value.equals(entry.getValue())) { 425 | outcome = true; 426 | return Decision.EXIT; 427 | } 428 | return Decision.CONTINUE; 429 | } 430 | public boolean getOutcome() { 431 | return outcome; 432 | } 433 | } 434 | 435 | public final boolean containsValue(Object v) { 436 | V val = AbstractCritBitTree.cast(v); 437 | ContainsValueCursor cvc = new ContainsValueCursor(val); 438 | traverse(cvc); 439 | return cvc.getOutcome(); 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/CritBitTree.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit; 2 | 3 | import java.util.Map; 4 | 5 | import org.ardverk.collection.Cursor; 6 | import org.ardverk.collection.Cursor.Decision; 7 | import org.ardverk.collection.KeyAnalyzer; 8 | 9 | /** 10 | * An OO/Functional Java crit-bit tree, inspired by 11 | * djb (http://cr.yp.to/critbit.html), 12 | * Adam Langley (https://github.com/agl/critbit), 13 | * and Okasaki (http://www.eecs.usma.edu/webs/people/okasaki/pubs.html) 14 | */ 15 | public final class CritBitTree extends AbstractCritBitTree { 16 | 17 | private static final long serialVersionUID = 20110212L; 18 | 19 | static final class ShortLeftNode extends AbstractInternal { 20 | private static final long serialVersionUID = 20110212L; 21 | private final K leftKey; 22 | private final V leftVal; 23 | private final Node right; 24 | public ShortLeftNode(int bit, K leftKey, V leftVal, Node right) { 25 | super(bit); 26 | this.leftKey = leftKey; 27 | this.leftVal = leftVal; 28 | this.right = right; 29 | } 30 | public Node left(Context ctx) { return ctx.nf.mkLeaf(leftKey, leftVal); } 31 | public Node right(Context ctx) { return right; } 32 | public Node setLeft(int diffBit, K key, V val, Context ctx) { 33 | if(diffBit < 0) { 34 | return ctx.nf.mkShortLeft(bit(), key, val, right); 35 | } 36 | Node newLeft = mkShortBothChild(diffBit, key, val, leftKey, leftVal, ctx); 37 | return ctx.nf.mkTall(bit(), newLeft, right); 38 | } 39 | public Node setRight(int diffBit, K key, V val, Context ctx) { 40 | Node newRight = right.insert(diffBit, key, val, ctx); 41 | return ctx.nf.mkShortLeft(bit(), leftKey, leftVal, newRight); 42 | } 43 | protected Node removeLeft(K key, Context ctx, boolean force) { 44 | if(force || ctx.chk.bitIndex(key, this.leftKey) < 0) { 45 | return right; 46 | } 47 | return this; 48 | } 49 | protected Node removeRight(K key, Context ctx, boolean force) { 50 | Node newRight = right.remove(key, ctx, force); 51 | if(right == newRight) { 52 | return this; 53 | } 54 | return newRight.isInternal() ? ctx.nf.mkShortLeft(bit(), leftKey, leftVal, newRight) 55 | : ctx.nf.mkShortBoth(bit(), leftKey, leftVal, newRight.getKey(), newRight.getValue()); 56 | } 57 | public boolean hasExternalLeft() { return true; } 58 | public boolean hasExternalRight() { return false; } 59 | } 60 | static final class ShortRightNode extends AbstractInternal { 61 | private static final long serialVersionUID = 20110212L; 62 | private final Node left; 63 | private final K rightKey; 64 | private final V rightVal; 65 | public ShortRightNode(int bit, Node left, K rightKey, V rightVal) { 66 | super(bit); 67 | this.left = left; 68 | this.rightKey = rightKey; 69 | this.rightVal = rightVal; 70 | } 71 | public Node left(Context ctx) { return left; } 72 | public Node right(Context ctx) { return ctx.nf.mkLeaf(rightKey, rightVal); } 73 | public Node setLeft(int diffBit, K key, V val, Context ctx) { 74 | Node newLeft = left.insert(diffBit, key, val, ctx); 75 | return ctx.nf.mkShortRight(bit(), newLeft, rightKey, rightVal); 76 | } 77 | public Node setRight(int diffBit, K key, V val, Context ctx) { 78 | if(diffBit < 0) { 79 | return ctx.nf.mkShortRight(bit(), left, key, val); 80 | } 81 | Node newRight = mkShortBothChild(diffBit, key, val, rightKey, rightVal, ctx); 82 | return ctx.nf.mkTall(bit(), left, newRight); 83 | } 84 | protected Node removeLeft(K key, Context ctx, boolean force) { 85 | Node newLeft = left.remove(key, ctx, force); 86 | if(left == newLeft) { 87 | return this; 88 | } 89 | return newLeft.isInternal() ? ctx.nf.mkShortRight(bit(), newLeft, rightKey, rightVal) 90 | : ctx.nf.mkShortBoth(bit(), newLeft.getKey(), newLeft.getValue(), rightKey, rightVal); 91 | } 92 | protected Node removeRight(K key, Context ctx, boolean force) { 93 | return (force || ctx.chk.bitIndex(key, this.rightKey) < 0) ? left 94 | : this; 95 | } 96 | public boolean hasExternalLeft() { return false; } 97 | public boolean hasExternalRight() { return true; } 98 | } 99 | static final class TallNode extends AbstractInternal { 100 | private static final long serialVersionUID = 20110212L; 101 | private final Node left; 102 | private final Node right; 103 | public TallNode(int bit, Node left, Node right) { 104 | super(bit); 105 | this.left = left; 106 | this.right = right; 107 | } 108 | public Node left(Context ctx) { return left; } 109 | public Node right(Context ctx) { return right; } 110 | public Node setLeft(int diffBit, K key, V val, Context ctx) { 111 | Node newLeft = left.insert(diffBit, key, val, ctx); 112 | return ctx.nf.mkTall(bit(), newLeft, right); 113 | } 114 | public Node setRight(int diffBit, K key, V val, Context ctx) { 115 | Node newRight = right.insert(diffBit, key, val, ctx); 116 | return ctx.nf.mkTall(bit(), left, newRight); 117 | } 118 | protected Node removeLeft(K key, Context ctx, boolean force) { 119 | Node newLeft = left.remove(key, ctx, force); 120 | if(left == newLeft) { 121 | return this; 122 | } 123 | return newLeft.isInternal() ? ctx.nf.mkTall(bit(), newLeft, right) 124 | : ctx.nf.mkShortLeft(bit(), newLeft.getKey(), newLeft.getValue(), right); 125 | } 126 | protected Node removeRight(K key, Context ctx, boolean force) { 127 | Node newRight = right.remove(key, ctx, force); 128 | if(right == newRight) { 129 | return this; 130 | } 131 | return newRight.isInternal() ? ctx.nf.mkTall(bit(), left, newRight) 132 | : ctx.nf.mkShortRight(bit(), left, newRight.getKey(), newRight.getValue()); 133 | } 134 | public boolean hasExternalLeft() { return false; } 135 | public boolean hasExternalRight() { return false; } 136 | } 137 | 138 | static final class ImmutableNodeFactory implements NodeFactory { 139 | private static final long serialVersionUID = 20110212L; 140 | public Node mkShortBoth(int diffBit, K lk, V lv, K rk, V rv) { 141 | return new ShortBothNode(diffBit, lk, lv, rk, rv); 142 | } 143 | public Node mkShortRight(int diffBit, Node left, K k, V v) { 144 | return new ShortRightNode(diffBit, left, k, v); 145 | } 146 | public Node mkShortLeft(int diffBit, K k, V v, Node right) { 147 | return new ShortLeftNode(diffBit, k, v, right); 148 | } 149 | public Node mkTall(int diffBit, Node left, Node right) { 150 | return new TallNode(diffBit, left, right); 151 | } 152 | public Node mkLeaf(K key, V val) { 153 | return new LeafNode(key, val); 154 | } 155 | } 156 | 157 | private final Node root; 158 | private final int size; 159 | 160 | public CritBitTree(KeyAnalyzer analyzer) { 161 | this(null, 0, 162 | new Context(analyzer, new ImmutableNodeFactory())); 163 | } 164 | 165 | private CritBitTree(Node root, int size, Context context) { 166 | super(context); 167 | this.root = root; 168 | this.size = size; 169 | } 170 | 171 | Node root() { return root; } 172 | public int size() { return size; } 173 | 174 | public CritBitTree put(K key, V val) { 175 | if(root() == null) { 176 | return new CritBitTree(ctx().nf.mkLeaf(key, val), 1, ctx()); 177 | } 178 | K compKey; 179 | if(root().isInternal()) { 180 | SearchResult sr = search(root(), key); 181 | compKey = sr.key(ctx()); 182 | } else { 183 | compKey = root().getKey(); 184 | } 185 | 186 | int diffBit = ctx().chk.bitIndex(key, compKey); 187 | return new CritBitTree(root().insert(diffBit, key, val, ctx()), 188 | (diffBit < 0) ? size : size + 1, 189 | ctx()); 190 | } 191 | 192 | public CritBitTree remove(K key) { 193 | if(root == null) { 194 | return this; 195 | } 196 | Node removed = root.remove(key, ctx(), false); 197 | return (removed == root) ? this 198 | : new CritBitTree(removed, size - 1, ctx()); 199 | } 200 | 201 | protected final Decision doTraverse(Node top, 202 | Cursor cursor) { 203 | if(top.isInternal()) { 204 | Decision d = doTraverse(top.left(ctx()), cursor); 205 | switch(d) { 206 | case REMOVE_AND_EXIT: //fall through 207 | case EXIT: 208 | return d; 209 | case REMOVE: //fall through 210 | case CONTINUE: 211 | default: 212 | return doTraverse(top.right(ctx()), cursor); 213 | } 214 | } else { 215 | Map.Entry e = AbstractCritBitTree.>cast(top); 216 | return cursor.select(e); 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/MCritBitTree.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit; 2 | 3 | import java.util.AbstractSet; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.NoSuchElementException; 10 | import java.util.Set; 11 | 12 | import org.ardverk.collection.Cursor; 13 | import org.ardverk.collection.Cursor.Decision; 14 | import org.ardverk.collection.KeyAnalyzer; 15 | 16 | /** 17 | * Like immutable.CritBitTree, except w/ nodes that are mutable where it makes 18 | * sense, hypothesis being we can improve performance and cut down on garbage 19 | * collection a bit. 20 | */ 21 | public final class MCritBitTree extends AbstractCritBitTree implements Map { 22 | 23 | private static final long serialVersionUID = 20110212L; 24 | 25 | static final class MShortLeftNode extends AbstractInternal { 26 | private static final long serialVersionUID = 20110212L; 27 | private final K leftKey; 28 | private V leftVal; 29 | private Node right; 30 | public MShortLeftNode(int bit, K leftKey, V leftVal, Node right) { 31 | super(bit); 32 | this.leftKey = leftKey; 33 | this.leftVal = leftVal; 34 | this.right = right; 35 | } 36 | public Node left(Context ctx) { return ctx.nf.mkLeaf(leftKey, leftVal); } 37 | public Node right(Context ctx) { return right; } 38 | public Node setLeft(int diffBit, K key, V val, Context ctx) { 39 | if(diffBit < 0) { 40 | this.leftVal = val; 41 | return this; 42 | } 43 | Node newLeft = mkShortBothChild(diffBit, key, val, leftKey, leftVal, ctx); 44 | return ctx.nf.mkTall(bit(), newLeft, right); 45 | } 46 | public Node setRight(int diffBit, K key, V val, Context ctx) { 47 | this.right = right.insert(diffBit, key, val, ctx); 48 | return this; 49 | } 50 | public boolean hasExternalLeft() { return true; } 51 | public boolean hasExternalRight() { return false; } 52 | protected Node removeLeft(K key, Context ctx, boolean force) { 53 | if(force || ctx.chk.bitIndex(key, this.leftKey) < 0) { 54 | return right; 55 | } 56 | return this; 57 | } 58 | protected Node removeRight(K key, Context ctx, boolean force) { 59 | Node newRight = right.remove(key, ctx, force); 60 | if(newRight.isInternal()) { 61 | this.right = newRight; 62 | return this; 63 | } else { 64 | return ctx.nf.mkShortBoth(bit(), leftKey, leftVal, newRight.getKey(), newRight.getValue()); 65 | } 66 | } 67 | } 68 | 69 | static final class MShortRightNode extends AbstractInternal { 70 | private static final long serialVersionUID = 20110212L; 71 | private Node left; 72 | private final K rightKey; 73 | private V rightVal; 74 | public MShortRightNode(int bit, Node left, K rightKey, V rightVal) { 75 | super(bit); 76 | this.left = left; 77 | this.rightKey = rightKey; 78 | this.rightVal = rightVal; 79 | } 80 | public Node left(Context ctx) { return left; } 81 | public Node right(Context ctx) { return ctx.nf.mkLeaf(rightKey, rightVal); } 82 | public Node setLeft(int diffBit, K key, V val, Context ctx) { 83 | this.left = left.insert(diffBit, key, val, ctx); 84 | return this; 85 | } 86 | public Node setRight(int diffBit, K key, V val, Context ctx) { 87 | if(diffBit < 0) { 88 | this.rightVal = val; 89 | return this; 90 | } 91 | Node newRight = mkShortBothChild(diffBit, key, val, rightKey, rightVal, ctx); 92 | return ctx.nf.mkTall(bit(), left, newRight); 93 | } 94 | protected Node removeLeft(K key, Context ctx, boolean force) { 95 | Node newLeft = left.remove(key, ctx, force); 96 | if(newLeft.isInternal()) { 97 | this.left = newLeft; 98 | return this; 99 | } else { 100 | return ctx.nf.mkShortBoth(bit(), newLeft.getKey(), newLeft.getValue(), rightKey, rightVal); 101 | } 102 | } 103 | protected Node removeRight(K key, Context ctx, boolean force) { 104 | if(force || ctx.chk.bitIndex(key, this.rightKey) < 0) { 105 | return left; 106 | } 107 | return this; 108 | } 109 | public boolean hasExternalLeft() { return false; } 110 | public boolean hasExternalRight() { return true; } 111 | } 112 | 113 | static final class MTallNode extends AbstractInternal { 114 | private static final long serialVersionUID = 20110212L; 115 | private Node left; 116 | private Node right; 117 | public MTallNode(int bit, Node left, Node right) { 118 | super(bit); 119 | this.left = left; 120 | this.right = right; 121 | } 122 | public Node left(Context ctx) { return left; } 123 | public Node right(Context ctx) { return right; } 124 | public Node setLeft(int diffBit, K key, V val, Context ctx) { 125 | this.left = left.insert(diffBit, key, val, ctx); 126 | return this; 127 | } 128 | public Node setRight(int diffBit, K key, V val, Context ctx) { 129 | this.right = right.insert(diffBit, key, val, ctx); 130 | return this; 131 | } 132 | protected Node removeLeft(K key, Context ctx, boolean force) { 133 | Node newLeft = left.remove(key, ctx, force); 134 | if(newLeft.isInternal()) { 135 | this.left = newLeft; 136 | return this; 137 | } else { 138 | return ctx.nf.mkShortLeft(bit(), newLeft.getKey(), newLeft.getValue(), right); 139 | } 140 | } 141 | protected Node removeRight(K key, Context ctx, boolean force) { 142 | Node newRight = right.remove(key, ctx, force); 143 | if(newRight.isInternal()) { 144 | this.right = newRight; 145 | return this; 146 | } else { 147 | return ctx.nf.mkShortRight(bit(), left, newRight.getKey(), newRight.getValue()); 148 | } 149 | } 150 | public boolean hasExternalLeft() { return false; } 151 | public boolean hasExternalRight() { return false; } 152 | } 153 | 154 | static final class MutableNodeFactory implements NodeFactory { 155 | private static final long serialVersionUID = 20110212L; 156 | public Node mkShortBoth(int diffBit, K lk, V lv, K rk, V rv) { 157 | return new ShortBothNode(diffBit, lk, lv, rk, rv); 158 | } 159 | public Node mkShortRight(int diffBit, Node left, K k, V v) { 160 | return new MShortRightNode(diffBit, left, k, v); 161 | } 162 | public Node mkShortLeft(int diffBit, K k, V v, Node right) { 163 | return new MShortLeftNode(diffBit, k, v, right); 164 | } 165 | public Node mkTall(int diffBit, Node left, Node right) { 166 | return new MTallNode(diffBit, left, right); 167 | } 168 | public Node mkLeaf(K key, V val) { 169 | return new LeafNode(key, val); 170 | } 171 | } 172 | 173 | private Node root; 174 | private int size = 0; 175 | 176 | public MCritBitTree(KeyAnalyzer analyzer) { 177 | this(null, 178 | new Context(analyzer, new MutableNodeFactory())); 179 | } 180 | 181 | private MCritBitTree(Node root, Context ctx) { 182 | super(ctx); 183 | this.root = root; 184 | } 185 | 186 | Node root() { return root; } 187 | public int size() { return size; } 188 | 189 | public V put(K key, V val) { 190 | if(root == null) { 191 | root = ctx().nf.mkLeaf(key, val); 192 | size++; 193 | return null; 194 | } 195 | if(!root.isInternal()) { 196 | int diffBit = ctx().chk.bitIndex(key, root.getKey()); 197 | V oldVal = root.getValue(); 198 | root = root.insert(diffBit, key, val, ctx()); 199 | if(diffBit >= 0) { 200 | size++; 201 | return null; 202 | } else { 203 | return oldVal; 204 | } 205 | } 206 | 207 | final SearchResult sr = search(root, key); 208 | final int diffBit = ctx().chk.bitIndex(key, sr.key(ctx())); 209 | final V out; 210 | if(diffBit >= 0) { 211 | out = null; 212 | size++; 213 | } else { 214 | out = sr.value(ctx()); 215 | } 216 | 217 | if(sr.parent == null) { 218 | root = root.insert(diffBit, key, val, ctx()); 219 | return out; 220 | } else if(diffBit < 0 || diffBit >= sr.parent.bit()) { 221 | switch(sr.pDirection) { 222 | case LEFT: 223 | sr.parent.setLeft(diffBit, key, val, ctx()); 224 | return out; 225 | case RIGHT: 226 | sr.parent.setRight(diffBit, key, val, ctx()); 227 | return out; 228 | } 229 | } 230 | 231 | if(diffBit < root.bit()) { 232 | root = root.insert(diffBit, key, val, ctx()); 233 | return out; 234 | } 235 | 236 | Node prev = root; 237 | Node current = prev.nextNode(key, ctx()); 238 | for(;;) { 239 | if(diffBit < current.bit()) { 240 | if(ctx().chk.isBitSet(key, prev.bit())) { 241 | prev.setRight(diffBit, key, val, ctx()); 242 | } else { 243 | prev.setLeft(diffBit, key, val, ctx()); 244 | } 245 | return out; 246 | } else { 247 | prev = current; 248 | current = current.nextNode(key, ctx()); 249 | } 250 | } 251 | } 252 | 253 | public void putAll(Map otherMap) { 254 | for(Map.Entry me: otherMap.entrySet()) { 255 | put(me.getKey(), me.getValue()); 256 | } 257 | } 258 | 259 | public V remove(Object k) { 260 | if(root == null) { 261 | return null; 262 | } 263 | final K key = AbstractCritBitTree.cast(k); 264 | if(!root.isInternal()) { 265 | if(ctx().chk.bitIndex(key, root.getKey()) < 0) { 266 | V out = root.getValue(); 267 | root = null; 268 | size--; 269 | return out; 270 | } else { 271 | return null; 272 | } 273 | } 274 | 275 | Node grandparent = null; 276 | Node parent = null; 277 | Node cur = root; 278 | for(;;) { 279 | switch(cur.next(key, ctx())) { 280 | case LEFT: 281 | if(cur.hasExternalLeft()) { 282 | Node leftNode = cur.left(ctx()); 283 | if(ctx().chk.bitIndex(key, leftNode.getKey()) < 0) { 284 | if(grandparent == null) { 285 | root = root.remove(key, ctx(), true); 286 | } else { 287 | grandparent.remove(key, ctx(), true); 288 | } 289 | size--; 290 | return leftNode.getValue(); 291 | } else { 292 | return null; 293 | } 294 | } 295 | grandparent = parent; 296 | parent = cur; 297 | cur = cur.left(ctx()); 298 | break; 299 | case RIGHT: 300 | if(cur.hasExternalRight()) { 301 | Node rightNode = cur.right(ctx()); 302 | if(ctx().chk.bitIndex(key, rightNode.getKey()) < 0) { 303 | if(grandparent == null) { 304 | root = root.remove(key, ctx(), true); 305 | } else { 306 | grandparent.remove(key, ctx(), true); 307 | } 308 | size--; 309 | return rightNode.getValue(); 310 | } else { 311 | return null; 312 | } 313 | } 314 | grandparent = parent; 315 | parent = cur; 316 | cur = cur.right(ctx()); 317 | break; 318 | } 319 | } 320 | } 321 | 322 | public void clear() { 323 | this.root = null; 324 | this.size = 0; 325 | } 326 | 327 | protected final Decision doTraverse(Node top, 328 | Cursor cursor) { 329 | if(top.isInternal()) { 330 | Decision d = doTraverse(top.left(ctx()), cursor); 331 | switch(d) { 332 | case REMOVE_AND_EXIT: //fall through 333 | case EXIT: 334 | return Decision.EXIT; 335 | case REMOVE: //fall through 336 | case CONTINUE: 337 | default: 338 | return doTraverse(top.right(ctx()), cursor); 339 | } 340 | } else { 341 | Map.Entry e = AbstractCritBitTree.>cast(top); 342 | return cursor.select(e); 343 | } 344 | } 345 | 346 | @Override 347 | public Set> entrySet() { 348 | return new MCritBitEntrySet(this); 349 | } 350 | 351 | @Override 352 | public Set keySet() { 353 | return new MCritBitKeySet(this); 354 | } 355 | 356 | @Override 357 | public Collection values() { 358 | return new MCritBitValueSet(this); 359 | } 360 | 361 | public Iterator> iterator() { 362 | return new NodeIterator(this); 363 | } 364 | 365 | protected static class NodeIterator implements Iterator> { 366 | //TODO: initialize list to max depth of tree 367 | private final MCritBitTree tree; 368 | private final List> stack = new ArrayList>(); 369 | private Node next = null; 370 | 371 | public NodeIterator(MCritBitTree tree) { 372 | this.tree = tree; 373 | primeStack(); 374 | } 375 | 376 | private void primeStack() { 377 | Context ctx = tree.ctx(); 378 | if(tree.isEmpty()) { 379 | return; 380 | } 381 | Node curr = tree.root(); 382 | while(curr.isInternal()) { 383 | push(curr); 384 | curr = curr.left(ctx); 385 | } 386 | next = curr; 387 | } 388 | 389 | private final void push(final Node node) { 390 | stack.add(node); 391 | } 392 | 393 | private final Node pop() { 394 | return stack.remove(stack.size() - 1); 395 | } 396 | 397 | @Override 398 | public boolean hasNext() { 399 | if(next != null) { 400 | return true; 401 | } 402 | if(stack.isEmpty()) { 403 | return false; 404 | } 405 | 406 | final Context ctx = tree.ctx(); 407 | Node curr = pop().right(ctx); 408 | while(curr.isInternal()) { 409 | push(curr); 410 | curr = curr.left(ctx); 411 | } 412 | next = curr; 413 | return next != null; 414 | } 415 | 416 | @Override 417 | public Node next() { 418 | if(hasNext()) { 419 | final Node out = next; 420 | next = null; 421 | return out; 422 | } 423 | throw new NoSuchElementException(); 424 | } 425 | 426 | @Override 427 | public void remove() { 428 | throw new UnsupportedOperationException(); 429 | } 430 | } 431 | 432 | private static class MCritBitEntrySet extends AbstractSet> { 433 | private final MCritBitTree tree; 434 | public MCritBitEntrySet(MCritBitTree tree) { 435 | super(); 436 | this.tree = tree; 437 | } 438 | @Override 439 | public Iterator> iterator() { 440 | return new EntryIterator(tree.iterator()); 441 | } 442 | @Override 443 | public int size() { return tree.size(); } 444 | private static class EntryIterator implements Iterator> { 445 | private final Iterator> base; 446 | public EntryIterator(Iterator> base) { 447 | this.base = base; 448 | } 449 | @Override 450 | public Entry next() { 451 | return AbstractCritBitTree.>cast(base.next()); 452 | } 453 | @Override public boolean hasNext() { return base.hasNext(); } 454 | @Override public void remove() { base.remove(); } 455 | } 456 | } 457 | 458 | private static class MCritBitKeySet extends AbstractSet { 459 | private final MCritBitTree tree; 460 | public MCritBitKeySet(MCritBitTree tree) { 461 | super(); 462 | this.tree = tree; 463 | } 464 | @Override 465 | public Iterator iterator() { 466 | return new KeyIterator(tree.iterator()); 467 | } 468 | @Override 469 | public int size() { return tree.size(); } 470 | private static class KeyIterator implements Iterator { 471 | private final Iterator> base; 472 | public KeyIterator(Iterator> base) { 473 | this.base = base; 474 | } 475 | @Override 476 | public K next() { 477 | return base.next().getKey(); 478 | } 479 | @Override public boolean hasNext() { return base.hasNext(); } 480 | @Override public void remove() { base.remove(); } 481 | } 482 | } 483 | 484 | private static class MCritBitValueSet extends AbstractSet { 485 | private final MCritBitTree tree; 486 | public MCritBitValueSet(MCritBitTree tree) { 487 | super(); 488 | this.tree = tree; 489 | } 490 | @Override 491 | public Iterator iterator() { 492 | return new ValueIterator(tree.iterator()); 493 | } 494 | @Override 495 | public int size() { return tree.size(); } 496 | private static class ValueIterator implements Iterator { 497 | private final Iterator> base; 498 | public ValueIterator(Iterator> base) { 499 | this.base = base; 500 | } 501 | @Override 502 | public V next() { 503 | return base.next().getValue(); 504 | } 505 | @Override public boolean hasNext() { return base.hasNext(); } 506 | @Override public void remove() { base.remove(); } 507 | } 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/sharedbytearray/AbstractSBA.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit.sharedbytearray; 2 | 3 | public abstract class AbstractSBA implements SharedByteArray { 4 | 5 | private void checkBoundsIncl(int index) { 6 | if(index < 0 || index >= length()) { 7 | throw new IndexOutOfBoundsException(); 8 | } 9 | } 10 | private void checkBoundsExcl(int index) { 11 | if(index < 0 || index > length()) { 12 | throw new IndexOutOfBoundsException(); 13 | } 14 | } 15 | 16 | public boolean equals(Object other) { 17 | if(this == other) { 18 | return true; 19 | } 20 | if(other != null && other instanceof SharedByteArray) { 21 | SharedByteArray sba = (SharedByteArray)other; 22 | if(length() != sba.length()) { 23 | return false; 24 | } 25 | for(int i=0; i effectiveLength) { 54 | return -1; 55 | } 56 | byte first = needle.byteAt(from); 57 | int maxIndex = length() - needle.length(); 58 | for (int i = from; i <= maxIndex; i++) { 59 | if (byteAt(i) != first) { 60 | while (++i <= maxIndex && byteAt(i) != first); 61 | } 62 | 63 | if (i <= maxIndex) { 64 | int j = i + 1; 65 | int k = 1; 66 | while(k < needle.length() && byteAt(j) == needle.byteAt(k)) { 67 | j++; k++; 68 | } 69 | if (k == needle.length()) { 70 | return i; 71 | } 72 | } 73 | } 74 | return -1; 75 | } 76 | 77 | public int lastIndexOf(byte needle, int to) { 78 | checkBoundsExcl(to); 79 | for (int i = to-1; i >= 0; i--) { 80 | if (byteAt(i) == needle) { 81 | return i; 82 | } 83 | } 84 | return -1; 85 | } 86 | public int lastIndexOf(byte[] needle, int to) { 87 | return lastIndexOf(new ThinSBA(needle), to); 88 | } 89 | public int lastIndexOf(SharedByteArray needle, int to) { 90 | checkBoundsExcl(to); 91 | if(needle.length() == 0 || needle.length() > to) { 92 | return -1; 93 | } 94 | byte first = needle.byteAt(0); 95 | int maxIndex = to - needle.length(); 96 | for (int i = maxIndex; i >= 0; i--) { 97 | if (byteAt(i) != first) { 98 | while (--i >= 0 && byteAt(i) != first); 99 | } 100 | 101 | if (i >= 0) { 102 | int j = i + 1; 103 | int k = 1; 104 | while(k < needle.length() && byteAt(j) == needle.byteAt(k)) { 105 | j++; k++; 106 | } 107 | if (k == needle.length()) { 108 | return i; 109 | } 110 | } 111 | } 112 | return -1; 113 | } 114 | 115 | 116 | 117 | public SharedByteArray prefix(int end) { 118 | return sub(0, end); 119 | } 120 | 121 | public SharedByteArray suffix(int start) { 122 | return sub(start, length()); 123 | } 124 | 125 | public SharedByteArray append(SharedByteArray sba) { 126 | return new JoinedSBA(this, sba); 127 | } 128 | 129 | public byte[] toByteArray() { 130 | byte[] out = new byte[length()]; 131 | toByteArray(0, out, 0, out.length); 132 | return out; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/sharedbytearray/EmptySBA.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit.sharedbytearray; 2 | 3 | final class EmptySBA implements SharedByteArray { 4 | 5 | public static EmptySBA INSTANCE = new EmptySBA(); 6 | private static final byte[] EMPTY_ARRAY = new byte[0]; 7 | 8 | private EmptySBA() {} 9 | 10 | public int length() { 11 | return 0; 12 | } 13 | 14 | public byte byteAt(int index) { 15 | throw new IndexOutOfBoundsException(); 16 | } 17 | 18 | private void ensureZero(int i) { 19 | if(i != 0) { 20 | throw new IndexOutOfBoundsException(); 21 | } 22 | } 23 | 24 | public int indexOf(byte needle, int from) { 25 | ensureZero(from); 26 | return -1; 27 | } 28 | 29 | public int indexOf(byte[] needle, int from) { 30 | ensureZero(from); 31 | if(needle.length == 0) { 32 | return 0; 33 | } 34 | return -1; 35 | } 36 | 37 | public int indexOf(SharedByteArray needle, int from) { 38 | ensureZero(from); 39 | if(needle.length() == 0) { 40 | return 0; 41 | } 42 | return -1; 43 | } 44 | 45 | public int lastIndexOf(byte needle, int to) { 46 | ensureZero(to); 47 | return -1; 48 | } 49 | 50 | public int lastIndexOf(byte[] needle, int to) { 51 | ensureZero(to); 52 | if(needle.length == 0) { 53 | return 0; 54 | } 55 | return -1; 56 | } 57 | 58 | public int lastIndexOf(SharedByteArray needle, int to) { 59 | ensureZero(to); 60 | if(needle.length() == 0) { 61 | return 0; 62 | } 63 | return -1; 64 | } 65 | 66 | public SharedByteArray prefix(int end) { 67 | ensureZero(end); 68 | return this; 69 | } 70 | 71 | public SharedByteArray suffix(int start) { 72 | ensureZero(start); 73 | return this; 74 | } 75 | 76 | public SharedByteArray sub(int start, int end) { 77 | ensureZero(start); 78 | ensureZero(end); 79 | return this; 80 | } 81 | 82 | public SharedByteArray append(SharedByteArray sba) { 83 | return sba; 84 | } 85 | 86 | public byte[] toByteArray() { 87 | return EMPTY_ARRAY; 88 | } 89 | 90 | public void toByteArray(int from, byte[] target, int targetStart, int len) { 91 | if(from != 0 || len != 0 || targetStart < 0 || targetStart > target.length) { 92 | throw new IndexOutOfBoundsException(); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/sharedbytearray/JoinedSBA.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit.sharedbytearray; 2 | 3 | final class JoinedSBA extends AbstractSBA { 4 | private final SharedByteArray head; 5 | private final SharedByteArray tail; 6 | 7 | public JoinedSBA(SharedByteArray head, SharedByteArray tail) { 8 | this.head = head; 9 | this.tail = tail; 10 | } 11 | 12 | public int length() { 13 | return head.length() + tail.length(); 14 | } 15 | 16 | public byte byteAt(int index) { 17 | if(index < 0 || index >= length()) { 18 | throw new IndexOutOfBoundsException(); 19 | } 20 | 21 | if(index < head.length()) { 22 | return head.byteAt(index); 23 | } else { 24 | return tail.byteAt(index - head.length()); 25 | } 26 | } 27 | 28 | public SharedByteArray sub(int start, int end) { 29 | if(start < 0 || end < start || end > length()) { 30 | throw new IndexOutOfBoundsException(); 31 | } 32 | if(start == end) { 33 | return EmptySBA.INSTANCE; 34 | } else if(start == 0 && end == length()) { 35 | return this; 36 | } else if(start == 0 && end == head.length()) { 37 | return head; 38 | } else if(start == head.length() && end == length()) { 39 | return tail; 40 | } else if(end < head.length()) { 41 | return head.sub(start, end); 42 | } else if(start >= head.length()) { 43 | return tail.sub(start-head.length(), end-head.length()); 44 | } else { 45 | return new JoinedSBA(head.suffix(start), 46 | tail.prefix(end-head.length())); 47 | } 48 | } 49 | 50 | public void toByteArray(int start, byte[] target, int targetStart, int len) { 51 | int end = start+len; 52 | if(start < 0 || end < start || end > length()) { 53 | throw new IndexOutOfBoundsException(); 54 | } 55 | if(start == end) { 56 | //do nothing 57 | } else if(end < head.length()) { 58 | head.toByteArray(start, target, targetStart, len); 59 | } else if(start >= head.length()) { 60 | tail.toByteArray(start-head.length(), target, targetStart, len); 61 | } else { 62 | int headlen = head.length() - start; 63 | head.toByteArray(start, target, targetStart, headlen); 64 | tail.toByteArray(0, target, targetStart+headlen, len - headlen); 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/sharedbytearray/PrefixSBA.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit.sharedbytearray; 2 | 3 | final class PrefixSBA extends AbstractSBA { 4 | 5 | private final byte[] bytes; 6 | private final int sharedEnd; 7 | 8 | public PrefixSBA(byte[] bytes, int end) { 9 | this.bytes = bytes; 10 | this.sharedEnd = end; 11 | } 12 | 13 | public int length() { 14 | return sharedEnd; 15 | } 16 | 17 | public byte byteAt(int index) { 18 | if(index < 0 || index >= sharedEnd) { 19 | throw new IndexOutOfBoundsException(); 20 | } 21 | return bytes[index]; 22 | } 23 | 24 | public SharedByteArray sub(int start, int end) { 25 | if(start < 0 || end < start || end > length()) { 26 | throw new IndexOutOfBoundsException(); 27 | } 28 | if(start == end) { 29 | return EmptySBA.INSTANCE; 30 | } else if(start == 0 && end == length()) { 31 | return this; 32 | } else if(start == 0) { 33 | return new PrefixSBA(bytes, end); 34 | } else { 35 | return new ThickSBA(bytes, start, end); 36 | } 37 | } 38 | 39 | public void toByteArray(int start, byte[] target, int targetStart, int len) { 40 | if(start + len > sharedEnd) { 41 | throw new IndexOutOfBoundsException(); 42 | } 43 | System.arraycopy(bytes, start, target, targetStart, len); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/sharedbytearray/SBAKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit.sharedbytearray; 2 | 3 | import java.io.Serializable; 4 | 5 | import org.ardverk.collection.AbstractKeyAnalyzer; 6 | import org.ardverk.collection.KeyAnalyzer; 7 | 8 | public class SBAKeyAnalyzer 9 | extends AbstractKeyAnalyzer 10 | implements Serializable 11 | { 12 | public static final SBAKeyAnalyzer INSTANCE = new SBAKeyAnalyzer(); 13 | 14 | private static final long serialVersionUID = 20110307L; 15 | 16 | private static final int MSB = 1 << Byte.SIZE-1; 17 | 18 | @Override 19 | public int compare(SharedByteArray o1, SharedByteArray o2) { 20 | if (o1 == null) { 21 | return (o2 == null) ? 0 : -1; 22 | } else if (o2 == null) { 23 | return (o1 == null) ? 0 : 1; 24 | } 25 | 26 | if (o1.length() != o2.length()) { 27 | return o1.length() - o2.length(); 28 | } 29 | 30 | for (int i = 0; i < o1.length(); i++) { 31 | int diff = (o1.byteAt(i) & 0xFF) - (o2.byteAt(i) & 0xFF); 32 | if (diff != 0) { 33 | return diff; 34 | } 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | @Override 41 | public int lengthInBits(SharedByteArray key) { 42 | return key.length() * Byte.SIZE; 43 | } 44 | 45 | @Override 46 | public boolean isPrefix(SharedByteArray key, SharedByteArray prefix) { 47 | if (key.length() < prefix.length()) { 48 | return false; 49 | } 50 | 51 | for (int i = 0; i < prefix.length(); i++) { 52 | if (key.byteAt(i) != prefix.byteAt(i)) { 53 | return false; 54 | } 55 | } 56 | 57 | return true; 58 | } 59 | 60 | /** 61 | * Returns a bit mask where the given bit is set 62 | */ 63 | private static int mask(int bit) { 64 | return MSB >>> bit; 65 | } 66 | 67 | /** 68 | * Returns the {@code byte} value at the given index. 69 | */ 70 | private static byte valueAt(SharedByteArray values, int index) { 71 | if (index >= 0 && index < values.length()) { 72 | return values.byteAt(index); 73 | } 74 | return 0; 75 | } 76 | 77 | @Override 78 | public boolean isBitSet(SharedByteArray key, int bitIndex) { 79 | if (bitIndex >= lengthInBits(key)) { 80 | return false; 81 | } 82 | 83 | int index = (int)(bitIndex / Byte.SIZE); 84 | int bit = (int)(bitIndex % Byte.SIZE); 85 | return (key.byteAt(index) & mask(bit)) != 0; 86 | } 87 | 88 | @Override 89 | public int bitIndex(SharedByteArray key, SharedByteArray otherKey) { 90 | int length = Math.max(key.length(), otherKey.length()); 91 | 92 | boolean allNull = true; 93 | for (int i = 0; i < length; i++) { 94 | byte b1 = valueAt(key, i); 95 | byte b2 = valueAt(otherKey, i); 96 | 97 | if (b1 != b2) { 98 | int xor = b1 ^ b2; 99 | for (int j = 0; j < Byte.SIZE; j++) { 100 | if ((xor & mask(j)) != 0) { 101 | return (i * Byte.SIZE) + j; 102 | } 103 | } 104 | } 105 | 106 | if (b1 != 0) { 107 | allNull = false; 108 | } 109 | } 110 | 111 | if (allNull) { 112 | return KeyAnalyzer.NULL_BIT_KEY; 113 | } 114 | 115 | return KeyAnalyzer.EQUAL_BIT_KEY; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/sharedbytearray/SharedByteArray.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit.sharedbytearray; 2 | 3 | public interface SharedByteArray { 4 | int length(); 5 | byte byteAt(int index); 6 | int indexOf(byte needle, int from); 7 | int indexOf(byte[] needle, int from); 8 | int indexOf(SharedByteArray needle, int from); 9 | int lastIndexOf(byte needle, int to); 10 | int lastIndexOf(byte[] needle, int to); 11 | int lastIndexOf(SharedByteArray needle, int to); 12 | SharedByteArray prefix(int end); 13 | SharedByteArray suffix(int start); 14 | SharedByteArray sub(int start, int end); 15 | SharedByteArray append(SharedByteArray sba); 16 | byte[] toByteArray(); 17 | void toByteArray(int from, byte[] target, int targetStart, int len); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/sharedbytearray/SuffixSBA.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit.sharedbytearray; 2 | 3 | final class SuffixSBA extends AbstractSBA { 4 | 5 | private final byte[] bytes; 6 | private final int sharedStart; 7 | 8 | public SuffixSBA(byte[] bytes, int start) { 9 | this.bytes = bytes; 10 | this.sharedStart = start; 11 | } 12 | 13 | public int length() { 14 | return bytes.length - sharedStart; 15 | } 16 | 17 | public byte byteAt(int index) { 18 | if(index < 0) { 19 | throw new IndexOutOfBoundsException(); 20 | } 21 | return bytes[sharedStart + index]; 22 | } 23 | 24 | public SharedByteArray sub(int start, int end) { 25 | if(start < 0 || end < start || end > length()) { 26 | throw new IndexOutOfBoundsException(); 27 | } 28 | if(start == end) { 29 | return EmptySBA.INSTANCE; 30 | } else if(start == 0 && end == length()) { 31 | return this; 32 | } else if(end == length()) { 33 | return new SuffixSBA(bytes, sharedStart + start); 34 | } else { 35 | return new ThickSBA(bytes, sharedStart + start, sharedStart + end); 36 | } 37 | } 38 | 39 | public void toByteArray(int start, byte[] target, int targetStart, int len) { 40 | if(start < 0) { 41 | throw new IndexOutOfBoundsException(); 42 | } 43 | System.arraycopy(bytes, sharedStart + start, target, targetStart, len); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/sharedbytearray/ThickSBA.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit.sharedbytearray; 2 | 3 | final class ThickSBA extends AbstractSBA { 4 | private final byte[] bytes; 5 | private final int sharedStart; 6 | private final int sharedEnd; 7 | 8 | public ThickSBA(byte[] bytes, int start, int end) { 9 | this.bytes = bytes; 10 | this.sharedStart = start; 11 | this.sharedEnd = end; 12 | } 13 | 14 | public int length() { 15 | return sharedEnd - sharedStart; 16 | } 17 | 18 | public byte byteAt(int index) { 19 | if(index < 0 || index >= length()) { 20 | throw new IndexOutOfBoundsException(); 21 | } 22 | return bytes[sharedStart + index]; 23 | } 24 | 25 | public SharedByteArray sub(int start, int end) { 26 | if(start < 0 || end < start || end > length()) { 27 | throw new IndexOutOfBoundsException(); 28 | } 29 | if(start == end) { 30 | return EmptySBA.INSTANCE; 31 | } else if(start == 0 && end == length()) { 32 | return this; 33 | } else { 34 | return new ThickSBA(bytes, sharedStart + start, sharedStart + end); 35 | } 36 | } 37 | 38 | public void toByteArray(int start, byte[] target, int targetStart, int len) { 39 | if(start < 0 || start+len > length()) { 40 | throw new IndexOutOfBoundsException(); 41 | } 42 | System.arraycopy(bytes, sharedStart + start, target, targetStart, len); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/prelink/critbit/sharedbytearray/ThinSBA.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit.sharedbytearray; 2 | 3 | public final class ThinSBA extends AbstractSBA { 4 | 5 | private final byte[] bytes; 6 | 7 | public ThinSBA(byte[] bytes) { 8 | this.bytes = bytes; 9 | } 10 | 11 | public int length() { 12 | return bytes.length; 13 | } 14 | 15 | public byte byteAt(int index) { 16 | return bytes[index]; 17 | } 18 | 19 | public SharedByteArray sub(int start, int end) { 20 | if(start < 0 || end < start || end > bytes.length) { 21 | throw new IndexOutOfBoundsException(); 22 | } 23 | if(start == end) { 24 | return EmptySBA.INSTANCE; 25 | } else if(start == 0 && end == length()) { 26 | return this; 27 | } else if(start == 0) { 28 | return new PrefixSBA(bytes, end); 29 | } else if(end == length()) { 30 | return new SuffixSBA(bytes, start); 31 | } else { 32 | return new ThickSBA(bytes, start, end); 33 | } 34 | } 35 | 36 | public void toByteArray(int start, byte[] target, int targetStart, int len) { 37 | System.arraycopy(bytes, start, target, targetStart, len); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/AbstractKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2010 Roger Kapsi, Sam Berlin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | /** 20 | * An abstract implementation of {@link KeyAnalyzer}. 21 | */ 22 | public abstract class AbstractKeyAnalyzer implements KeyAnalyzer { 23 | 24 | @SuppressWarnings("unchecked") 25 | @Override 26 | public int compare(K o1, K o2) { 27 | return ((Comparable)o1).compareTo(o2); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/ByteArrayKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2010 Roger Kapsi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.io.Serializable; 20 | 21 | /** 22 | * A {@link KeyAnalyzer} for {@code byte[]}s 23 | */ 24 | public class ByteArrayKeyAnalyzer extends AbstractKeyAnalyzer 25 | implements Serializable { 26 | 27 | private static final long serialVersionUID = -3642547492709094018L; 28 | 29 | private static final int DEFAULT_LENGTH = Integer.MAX_VALUE / Byte.SIZE; 30 | 31 | public static final ByteArrayKeyAnalyzer INSTANCE = new ByteArrayKeyAnalyzer(); 32 | 33 | /** 34 | * A bit mask where the first bit is 1 and the others are zero 35 | */ 36 | private static final int MSB = 1 << Byte.SIZE-1; 37 | 38 | private final int maxLengthInBits; 39 | 40 | public ByteArrayKeyAnalyzer() { 41 | this(DEFAULT_LENGTH); 42 | } 43 | 44 | public ByteArrayKeyAnalyzer(int maxLengthInBits) { 45 | if (maxLengthInBits < 0 || DEFAULT_LENGTH < maxLengthInBits) { 46 | throw new IllegalArgumentException( 47 | "maxLengthInBits=" + maxLengthInBits); 48 | } 49 | 50 | this.maxLengthInBits = maxLengthInBits; 51 | } 52 | 53 | public int getMaxLengthInBits() { 54 | return maxLengthInBits; 55 | } 56 | 57 | @Override 58 | public int compare(byte[] o1, byte[] o2) { 59 | if (o1 == null) { 60 | return (o2 == null) ? 0 : -1; 61 | } else if (o2 == null) { 62 | return (o1 == null) ? 0 : 1; 63 | } 64 | 65 | if (o1.length != o2.length) { 66 | return o1.length - o2.length; 67 | } 68 | 69 | for (int i = 0; i < o1.length; i++) { 70 | int diff = (o1[i] & 0xFF) - (o2[i] & 0xFF); 71 | if (diff != 0) { 72 | return diff; 73 | } 74 | } 75 | 76 | return 0; 77 | } 78 | 79 | @Override 80 | public int lengthInBits(byte[] key) { 81 | return key.length * Byte.SIZE; 82 | } 83 | 84 | @Override 85 | public boolean isBitSet(byte[] key, int bitIndex) { 86 | int lengthInBits = lengthInBits(key); 87 | int prefix = maxLengthInBits - lengthInBits; 88 | int keyBitIndex = bitIndex - prefix; 89 | 90 | if (keyBitIndex >= lengthInBits || keyBitIndex < 0) { 91 | return false; 92 | } 93 | 94 | int index = (int)(keyBitIndex / Byte.SIZE); 95 | int bit = (int)(keyBitIndex % Byte.SIZE); 96 | return (key[index] & mask(bit)) != 0; 97 | } 98 | 99 | @Override 100 | public int bitIndex(byte[] key, byte[] otherKey) { 101 | 102 | int length1 = lengthInBits(key); 103 | int length2 = lengthInBits(otherKey); 104 | int length = Math.max(length1, length2); 105 | int prefix = maxLengthInBits - length; 106 | 107 | if (prefix < 0) { 108 | return KeyAnalyzer.OUT_OF_BOUNDS_BIT_KEY; 109 | } 110 | 111 | boolean allNull = true; 112 | for (int i = 0; i < length; i++) { 113 | int bitIndex = prefix + i; 114 | boolean value = isBitSet(key, bitIndex); 115 | 116 | if (value) { 117 | allNull = false; 118 | } 119 | 120 | boolean otherValue = isBitSet(otherKey, bitIndex); 121 | 122 | if (value != otherValue) { 123 | return bitIndex; 124 | } 125 | } 126 | 127 | if (allNull) { 128 | return KeyAnalyzer.NULL_BIT_KEY; 129 | } 130 | 131 | return KeyAnalyzer.EQUAL_BIT_KEY; 132 | } 133 | 134 | @Override 135 | public boolean isPrefix(byte[] key, byte[] prefix) { 136 | if (key.length < prefix.length) { 137 | return false; 138 | } 139 | 140 | for (int i = 0; i < prefix.length; i++) { 141 | if (key[i] != prefix[i]) { 142 | return false; 143 | } 144 | } 145 | 146 | return true; 147 | } 148 | 149 | /** 150 | * Returns a bit mask where the given bit is set 151 | */ 152 | private static int mask(int bit) { 153 | return MSB >>> bit; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/ByteKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2010 Roger Kapsi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.io.Serializable; 20 | 21 | 22 | /** 23 | * A {@link KeyAnalyzer} for {@link Byte}s 24 | */ 25 | public class ByteKeyAnalyzer extends AbstractKeyAnalyzer implements Serializable { 26 | 27 | private static final long serialVersionUID = -5294514513354687850L; 28 | 29 | /** 30 | * A singleton instance of {@link ByteKeyAnalyzer} 31 | */ 32 | public static final ByteKeyAnalyzer INSTANCE = new ByteKeyAnalyzer(); 33 | 34 | /** 35 | * A bit mask where the first bit is 1 and the others are zero 36 | */ 37 | private static final int MSB = 1 << Byte.SIZE-1; 38 | 39 | /** 40 | * Returns a bit mask where the given bit is set 41 | */ 42 | private static int mask(int bit) { 43 | return MSB >>> bit; 44 | } 45 | 46 | @Override 47 | public int lengthInBits(Byte key) { 48 | return Byte.SIZE; 49 | } 50 | 51 | @Override 52 | public boolean isBitSet(Byte key, int bitIndex) { 53 | return (key & mask(bitIndex)) != 0; 54 | } 55 | 56 | @Override 57 | public int bitIndex(Byte key, Byte otherKey) { 58 | byte keyValue = key.byteValue(); 59 | if (keyValue == 0) { 60 | return NULL_BIT_KEY; 61 | } 62 | 63 | byte otherValue = otherKey.byteValue(); 64 | 65 | if (keyValue != otherValue) { 66 | int xorValue = keyValue ^ otherValue; 67 | for (int i = 0; i < Byte.SIZE; i++) { 68 | if ((xorValue & mask(i)) != 0) { 69 | return i; 70 | } 71 | } 72 | } 73 | 74 | return KeyAnalyzer.EQUAL_BIT_KEY; 75 | } 76 | 77 | @Override 78 | public boolean isPrefix(Byte key, Byte prefix) { 79 | return key.equals(prefix); 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/CharArrayKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2010 Roger Kapsi, Sam Berlin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.io.Serializable; 20 | 21 | /** 22 | * An {@link KeyAnalyzer} for {@code char[]}s 23 | */ 24 | public class CharArrayKeyAnalyzer extends AbstractKeyAnalyzer implements Serializable { 25 | 26 | private static final long serialVersionUID = -253675854844425270L; 27 | 28 | private static final int DEFAULT_LENGTH = Integer.MAX_VALUE / Character.SIZE; 29 | 30 | /** 31 | * A singleton instance of {@link CharArrayKeyAnalyzer} 32 | */ 33 | public static final CharArrayKeyAnalyzer INSTANCE = new CharArrayKeyAnalyzer(); 34 | 35 | /** 36 | * A bit mask where the first bit is 1 and the others are zero 37 | */ 38 | private static final int MSB = 0x8000; 39 | 40 | private final int maxLengthInBits; 41 | 42 | public CharArrayKeyAnalyzer() { 43 | this(DEFAULT_LENGTH); 44 | } 45 | 46 | public CharArrayKeyAnalyzer(int maxLengthInBits) { 47 | if (maxLengthInBits < 0 || DEFAULT_LENGTH < maxLengthInBits) { 48 | throw new IllegalArgumentException( 49 | "maxLengthInBits=" + maxLengthInBits); 50 | } 51 | 52 | this.maxLengthInBits = maxLengthInBits; 53 | } 54 | 55 | public int getMaxLengthInBits() { 56 | return maxLengthInBits; 57 | } 58 | 59 | @Override 60 | public int compare(char[] o1, char[] o2) { 61 | if (o1 == null) { 62 | return (o2 == null) ? 0 : -1; 63 | } else if (o2 == null) { 64 | return (o1 == null) ? 0 : 1; 65 | } 66 | 67 | if (o1.length != o2.length) { 68 | return o1.length - o2.length; 69 | } 70 | 71 | for (int i = 0; i < o1.length; i++) { 72 | int diff = (o1[i] & 0xFF) - (o2[i] & 0xFF); 73 | if (diff != 0) { 74 | return diff; 75 | } 76 | } 77 | 78 | return 0; 79 | } 80 | 81 | @Override 82 | public int lengthInBits(char[] key) { 83 | return key.length * Character.SIZE; 84 | } 85 | 86 | @Override 87 | public boolean isBitSet(char[] key, int bitIndex) { 88 | int lengthInBits = lengthInBits(key); 89 | int prefix = maxLengthInBits - lengthInBits; 90 | int keyBitIndex = bitIndex - prefix; 91 | 92 | if (keyBitIndex >= lengthInBits || keyBitIndex < 0) { 93 | return false; 94 | } 95 | 96 | int index = (int)(keyBitIndex / Character.SIZE); 97 | int bit = (int)(keyBitIndex % Character.SIZE); 98 | return (key[index] & mask(bit)) != 0; 99 | } 100 | 101 | @Override 102 | public int bitIndex(char[] key, char[] otherKey) { 103 | 104 | int length1 = lengthInBits(key); 105 | int length2 = lengthInBits(otherKey); 106 | int length = Math.max(length1, length2); 107 | int prefix = maxLengthInBits - length; 108 | 109 | if (prefix < 0) { 110 | return KeyAnalyzer.OUT_OF_BOUNDS_BIT_KEY; 111 | } 112 | 113 | boolean allNull = true; 114 | for (int i = 0; i < length; i++) { 115 | int bitIndex = prefix + i; 116 | boolean value = isBitSet(key, bitIndex); 117 | 118 | if (value) { 119 | allNull = false; 120 | } 121 | 122 | boolean otherValue = isBitSet(otherKey, bitIndex); 123 | 124 | if (value != otherValue) { 125 | return bitIndex; 126 | } 127 | } 128 | 129 | if (allNull) { 130 | return KeyAnalyzer.NULL_BIT_KEY; 131 | } 132 | 133 | return KeyAnalyzer.EQUAL_BIT_KEY; 134 | } 135 | 136 | @Override 137 | public boolean isPrefix(char[] key, char[] prefix) { 138 | if (key.length < prefix.length) { 139 | return false; 140 | } 141 | 142 | for (int i = 0; i < prefix.length; i++) { 143 | if (key[i] != prefix[i]) { 144 | return false; 145 | } 146 | } 147 | 148 | return true; 149 | } 150 | 151 | /** 152 | * Returns a bit mask where the given bit is set 153 | */ 154 | private static int mask(int bit) { 155 | return MSB >>> bit; 156 | } 157 | } -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/CharacterKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2010 Roger Kapsi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.io.Serializable; 20 | 21 | /** 22 | * A {@link KeyAnalyzer} for {@link Character}s. 23 | */ 24 | public class CharacterKeyAnalyzer extends AbstractKeyAnalyzer implements Serializable { 25 | 26 | private static final long serialVersionUID = 7768219441533281842L; 27 | 28 | public static final CharacterKeyAnalyzer INSTANCE = new CharacterKeyAnalyzer(); 29 | 30 | /** 31 | * A bit mask where the first bit is 1 and the others are zero 32 | */ 33 | private static final int MSB = 1 << Character.SIZE-1; 34 | 35 | /** 36 | * Returns a bit mask where the given bit is set 37 | */ 38 | private static int mask(int bit) { 39 | return MSB >>> bit; 40 | } 41 | 42 | @Override 43 | public int lengthInBits(Character key) { 44 | return Character.SIZE; 45 | } 46 | 47 | @Override 48 | public boolean isBitSet(Character key, int bitIndex) { 49 | return (key & mask(bitIndex)) != 0; 50 | } 51 | 52 | @Override 53 | public int bitIndex(Character key, Character otherKey) { 54 | char keyValue = key.charValue(); 55 | if (keyValue == 0) { 56 | return NULL_BIT_KEY; 57 | } 58 | 59 | char otherValue = otherKey.charValue(); 60 | 61 | if (keyValue != otherValue) { 62 | int xorValue = keyValue ^ otherValue; 63 | for (int i = 0; i < Character.SIZE; i++) { 64 | if ((xorValue & mask(i)) != 0) { 65 | return i; 66 | } 67 | } 68 | } 69 | 70 | return KeyAnalyzer.EQUAL_BIT_KEY; 71 | } 72 | 73 | @Override 74 | public boolean isPrefix(Character key, Character prefix) { 75 | return key.equals(prefix); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/Cursor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2010 Roger Kapsi, Sam Berlin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.util.Map; 20 | import java.util.Map.Entry; 21 | 22 | /** 23 | * A {@link Cursor} can be used to traverse a {@link Trie}, visit each node 24 | * step by step and make {@link Decision}s on each step how to continue with 25 | * traversing the {@link Trie}. 26 | */ 27 | public interface Cursor { 28 | 29 | /** 30 | * The {@link Decision} tells the {@link Cursor} what to do on each step 31 | * while traversing the {@link Trie}. 32 | * 33 | * NOTE: Not all operations that work with a {@link Cursor} support all 34 | * {@link Decision} types 35 | */ 36 | public static enum Decision { 37 | 38 | /** 39 | * Exit the traverse operation 40 | */ 41 | EXIT, 42 | 43 | /** 44 | * Continue with the traverse operation 45 | */ 46 | CONTINUE, 47 | 48 | /** 49 | * Remove the previously returned element 50 | * from the {@link Trie} and continue 51 | */ 52 | REMOVE, 53 | 54 | /** 55 | * Remove the previously returned element 56 | * from the {@link Trie} and exit from the 57 | * traverse operation 58 | */ 59 | REMOVE_AND_EXIT; 60 | } 61 | 62 | /** 63 | * Called for each {@link Entry} in the {@link Trie}. Return 64 | * {@link Decision#EXIT} to finish the {@link Trie} operation, 65 | * {@link Decision#CONTINUE} to go to the next {@link Entry}, 66 | * {@link Decision#REMOVE} to remove the {@link Entry} and 67 | * continue iterating or {@link Decision#REMOVE_AND_EXIT} to 68 | * remove the {@link Entry} and stop iterating. 69 | * 70 | * Note: Not all operations support {@link Decision#REMOVE}. 71 | */ 72 | public Decision select(Map.Entry entry); 73 | } -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/DefaultKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Roger Kapsi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.io.Serializable; 20 | 21 | /** 22 | * An implementation of {@link KeyAnalyzer} that assumes all keys 23 | * have the {@link Key} interface implemented. 24 | */ 25 | public class DefaultKeyAnalyzer> 26 | extends AbstractKeyAnalyzer implements Serializable { 27 | 28 | private static final long serialVersionUID = 5340568481346940964L; 29 | 30 | @SuppressWarnings("rawtypes") 31 | private static final DefaultKeyAnalyzer INSTANCE = new DefaultKeyAnalyzer(); 32 | 33 | @SuppressWarnings("unchecked") 34 | public static KeyAnalyzer singleton() { 35 | return (KeyAnalyzer)INSTANCE; 36 | } 37 | 38 | @Override 39 | public int lengthInBits(K key) { 40 | return key.lengthInBits(); 41 | } 42 | 43 | @Override 44 | public boolean isBitSet(K key, int bitIndex) { 45 | return key.isBitSet(bitIndex); 46 | } 47 | 48 | @Override 49 | public int bitIndex(K key, K otherKey) { 50 | return key.bitIndex(otherKey); 51 | } 52 | 53 | @Override 54 | public boolean isPrefix(K key, K prefix) { 55 | return key.isPrefixedBy(prefix); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/IntegerKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2010 Roger Kapsi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.io.Serializable; 20 | 21 | /** 22 | * A {@link KeyAnalyzer} for {@link Integer}s. 23 | */ 24 | public class IntegerKeyAnalyzer extends AbstractKeyAnalyzer implements Serializable { 25 | 26 | private static final long serialVersionUID = 8805465126366464399L; 27 | 28 | public static final IntegerKeyAnalyzer INSTANCE = new IntegerKeyAnalyzer(); 29 | 30 | /** 31 | * A bit mask where the first bit is 1 and the others are zero 32 | */ 33 | private static final int MSB = 1 << Integer.SIZE-1; 34 | 35 | /** 36 | * Returns a bit mask where the given bit is set 37 | */ 38 | private static int mask(int bit) { 39 | return MSB >>> bit; 40 | } 41 | 42 | @Override 43 | public int lengthInBits(Integer key) { 44 | return Integer.SIZE; 45 | } 46 | 47 | @Override 48 | public boolean isBitSet(Integer key, int bitIndex) { 49 | return (key & mask(bitIndex)) != 0; 50 | } 51 | 52 | @Override 53 | public int bitIndex(Integer key, Integer otherKey) { 54 | int keyValue = key.intValue(); 55 | if (keyValue == 0) { 56 | return NULL_BIT_KEY; 57 | } 58 | 59 | int otherValue = otherKey.intValue(); 60 | 61 | if (keyValue != otherValue) { 62 | int xorValue = keyValue ^ otherValue; 63 | for (int i = 0; i < Integer.SIZE; i++) { 64 | if ((xorValue & mask(i)) != 0) { 65 | return i; 66 | } 67 | } 68 | } 69 | 70 | return KeyAnalyzer.EQUAL_BIT_KEY; 71 | } 72 | 73 | @Override 74 | public boolean isPrefix(Integer key, Integer prefix) { 75 | return key.equals(prefix); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/Key.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Roger Kapsi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | /** 20 | * An interface that {@link PatriciaTrie} keys may implement. 21 | * 22 | * @see KeyAnalyzer 23 | * @see DefaultKeyAnalyzer 24 | */ 25 | public interface Key { 26 | 27 | /** 28 | * Returns the key's length in bits. 29 | */ 30 | public int lengthInBits(); 31 | 32 | /** 33 | * Returns {@code true} if the given bit is set. 34 | */ 35 | public boolean isBitSet(int bitIndex); 36 | 37 | /** 38 | * Returns the index of the first bit that is different in the two keys. 39 | */ 40 | public int bitIndex(K otherKey); 41 | 42 | /** 43 | * Returns {@code true} if this key is prefixed by the given key. 44 | */ 45 | public boolean isPrefixedBy(K prefix); 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/KeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Roger Kapsi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.util.Comparator; 20 | 21 | /** 22 | * The {@link KeyAnalyzer} provides bit-level access to keys 23 | * for the {@link PatriciaTrie}. 24 | */ 25 | public interface KeyAnalyzer extends Comparator { 26 | 27 | /** 28 | * Returned by {@link #bitIndex(Object, Object)} if a key's 29 | * bits were all zero (0). 30 | */ 31 | public static final int NULL_BIT_KEY = -1; 32 | 33 | /** 34 | * Returned by {@link #bitIndex(Object, Object)} if a the 35 | * bits of two keys were all equal. 36 | */ 37 | public static final int EQUAL_BIT_KEY = -2; 38 | 39 | /** 40 | * Returned by {@link #bitIndex(Object, Object)} if a keys 41 | * indices are out of bounds. 42 | */ 43 | public static final int OUT_OF_BOUNDS_BIT_KEY = -3; 44 | 45 | /** 46 | * Returns the key's length in bits. 47 | */ 48 | public int lengthInBits(K key); 49 | 50 | /** 51 | * Returns {@code true} if a key's bit it set at the given index. 52 | */ 53 | public boolean isBitSet(K key, int bitIndex); 54 | 55 | /** 56 | * Returns the index of the first bit that is different in the two keys. 57 | */ 58 | public int bitIndex(K key, K otherKey); 59 | 60 | /** 61 | * Returns {@code true} if the second argument is a 62 | * prefix of the first argument. 63 | */ 64 | public boolean isPrefix(K key, K prefix); 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/LongKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2010 Roger Kapsi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.io.Serializable; 20 | 21 | /** 22 | * A {@link KeyAnalyzer} for {@link Long}s 23 | */ 24 | public class LongKeyAnalyzer extends AbstractKeyAnalyzer implements Serializable { 25 | 26 | private static final long serialVersionUID = -7611788114037795486L; 27 | 28 | /** 29 | * A singleton instance of {@link LongKeyAnalyzer} 30 | */ 31 | public static final LongKeyAnalyzer INSTANCE = new LongKeyAnalyzer(); 32 | 33 | /** 34 | * A bit mask where the first bit is 1 and the others are zero 35 | */ 36 | private static final long MSB = 1L << Long.SIZE-1; 37 | 38 | /** 39 | * Returns a bit mask where the given bit is set 40 | */ 41 | private static long mask(int bit) { 42 | return MSB >>> bit; 43 | } 44 | 45 | @Override 46 | public int lengthInBits(Long key) { 47 | return Long.SIZE; 48 | } 49 | 50 | @Override 51 | public boolean isBitSet(Long key, int bitIndex) { 52 | return (key & mask(bitIndex)) != 0; 53 | } 54 | 55 | @Override 56 | public int bitIndex(Long key, Long otherKey) { 57 | long keyValue = key.longValue(); 58 | if (keyValue == 0) { 59 | return NULL_BIT_KEY; 60 | } 61 | 62 | long otherValue = otherKey.longValue(); 63 | 64 | if (keyValue != otherValue) { 65 | long xorValue = keyValue ^ otherValue; 66 | for (int i = 0; i < Long.SIZE; i++) { 67 | if ((xorValue & mask(i)) != 0) { 68 | return i; 69 | } 70 | } 71 | } 72 | 73 | return KeyAnalyzer.EQUAL_BIT_KEY; 74 | } 75 | 76 | @Override 77 | public boolean isPrefix(Long key, Long prefix) { 78 | return key.equals(prefix); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/ShortKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2010 Roger Kapsi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.io.Serializable; 20 | 21 | 22 | /** 23 | * A {@link KeyAnalyzer} for {@link Short}s 24 | */ 25 | public class ShortKeyAnalyzer extends AbstractKeyAnalyzer implements Serializable { 26 | 27 | private static final long serialVersionUID = 5263816158638832817L; 28 | 29 | /** 30 | * A singleton instance of {@link ShortKeyAnalyzer} 31 | */ 32 | public static final ShortKeyAnalyzer INSTANCE = new ShortKeyAnalyzer(); 33 | 34 | /** 35 | * A bit mask where the first bit is 1 and the others are zero 36 | */ 37 | private static final int MSB = 1 << Short.SIZE-1; 38 | 39 | /** 40 | * Returns a bit mask where the given bit is set 41 | */ 42 | private static int mask(int bit) { 43 | return MSB >>> bit; 44 | } 45 | 46 | @Override 47 | public int lengthInBits(Short key) { 48 | return Byte.SIZE; 49 | } 50 | 51 | @Override 52 | public boolean isBitSet(Short key, int bitIndex) { 53 | return (key & mask(bitIndex)) != 0; 54 | } 55 | 56 | @Override 57 | public int bitIndex(Short key, Short otherKey) { 58 | short keyValue = key.shortValue(); 59 | if (keyValue == 0) { 60 | return NULL_BIT_KEY; 61 | } 62 | 63 | short otherValue = otherKey.shortValue(); 64 | 65 | if (keyValue != otherValue) { 66 | int xorValue = keyValue ^ otherValue; 67 | for (int i = 0; i < Short.SIZE; i++) { 68 | if ((xorValue & mask(i)) != 0) { 69 | return i; 70 | } 71 | } 72 | } 73 | 74 | return KeyAnalyzer.EQUAL_BIT_KEY; 75 | } 76 | 77 | @Override 78 | public boolean isPrefix(Short key, Short prefix) { 79 | return key.equals(prefix); 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/java/org/ardverk/collection/StringKeyAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Roger Kapsi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.ardverk.collection; 18 | 19 | import java.io.Serializable; 20 | 21 | /** 22 | * A {@link KeyAnalyzer} for {@link String}s. 23 | */ 24 | public class StringKeyAnalyzer extends AbstractKeyAnalyzer 25 | implements Serializable { 26 | 27 | private static final long serialVersionUID = -4927553200563548034L; 28 | 29 | public static final StringKeyAnalyzer INSTANCE = new StringKeyAnalyzer(); 30 | 31 | /** 32 | * A 16-bit mask where the MSB bit is 1 and the others are zero 33 | */ 34 | private static final int MSB = 1 << Character.SIZE-1; 35 | 36 | /** 37 | * Returns a bit mask where the given bit is set 38 | */ 39 | private static int mask(int bit) { 40 | return MSB >>> bit; 41 | } 42 | 43 | @Override 44 | public int lengthInBits(String key) { 45 | return key.length() * Character.SIZE; 46 | } 47 | 48 | @Override 49 | public boolean isBitSet(String key, int bitIndex) { 50 | if (bitIndex >= lengthInBits(key)) { 51 | return false; 52 | } 53 | 54 | int index = (int)(bitIndex / Character.SIZE); 55 | int bit = (int)(bitIndex % Character.SIZE); 56 | 57 | return (key.charAt(index) & mask(bit)) != 0; 58 | } 59 | 60 | @Override 61 | public int bitIndex(String key, String otherKey) { 62 | 63 | boolean allNull = true; 64 | 65 | int length1 = key.length(); 66 | int length2 = otherKey.length(); 67 | 68 | int length = Math.max(length1, length2); 69 | 70 | char ch1, ch2 = 0; 71 | for (int i = 0; i < length; i++) { 72 | if (i < length1) { 73 | ch1 = key.charAt(i); 74 | } else { 75 | ch1 = 0; 76 | } 77 | 78 | if (i < length2) { 79 | ch2 = otherKey.charAt(i); 80 | } else { 81 | ch2 = 0; 82 | } 83 | 84 | if (ch1 != ch2) { 85 | int x = ch1 ^ ch2; 86 | return i * Character.SIZE + (Integer.numberOfLeadingZeros(x) - Character.SIZE); 87 | } 88 | 89 | if (ch1 != 0) { 90 | allNull = false; 91 | } 92 | } 93 | 94 | // All bits are 0 95 | if (allNull) { 96 | return KeyAnalyzer.NULL_BIT_KEY; 97 | } 98 | 99 | // Both keys are equal 100 | return KeyAnalyzer.EQUAL_BIT_KEY; 101 | } 102 | 103 | @Override 104 | public boolean isPrefix(String key, String prefix) { 105 | return key.startsWith(prefix); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/io/prelink/critbit/CritBitTest.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit; 2 | 3 | import io.prelink.critbit.sharedbytearray.SBAKeyAnalyzer; 4 | import io.prelink.critbit.sharedbytearray.SharedByteArray; 5 | import io.prelink.critbit.sharedbytearray.ThinSBA; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import junit.framework.TestCase; 14 | 15 | import org.ardverk.collection.Cursor; 16 | import org.ardverk.collection.StringKeyAnalyzer; 17 | import org.junit.Test; 18 | 19 | public class CritBitTest extends TestCase { 20 | 21 | private static interface CBWrapper { 22 | void put(K key, String val); 23 | void remove(K key); 24 | AbstractCritBitTree get(); 25 | } 26 | 27 | private void commonTests(CBWrapper wrap, Keyifier k) { 28 | assertTrue(wrap.get().isEmpty()); 29 | assertNull(wrap.get().get(k.key("u"))); 30 | assertNull(wrap.get().min()); 31 | assertNull(wrap.get().max()); 32 | wrap.get().traverse(new AssertDoNothingCursor()); 33 | wrap.get().traverseWithPrefix(k.key("u"), new AssertDoNothingCursor()); 34 | 35 | wrap.put(k.key("a"), "a"); 36 | assertEquals("a", wrap.get().get(k.key("a"))); 37 | assertNull(wrap.get().get(k.key("b"))); 38 | wrap.remove(k.key("a")); 39 | assertTrue(wrap.get().isEmpty()); 40 | 41 | wrap.put(k.key("a"), "a"); 42 | wrap.put(k.key("b"), "b"); 43 | assertEquals("a", wrap.get().get(k.key("a"))); 44 | assertEquals("b", wrap.get().get(k.key("b"))); 45 | wrap.remove(k.key("a")); 46 | wrap.remove(k.key("b")); 47 | assertTrue(wrap.get().isEmpty()); 48 | assertNull(wrap.get().get(k.key("a"))); 49 | assertNull(wrap.get().get(k.key("b"))); 50 | 51 | String[] items = { 52 | "u", "un", "unh", "uni", "unj", "unim", "unin", "unio", 53 | "uninc", "unind", "unine", "unindd", "uninde", "unindf", 54 | "unindew", "unindex", "unindey", "a", "z" 55 | }; 56 | 57 | for(String s: items) { 58 | wrap.put(k.key(s), s); 59 | assertTrue("Tree must contain key "+s, wrap.get().containsKey(k.key(s))); 60 | assertTrue("Tree must contain val "+s, wrap.get().containsValue(s)); 61 | assertEquals(s, wrap.get().get(k.key(s))); 62 | } 63 | 64 | assertFalse(wrap.get().isEmpty()); 65 | assertEquals(items.length, wrap.get().size()); 66 | assertFalse(wrap.get().containsKey(k.key("monkey"))); 67 | assertFalse(wrap.get().containsValue("monkey")); 68 | 69 | assertEquals("a", wrap.get().min().getValue()); 70 | assertEquals("z", wrap.get().max().getValue()); 71 | 72 | final List target 73 | = Arrays.asList(new String[]{"unin", "uninc", "unind", "unindd", 74 | "uninde", "unindew", "unindex", "unindey", 75 | "unindf", "unine"}); 76 | 77 | final List gathered = new ArrayList(); 78 | wrap.get().traverseWithPrefix(k.key("unin"), new ValueListCursor(gathered)); 79 | assertEquals(target, gathered); 80 | 81 | final List filtered = new ArrayList(); 82 | wrap.get().traverseWithPrefix(k.key("unin"), 83 | new FilterLimitCursor(filtered)); 84 | assertEquals(Arrays.asList( 85 | new String[]{"unindd","uninde","unindew"}), filtered); 86 | 87 | int size = items.length; 88 | for(String s: target) { 89 | wrap.remove(k.key(s)); 90 | assertFalse(wrap.get().containsKey(k.key(s))); 91 | assertFalse(wrap.get().containsValue(k.key(s))); 92 | assertNull(wrap.get().get(k.key(s))); 93 | assertEquals(--size, wrap.get().size()); 94 | wrap.remove(k.key(s)); 95 | assertEquals(size, wrap.get().size()); 96 | } 97 | 98 | assertFalse(wrap.get().isEmpty()); 99 | assertEquals(items.length - target.size(), wrap.get().size()); 100 | assertEquals("a", wrap.get().min().getValue()); 101 | assertEquals("z", wrap.get().max().getValue()); 102 | 103 | wrap.get().traverseWithPrefix(k.key("unin"), new AssertDoNothingCursor()); 104 | } 105 | 106 | @Test 107 | public void testMutable() { 108 | final MCritBitTree test = 109 | new MCritBitTree(StringKeyAnalyzer.INSTANCE); 110 | 111 | commonTests(new MutableCBWrapper(test), skier); 112 | 113 | List iterkeys = new LinkedList(); 114 | List itervals = new LinkedList(); 115 | for(Map.Entry e: test.entrySet()) { 116 | iterkeys.add(e.getKey()); 117 | itervals.add(e.getValue()); 118 | } 119 | 120 | List curskeys = new LinkedList(); 121 | List cursvals = new LinkedList(); 122 | test.traverse(new EntryListsCursor(curskeys, cursvals)); 123 | 124 | assertEquals(curskeys, iterkeys); 125 | assertEquals(cursvals, itervals); 126 | } 127 | 128 | @Test 129 | public void testImmutable() { 130 | final CritBitTree cb = 131 | new CritBitTree(StringKeyAnalyzer.INSTANCE); 132 | 133 | commonTests(new ImmutableCBWrapper(cb), skier); 134 | } 135 | 136 | @Test 137 | public void testSBAKey() { 138 | final MCritBitTree test = 139 | new MCritBitTree(SBAKeyAnalyzer.INSTANCE); 140 | 141 | commonTests(new MutableCBWrapper(test), bytekier); 142 | } 143 | 144 | private static class ImmutableCBWrapper implements CBWrapper { 145 | private CritBitTree test; 146 | public ImmutableCBWrapper(CritBitTree cb) { 147 | this.test = cb; 148 | } 149 | public void put(K key, String val) { 150 | test = test.put(key, val); 151 | } 152 | public void remove(K key) { 153 | test = test.remove(key); 154 | } 155 | public AbstractCritBitTree get() { 156 | return test; 157 | } 158 | } 159 | private static class MutableCBWrapper implements CBWrapper { 160 | private MCritBitTree test; 161 | public MutableCBWrapper(MCritBitTree cb) { 162 | this.test = cb; 163 | } 164 | public void put(K key, String val) { 165 | test.put(key, val); 166 | } 167 | public void remove(K key) { 168 | test.remove(key); 169 | } 170 | public AbstractCritBitTree get() { 171 | return test; 172 | } 173 | } 174 | 175 | private static interface Keyifier { 176 | K key(String s); 177 | } 178 | private static Keyifier skier = new Keyifier() { 179 | public String key(String s) { return s; } 180 | }; 181 | private static Keyifier bytekier = new Keyifier() { 182 | public SharedByteArray key(String s) { return sba(s); } 183 | }; 184 | 185 | private static SharedByteArray sba(String s) { 186 | return new ThinSBA(bytes(s)); 187 | } 188 | 189 | private static byte[] bytes(String s) { 190 | try { 191 | return s.getBytes("UTF-8"); 192 | } catch (Exception e) { 193 | throw new RuntimeException("won't happen"); 194 | } 195 | } 196 | 197 | private class AssertDoNothingCursor implements Cursor { 198 | public Decision select(Map.Entry entry) { 199 | throw new RuntimeException("Shouldn't do anything"); 200 | } 201 | } 202 | 203 | private class ValueListCursor implements Cursor { 204 | private final List list; 205 | public ValueListCursor(List basket) { 206 | this.list = basket; 207 | } 208 | public Decision select(Map.Entry entry) { 209 | list.add(entry.getValue()); 210 | return Decision.CONTINUE; 211 | } 212 | } 213 | 214 | private class EntryListsCursor implements Cursor { 215 | private final List klist; 216 | private final List vlist; 217 | public EntryListsCursor(List klist, List vlist) { 218 | this.klist = klist; 219 | this.vlist = vlist; 220 | } 221 | public Decision select(Map.Entry entry) { 222 | klist.add(entry.getKey()); 223 | vlist.add(entry.getValue()); 224 | return Decision.CONTINUE; 225 | } 226 | } 227 | 228 | private class FilterLimitCursor implements Cursor { 229 | private final List list; 230 | private int counter = 0; 231 | public FilterLimitCursor(List basket) { 232 | this.list = basket; 233 | } 234 | public Decision select(Map.Entry entry) { 235 | String val = entry.getValue(); 236 | if(val.length() > 5) { 237 | list.add(val); 238 | counter++; 239 | } 240 | return (counter < 3) ? Decision.CONTINUE : Decision.EXIT; 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/test/java/io/prelink/critbit/IterationSpeedTest.java: -------------------------------------------------------------------------------- 1 | package io.prelink.critbit; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Map.Entry; 6 | import java.util.Random; 7 | import java.util.TreeMap; 8 | 9 | import org.apache.commons.lang.RandomStringUtils; 10 | import org.apache.commons.lang.math.RandomUtils; 11 | import org.ardverk.collection.Cursor; 12 | import org.ardverk.collection.Cursor.Decision; 13 | import org.ardverk.collection.StringKeyAnalyzer; 14 | 15 | //import org.ardverk.collection.PatriciaTrie; 16 | //import org.ardverk.collection.StringKeyAnalyzer; 17 | 18 | public class IterationSpeedTest { 19 | private static final int ITEMS = 10000; 20 | private static final int WARMUPS = 10000; 21 | private static final int ITERS = 10000; 22 | private static final int SEED = 42; 23 | 24 | private static interface Iterer { 25 | String name(); 26 | void put(String key, String val); 27 | int iterate(); 28 | } 29 | private static String randomString(Random rand, int i) { 30 | int len = RandomUtils.nextInt(rand, 100) + 1; 31 | return RandomStringUtils.random(len, 0, 0, false, false, null, rand) + i; 32 | } 33 | private static void iterTest(Iterer iter) { 34 | Random rand = new Random(SEED); 35 | for(int i=0; i implements Cursor { 62 | public int count = 0; 63 | public Decision select(Entry entry) { 64 | count++; 65 | return Decision.CONTINUE; 66 | } 67 | } 68 | 69 | public static void main(final String[] args) throws Exception { 70 | System.out.println("Waiting to start"); 71 | //Thread.sleep(30000); 72 | System.out.println("Starting"); 73 | 74 | // final Map hm = new HashMap(); 75 | // iterTest(new Iterer() { 76 | // public String name() { return "HashMap"; } 77 | // public void put(String k, String v) { hm.put(k, v); } 78 | // public int iterate() { 79 | // int out = 0; 80 | // for(Entry e: hm.entrySet()) { 81 | // out++; 82 | // } 83 | // return out; 84 | // } 85 | // }); 86 | // 87 | // final Map tm = new TreeMap(); 88 | // iterTest(new Iterer() { 89 | // public String name() { return "TreeMap"; } 90 | // public void put(String k, String v) { tm.put(k, v); } 91 | // public int iterate() { 92 | // int out = 0; 93 | // for(Entry e: tm.entrySet()) { 94 | // out++; 95 | // } 96 | // return out; 97 | // } 98 | // }); 99 | 100 | final MCritBitTree mcbc = 101 | new MCritBitTree(StringKeyAnalyzer.INSTANCE); 102 | iterTest(new Iterer() { 103 | public String name() { return "Mutable Crit Bit - Cursor"; } 104 | public void put(String k, String v) { mcbc.put(k, v); } 105 | public int iterate() { 106 | CountCursor c = new CountCursor(); 107 | mcbc.traverse(c); 108 | return c.count; 109 | } 110 | }); 111 | 112 | final MCritBitTree mcbi = 113 | new MCritBitTree(StringKeyAnalyzer.INSTANCE); 114 | iterTest(new Iterer() { 115 | public String name() { return "Mutable Crit Bit - Iterator"; } 116 | public void put(String k, String v) { mcbi.put(k, v); } 117 | public int iterate() { 118 | int out = 0; 119 | for(Entry e: mcbi.entrySet()) { 120 | out++; 121 | } 122 | return out; 123 | } 124 | }); 125 | 126 | //Give us a chance to look at the heap. 127 | for(int i=0;i fcb; 56 | public FCBFlipper() { 57 | this.fcb = new CritBitTree(StringKeyAnalyzer.INSTANCE); 58 | } 59 | public void put(String key, String val) { 60 | this.fcb = fcb.put(key, val); 61 | } 62 | } 63 | 64 | public static void main(final String[] args) throws Exception { 65 | 66 | final Map hm = new HashMap(); 67 | putTest(new Putter() { 68 | public String name() { return "HashMap"; } 69 | public void put(String k, String v) { hm.put(k, v); } 70 | }); 71 | 72 | final Map pshm = 73 | new HashMap(WARMUPS+ITERS); 74 | putTest(new Putter() { 75 | public String name() { return "Presized HashMap"; } 76 | public void put(String k, String v) { pshm.put(k, v); } 77 | }); 78 | 79 | final Map tm = new TreeMap(); 80 | putTest(new Putter() { 81 | public String name() { return "TreeMap"; } 82 | public void put(String k, String v) { tm.put(k, v); } 83 | }); 84 | 85 | final FCBFlipper fcb = new FCBFlipper(); 86 | putTest(new Putter() { 87 | public String name() { return "Functional Crit Bit"; } 88 | public void put(String k, String v) { fcb.put(k, v); } 89 | }); 90 | 91 | final MCritBitTree mcb = 92 | new MCritBitTree(StringKeyAnalyzer.INSTANCE); 93 | putTest(new Putter() { 94 | public String name() { return "Mutable Crit Bit"; } 95 | public void put(String k, String v) { mcb.put(k, v); } 96 | }); 97 | 98 | // final PatriciaTrie ptrie = 99 | // new PatriciaTrie(StringKeyAnalyzer.INSTANCE); 100 | // putTest(new Putter() { 101 | // public String name() { return "Patricia Trie"; } 102 | // public void put(String k, String v) { ptrie.put(k, v); } 103 | // }); 104 | 105 | 106 | 107 | //Give us a chance to look at the heap. 108 | for(int i=0;i trie 53 | = new MCritBitTree( 54 | ByteArrayKeyAnalyzer.INSTANCE); 55 | 56 | Map map 57 | = new TreeMap( 58 | ByteArrayKeyAnalyzer.INSTANCE); 59 | 60 | for (int i = 0; i < SIZE; i++) { 61 | BigInteger value = BigInteger.valueOf(i); 62 | byte[] key = toByteArray(value); 63 | 64 | BigInteger existing = trie.put(key, value); 65 | TestCase.assertNull(existing); 66 | 67 | map.put(key, value); 68 | } 69 | 70 | TestCase.assertEquals(map.size(), trie.size()); 71 | 72 | for (byte[] key : map.keySet()) { 73 | BigInteger expected = new BigInteger(1, key); 74 | BigInteger value = trie.get(key); 75 | 76 | TestCase.assertEquals(expected, value); 77 | } 78 | } 79 | 80 | private static byte[] toByteArray(String value, int radix) { 81 | return toByteArray(Long.parseLong(value, radix)); 82 | } 83 | 84 | private static byte[] toByteArray(long value) { 85 | return toByteArray(BigInteger.valueOf(value)); 86 | } 87 | 88 | private static byte[] toByteArray(BigInteger value) { 89 | byte[] src = value.toByteArray(); 90 | if (src.length <= 1) { 91 | return src; 92 | } 93 | 94 | if (src[0] != 0) { 95 | return src; 96 | } 97 | 98 | byte[] dst = new byte[src.length-1]; 99 | System.arraycopy(src, 1, dst, 0, dst.length); 100 | return dst; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/org/ardverk/collection/PatriciaTrieTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2008 Roger Kapsi, Sam Berlin 3 | * 4 | * Modified 2011 Jason Fager 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package org.ardverk.collection; 20 | 21 | import io.prelink.critbit.MCritBitTree; 22 | 23 | import java.io.BufferedReader; 24 | import java.io.InputStream; 25 | import java.io.InputStreamReader; 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.Collections; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.Map.Entry; 32 | import java.util.SortedMap; 33 | import java.util.StringTokenizer; 34 | import java.util.TreeMap; 35 | 36 | import junit.framework.TestCase; 37 | 38 | import org.junit.Test; 39 | 40 | public class PatriciaTrieTest { 41 | 42 | @Test 43 | public void testSimple() { 44 | MCritBitTree intTrie = 45 | new MCritBitTree(IntegerKeyAnalyzer.INSTANCE); 46 | TestCase.assertTrue(intTrie.isEmpty()); 47 | TestCase.assertEquals(0, intTrie.size()); 48 | 49 | intTrie.put(1, "One"); 50 | TestCase.assertFalse(intTrie.isEmpty()); 51 | TestCase.assertEquals(1, intTrie.size()); 52 | 53 | TestCase.assertEquals("One", intTrie.remove(1)); 54 | TestCase.assertNull(intTrie.remove(1)); 55 | TestCase.assertTrue(intTrie.isEmpty()); 56 | TestCase.assertEquals(0, intTrie.size()); 57 | 58 | intTrie.put(1, "One"); 59 | TestCase.assertEquals("One", intTrie.get(1)); 60 | TestCase.assertEquals("One", intTrie.put(1, "NotOne")); 61 | TestCase.assertEquals(1, intTrie.size()); 62 | TestCase.assertEquals("NotOne", intTrie.get(1)); 63 | TestCase.assertEquals("NotOne", intTrie.remove(1)); 64 | TestCase.assertNull(intTrie.put(1, "One")); 65 | } 66 | 67 | @Test 68 | public void testCeilingEntry() { 69 | MCritBitTree charTrie = 70 | new MCritBitTree(CharacterKeyAnalyzer.INSTANCE); 71 | charTrie.put('c', "c"); 72 | charTrie.put('p', "p"); 73 | charTrie.put('l', "l"); 74 | charTrie.put('t', "t"); 75 | charTrie.put('k', "k"); 76 | charTrie.put('a', "a"); 77 | charTrie.put('y', "y"); 78 | charTrie.put('r', "r"); 79 | charTrie.put('u', "u"); 80 | charTrie.put('o', "o"); 81 | charTrie.put('w', "w"); 82 | charTrie.put('i', "i"); 83 | charTrie.put('e', "e"); 84 | charTrie.put('x', "x"); 85 | charTrie.put('q', "q"); 86 | charTrie.put('b', "b"); 87 | charTrie.put('j', "j"); 88 | charTrie.put('s', "s"); 89 | charTrie.put('n', "n"); 90 | charTrie.put('v', "v"); 91 | charTrie.put('g', "g"); 92 | charTrie.put('h', "h"); 93 | charTrie.put('m', "m"); 94 | charTrie.put('z', "z"); 95 | charTrie.put('f', "f"); 96 | charTrie.put('d', "d"); 97 | 98 | Object[] results = new Object[] { 99 | 'a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", 100 | 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", 101 | 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", 102 | 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", 103 | 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", 104 | 'z', "z" 105 | }; 106 | 107 | // for(int i = 0; i < results.length; i++) { 108 | // Map.Entry found = charTrie.ceilingEntry((Character)results[i]); 109 | // TestCase.assertNotNull(found); 110 | // TestCase.assertEquals(results[i], found.getKey()); 111 | // TestCase.assertEquals(results[++i], found.getValue()); 112 | // } 113 | 114 | // Remove some & try again... 115 | charTrie.remove('a'); 116 | charTrie.remove('z'); 117 | charTrie.remove('q'); 118 | charTrie.remove('l'); 119 | charTrie.remove('p'); 120 | charTrie.remove('m'); 121 | charTrie.remove('u'); 122 | 123 | // Map.Entry found = charTrie.ceilingEntry('u'); 124 | // TestCase.assertNotNull(found); 125 | // TestCase.assertEquals((Character)'v', found.getKey()); 126 | // 127 | // found = charTrie.ceilingEntry('a'); 128 | // TestCase.assertNotNull(found); 129 | // TestCase.assertEquals((Character)'b', found.getKey()); 130 | // 131 | // found = charTrie.ceilingEntry('z'); 132 | // TestCase.assertNull(found); 133 | // 134 | // found = charTrie.ceilingEntry('q'); 135 | // TestCase.assertNotNull(found); 136 | // TestCase.assertEquals((Character)'r', found.getKey()); 137 | // 138 | // found = charTrie.ceilingEntry('l'); 139 | // TestCase.assertNotNull(found); 140 | // TestCase.assertEquals((Character)'n', found.getKey()); 141 | // 142 | // found = charTrie.ceilingEntry('p'); 143 | // TestCase.assertNotNull(found); 144 | // TestCase.assertEquals((Character)'r', found.getKey()); 145 | // 146 | // found = charTrie.ceilingEntry('m'); 147 | // TestCase.assertNotNull(found); 148 | // TestCase.assertEquals((Character)'n', found.getKey()); 149 | // 150 | // found = charTrie.ceilingEntry('\0'); 151 | // TestCase.assertNotNull(found); 152 | // TestCase.assertEquals((Character)'b', found.getKey()); 153 | // 154 | // charTrie.put('\0', ""); 155 | // found = charTrie.ceilingEntry('\0'); 156 | // TestCase.assertNotNull(found); 157 | // TestCase.assertEquals((Character)'\0', found.getKey()); 158 | } 159 | 160 | @Test 161 | public void testLowerEntry() { 162 | MCritBitTree charTrie = 163 | new MCritBitTree(CharacterKeyAnalyzer.INSTANCE); 164 | charTrie.put('c', "c"); 165 | charTrie.put('p', "p"); 166 | charTrie.put('l', "l"); 167 | charTrie.put('t', "t"); 168 | charTrie.put('k', "k"); 169 | charTrie.put('a', "a"); 170 | charTrie.put('y', "y"); 171 | charTrie.put('r', "r"); 172 | charTrie.put('u', "u"); 173 | charTrie.put('o', "o"); 174 | charTrie.put('w', "w"); 175 | charTrie.put('i', "i"); 176 | charTrie.put('e', "e"); 177 | charTrie.put('x', "x"); 178 | charTrie.put('q', "q"); 179 | charTrie.put('b', "b"); 180 | charTrie.put('j', "j"); 181 | charTrie.put('s', "s"); 182 | charTrie.put('n', "n"); 183 | charTrie.put('v', "v"); 184 | charTrie.put('g', "g"); 185 | charTrie.put('h', "h"); 186 | charTrie.put('m', "m"); 187 | charTrie.put('z', "z"); 188 | charTrie.put('f', "f"); 189 | charTrie.put('d', "d"); 190 | 191 | Object[] results = new Object[] { 192 | 'a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", 193 | 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", 194 | 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", 195 | 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", 196 | 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", 197 | 'z', "z" 198 | }; 199 | 200 | // for(int i = 0; i < results.length; i+=2) { 201 | // //System.out.println("Looking for: " + results[i]); 202 | // Map.Entry found = charTrie.lowerEntry((Character)results[i]); 203 | // if(i == 0) { 204 | // TestCase.assertNull(found); 205 | // } else { 206 | // TestCase.assertNotNull(found); 207 | // TestCase.assertEquals(results[i-2], found.getKey()); 208 | // TestCase.assertEquals(results[i-1], found.getValue()); 209 | // } 210 | // } 211 | // 212 | // Map.Entry found = charTrie.lowerEntry((char)('z' + 1)); 213 | // TestCase.assertNotNull(found); 214 | // TestCase.assertEquals((Character)'z', found.getKey()); 215 | 216 | 217 | // Remove some & try again... 218 | charTrie.remove('a'); 219 | charTrie.remove('z'); 220 | charTrie.remove('q'); 221 | charTrie.remove('l'); 222 | charTrie.remove('p'); 223 | charTrie.remove('m'); 224 | charTrie.remove('u'); 225 | 226 | // found = charTrie.lowerEntry('u'); 227 | // TestCase.assertNotNull(found); 228 | // TestCase.assertEquals((Character)'t', found.getKey()); 229 | // 230 | // found = charTrie.lowerEntry('v'); 231 | // TestCase.assertNotNull(found); 232 | // TestCase.assertEquals((Character)'t', found.getKey()); 233 | // 234 | // found = charTrie.lowerEntry('a'); 235 | // TestCase.assertNull(found); 236 | // 237 | // found = charTrie.lowerEntry('z'); 238 | // TestCase.assertNotNull(found); 239 | // TestCase.assertEquals((Character)'y', found.getKey()); 240 | // 241 | // found = charTrie.lowerEntry((char)('z'+1)); 242 | // TestCase.assertNotNull(found); 243 | // TestCase.assertEquals((Character)'y', found.getKey()); 244 | // 245 | // found = charTrie.lowerEntry('q'); 246 | // TestCase.assertNotNull(found); 247 | // TestCase.assertEquals((Character)'o', found.getKey()); 248 | // 249 | // found = charTrie.lowerEntry('r'); 250 | // TestCase.assertNotNull(found); 251 | // TestCase.assertEquals((Character)'o', found.getKey()); 252 | // 253 | // found = charTrie.lowerEntry('p'); 254 | // TestCase.assertNotNull(found); 255 | // TestCase.assertEquals((Character)'o', found.getKey()); 256 | // 257 | // found = charTrie.lowerEntry('l'); 258 | // TestCase.assertNotNull(found); 259 | // TestCase.assertEquals((Character)'k', found.getKey()); 260 | // 261 | // found = charTrie.lowerEntry('m'); 262 | // TestCase.assertNotNull(found); 263 | // TestCase.assertEquals((Character)'k', found.getKey()); 264 | // 265 | // found = charTrie.lowerEntry('\0'); 266 | // TestCase.assertNull(found); 267 | // 268 | // charTrie.put('\0', ""); 269 | // found = charTrie.lowerEntry('\0'); 270 | // TestCase.assertNull(found); 271 | } 272 | 273 | @Test 274 | public void testIteration() { 275 | MCritBitTree intTrie = 276 | new MCritBitTree(IntegerKeyAnalyzer.INSTANCE); 277 | intTrie.put(1, "One"); 278 | intTrie.put(5, "Five"); 279 | intTrie.put(4, "Four"); 280 | intTrie.put(2, "Two"); 281 | intTrie.put(3, "Three"); 282 | intTrie.put(15, "Fifteen"); 283 | intTrie.put(13, "Thirteen"); 284 | intTrie.put(14, "Fourteen"); 285 | intTrie.put(16, "Sixteen"); 286 | 287 | TestCursor cursor = new TestCursor( 288 | 1, "One", 2, "Two", 3, "Three", 4, "Four", 5, "Five", 13, "Thirteen", 289 | 14, "Fourteen", 15, "Fifteen", 16, "Sixteen"); 290 | 291 | cursor.starting(); 292 | intTrie.traverse(cursor); 293 | cursor.finished(); 294 | 295 | // cursor.starting(); 296 | // for (Map.Entry entry : intTrie.entrySet()) 297 | // cursor.select(entry); 298 | // cursor.finished(); 299 | // 300 | // cursor.starting(); 301 | // for (Integer integer : intTrie.keySet()) 302 | // cursor.checkKey(integer); 303 | // cursor.finished(); 304 | // 305 | // cursor.starting(); 306 | // for (String string : intTrie.values()) 307 | // cursor.checkValue(string); 308 | // cursor.finished(); 309 | 310 | MCritBitTree charTrie = 311 | new MCritBitTree(CharacterKeyAnalyzer.INSTANCE); 312 | charTrie.put('c', "c"); 313 | charTrie.put('p', "p"); 314 | charTrie.put('l', "l"); 315 | charTrie.put('t', "t"); 316 | charTrie.put('k', "k"); 317 | charTrie.put('a', "a"); 318 | charTrie.put('y', "y"); 319 | charTrie.put('r', "r"); 320 | charTrie.put('u', "u"); 321 | charTrie.put('o', "o"); 322 | charTrie.put('w', "w"); 323 | charTrie.put('i', "i"); 324 | charTrie.put('e', "e"); 325 | charTrie.put('x', "x"); 326 | charTrie.put('q', "q"); 327 | charTrie.put('b', "b"); 328 | charTrie.put('j', "j"); 329 | charTrie.put('s', "s"); 330 | charTrie.put('n', "n"); 331 | charTrie.put('v', "v"); 332 | charTrie.put('g', "g"); 333 | charTrie.put('h', "h"); 334 | charTrie.put('m', "m"); 335 | charTrie.put('z', "z"); 336 | charTrie.put('f', "f"); 337 | charTrie.put('d', "d"); 338 | cursor = new TestCursor('a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", 339 | 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", 340 | 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", 341 | 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", 342 | 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", 343 | 'z', "z"); 344 | 345 | cursor.starting(); 346 | charTrie.traverse(cursor); 347 | cursor.finished(); 348 | 349 | // cursor.starting(); 350 | // for (Map.Entry entry : charTrie.entrySet()) 351 | // cursor.select(entry); 352 | // cursor.finished(); 353 | // 354 | // cursor.starting(); 355 | // for (Character character : charTrie.keySet()) 356 | // cursor.checkKey(character); 357 | // cursor.finished(); 358 | // 359 | // cursor.starting(); 360 | // for (String string : charTrie.values()) 361 | // cursor.checkValue(string); 362 | // cursor.finished(); 363 | } 364 | 365 | @Test 366 | public void testSelect() { 367 | MCritBitTree charTrie = 368 | new MCritBitTree(CharacterKeyAnalyzer.INSTANCE); 369 | charTrie.put('c', "c"); 370 | charTrie.put('p', "p"); 371 | charTrie.put('l', "l"); 372 | charTrie.put('t', "t"); 373 | charTrie.put('k', "k"); 374 | charTrie.put('a', "a"); 375 | charTrie.put('y', "y"); 376 | charTrie.put('r', "r"); 377 | charTrie.put('u', "u"); 378 | charTrie.put('o', "o"); 379 | charTrie.put('w', "w"); 380 | charTrie.put('i', "i"); 381 | charTrie.put('e', "e"); 382 | charTrie.put('x', "x"); 383 | charTrie.put('q', "q"); 384 | charTrie.put('b', "b"); 385 | charTrie.put('j', "j"); 386 | charTrie.put('s', "s"); 387 | charTrie.put('n', "n"); 388 | charTrie.put('v', "v"); 389 | charTrie.put('g', "g"); 390 | charTrie.put('h', "h"); 391 | charTrie.put('m', "m"); 392 | charTrie.put('z', "z"); 393 | charTrie.put('f', "f"); 394 | charTrie.put('d', "d"); 395 | TestCursor cursor = new TestCursor( 396 | 'd', "d", 'e', "e", 'f', "f", 'g', "g", 397 | 'a', "a", 'b', "b", 'c', "c", 398 | 'l', "l", 'm', "m", 'n', "n", 'o', "o", 399 | 'h', "h", 'i', "i", 'j', "j", 'k', "k", 400 | 't', "t", 'u', "u", 'v', "v", 'w', "w", 401 | 'p', "p", 'q', "q", 'r', "r", 's', "s", 402 | 'x', "x", 'y', "y", 'z', "z"); 403 | 404 | TestCase.assertEquals(26, charTrie.size()); 405 | 406 | // cursor.starting(); 407 | // charTrie.select('d', cursor); 408 | // cursor.finished(); 409 | } 410 | 411 | @Test 412 | public void testTraverseCursorRemove() { 413 | MCritBitTree charTrie = new MCritBitTree(CharacterKeyAnalyzer.INSTANCE); 414 | charTrie.put('c', "c"); 415 | charTrie.put('p', "p"); 416 | charTrie.put('l', "l"); 417 | charTrie.put('t', "t"); 418 | charTrie.put('k', "k"); 419 | charTrie.put('a', "a"); 420 | charTrie.put('y', "y"); 421 | charTrie.put('r', "r"); 422 | charTrie.put('u', "u"); 423 | charTrie.put('o', "o"); 424 | charTrie.put('w', "w"); 425 | charTrie.put('i', "i"); 426 | charTrie.put('e', "e"); 427 | charTrie.put('x', "x"); 428 | charTrie.put('q', "q"); 429 | charTrie.put('b', "b"); 430 | charTrie.put('j', "j"); 431 | charTrie.put('s', "s"); 432 | charTrie.put('n', "n"); 433 | charTrie.put('v', "v"); 434 | charTrie.put('g', "g"); 435 | charTrie.put('h', "h"); 436 | charTrie.put('m', "m"); 437 | charTrie.put('z', "z"); 438 | charTrie.put('f', "f"); 439 | charTrie.put('d', "d"); 440 | TestCursor cursor = new TestCursor('a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", 441 | 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", 442 | 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", 443 | 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", 444 | 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", 445 | 'z', "z"); 446 | 447 | cursor.starting(); 448 | charTrie.traverse(cursor); 449 | cursor.finished(); 450 | 451 | // Test removing both an internal & external node. 452 | // 'm' is an example External node in this Trie, and 'p' is an internal. 453 | 454 | TestCase.assertEquals(26, charTrie.size()); 455 | // 456 | // Object[] toRemove = new Object[] { 'g', 'd', 'e', 'm', 'p', 'q', 'r', 's' }; 457 | // cursor.addToRemove(toRemove); 458 | // 459 | // cursor.starting(); 460 | // charTrie.traverse(cursor); 461 | // cursor.finished(); 462 | // 463 | // TestCase.assertEquals(26 - toRemove.length, charTrie.size()); 464 | // 465 | // cursor.starting(); 466 | // charTrie.traverse(cursor); 467 | // cursor.finished(); 468 | // 469 | // cursor.starting(); 470 | // for (Entry entry : charTrie.entrySet()) { 471 | // cursor.select(entry); 472 | // if (Arrays.asList(toRemove).contains(entry.getKey())) { 473 | // TestCase.fail("got an: " + entry); 474 | // } 475 | // } 476 | // cursor.finished(); 477 | } 478 | 479 | @Test 480 | public void testIteratorRemove() { 481 | MCritBitTree charTrie = new MCritBitTree(CharacterKeyAnalyzer.INSTANCE); 482 | charTrie.put('c', "c"); 483 | charTrie.put('p', "p"); 484 | charTrie.put('l', "l"); 485 | charTrie.put('t', "t"); 486 | charTrie.put('k', "k"); 487 | charTrie.put('a', "a"); 488 | charTrie.put('y', "y"); 489 | charTrie.put('r', "r"); 490 | charTrie.put('u', "u"); 491 | charTrie.put('o', "o"); 492 | charTrie.put('w', "w"); 493 | charTrie.put('i', "i"); 494 | charTrie.put('e', "e"); 495 | charTrie.put('x', "x"); 496 | charTrie.put('q', "q"); 497 | charTrie.put('b', "b"); 498 | charTrie.put('j', "j"); 499 | charTrie.put('s', "s"); 500 | charTrie.put('n', "n"); 501 | charTrie.put('v', "v"); 502 | charTrie.put('g', "g"); 503 | charTrie.put('h', "h"); 504 | charTrie.put('m', "m"); 505 | charTrie.put('z', "z"); 506 | charTrie.put('f', "f"); 507 | charTrie.put('d', "d"); 508 | // TestCursor cursor = new TestCursor('a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", 509 | // 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", 510 | // 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", 511 | // 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", 512 | // 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", 513 | // 'z', "z"); 514 | 515 | // Test removing both an internal & external node. 516 | // 'm' is an example External node in this Trie, and 'p' is an internal. 517 | 518 | TestCase.assertEquals(26, charTrie.size()); 519 | 520 | Object[] toRemove = new Object[] { 'e', 'm', 'p', 'q', 'r', 's' }; 521 | 522 | // cursor.starting(); 523 | // for(Iterator> i = charTrie.entrySet().iterator(); i.hasNext(); ) { 524 | // Map.Entry entry = i.next(); 525 | // cursor.select(entry); 526 | // if(Arrays.asList(toRemove).contains(entry.getKey())) 527 | // i.remove(); 528 | // } 529 | // cursor.finished(); 530 | // 531 | // TestCase.assertEquals(26 - toRemove.length, charTrie.size()); 532 | // 533 | // cursor.remove(toRemove); 534 | // 535 | // cursor.starting(); 536 | // for (Entry entry : charTrie.entrySet()) { 537 | // cursor.select(entry); 538 | // if (Arrays.asList(toRemove).contains(entry.getKey())) { 539 | // TestCase.fail("got an: " + entry); 540 | // } 541 | // } 542 | // cursor.finished(); 543 | } 544 | 545 | @Test 546 | public void testHamlet() throws Exception { 547 | // Make sure that Hamlet is read & stored in the same order as a SortedSet. 548 | List original = new ArrayList(); 549 | List control = new ArrayList(); 550 | SortedMap sortedControl = new TreeMap(); 551 | MCritBitTree trie = new MCritBitTree(StringKeyAnalyzer.INSTANCE); 552 | 553 | InputStream in = getClass().getResourceAsStream("hamlet.txt"); 554 | BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 555 | 556 | String read = null; 557 | String controlCollision = null; 558 | String testingCollision = null; 559 | while( (read = reader.readLine()) != null) { 560 | StringTokenizer st = new StringTokenizer(read); 561 | while(st.hasMoreTokens()) { 562 | String token = st.nextToken(); 563 | original.add(token); 564 | controlCollision = sortedControl.put(token, token); 565 | testingCollision = trie.put(token, token); 566 | TestCase.assertEquals(controlCollision, testingCollision); 567 | } 568 | } 569 | control.addAll(sortedControl.values()); 570 | 571 | TestCase.assertEquals(control.size(), sortedControl.size()); 572 | TestCase.assertEquals(sortedControl.size(), trie.size()); 573 | // Iterator iter = trie.values().iterator(); 574 | // for (String aControl : control) { 575 | // TestCase.assertEquals(aControl, iter.next()); 576 | // } 577 | // 578 | // Random rnd = new Random(); 579 | // int item = 0; 580 | // iter = trie.values().iterator(); 581 | // int removed = 0; 582 | // for(; item < control.size(); item++) { 583 | // TestCase.assertEquals(control.get(item), iter.next()); 584 | // if(rnd.nextBoolean()) { 585 | // iter.remove(); 586 | // removed++; 587 | // } 588 | // } 589 | // 590 | // TestCase.assertEquals(control.size(), item); 591 | // TestCase.assertTrue(removed > 0); 592 | // TestCase.assertEquals(control.size(), trie.size() + removed); 593 | 594 | // reset hamlet 595 | trie.clear(); 596 | TestCase.assertTrue(trie.isEmpty()); 597 | for (String anOriginal : original) { 598 | trie.put(anOriginal, anOriginal); 599 | } 600 | 601 | // assertEqualArrays(sortedControl.values().toArray(), trie.values().toArray()); 602 | // assertEqualArrays(sortedControl.keySet().toArray(), trie.keySet().toArray()); 603 | // assertEqualArrays(sortedControl.entrySet().toArray(), trie.entrySet().toArray()); 604 | // 605 | // TestCase.assertEquals(sortedControl.firstKey(), trie.firstKey()); 606 | // TestCase.assertEquals(sortedControl.lastKey(), trie.lastKey()); 607 | // 608 | // SortedMap sub = trie.headMap(control.get(523)); 609 | // TestCase.assertEquals(523, sub.size()); 610 | // for(int i = 0; i < control.size(); i++) { 611 | // if(i < 523) 612 | // TestCase.assertTrue(sub.containsKey(control.get(i))); 613 | // else 614 | // TestCase.assertFalse(sub.containsKey(control.get(i))); 615 | // } 616 | // // Too slow to check values on all, so just do a few. 617 | // TestCase.assertTrue(sub.containsValue(control.get(522))); 618 | // TestCase.assertFalse(sub.containsValue(control.get(523))); 619 | // TestCase.assertFalse(sub.containsValue(control.get(524))); 620 | // 621 | // try { 622 | // sub.headMap(control.get(524)); 623 | // TestCase.fail("should have thrown IAE"); 624 | // } catch(IllegalArgumentException expected) {} 625 | // 626 | // TestCase.assertEquals(sub.lastKey(), control.get(522)); 627 | // TestCase.assertEquals(sub.firstKey(), control.get(0)); 628 | // 629 | // sub = sub.tailMap(control.get(234)); 630 | // TestCase.assertEquals(289, sub.size()); 631 | // TestCase.assertEquals(control.get(234), sub.firstKey()); 632 | // TestCase.assertEquals(control.get(522), sub.lastKey()); 633 | // for(int i = 0; i < control.size(); i++) { 634 | // if(i < 523 && i > 233) 635 | // TestCase.assertTrue(sub.containsKey(control.get(i))); 636 | // else 637 | // TestCase.assertFalse(sub.containsKey(control.get(i))); 638 | // } 639 | // 640 | // try { 641 | // sub.tailMap(control.get(232)); 642 | // TestCase.fail("should have thrown IAE"); 643 | // } catch(IllegalArgumentException expected) {} 644 | // 645 | // sub = sub.subMap(control.get(300), control.get(400)); 646 | // TestCase.assertEquals(100, sub.size()); 647 | // TestCase.assertEquals(control.get(300), sub.firstKey()); 648 | // TestCase.assertEquals(control.get(399), sub.lastKey()); 649 | // 650 | // for(int i = 0; i < control.size(); i++) { 651 | // if(i < 400 && i > 299) 652 | // TestCase.assertTrue(sub.containsKey(control.get(i))); 653 | // else 654 | // TestCase.assertFalse(sub.containsKey(control.get(i))); 655 | // } 656 | } 657 | 658 | @Test 659 | public void testPrefixedBy() { 660 | MCritBitTree trie 661 | = new MCritBitTree(StringKeyAnalyzer.INSTANCE); 662 | 663 | final String[] keys = new String[]{ 664 | "", 665 | "Albert", "Xavier", "XyZ", "Anna", "Alien", "Alberto", 666 | "Alberts", "Allie", "Alliese", "Alabama", "Banane", 667 | "Blabla", "Amber", "Ammun", "Akka", "Akko", "Albertoo", 668 | "Amma" 669 | }; 670 | 671 | for (String key : keys) { 672 | trie.put(key, key); 673 | } 674 | 675 | // SortedMap map; 676 | // Iterator iterator; 677 | // Iterator> entryIterator; 678 | // Map.Entry entry; 679 | // 680 | // map = trie.prefixMap("Al"); 681 | // TestCase.assertEquals(8, map.size()); 682 | // TestCase.assertEquals("Alabama", map.firstKey()); 683 | // TestCase.assertEquals("Alliese", map.lastKey()); 684 | // TestCase.assertEquals("Albertoo", map.get("Albertoo")); 685 | // TestCase.assertNotNull(trie.get("Xavier")); 686 | // TestCase.assertNull(map.get("Xavier")); 687 | // TestCase.assertNull(trie.get("Alice")); 688 | // TestCase.assertNull(map.get("Alice")); 689 | // iterator = map.values().iterator(); 690 | // TestCase.assertEquals("Alabama", iterator.next()); 691 | // TestCase.assertEquals("Albert", iterator.next()); 692 | // TestCase.assertEquals("Alberto", iterator.next()); 693 | // TestCase.assertEquals("Albertoo", iterator.next()); 694 | // TestCase.assertEquals("Alberts", iterator.next()); 695 | // TestCase.assertEquals("Alien", iterator.next()); 696 | // TestCase.assertEquals("Allie", iterator.next()); 697 | // TestCase.assertEquals("Alliese", iterator.next()); 698 | // TestCase.assertFalse(iterator.hasNext()); 699 | // 700 | // map = trie.prefixMap("Albert"); 701 | // iterator = map.keySet().iterator(); 702 | // TestCase.assertEquals("Albert", iterator.next()); 703 | // TestCase.assertEquals("Alberto", iterator.next()); 704 | // TestCase.assertEquals("Albertoo", iterator.next()); 705 | // TestCase.assertEquals("Alberts", iterator.next()); 706 | // TestCase.assertFalse(iterator.hasNext()); 707 | // TestCase.assertEquals(4, map.size()); 708 | // TestCase.assertEquals("Albert", map.firstKey()); 709 | // TestCase.assertEquals("Alberts", map.lastKey()); 710 | // TestCase.assertNull(trie.get("Albertz")); 711 | // map.put("Albertz", "Albertz"); 712 | // TestCase.assertEquals("Albertz", trie.get("Albertz")); 713 | // TestCase.assertEquals(5, map.size()); 714 | // TestCase.assertEquals("Albertz", map.lastKey()); 715 | // iterator = map.keySet().iterator(); 716 | // TestCase.assertEquals("Albert", iterator.next()); 717 | // TestCase.assertEquals("Alberto", iterator.next()); 718 | // TestCase.assertEquals("Albertoo", iterator.next()); 719 | // TestCase.assertEquals("Alberts", iterator.next()); 720 | // TestCase.assertEquals("Albertz", iterator.next()); 721 | // TestCase.assertFalse(iterator.hasNext()); 722 | // TestCase.assertEquals("Albertz", map.remove("Albertz")); 723 | // 724 | // map = trie.prefixMap("Alberto"); 725 | // TestCase.assertEquals(2, map.size()); 726 | // TestCase.assertEquals("Alberto", map.firstKey()); 727 | // TestCase.assertEquals("Albertoo", map.lastKey()); 728 | // entryIterator = map.entrySet().iterator(); 729 | // entry = entryIterator.next(); 730 | // TestCase.assertEquals("Alberto", entry.getKey()); 731 | // TestCase.assertEquals("Alberto", entry.getValue()); 732 | // entry = entryIterator.next(); 733 | // TestCase.assertEquals("Albertoo", entry.getKey()); 734 | // TestCase.assertEquals("Albertoo", entry.getValue()); 735 | // TestCase.assertFalse(entryIterator.hasNext()); 736 | // trie.put("Albertoad", "Albertoad"); 737 | // TestCase.assertEquals(3, map.size()); 738 | // TestCase.assertEquals("Alberto", map.firstKey()); 739 | // TestCase.assertEquals("Albertoo", map.lastKey()); 740 | // entryIterator = map.entrySet().iterator(); 741 | // entry = entryIterator.next(); 742 | // TestCase.assertEquals("Alberto", entry.getKey()); 743 | // TestCase.assertEquals("Alberto", entry.getValue()); 744 | // entry = entryIterator.next(); 745 | // TestCase.assertEquals("Albertoad", entry.getKey()); 746 | // TestCase.assertEquals("Albertoad", entry.getValue()); 747 | // entry = entryIterator.next(); 748 | // TestCase.assertEquals("Albertoo", entry.getKey()); 749 | // TestCase.assertEquals("Albertoo", entry.getValue()); 750 | // TestCase.assertFalse(entryIterator.hasNext()); 751 | // TestCase.assertEquals("Albertoo", trie.remove("Albertoo")); 752 | // TestCase.assertEquals("Alberto", map.firstKey()); 753 | // TestCase.assertEquals("Albertoad", map.lastKey()); 754 | // TestCase.assertEquals(2, map.size()); 755 | // entryIterator = map.entrySet().iterator(); 756 | // entry = entryIterator.next(); 757 | // TestCase.assertEquals("Alberto", entry.getKey()); 758 | // TestCase.assertEquals("Alberto", entry.getValue()); 759 | // entry = entryIterator.next(); 760 | // TestCase.assertEquals("Albertoad", entry.getKey()); 761 | // TestCase.assertEquals("Albertoad", entry.getValue()); 762 | // TestCase.assertFalse(entryIterator.hasNext()); 763 | // TestCase.assertEquals("Albertoad", trie.remove("Albertoad")); 764 | // trie.put("Albertoo", "Albertoo"); 765 | // 766 | // map = trie.prefixMap("X"); 767 | // TestCase.assertEquals(2, map.size()); 768 | // TestCase.assertFalse(map.containsKey("Albert")); 769 | // TestCase.assertTrue(map.containsKey("Xavier")); 770 | // TestCase.assertFalse(map.containsKey("Xalan")); 771 | // iterator = map.values().iterator(); 772 | // TestCase.assertEquals("Xavier", iterator.next()); 773 | // TestCase.assertEquals("XyZ", iterator.next()); 774 | // TestCase.assertFalse(iterator.hasNext()); 775 | // 776 | // map = trie.prefixMap("An"); 777 | // TestCase.assertEquals(1, map.size()); 778 | // TestCase.assertEquals("Anna", map.firstKey()); 779 | // TestCase.assertEquals("Anna", map.lastKey()); 780 | // iterator = map.keySet().iterator(); 781 | // TestCase.assertEquals("Anna", iterator.next()); 782 | // TestCase.assertFalse(iterator.hasNext()); 783 | // 784 | // map = trie.prefixMap("Ban"); 785 | // TestCase.assertEquals(1, map.size()); 786 | // TestCase.assertEquals("Banane", map.firstKey()); 787 | // TestCase.assertEquals("Banane", map.lastKey()); 788 | // iterator = map.keySet().iterator(); 789 | // TestCase.assertEquals("Banane", iterator.next()); 790 | // TestCase.assertFalse(iterator.hasNext()); 791 | // 792 | // map = trie.prefixMap("Am"); 793 | // TestCase.assertFalse(map.isEmpty()); 794 | // TestCase.assertEquals(3, map.size()); 795 | // TestCase.assertEquals("Amber", trie.remove("Amber")); 796 | // iterator = map.keySet().iterator(); 797 | // TestCase.assertEquals("Amma", iterator.next()); 798 | // TestCase.assertEquals("Ammun", iterator.next()); 799 | // TestCase.assertFalse(iterator.hasNext()); 800 | // iterator = map.keySet().iterator(); 801 | // map.put("Amber", "Amber"); 802 | // TestCase.assertEquals(3, map.size()); 803 | // try { 804 | // iterator.next(); 805 | // TestCase.fail("CME expected"); 806 | // } catch(ConcurrentModificationException expected) {} 807 | // TestCase.assertEquals("Amber", map.firstKey()); 808 | // TestCase.assertEquals("Ammun", map.lastKey()); 809 | // 810 | // map = trie.prefixMap("Ak\0"); 811 | // TestCase.assertTrue(map.isEmpty()); 812 | // 813 | // map = trie.prefixMap("Ak"); 814 | // TestCase.assertEquals(2, map.size()); 815 | // TestCase.assertEquals("Akka", map.firstKey()); 816 | // TestCase.assertEquals("Akko", map.lastKey()); 817 | // map.put("Ak", "Ak"); 818 | // TestCase.assertEquals("Ak", map.firstKey()); 819 | // TestCase.assertEquals("Akko", map.lastKey()); 820 | // TestCase.assertEquals(3, map.size()); 821 | // trie.put("Al", "Al"); 822 | // TestCase.assertEquals(3, map.size()); 823 | // TestCase.assertEquals("Ak", map.remove("Ak")); 824 | // TestCase.assertEquals("Akka", map.firstKey()); 825 | // TestCase.assertEquals("Akko", map.lastKey()); 826 | // TestCase.assertEquals(2, map.size()); 827 | // iterator = map.keySet().iterator(); 828 | // TestCase.assertEquals("Akka", iterator.next()); 829 | // TestCase.assertEquals("Akko", iterator.next()); 830 | // TestCase.assertFalse(iterator.hasNext()); 831 | // TestCase.assertEquals("Al", trie.remove("Al")); 832 | // 833 | // map = trie.prefixMap("Akka"); 834 | // TestCase.assertEquals(1, map.size()); 835 | // TestCase.assertEquals("Akka", map.firstKey()); 836 | // TestCase.assertEquals("Akka", map.lastKey()); 837 | // iterator = map.keySet().iterator(); 838 | // TestCase.assertEquals("Akka", iterator.next()); 839 | // TestCase.assertFalse(iterator.hasNext()); 840 | // 841 | // map = trie.prefixMap("Ab"); 842 | // TestCase.assertTrue(map.isEmpty()); 843 | // TestCase.assertEquals(0, map.size()); 844 | // try { 845 | // Object o = map.firstKey(); 846 | // TestCase.fail("got a first key: " + o); 847 | // } catch(NoSuchElementException nsee) {} 848 | // try { 849 | // Object o = map.lastKey(); 850 | // TestCase.fail("got a last key: " + o); 851 | // } catch(NoSuchElementException nsee) {} 852 | // iterator = map.values().iterator(); 853 | // TestCase.assertFalse(iterator.hasNext()); 854 | // 855 | // map = trie.prefixMap("Albertooo"); 856 | // TestCase.assertTrue(map.isEmpty()); 857 | // TestCase.assertEquals(0, map.size()); 858 | // try { 859 | // Object o = map.firstKey(); 860 | // TestCase.fail("got a first key: " + o); 861 | // } catch(NoSuchElementException nsee) {} 862 | // try { 863 | // Object o = map.lastKey(); 864 | // TestCase.fail("got a last key: " + o); 865 | // } catch(NoSuchElementException nsee) {} 866 | // iterator = map.values().iterator(); 867 | // TestCase.assertFalse(iterator.hasNext()); 868 | // 869 | // map = trie.prefixMap(""); 870 | // TestCase.assertSame(trie, map); // stricter than necessary, but a good check 871 | // 872 | // map = trie.prefixMap("\0"); 873 | // TestCase.assertTrue(map.isEmpty()); 874 | // TestCase.assertEquals(0, map.size()); 875 | // try { 876 | // Object o = map.firstKey(); 877 | // TestCase.fail("got a first key: " + o); 878 | // } catch(NoSuchElementException nsee) {} 879 | // try { 880 | // Object o = map.lastKey(); 881 | // TestCase.fail("got a last key: " + o); 882 | // } catch(NoSuchElementException nsee) {} 883 | // iterator = map.values().iterator(); 884 | // TestCase.assertFalse(iterator.hasNext()); 885 | } 886 | 887 | @Test 888 | public void testPrefixedByRemoval() { 889 | MCritBitTree trie 890 | = new MCritBitTree(StringKeyAnalyzer.INSTANCE); 891 | 892 | final String[] keys = new String[]{ 893 | "Albert", "Xavier", "XyZ", "Anna", "Alien", "Alberto", 894 | "Alberts", "Allie", "Alliese", "Alabama", "Banane", 895 | "Blabla", "Amber", "Ammun", "Akka", "Akko", "Albertoo", 896 | "Amma" 897 | }; 898 | 899 | for (String key : keys) { 900 | trie.put(key, key); 901 | } 902 | 903 | // SortedMap map = trie.prefixMap("Al"); 904 | // TestCase.assertEquals(8, map.size()); 905 | // Iterator iter = map.keySet().iterator(); 906 | // TestCase.assertEquals("Alabama", iter.next()); 907 | // TestCase.assertEquals("Albert", iter.next()); 908 | // TestCase.assertEquals("Alberto", iter.next()); 909 | // TestCase.assertEquals("Albertoo", iter.next()); 910 | // TestCase.assertEquals("Alberts", iter.next()); 911 | // TestCase.assertEquals("Alien", iter.next()); 912 | // iter.remove(); 913 | // TestCase.assertEquals(7, map.size()); 914 | // TestCase.assertEquals("Allie", iter.next()); 915 | // TestCase.assertEquals("Alliese", iter.next()); 916 | // TestCase.assertFalse(iter.hasNext()); 917 | // 918 | // map = trie.prefixMap("Ak"); 919 | // TestCase.assertEquals(2, map.size()); 920 | // iter = map.keySet().iterator(); 921 | // TestCase.assertEquals("Akka", iter.next()); 922 | // iter.remove(); 923 | // TestCase.assertEquals(1, map.size()); 924 | // TestCase.assertEquals("Akko", iter.next()); 925 | // if(iter.hasNext()) 926 | // TestCase.fail("shouldn't have next (but was: " + iter.next() + ")"); 927 | // TestCase.assertFalse(iter.hasNext()); 928 | } 929 | 930 | @Test 931 | public void testTraverseWithAllNullBitKey() { 932 | MCritBitTree trie 933 | = new MCritBitTree(StringKeyAnalyzer.INSTANCE); 934 | 935 | // 936 | // One entry in the Trie 937 | // Entry is stored at the root 938 | // 939 | 940 | // trie.put("", "All Bits Are Zero"); 941 | trie.put("\0", "All Bits Are Zero"); 942 | 943 | // 944 | // / ("") <-- root 945 | // \_/ \ 946 | // null 947 | // 948 | 949 | final List strings = new ArrayList(); 950 | trie.traverse(new Cursor() { 951 | public Decision select(Entry entry) { 952 | strings.add(entry.getValue()); 953 | return Decision.CONTINUE; 954 | } 955 | }); 956 | 957 | TestCase.assertEquals(1, strings.size()); 958 | // 959 | // strings.clear(); 960 | // for (String s : trie.values()) { 961 | // strings.add(s); 962 | // } 963 | // TestCase.assertEquals(1, strings.size()); 964 | } 965 | 966 | @Test 967 | public void testSelectWithAllNullBitKey() { 968 | MCritBitTree trie 969 | = new MCritBitTree(StringKeyAnalyzer.INSTANCE); 970 | 971 | // trie.put("", "All Bits Are Zero"); 972 | trie.put("\0", "All Bits Are Zero"); 973 | 974 | // final List strings = new ArrayList(); 975 | // trie.select("Hello", new Cursor() { 976 | // public Decision select(Entry entry) { 977 | // strings.add(entry.getValue()); 978 | // return Decision.CONTINUE; 979 | // } 980 | // }); 981 | // TestCase.assertEquals(1, strings.size()); 982 | } 983 | 984 | private static class TestCursor implements Cursor { 985 | private List keys; 986 | private List values; 987 | private Object selectFor; 988 | private List toRemove; 989 | private int index = 0; 990 | 991 | TestCursor(Object... objects) { 992 | if(objects.length % 2 != 0) 993 | throw new IllegalArgumentException("must be * 2"); 994 | 995 | keys = new ArrayList(objects.length / 2); 996 | values = new ArrayList(keys.size()); 997 | toRemove = Collections.emptyList(); 998 | for(int i = 0; i < objects.length; i++) { 999 | keys.add(objects[i]); 1000 | values.add(objects[++i]); 1001 | } 1002 | } 1003 | 1004 | void addToRemove(Object... objects) { 1005 | toRemove = new ArrayList(Arrays.asList(objects)); 1006 | } 1007 | 1008 | void remove(Object... objects) { 1009 | for (Object object : objects) { 1010 | int idx = keys.indexOf(object); 1011 | keys.remove(idx); 1012 | values.remove(idx); 1013 | } 1014 | } 1015 | 1016 | void starting() { 1017 | index = 0; 1018 | } 1019 | 1020 | public void checkKey(Object k) { 1021 | TestCase.assertEquals(keys.get(index++), k); 1022 | } 1023 | 1024 | public void checkValue(Object o) { 1025 | TestCase.assertEquals(values.get(index++), o); 1026 | } 1027 | 1028 | public Decision select(Entry entry) { 1029 | // System.out.println("Scanning: " + entry.getKey()); 1030 | TestCase.assertEquals(keys.get(index), entry.getKey()); 1031 | TestCase.assertEquals(values.get(index), entry.getValue()); 1032 | index++; 1033 | 1034 | if(toRemove.contains(entry.getKey())) { 1035 | // System.out.println("Removing: " + entry.getKey()); 1036 | index--; 1037 | keys.remove(index); 1038 | values.remove(index); 1039 | toRemove.remove(entry.getKey()); 1040 | return Decision.REMOVE; 1041 | } 1042 | 1043 | if(selectFor != null && selectFor.equals(entry.getKey())) 1044 | return Decision.EXIT; 1045 | else 1046 | return Decision.CONTINUE; 1047 | } 1048 | 1049 | void finished() { 1050 | TestCase.assertEquals(keys.size(), index); 1051 | } 1052 | } 1053 | 1054 | private static void assertEqualArrays(Object[] a, Object[] b) { 1055 | TestCase.assertTrue(Arrays.equals(a, b)); 1056 | } 1057 | } 1058 | -------------------------------------------------------------------------------- /src/test/java/org/ardverk/collection/SerializationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005-2008 Roger Kapsi, Sam Berlin 3 | * 4 | * Modified 2011 Jason Fager 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package org.ardverk.collection; 20 | 21 | import io.prelink.critbit.MCritBitTree; 22 | 23 | import java.io.ByteArrayInputStream; 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.IOException; 26 | import java.io.ObjectInputStream; 27 | import java.io.ObjectOutputStream; 28 | 29 | import junit.framework.TestCase; 30 | 31 | import org.junit.Test; 32 | 33 | public class SerializationTest { 34 | 35 | @Test 36 | public void serialize() throws IOException, ClassNotFoundException { 37 | MCritBitTree trie1 38 | = new MCritBitTree( 39 | StringKeyAnalyzer.INSTANCE); 40 | trie1.put("Hello", "World"); 41 | 42 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 43 | ObjectOutputStream oos = new ObjectOutputStream(baos); 44 | oos.writeObject(trie1); 45 | oos.close(); 46 | 47 | ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 48 | ObjectInputStream ois = new ObjectInputStream(bais); 49 | 50 | @SuppressWarnings("unchecked") 51 | MCritBitTree trie2 = 52 | (MCritBitTree)ois.readObject(); 53 | ois.close(); 54 | 55 | TestCase.assertEquals(trie1.size(), trie2.size()); 56 | TestCase.assertEquals("World", trie2.get("Hello")); 57 | } 58 | 59 | @Test 60 | public void prefixMap() throws IOException, ClassNotFoundException { 61 | MCritBitTree trie1 62 | = new MCritBitTree( 63 | StringKeyAnalyzer.INSTANCE); 64 | trie1.put("Hello", "World"); 65 | 66 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 67 | ObjectOutputStream oos = new ObjectOutputStream(baos); 68 | oos.writeObject(trie1); 69 | oos.close(); 70 | 71 | ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 72 | ObjectInputStream ois = new ObjectInputStream(bais); 73 | 74 | @SuppressWarnings("unchecked") 75 | MCritBitTree trie2 = 76 | (MCritBitTree)ois.readObject(); 77 | ois.close(); 78 | 79 | // TestCase.assertEquals(1, trie1.prefixMap("Hello").size()); 80 | // TestCase.assertEquals(1, trie2.prefixMap("Hello").size()); 81 | } 82 | } 83 | --------------------------------------------------------------------------------