Mercurial Hosting > luan
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 } |