1 //
2 // ========================================================================
3 // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.util;
20
21 import java.nio.ByteBuffer;
22 import java.util.Arrays;
23 import java.util.HashSet;
24 import java.util.Set;
25
26
27 /**
28 * <p>A Ternary Trie String lookup data structure.</p>
29 * <p>
30 * This Trie is of a fixed size and cannot grow (which can be a good thing with regards to DOS when used as a cache).
31 * </p>
32 * <p>
33 * The Trie is stored in 3 arrays:
34 * </p>
35 * <dl>
36 * <dt>char[] _tree</dt><dd>This is semantically 2 dimensional array flattened into a 1 dimensional char array. The second dimension
37 * is that every 4 sequential elements represents a row of: character; hi index; eq index; low index, used to build a
38 * ternary trie of key strings.</dd>
39 * <dt>String[] _key</dt><dd>An array of key values where each element matches a row in the _tree array. A non zero key element
40 * indicates that the _tree row is a complete key rather than an intermediate character of a longer key.</dd>
41 * <dt>V[] _value</dt><dd>An array of values corresponding to the _key array</dd>
42 * </dl>
43 * <p>The lookup of a value will iterate through the _tree array matching characters. If the equal tree branch is followed,
44 * then the _key array is looked up to see if this is a complete match. If a match is found then the _value array is looked up
45 * to return the matching value.
46 * </p>
47 * <p>
48 * This Trie may be instantiated either as case sensitive or insensitive.
49 * </p>
50 * <p>This Trie is not Threadsafe and contains no mutual exclusion
51 * or deliberate memory barriers. It is intended for an ArrayTrie to be
52 * built by a single thread and then used concurrently by multiple threads
53 * and not mutated during that access. If concurrent mutations of the
54 * Trie is required external locks need to be applied.
55 * </p>
56 *
57 * @param <V> the Entry type
58 */
59 public class ArrayTernaryTrie<V> extends AbstractTrie<V>
60 {
61 private static int LO=1;
62 private static int EQ=2;
63 private static int HI=3;
64
65 /**
66 * The Size of a Trie row is the char, and the low, equal and high
67 * child pointers
68 */
69 private static final int ROW_SIZE = 4;
70
71 /**
72 * The Trie rows in a single array which allows a lookup of row,character
73 * to the next row in the Trie. This is actually a 2 dimensional
74 * array that has been flattened to achieve locality of reference.
75 */
76 private final char[] _tree;
77
78 /**
79 * The key (if any) for a Trie row.
80 * A row may be a leaf, a node or both in the Trie tree.
81 */
82 private final String[] _key;
83
84 /**
85 * The value (if any) for a Trie row.
86 * A row may be a leaf, a node or both in the Trie tree.
87 */
88 private final V[] _value;
89
90 /**
91 * The number of rows allocated
92 */
93 private char _rows;
94
95 /* ------------------------------------------------------------ */
96 /** Create a case insensitive Trie of default capacity.
97 */
98 public ArrayTernaryTrie()
99 {
100 this(128);
101 }
102
103 /* ------------------------------------------------------------ */
104 /** Create a Trie of default capacity
105 * @param insensitive true if the Trie is insensitive to the case of the key.
106 */
107 public ArrayTernaryTrie(boolean insensitive)
108 {
109 this(insensitive,128);
110 }
111
112 /* ------------------------------------------------------------ */
113 /** Create a case insensitive Trie
114 * @param capacity The capacity of the Trie, which is in the worst case
115 * is the total number of characters of all keys stored in the Trie.
116 * The capacity needed is dependent of the shared prefixes of the keys.
117 * For example, a capacity of 6 nodes is required to store keys "foo"
118 * and "bar", but a capacity of only 4 is required to
119 * store "bar" and "bat".
120 */
121 public ArrayTernaryTrie(int capacity)
122 {
123 this(true,capacity);
124 }
125
126 /* ------------------------------------------------------------ */
127 /** Create a Trie
128 * @param insensitive true if the Trie is insensitive to the case of the key.
129 * @param capacity The capacity of the Trie, which is in the worst case
130 * is the total number of characters of all keys stored in the Trie.
131 * The capacity needed is dependent of the shared prefixes of the keys.
132 * For example, a capacity of 6 nodes is required to store keys "foo"
133 * and "bar", but a capacity of only 4 is required to
134 * store "bar" and "bat".
135 */
136 public ArrayTernaryTrie(boolean insensitive, int capacity)
137 {
138 super(insensitive);
139 _value=(V[])new Object[capacity];
140 _tree=new char[capacity*ROW_SIZE];
141 _key=new String[capacity];
142 }
143
144 /* ------------------------------------------------------------ */
145 /** Copy Trie and change capacity by a factor
146 * @param trie the trie to copy from
147 * @param factor the factor to grow the capacity by
148 */
149 public ArrayTernaryTrie(ArrayTernaryTrie<V> trie, double factor)
150 {
151 super(trie.isCaseInsensitive());
152 int capacity=(int)(trie._value.length*factor);
153 _rows=trie._rows;
154 _value=Arrays.copyOf(trie._value, capacity);
155 _tree=Arrays.copyOf(trie._tree, capacity*ROW_SIZE);
156 _key=Arrays.copyOf(trie._key, capacity);
157 }
158
159 /* ------------------------------------------------------------ */
160 @Override
161 public void clear()
162 {
163 _rows=0;
164 Arrays.fill(_value,null);
165 Arrays.fill(_tree,(char)0);
166 Arrays.fill(_key,null);
167 }
168
169 /* ------------------------------------------------------------ */
170 @Override
171 public boolean put(String s, V v)
172 {
173 int t=0;
174 int limit = s.length();
175 int last=0;
176 for(int k=0; k < limit; k++)
177 {
178 char c=s.charAt(k);
179 if(isCaseInsensitive() && c<128)
180 c=StringUtil.lowercases[c];
181
182 while (true)
183 {
184 int row=ROW_SIZE*t;
185
186 // Do we need to create the new row?
187 if (t==_rows)
188 {
189 _rows++;
190 if (_rows>=_key.length)
191 {
192 _rows--;
193 return false;
194 }
195 _tree[row]=c;
196 }
197
198 char n=_tree[row];
199 int diff=n-c;
200 if (diff==0)
201 t=_tree[last=(row+EQ)];
202 else if (diff<0)
203 t=_tree[last=(row+LO)];
204 else
205 t=_tree[last=(row+HI)];
206
207 // do we need a new row?
208 if (t==0)
209 {
210 t=_rows;
211 _tree[last]=(char)t;
212 }
213
214 if (diff==0)
215 break;
216 }
217 }
218
219 // Do we need to create the new row?
220 if (t==_rows)
221 {
222 _rows++;
223 if (_rows>=_key.length)
224 {
225 _rows--;
226 return false;
227 }
228 }
229
230 // Put the key and value
231 _key[t]=v==null?null:s;
232 _value[t] = v;
233
234 return true;
235 }
236
237
238 /* ------------------------------------------------------------ */
239 @Override
240 public V get(String s,int offset, int len)
241 {
242 int t = 0;
243 for(int i=0; i < len;)
244 {
245 char c=s.charAt(offset+i++);
246 if(isCaseInsensitive() && c<128)
247 c=StringUtil.lowercases[c];
248
249 while (true)
250 {
251 int row = ROW_SIZE*t;
252 char n=_tree[row];
253 int diff=n-c;
254
255 if (diff==0)
256 {
257 t=_tree[row+EQ];
258 if (t==0)
259 return null;
260 break;
261 }
262
263 t=_tree[row+hilo(diff)];
264 if (t==0)
265 return null;
266 }
267 }
268
269 return _value[t];
270 }
271
272
273 @Override
274 public V get(ByteBuffer b, int offset, int len)
275 {
276 int t = 0;
277 offset+=b.position();
278
279 for(int i=0; i < len;)
280 {
281 byte c=(byte)(b.get(offset+i++)&0x7f);
282 if(isCaseInsensitive())
283 c=(byte)StringUtil.lowercases[c];
284
285 while (true)
286 {
287 int row = ROW_SIZE*t;
288 char n=_tree[row];
289 int diff=n-c;
290
291 if (diff==0)
292 {
293 t=_tree[row+EQ];
294 if (t==0)
295 return null;
296 break;
297 }
298
299 t=_tree[row+hilo(diff)];
300 if (t==0)
301 return null;
302 }
303 }
304
305 return (V)_value[t];
306 }
307
308 /* ------------------------------------------------------------ */
309 @Override
310 public V getBest(String s)
311 {
312 return getBest(0,s,0,s.length());
313 }
314
315 /* ------------------------------------------------------------ */
316 @Override
317 public V getBest(String s, int offset, int length)
318 {
319 return getBest(0,s,offset,length);
320 }
321
322 /* ------------------------------------------------------------ */
323 private V getBest(int t,String s,int offset,int len)
324 {
325 int node=t;
326 int end=offset+len;
327 loop: while(offset<end)
328 {
329 char c=s.charAt(offset++);
330 len--;
331 if(isCaseInsensitive() && c<128)
332 c=StringUtil.lowercases[c];
333
334 while (true)
335 {
336 int row = ROW_SIZE*t;
337 char n=_tree[row];
338 int diff=n-c;
339
340 if (diff==0)
341 {
342 t=_tree[row+EQ];
343 if (t==0)
344 break loop;
345
346 // if this node is a match, recurse to remember
347 if (_key[t]!=null)
348 {
349 node=t;
350 V better=getBest(t,s,offset,len);
351 if (better!=null)
352 return better;
353 }
354 break;
355 }
356
357 t=_tree[row+hilo(diff)];
358 if (t==0)
359 break loop;
360 }
361 }
362 return (V)_value[node];
363 }
364
365
366 /* ------------------------------------------------------------ */
367 @Override
368 public V getBest(ByteBuffer b, int offset, int len)
369 {
370 if (b.hasArray())
371 return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
372 return getBest(0,b,offset,len);
373 }
374
375 /* ------------------------------------------------------------ */
376 private V getBest(int t,byte[] b, int offset, int len)
377 {
378 int node=t;
379 int end=offset+len;
380 loop: while(offset<end)
381 {
382 byte c=(byte)(b[offset++]&0x7f);
383 len--;
384 if(isCaseInsensitive())
385 c=(byte)StringUtil.lowercases[c];
386
387 while (true)
388 {
389 int row = ROW_SIZE*t;
390 char n=_tree[row];
391 int diff=n-c;
392
393 if (diff==0)
394 {
395 t=_tree[row+EQ];
396 if (t==0)
397 break loop;
398
399 // if this node is a match, recurse to remember
400 if (_key[t]!=null)
401 {
402 node=t;
403 V better=getBest(t,b,offset,len);
404 if (better!=null)
405 return better;
406 }
407 break;
408 }
409
410 t=_tree[row+hilo(diff)];
411 if (t==0)
412 break loop;
413 }
414 }
415 return (V)_value[node];
416 }
417
418 /* ------------------------------------------------------------ */
419 private V getBest(int t,ByteBuffer b, int offset, int len)
420 {
421 int node=t;
422 int o= offset+b.position();
423
424 loop: for(int i=0; i<len; i++)
425 {
426 byte c=(byte)(b.get(o+i)&0x7f);
427 if(isCaseInsensitive())
428 c=(byte)StringUtil.lowercases[c];
429
430 while (true)
431 {
432 int row = ROW_SIZE*t;
433 char n=_tree[row];
434 int diff=n-c;
435
436 if (diff==0)
437 {
438 t=_tree[row+EQ];
439 if (t==0)
440 break loop;
441
442 // if this node is a match, recurse to remember
443 if (_key[t]!=null)
444 {
445 node=t;
446 V best=getBest(t,b,offset+i+1,len-i-1);
447 if (best!=null)
448 return best;
449 }
450 break;
451 }
452
453 t=_tree[row+hilo(diff)];
454 if (t==0)
455 break loop;
456 }
457 }
458 return (V)_value[node];
459 }
460
461 @Override
462 public String toString()
463 {
464 StringBuilder buf = new StringBuilder();
465 for (int r=0;r<=_rows;r++)
466 {
467 if (_key[r]!=null && _value[r]!=null)
468 {
469 buf.append(',');
470 buf.append(_key[r]);
471 buf.append('=');
472 buf.append(_value[r].toString());
473 }
474 }
475 if (buf.length()==0)
476 return "{}";
477
478 buf.setCharAt(0,'{');
479 buf.append('}');
480 return buf.toString();
481 }
482
483
484
485 @Override
486 public Set<String> keySet()
487 {
488 Set<String> keys = new HashSet<>();
489
490 for (int r=0;r<=_rows;r++)
491 {
492 if (_key[r]!=null && _value[r]!=null)
493 keys.add(_key[r]);
494 }
495 return keys;
496 }
497
498 @Override
499 public boolean isFull()
500 {
501 return _rows+1==_key.length;
502 }
503
504 public static int hilo(int diff)
505 {
506 // branchless equivalent to return ((diff<0)?LO:HI);
507 // return 3+2*((diff&Integer.MIN_VALUE)>>Integer.SIZE-1);
508 return 1+(diff|Integer.MAX_VALUE)/(Integer.MAX_VALUE/2);
509 }
510
511 public void dump()
512 {
513 for (int r=0;r<_rows;r++)
514 {
515 char c=_tree[r*ROW_SIZE+0];
516 System.err.printf("%4d [%s,%d,%d,%d] '%s':%s%n",
517 r,
518 (c<' '||c>127)?(""+(int)c):"'"+c+"'",
519 (int)_tree[r*ROW_SIZE+LO],
520 (int)_tree[r*ROW_SIZE+EQ],
521 (int)_tree[r*ROW_SIZE+HI],
522 _key[r],
523 _value[r]);
524 }
525
526 }
527 }