View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.io.IOException;
22  import java.nio.ByteBuffer;
23  import java.util.HashSet;
24  import java.util.Set;
25  
26  /* ------------------------------------------------------------ */
27  /** A Trie String lookup data structure using a fixed size array.
28   * <p>This implementation is always case insensitive and is optimal for
29   * a small number of fixed strings with few special characters.
30   * </p>
31   * @param <V>
32   */
33  public class ArrayTrie<V> extends AbstractTrie<V>
34  {
35      /**
36       * The Size of a Trie row is how many characters can be looked
37       * up directly without going to a big index.  This is set at 
38       * 32 to cover case insensitive alphabet and a few other common
39       * characters. 
40       */
41      private static final int ROW_SIZE = 32;
42      
43      /**
44       * The index lookup table, this maps a character as a byte 
45       * (ISO-8859-1 or UTF8) to an index within a Trie row
46       */
47      private static final int[] __lookup = 
48      { // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
49     /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
50     /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30, 
51     /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, -1, -1,
52     /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1,
53     /*4*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
54     /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
55     /*6*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
56     /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
57      };
58      
59      /**
60       * The Trie rows in a single array which allows a lookup of row,character
61       * to the next row in the Trie.  This is actually a 2 dimensional
62       * array that has been flattened to achieve locality of reference.
63       * The first ROW_SIZE entries are for row 0, then next ROW_SIZE 
64       * entries are for row 1 etc.   So in general instead of using
65       * _rows[row][index], we use _rows[row*ROW_SIZE+index] to look up
66       * the next row for a given character.
67       * 
68       * The array is of characters rather than integers to save space. 
69       */
70      private final char[] _rowIndex;
71      
72      /**
73       * The key (if any) for a Trie row. 
74       * A row may be a leaf, a node or both in the Trie tree.
75       */
76      private final String[] _key;
77      
78      /**
79       * The value (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 Object[] _value;
83      
84      /**
85       * A big index for each row.
86       * If a character outside of the lookup map is needed,
87       * then a big index will be created for the row, with
88       * 256 entries, one for each possible byte.
89       */
90      private char[][] _bigIndex;
91      
92      /**
93       * The number of rows allocated
94       */
95      private char _rows;
96  
97      public ArrayTrie()
98      {
99          this(128);
100     }
101     
102     public ArrayTrie(int capacityInNodes)
103     {
104         super(true);
105         _value=new Object[capacityInNodes];
106         _rowIndex=new char[capacityInNodes*32];
107         _key=new String[capacityInNodes];
108     }
109     
110     
111     /* ------------------------------------------------------------ */
112     @Override
113     public boolean put(String s, V v)
114     {
115         int t=0;
116         int k;
117         int limit = s.length();
118         for(k=0; k < limit; k++)
119         {
120             char c=s.charAt(k);
121             
122             int index=__lookup[c&0x7f];
123             if (index>=0)
124             {
125                 int idx=t*ROW_SIZE+index;
126                 t=_rowIndex[idx];
127                 if (t==0)
128                 {
129                     if (++_rows>=_value.length)
130                         return false;
131                     t=_rowIndex[idx]=_rows;
132                 }
133             }
134             else if (c>127)
135                 throw new IllegalArgumentException("non ascii character");
136             else
137             {
138                 if (_bigIndex==null)
139                     _bigIndex=new char[_value.length][];
140                 if (t>=_bigIndex.length)
141                     return false;
142                 char[] big=_bigIndex[t];
143                 if (big==null)
144                     big=_bigIndex[t]=new char[128];
145                 t=big[c];
146                 if (t==0)
147                 {
148                     if (_rows==_value.length)
149                         return false;
150                     t=big[c]=++_rows;
151                 }
152             }
153         }
154         _key[t]=v==null?null:s;
155         V old=(V)_value[t];
156         _value[t] = v;
157         return true;
158     }
159 
160     /* ------------------------------------------------------------ */
161     @Override
162     public V get(String s, int offset, int len)
163     {
164         int t = 0;
165         for(int i=0; i < len; i++)
166         {
167             char c=s.charAt(offset+i);
168             int index=__lookup[c&0x7f];
169             if (index>=0)
170             {
171                 int idx=t*ROW_SIZE+index;
172                 t=_rowIndex[idx];
173                 if (t==0)
174                     return null;
175             }
176             else
177             {
178                 char[] big = _bigIndex==null?null:_bigIndex[t];
179                 if (big==null)
180                     return null;
181                 t=big[c];
182                 if (t==0)
183                     return null;
184             }
185         }
186         return (V)_value[t];
187     }
188 
189     /* ------------------------------------------------------------ */
190     @Override
191     public V get(ByteBuffer b,int offset,int len)
192     {
193         int t = 0;
194         for(int i=0; i < len; i++)
195         {
196             byte c=b.get(offset+i);
197             int index=__lookup[c&0x7f];
198             if (index>=0)
199             {
200                 int idx=t*ROW_SIZE+index;
201                 t=_rowIndex[idx];
202                 if (t==0)
203                     return null;
204             }
205             else
206             {
207                 char[] big = _bigIndex==null?null:_bigIndex[t];
208                 if (big==null)
209                     return null;
210                 t=big[c];
211                 if (t==0)
212                     return null;
213             }
214         }
215         return (V)_value[t];
216     }
217 
218     /* ------------------------------------------------------------ */
219     @Override
220     public V getBest(byte[] b,int offset,int len)
221     {
222         return getBest(0,b,offset,len);
223     }
224 
225     /* ------------------------------------------------------------ */
226     @Override
227     public V getBest(ByteBuffer b,int offset,int len)
228     {
229         if (b.hasArray())
230             return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
231         return getBest(0,b,offset,len);
232     }
233 
234     /* ------------------------------------------------------------ */
235     @Override
236     public V getBest(String s, int offset, int len)
237     {
238         return getBest(0,s,offset,len);
239     }
240     
241     /* ------------------------------------------------------------ */
242     private V getBest(int t, String s, int offset, int len)
243     {
244         int pos=offset;
245         for(int i=0; i < len; i++)
246         {
247             char c=s.charAt(pos++);
248             int index=__lookup[c&0x7f];
249             if (index>=0)
250             {
251                 int idx=t*ROW_SIZE+index;
252                 int nt=_rowIndex[idx];
253                 if (nt==0)
254                     break;
255                 t=nt;
256             }
257             else
258             {
259                 char[] big = _bigIndex==null?null:_bigIndex[t];
260                 if (big==null)
261                     return null;
262                 int nt=big[c];
263                 if (nt==0)
264                     break;
265                 t=nt;
266             }
267             
268             // Is the next Trie is a match
269             if (_key[t]!=null)
270             {
271                 // Recurse so we can remember this possibility
272                 V best=getBest(t,s,offset+i+1,len-i-1);
273                 if (best!=null)
274                     return best;
275                 return (V)_value[t];
276             }
277         }
278         return (V)_value[t];
279     }
280 
281     /* ------------------------------------------------------------ */
282     private V getBest(int t,byte[] b,int offset,int len)
283     {
284         for(int i=0; i < len; i++)
285         {
286             byte c=b[offset+i];
287             int index=__lookup[c&0x7f];
288             if (index>=0)
289             {
290                 int idx=t*ROW_SIZE+index;
291                 int nt=_rowIndex[idx];
292                 if (nt==0)
293                     break;
294                 t=nt;
295             }
296             else
297             {
298                 char[] big = _bigIndex==null?null:_bigIndex[t];
299                 if (big==null)
300                     return null;
301                 int nt=big[c];
302                 if (nt==0)
303                     break;
304                 t=nt;
305             }
306             
307             // Is the next Trie is a match
308             if (_key[t]!=null)
309             {
310                 // Recurse so we can remember this possibility
311                 V best=getBest(t,b,offset+i+1,len-i-1);
312                 if (best!=null)
313                     return best;
314                 break;
315             }
316         }
317         return (V)_value[t];
318     }
319     
320     private V getBest(int t,ByteBuffer b,int offset,int len)
321     {
322         int pos=b.position()+offset;
323         for(int i=0; i < len; i++)
324         {
325             byte c=b.get(pos++);
326             int index=__lookup[c&0x7f];
327             if (index>=0)
328             {
329                 int idx=t*ROW_SIZE+index;
330                 int nt=_rowIndex[idx];
331                 if (nt==0)
332                     break;
333                 t=nt;
334             }
335             else
336             {
337                 char[] big = _bigIndex==null?null:_bigIndex[t];
338                 if (big==null)
339                     return null;
340                 int nt=big[c];
341                 if (nt==0)
342                     break;
343                 t=nt;
344             }
345             
346             // Is the next Trie is a match
347             if (_key[t]!=null)
348             {
349                 // Recurse so we can remember this possibility
350                 V best=getBest(t,b,offset+i+1,len-i-1);
351                 if (best!=null)
352                     return best;
353                 break;
354             }
355         }
356         return (V)_value[t];
357     }
358     
359     
360     
361 
362     @Override
363     public String toString()
364     {
365         StringBuilder buf = new StringBuilder();
366         toString(buf,0);
367         
368         if (buf.length()==0)
369             return "{}";
370         
371         buf.setCharAt(0,'{');
372         buf.append('}');
373         return buf.toString();
374     }
375 
376 
377     private <V> void toString(Appendable out, int t)
378     {
379         if (_value[t]!=null)
380         {
381             try
382             {
383                 out.append(',');
384                 out.append(_key[t]);
385                 out.append('=');
386                 out.append(_value[t].toString());
387             }
388             catch (IOException e)
389             {
390                 throw new RuntimeException(e);
391             }
392         }
393 
394         for(int i=0; i < ROW_SIZE; i++)
395         {
396             int idx=t*ROW_SIZE+i;
397             if (_rowIndex[idx] != 0)
398                 toString(out,_rowIndex[idx]);
399         }
400 
401         char[] big = _bigIndex==null?null:_bigIndex[t];
402         if (big!=null)
403         {
404             for (int i:big)
405                 if (i!=0)
406                     toString(out,i);
407         }
408 
409     }
410 
411     @Override
412     public Set<String> keySet()
413     {
414         Set<String> keys = new HashSet<>();
415         keySet(keys,0);
416         return keys;
417     }
418     
419     private void keySet(Set<String> set, int t)
420     {
421         if (t<_value.length&&_value[t]!=null)
422             set.add(_key[t]);
423 
424         for(int i=0; i < ROW_SIZE; i++)
425         {
426             int idx=t*ROW_SIZE+i;
427             if (idx<_rowIndex.length && _rowIndex[idx] != 0)
428                 keySet(set,_rowIndex[idx]);
429         }
430         
431         char[] big = _bigIndex==null||t>=_bigIndex.length?null:_bigIndex[t];
432         if (big!=null)
433         {
434             for (int i:big)
435                 if (i!=0)
436                     keySet(set,i);
437         }
438     }
439     
440     @Override
441     public boolean isFull()
442     {
443         return _rows+1==_key.length;
444     }
445 }