comparison src/org/eclipse/jetty/util/StringMap.java @ 802:3428c60d7cfc

replace jetty jars with source
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 07 Sep 2016 21:15:48 -0600
parents
children
comparison
equal deleted inserted replaced
801:6a21393191c1 802:3428c60d7cfc
1 //
2 // ========================================================================
3 // Copyright (c) 1995-2014 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.Externalizable;
22 import java.util.AbstractMap;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Map;
27 import java.util.Set;
28
29 /* ------------------------------------------------------------ */
30 /** Map implementation Optimized for Strings keys..
31 * This String Map has been optimized for mapping small sets of
32 * Strings where the most frequently accessed Strings have been put to
33 * the map first.
34 *
35 * It also has the benefit that it can look up entries by substring or
36 * sections of char and byte arrays. This can prevent many String
37 * objects from being created just to look up in the map.
38 *
39 * This map is NOT synchronized.
40 */
41 public class StringMap extends AbstractMap implements Externalizable
42 {
43 public static final boolean CASE_INSENSTIVE=true;
44 protected static final int __HASH_WIDTH=17;
45
46 /* ------------------------------------------------------------ */
47 protected int _width=__HASH_WIDTH;
48 protected Node _root=new Node();
49 protected boolean _ignoreCase=false;
50 protected NullEntry _nullEntry=null;
51 protected Object _nullValue=null;
52 protected HashSet _entrySet=new HashSet(3);
53 protected Set _umEntrySet=Collections.unmodifiableSet(_entrySet);
54
55 /* ------------------------------------------------------------ */
56 /** Constructor.
57 */
58 public StringMap()
59 {}
60
61 /* ------------------------------------------------------------ */
62 /** Constructor.
63 * @param ignoreCase
64 */
65 public StringMap(boolean ignoreCase)
66 {
67 this();
68 _ignoreCase=ignoreCase;
69 }
70
71 /* ------------------------------------------------------------ */
72 /** Constructor.
73 * @param ignoreCase
74 * @param width Width of hash tables, larger values are faster but
75 * use more memory.
76 */
77 public StringMap(boolean ignoreCase,int width)
78 {
79 this();
80 _ignoreCase=ignoreCase;
81 _width=width;
82 }
83
84 /* ------------------------------------------------------------ */
85 /** Set the ignoreCase attribute.
86 * @param ic If true, the map is case insensitive for keys.
87 */
88 public void setIgnoreCase(boolean ic)
89 {
90 if (_root._children!=null)
91 throw new IllegalStateException("Must be set before first put");
92 _ignoreCase=ic;
93 }
94
95 /* ------------------------------------------------------------ */
96 public boolean isIgnoreCase()
97 {
98 return _ignoreCase;
99 }
100
101 /* ------------------------------------------------------------ */
102 /** Set the hash width.
103 * @param width Width of hash tables, larger values are faster but
104 * use more memory.
105 */
106 public void setWidth(int width)
107 {
108 _width=width;
109 }
110
111 /* ------------------------------------------------------------ */
112 public int getWidth()
113 {
114 return _width;
115 }
116
117 /* ------------------------------------------------------------ */
118 @Override
119 public Object put(Object key, Object value)
120 {
121 if (key==null)
122 return put(null,value);
123 return put(key.toString(),value);
124 }
125
126 /* ------------------------------------------------------------ */
127 public Object put(String key, Object value)
128 {
129 if (key==null)
130 {
131 Object oldValue=_nullValue;
132 _nullValue=value;
133 if (_nullEntry==null)
134 {
135 _nullEntry=new NullEntry();
136 _entrySet.add(_nullEntry);
137 }
138 return oldValue;
139 }
140
141 Node node = _root;
142 int ni=-1;
143 Node prev = null;
144 Node parent = null;
145
146 // look for best match
147 charLoop:
148 for (int i=0;i<key.length();i++)
149 {
150 char c=key.charAt(i);
151
152 // Advance node
153 if (ni==-1)
154 {
155 parent=node;
156 prev=null;
157 ni=0;
158 node=(node._children==null)?null:node._children[c%_width];
159 }
160
161 // Loop through a node chain at the same level
162 while (node!=null)
163 {
164 // If it is a matching node, goto next char
165 if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
166 {
167 prev=null;
168 ni++;
169 if (ni==node._char.length)
170 ni=-1;
171 continue charLoop;
172 }
173
174 // no char match
175 // if the first char,
176 if (ni==0)
177 {
178 // look along the chain for a char match
179 prev=node;
180 node=node._next;
181 }
182 else
183 {
184 // Split the current node!
185 node.split(this,ni);
186 i--;
187 ni=-1;
188 continue charLoop;
189 }
190 }
191
192 // We have run out of nodes, so as this is a put, make one
193 node = new Node(_ignoreCase,key,i);
194
195 if (prev!=null) // add to end of chain
196 prev._next=node;
197 else if (parent!=null) // add new child
198 {
199 if (parent._children==null)
200 parent._children=new Node[_width];
201 parent._children[c%_width]=node;
202 int oi=node._ochar[0]%_width;
203 if (node._ochar!=null && node._char[0]%_width!=oi)
204 {
205 if (parent._children[oi]==null)
206 parent._children[oi]=node;
207 else
208 {
209 Node n=parent._children[oi];
210 while(n._next!=null)
211 n=n._next;
212 n._next=node;
213 }
214 }
215 }
216 else // this is the root.
217 _root=node;
218 break;
219 }
220
221 // Do we have a node
222 if (node!=null)
223 {
224 // Split it if we are in the middle
225 if(ni>0)
226 node.split(this,ni);
227
228 Object old = node._value;
229 node._key=key;
230 node._value=value;
231 _entrySet.add(node);
232 return old;
233 }
234 return null;
235 }
236
237 /* ------------------------------------------------------------ */
238 @Override
239 public Object get(Object key)
240 {
241 if (key==null)
242 return _nullValue;
243 if (key instanceof String)
244 return get((String)key);
245 return get(key.toString());
246 }
247
248 /* ------------------------------------------------------------ */
249 public Object get(String key)
250 {
251 if (key==null)
252 return _nullValue;
253
254 Map.Entry entry = getEntry(key,0,key.length());
255 if (entry==null)
256 return null;
257 return entry.getValue();
258 }
259
260 /* ------------------------------------------------------------ */
261 /** Get a map entry by substring key.
262 * @param key String containing the key
263 * @param offset Offset of the key within the String.
264 * @param length The length of the key
265 * @return The Map.Entry for the key or null if the key is not in
266 * the map.
267 */
268 public Map.Entry getEntry(String key,int offset, int length)
269 {
270 if (key==null)
271 return _nullEntry;
272
273 Node node = _root;
274 int ni=-1;
275
276 // look for best match
277 charLoop:
278 for (int i=0;i<length;i++)
279 {
280 char c=key.charAt(offset+i);
281
282 // Advance node
283 if (ni==-1)
284 {
285 ni=0;
286 node=(node._children==null)?null:node._children[c%_width];
287 }
288
289 // Look through the node chain
290 while (node!=null)
291 {
292 // If it is a matching node, goto next char
293 if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
294 {
295 ni++;
296 if (ni==node._char.length)
297 ni=-1;
298 continue charLoop;
299 }
300
301 // No char match, so if mid node then no match at all.
302 if (ni>0) return null;
303
304 // try next in chain
305 node=node._next;
306 }
307 return null;
308 }
309
310 if (ni>0) return null;
311 if (node!=null && node._key==null)
312 return null;
313 return node;
314 }
315
316 /* ------------------------------------------------------------ */
317 /** Get a map entry by char array key.
318 * @param key char array containing the key
319 * @param offset Offset of the key within the array.
320 * @param length The length of the key
321 * @return The Map.Entry for the key or null if the key is not in
322 * the map.
323 */
324 public Map.Entry getEntry(char[] key,int offset, int length)
325 {
326 if (key==null)
327 return _nullEntry;
328
329 Node node = _root;
330 int ni=-1;
331
332 // look for best match
333 charLoop:
334 for (int i=0;i<length;i++)
335 {
336 char c=key[offset+i];
337
338 // Advance node
339 if (ni==-1)
340 {
341 ni=0;
342 node=(node._children==null)?null:node._children[c%_width];
343 }
344
345 // While we have a node to try
346 while (node!=null)
347 {
348 // If it is a matching node, goto next char
349 if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
350 {
351 ni++;
352 if (ni==node._char.length)
353 ni=-1;
354 continue charLoop;
355 }
356
357 // No char match, so if mid node then no match at all.
358 if (ni>0) return null;
359
360 // try next in chain
361 node=node._next;
362 }
363 return null;
364 }
365
366 if (ni>0) return null;
367 if (node!=null && node._key==null)
368 return null;
369 return node;
370 }
371
372 /* ------------------------------------------------------------ */
373 /** Get a map entry by byte array key, using as much of the passed key as needed for a match.
374 * A simple 8859-1 byte to char mapping is assumed.
375 * @param key char array containing the key
376 * @param offset Offset of the key within the array.
377 * @param maxLength The length of the key
378 * @return The Map.Entry for the key or null if the key is not in
379 * the map.
380 */
381 public Map.Entry getBestEntry(byte[] key,int offset, int maxLength)
382 {
383 if (key==null)
384 return _nullEntry;
385
386 Node node = _root;
387 int ni=-1;
388
389 // look for best match
390 charLoop:
391 for (int i=0;i<maxLength;i++)
392 {
393 char c=(char)key[offset+i];
394
395 // Advance node
396 if (ni==-1)
397 {
398 ni=0;
399
400 Node child = (node._children==null)?null:node._children[c%_width];
401
402 if (child==null && i>0)
403 return node; // This is the best match
404 node=child;
405 }
406
407 // While we have a node to try
408 while (node!=null)
409 {
410 // If it is a matching node, goto next char
411 if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
412 {
413 ni++;
414 if (ni==node._char.length)
415 ni=-1;
416 continue charLoop;
417 }
418
419 // No char match, so if mid node then no match at all.
420 if (ni>0) return null;
421
422 // try next in chain
423 node=node._next;
424 }
425 return null;
426 }
427
428 if (ni>0) return null;
429 if (node!=null && node._key==null)
430 return null;
431 return node;
432 }
433
434
435 /* ------------------------------------------------------------ */
436 @Override
437 public Object remove(Object key)
438 {
439 if (key==null)
440 return remove(null);
441 return remove(key.toString());
442 }
443
444 /* ------------------------------------------------------------ */
445 public Object remove(String key)
446 {
447 if (key==null)
448 {
449 Object oldValue=_nullValue;
450 if (_nullEntry!=null)
451 {
452 _entrySet.remove(_nullEntry);
453 _nullEntry=null;
454 _nullValue=null;
455 }
456 return oldValue;
457 }
458
459 Node node = _root;
460 int ni=-1;
461
462 // look for best match
463 charLoop:
464 for (int i=0;i<key.length();i++)
465 {
466 char c=key.charAt(i);
467
468 // Advance node
469 if (ni==-1)
470 {
471 ni=0;
472 node=(node._children==null)?null:node._children[c%_width];
473 }
474
475 // While we have a node to try
476 while (node!=null)
477 {
478 // If it is a matching node, goto next char
479 if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
480 {
481 ni++;
482 if (ni==node._char.length)
483 ni=-1;
484 continue charLoop;
485 }
486
487 // No char match, so if mid node then no match at all.
488 if (ni>0) return null;
489
490 // try next in chain
491 node=node._next;
492 }
493 return null;
494 }
495
496 if (ni>0) return null;
497 if (node!=null && node._key==null)
498 return null;
499
500 Object old = node._value;
501 _entrySet.remove(node);
502 node._value=null;
503 node._key=null;
504
505 return old;
506 }
507
508 /* ------------------------------------------------------------ */
509 @Override
510 public Set entrySet()
511 {
512 return _umEntrySet;
513 }
514
515 /* ------------------------------------------------------------ */
516 @Override
517 public int size()
518 {
519 return _entrySet.size();
520 }
521
522 /* ------------------------------------------------------------ */
523 @Override
524 public boolean isEmpty()
525 {
526 return _entrySet.isEmpty();
527 }
528
529 /* ------------------------------------------------------------ */
530 @Override
531 public boolean containsKey(Object key)
532 {
533 if (key==null)
534 return _nullEntry!=null;
535 return
536 getEntry(key.toString(),0,key==null?0:key.toString().length())!=null;
537 }
538
539 /* ------------------------------------------------------------ */
540 @Override
541 public void clear()
542 {
543 _root=new Node();
544 _nullEntry=null;
545 _nullValue=null;
546 _entrySet.clear();
547 }
548
549
550 /* ------------------------------------------------------------ */
551 /* ------------------------------------------------------------ */
552 /* ------------------------------------------------------------ */
553 private static class Node implements Map.Entry
554 {
555 char[] _char;
556 char[] _ochar;
557 Node _next;
558 Node[] _children;
559 String _key;
560 Object _value;
561
562 Node(){}
563
564 Node(boolean ignoreCase,String s, int offset)
565 {
566 int l=s.length()-offset;
567 _char=new char[l];
568 _ochar=new char[l];
569 for (int i=0;i<l;i++)
570 {
571 char c=s.charAt(offset+i);
572 _char[i]=c;
573 if (ignoreCase)
574 {
575 char o=c;
576 if (Character.isUpperCase(c))
577 o=Character.toLowerCase(c);
578 else if (Character.isLowerCase(c))
579 o=Character.toUpperCase(c);
580 _ochar[i]=o;
581 }
582 }
583 }
584
585 Node split(StringMap map,int offset)
586 {
587 Node split = new Node();
588 int sl=_char.length-offset;
589
590 char[] tmp=this._char;
591 this._char=new char[offset];
592 split._char = new char[sl];
593 System.arraycopy(tmp,0,this._char,0,offset);
594 System.arraycopy(tmp,offset,split._char,0,sl);
595
596 if (this._ochar!=null)
597 {
598 tmp=this._ochar;
599 this._ochar=new char[offset];
600 split._ochar = new char[sl];
601 System.arraycopy(tmp,0,this._ochar,0,offset);
602 System.arraycopy(tmp,offset,split._ochar,0,sl);
603 }
604
605 split._key=this._key;
606 split._value=this._value;
607 this._key=null;
608 this._value=null;
609 if (map._entrySet.remove(this))
610 map._entrySet.add(split);
611
612 split._children=this._children;
613 this._children=new Node[map._width];
614 this._children[split._char[0]%map._width]=split;
615 if (split._ochar!=null && this._children[split._ochar[0]%map._width]!=split)
616 this._children[split._ochar[0]%map._width]=split;
617
618 return split;
619 }
620
621 public Object getKey(){return _key;}
622 public Object getValue(){return _value;}
623 public Object setValue(Object o){Object old=_value;_value=o;return old;}
624 @Override
625 public String toString()
626 {
627 StringBuilder buf=new StringBuilder();
628 toString(buf);
629 return buf.toString();
630 }
631
632 private void toString(StringBuilder buf)
633 {
634 buf.append("{[");
635 if (_char==null)
636 buf.append('-');
637 else
638 for (int i=0;i<_char.length;i++)
639 buf.append(_char[i]);
640 buf.append(':');
641 buf.append(_key);
642 buf.append('=');
643 buf.append(_value);
644 buf.append(']');
645 if (_children!=null)
646 {
647 for (int i=0;i<_children.length;i++)
648 {
649 buf.append('|');
650 if (_children[i]!=null)
651 _children[i].toString(buf);
652 else
653 buf.append("-");
654 }
655 }
656 buf.append('}');
657 if (_next!=null)
658 {
659 buf.append(",\n");
660 _next.toString(buf);
661 }
662 }
663 }
664
665 /* ------------------------------------------------------------ */
666 /* ------------------------------------------------------------ */
667 private class NullEntry implements Map.Entry
668 {
669 public Object getKey(){return null;}
670 public Object getValue(){return _nullValue;}
671 public Object setValue(Object o)
672 {Object old=_nullValue;_nullValue=o;return old;}
673 @Override
674 public String toString(){return "[:null="+_nullValue+"]";}
675 }
676
677 /* ------------------------------------------------------------ */
678 public void writeExternal(java.io.ObjectOutput out)
679 throws java.io.IOException
680 {
681 HashMap map = new HashMap(this);
682 out.writeBoolean(_ignoreCase);
683 out.writeObject(map);
684 }
685
686 /* ------------------------------------------------------------ */
687 public void readExternal(java.io.ObjectInput in)
688 throws java.io.IOException, ClassNotFoundException
689 {
690 boolean ic=in.readBoolean();
691 HashMap map = (HashMap)in.readObject();
692 setIgnoreCase(ic);
693 this.putAll(map);
694 }
695 }