1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
31
32
33
34
35
36
37
38
39
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
57
58 public StringMap()
59 {}
60
61
62
63
64
65 public StringMap(boolean ignoreCase)
66 {
67 this();
68 _ignoreCase=ignoreCase;
69 }
70
71
72
73
74
75
76
77 public StringMap(boolean ignoreCase,int width)
78 {
79 this();
80 _ignoreCase=ignoreCase;
81 _width=width;
82 }
83
84
85
86
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
103
104
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
147 charLoop:
148 for (int i=0;i<key.length();i++)
149 {
150 char c=key.charAt(i);
151
152
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
162 while (node!=null)
163 {
164
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
175
176 if (ni==0)
177 {
178
179 prev=node;
180 node=node._next;
181 }
182 else
183 {
184
185 node.split(this,ni);
186 i--;
187 ni=-1;
188 continue charLoop;
189 }
190 }
191
192
193 node = new Node(_ignoreCase,key,i);
194
195 if (prev!=null)
196 prev._next=node;
197 else if (parent!=null)
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
217 _root=node;
218 break;
219 }
220
221
222 if (node!=null)
223 {
224
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
262
263
264
265
266
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
277 charLoop:
278 for (int i=0;i<length;i++)
279 {
280 char c=key.charAt(offset+i);
281
282
283 if (ni==-1)
284 {
285 ni=0;
286 node=(node._children==null)?null:node._children[c%_width];
287 }
288
289
290 while (node!=null)
291 {
292
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
302 if (ni>0) return null;
303
304
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
318
319
320
321
322
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
333 charLoop:
334 for (int i=0;i<length;i++)
335 {
336 char c=key[offset+i];
337
338
339 if (ni==-1)
340 {
341 ni=0;
342 node=(node._children==null)?null:node._children[c%_width];
343 }
344
345
346 while (node!=null)
347 {
348
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
358 if (ni>0) return null;
359
360
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
374
375
376
377
378
379
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
390 charLoop:
391 for (int i=0;i<maxLength;i++)
392 {
393 char c=(char)key[offset+i];
394
395
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;
404 node=child;
405 }
406
407
408 while (node!=null)
409 {
410
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
420 if (ni>0) return null;
421
422
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
463 charLoop:
464 for (int i=0;i<key.length();i++)
465 {
466 char c=key.charAt(i);
467
468
469 if (ni==-1)
470 {
471 ni=0;
472 node=(node._children==null)?null:node._children[c%_width];
473 }
474
475
476 while (node!=null)
477 {
478
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
488 if (ni>0) return null;
489
490
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 }