Line | Hits | Source |
---|---|---|
1 | /* | |
2 | * The Apache Software License, Version 1.1 | |
3 | * | |
4 | * Copyright (c) 1999-2002 The Apache Software Foundation. All rights | |
5 | * reserved. | |
6 | * | |
7 | * Redistribution and use in source and binary forms, with or without | |
8 | * modification, are permitted provided that the following conditions | |
9 | * are met: | |
10 | * | |
11 | * 1. Redistributions of source code must retain the above copyright | |
12 | * notice, this list of conditions and the following disclaimer. | |
13 | * | |
14 | * 2. Redistributions in binary form must reproduce the above copyright | |
15 | * notice, this list of conditions and the following disclaimer in | |
16 | * the documentation and/or other materials provided with the | |
17 | * distribution. | |
18 | * | |
19 | * 3. The end-user documentation included with the redistribution, if | |
20 | * any, must include the following acknowlegement: | |
21 | * "This product includes software developed by the | |
22 | * Apache Software Foundation (http://www.apache.org/)." | |
23 | * Alternately, this acknowlegement may appear in the software itself, | |
24 | * if and wherever such third-party acknowlegements normally appear. | |
25 | * | |
26 | * 4. The names "The Jakarta Project", "Commons", and "Apache Software | |
27 | * Foundation" must not be used to endorse or promote products derived | |
28 | * from this software without prior written permission. For written | |
29 | * permission, please contact apache@apache.org. | |
30 | * | |
31 | * 5. Products derived from this software may not be called "Apache" | |
32 | * nor may "Apache" appear in their names without prior written | |
33 | * permission of the Apache Group. | |
34 | * | |
35 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |
36 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
37 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
38 | * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |
39 | * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
40 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
41 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |
42 | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
43 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
44 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |
45 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
46 | * SUCH DAMAGE. | |
47 | * ==================================================================== | |
48 | * | |
49 | * This software consists of voluntary contributions made by many | |
50 | * individuals on behalf of the Apache Software Foundation. For more | |
51 | * information on the Apache Software Foundation, please see | |
52 | * <http://www.apache.org/>. | |
53 | * | |
54 | */ | |
55 | package org.chwf.util; | |
56 | ||
57 | import java.io.Externalizable; | |
58 | import java.io.IOException; | |
59 | import java.io.ObjectInput; | |
60 | import java.io.ObjectOutput; | |
61 | import java.util.AbstractCollection; | |
62 | import java.util.AbstractSet; | |
63 | import java.util.ArrayList; | |
64 | import java.util.Collection; | |
65 | import java.util.Collections; | |
66 | import java.util.ConcurrentModificationException; | |
67 | import java.util.HashMap; | |
68 | import java.util.Iterator; | |
69 | import java.util.List; | |
70 | import java.util.Map; | |
71 | import java.util.NoSuchElementException; | |
72 | import java.util.Set; | |
73 | ||
74 | /** | |
75 | * A map of objects whose mapping entries are sequenced based on the order in | |
76 | * which they were added. This data structure has fast <I>O(1)</I> search | |
77 | * time, deletion time, and insertion time. | |
78 | * | |
79 | * <p>Although this map is sequenced, it cannot implement {@link | |
80 | * java.util.List} because of incompatible interface definitions. The remove | |
81 | * methods in List and Map have different return values (see: {@link | |
82 | * java.util.List#remove(Object)} and {@link java.util.Map#remove(Object)}). | |
83 | * | |
84 | * <p>This class is not thread safe. When a thread safe implementation is | |
85 | * required, use {@link Collections#synchronizedMap(Map)} as it is documented, | |
86 | * or use explicit synchronization controls. | |
87 | * | |
88 | * <p><b>Note</b>: This code was poached from org.apache.commons.collections. | |
89 | * It is replicated here (and its package renamed) to minimize dependencies on | |
90 | * external code in the Chrysalis library. | |
91 | * | |
92 | * @author <a href="mailto:mas@apache.org">Michael A. Smith</A> | |
93 | * @author <a href="mailto:dlr@collab.net">Daniel Rall</a> | |
94 | * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> | |
95 | * @author <a href="mailto:pfstrack@users.sourceforge.net">Paul Strack</a> | |
96 | */ | |
97 | 221732 | public class SequencedHashMap implements Map, Cloneable, Externalizable { |
98 | ||
99 | /** | |
100 | * {@link java.util.Map.Entry} that doubles as a node in the linked list | |
101 | * of sequenced mappings. | |
102 | **/ | |
103 | private static class Entry implements Map.Entry { | |
104 | // Note: This class cannot easily be made clonable. While the actual | |
105 | // implementation of a clone would be simple, defining the semantics is | |
106 | // difficult. If a shallow clone is implemented, then entry.next.prev != | |
107 | // entry, which is unintuitive and probably breaks all sorts of assumptions | |
108 | // in code that uses this implementation. If a deep clone is | |
109 | // implementated, then what happens when the linked list is cyclical (as is | |
110 | // the case with SequencedHashMap)? It's impossible to know in the clone | |
111 | // when to stop cloning, and thus you end up in a recursive loop, | |
112 | // continuously cloning the "next" in the list. | |
113 | ||
114 | private final Object key; | |
115 | private Object value; | |
116 | ||
117 | // package private to allow the SequencedHashMap to access and manipulate | |
118 | // them. | |
119 | Entry next = null; | |
120 | Entry prev = null; | |
121 | ||
122 | public Entry(Object key, Object value) { | |
123 | this.key = key; | |
124 | this.value = value; | |
125 | } | |
126 | ||
127 | // per Map.Entry.getKey() | |
128 | public Object getKey() { | |
129 | return this.key; | |
130 | } | |
131 | ||
132 | // per Map.Entry.getValue() | |
133 | public Object getValue() { | |
134 | return this.value; | |
135 | } | |
136 | ||
137 | // per Map.Entry.setValue() | |
138 | public Object setValue(Object value) { | |
139 | Object oldValue = this.value; | |
140 | this.value = value; | |
141 | return oldValue; | |
142 | } | |
143 | ||
144 | public int hashCode() { | |
145 | // implemented per api docs for Map.Entry.hashCode() | |
146 | return ( | |
147 | (getKey() == null ? 0 : getKey().hashCode()) | |
148 | ^ (getValue() == null ? 0 : getValue().hashCode())); | |
149 | } | |
150 | ||
151 | public boolean equals(Object obj) { | |
152 | if (obj == null) | |
153 | return false; | |
154 | if (obj == this) | |
155 | return true; | |
156 | if (!(obj instanceof Map.Entry)) | |
157 | return false; | |
158 | ||
159 | Map.Entry other = (Map.Entry) obj; | |
160 | ||
161 | // implemented per api docs for Map.Entry.equals(Object) | |
162 | return ( | |
163 | (getKey() == null | |
164 | ? other.getKey() == null | |
165 | : getKey().equals(other.getKey())) | |
166 | && (getValue() == null | |
167 | ? other.getValue() == null | |
168 | : getValue().equals(other.getValue()))); | |
169 | } | |
170 | public String toString() { | |
171 | return "[" + getKey() + "=" + getValue() + "]"; | |
172 | } | |
173 | } | |
174 | ||
175 | /** | |
176 | * Construct an empty sentinel used to hold the head (sentinel.next) and the | |
177 | * tail (sentinel.prev) of the list. The sentinal has a <code>null</code> | |
178 | * key and value. | |
179 | **/ | |
180 | private static final Entry createSentinel() { | |
181 | 19992 | Entry s = new Entry(null, null); |
182 | 19992 | s.prev = s; |
183 | 19992 | s.next = s; |
184 | 19992 | return s; |
185 | } | |
186 | ||
187 | /** | |
188 | * Sentinel used to hold the head and tail of the list of entries. | |
189 | **/ | |
190 | private Entry sentinel; | |
191 | ||
192 | /** | |
193 | * Map of keys to entries | |
194 | **/ | |
195 | private HashMap entries; | |
196 | ||
197 | /** | |
198 | * Holds the number of modifications that have occurred to the map, | |
199 | * excluding modifications made through a collection view's iterator | |
200 | * (e.g. entrySet().iterator().remove()). This is used to create a | |
201 | * fail-fast behavior with the iterators. | |
202 | **/ | |
203 | 19991 | private transient long modCount = 0; |
204 | ||
205 | /** | |
206 | * Construct a new sequenced hash map with default initial size and load | |
207 | * factor. | |
208 | **/ | |
209 | 19989 | public SequencedHashMap() { |
210 | 19989 | sentinel = createSentinel(); |
211 | 19989 | entries = new HashMap(); |
212 | 19989 | } |
213 | ||
214 | /** | |
215 | * Construct a new sequenced hash map with the specified initial size and | |
216 | * default load factor. | |
217 | * | |
218 | * @param initialSize the initial size for the hash table | |
219 | * | |
220 | * @see HashMap#HashMap(int) | |
221 | **/ | |
222 | 1 | public SequencedHashMap(int initialSize) { |
223 | 1 | sentinel = createSentinel(); |
224 | 1 | entries = new HashMap(initialSize); |
225 | 1 | } |
226 | ||
227 | /** | |
228 | * Construct a new sequenced hash map with the specified initial size and | |
229 | * load factor. | |
230 | * | |
231 | * @param initialSize the initial size for the hash table | |
232 | * | |
233 | * @param loadFactor the load factor for the hash table. | |
234 | * | |
235 | * @see HashMap#HashMap(int,float) | |
236 | **/ | |
237 | 1 | public SequencedHashMap(int initialSize, float loadFactor) { |
238 | 1 | sentinel = createSentinel(); |
239 | 1 | entries = new HashMap(initialSize, loadFactor); |
240 | 1 | } |
241 | ||
242 | /** | |
243 | * Construct a new sequenced hash map and add all the elements in the | |
244 | * specified map. The order in which the mappings in the specified map are | |
245 | * added is defined by {@link #putAll(Map)}. | |
246 | **/ | |
247 | public SequencedHashMap(Map m) { | |
248 | 6 | this(); |
249 | 6 | putAll(m); |
250 | 6 | } |
251 | ||
252 | /** | |
253 | * Removes an internal entry from the linked list. This does not remove | |
254 | * it from the underlying map. | |
255 | **/ | |
256 | private void removeEntry(Entry entry) { | |
257 | 8100 | entry.next.prev = entry.prev; |
258 | 8100 | entry.prev.next = entry.next; |
259 | 8100 | } |
260 | ||
261 | /** | |
262 | * Inserts a new internal entry to the tail of the linked list. This does | |
263 | * not add the entry to the underlying map. | |
264 | **/ | |
265 | private void insertEntry(Entry entry) { | |
266 | 24172 | entry.next = sentinel; |
267 | 24172 | entry.prev = sentinel.prev; |
268 | 24172 | sentinel.prev.next = entry; |
269 | 24172 | sentinel.prev = entry; |
270 | 24172 | } |
271 | ||
272 | // per Map.size() | |
273 | ||
274 | /** | |
275 | * Implements {@link Map#size()}. | |
276 | */ | |
277 | public int size() { | |
278 | // use the underlying Map's size since size is not maintained here. | |
279 | 7242 | return entries.size(); |
280 | } | |
281 | ||
282 | /** | |
283 | * Implements {@link Map#isEmpty()}. | |
284 | */ | |
285 | public boolean isEmpty() { | |
286 | // for quick check whether the map is entry, we can check the linked list | |
287 | // and see if there's anything in it. | |
288 | 19930 | return sentinel.next == sentinel; |
289 | } | |
290 | ||
291 | /** | |
292 | * Implements {@link Map#containsKey(Object)}. | |
293 | */ | |
294 | public boolean containsKey(Object key) { | |
295 | // pass on to underlying map implementation | |
296 | 4 | return entries.containsKey(key); |
297 | } | |
298 | ||
299 | /** | |
300 | * Implements {@link Map#containsValue(Object)}. | |
301 | */ | |
302 | public boolean containsValue(Object value) { | |
303 | // unfortunately, we cannot just pass this call to the underlying map | |
304 | // because we are mapping keys to entries, not keys to values. The | |
305 | // underlying map doesn't have an efficient implementation anyway, so this | |
306 | // isn't a big deal. | |
307 | ||
308 | // do null comparison outside loop so we only need to do it once. This | |
309 | // provides a tighter, more efficient loop at the expense of slight | |
310 | // code duplication. | |
311 | 3 | if (value == null) { |
312 | 2 | for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) { |
313 | 1 | if (pos.getValue() == null) |
314 | 0 | return true; |
315 | } | |
316 | } else { | |
317 | 3 | for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) { |
318 | 2 | if (value.equals(pos.getValue())) |
319 | 1 | return true; |
320 | } | |
321 | } | |
322 | 2 | return false; |
323 | } | |
324 | ||
325 | /** | |
326 | * Implements {@link Map#get(Object)}. | |
327 | */ | |
328 | public Object get(Object o) { | |
329 | // find entry for the specified key object | |
330 | 42384 | Entry entry = (Entry) entries.get(o); |
331 | 42384 | if (entry == null) |
332 | 14271 | return null; |
333 | ||
334 | 28113 | return entry.getValue(); |
335 | } | |
336 | ||
337 | /** | |
338 | * Return the entry for the "oldest" mapping. That is, return the Map.Entry | |
339 | * for the key-value pair that was first put into the map when compared to | |
340 | * all the other pairings in the map. This behavior is equivalent to using | |
341 | * <code>entrySet().iterator().next()</code>, but this method provides an | |
342 | * optimized implementation. | |
343 | * | |
344 | * @return The first entry in the sequence, or <code>null</code> if the | |
345 | * map is empty. | |
346 | **/ | |
347 | public Map.Entry getFirst() { | |
348 | // sentinel.next points to the "first" element of the sequence -- the head | |
349 | // of the list, which is exactly the entry we need to return. We must test | |
350 | // for an empty list though because we don't want to return the sentinel! | |
351 | 1 | return (isEmpty()) ? null : sentinel.next; |
352 | } | |
353 | ||
354 | /** | |
355 | * Return the key for the "oldest" mapping. That is, return the key for the | |
356 | * mapping that was first put into the map when compared to all the other | |
357 | * objects in the map. This behavior is equivalent to using | |
358 | * <code>getFirst().getKey()</code>, but this method provides a slightly | |
359 | * optimized implementation. | |
360 | * | |
361 | * @return The first key in the sequence, or <code>null</code> if the | |
362 | * map is empty. | |
363 | **/ | |
364 | public Object getFirstKey() { | |
365 | // sentinel.next points to the "first" element of the sequence -- the head | |
366 | // of the list -- and the requisite key is returned from it. An empty list | |
367 | // does not need to be tested. In cases where the list is empty, | |
368 | // sentinel.next will point to the sentinel itself which has a null key, | |
369 | // which is exactly what we would want to return if the list is empty (a | |
370 | // nice convient way to avoid test for an empty list) | |
371 | 1 | return sentinel.next.getKey(); |
372 | } | |
373 | ||
374 | /** | |
375 | * Return the value for the "oldest" mapping. That is, return the value for | |
376 | * the mapping that was first put into the map when compared to all the | |
377 | * other objects in the map. This behavior is equivalent to using | |
378 | * <code>getFirst().getValue()</code>, but this method provides a slightly | |
379 | * optimized implementation. | |
380 | * | |
381 | * @return The first value in the sequence, or <code>null</code> if the | |
382 | * map is empty. | |
383 | **/ | |
384 | public Object getFirstValue() { | |
385 | // sentinel.next points to the "first" element of the sequence -- the head | |
386 | // of the list -- and the requisite value is returned from it. An empty | |
387 | // list does not need to be tested. In cases where the list is empty, | |
388 | // sentinel.next will point to the sentinel itself which has a null value, | |
389 | // which is exactly what we would want to return if the list is empty (a | |
390 | // nice convient way to avoid test for an empty list) | |
391 | 1 | return sentinel.next.getValue(); |
392 | } | |
393 | ||
394 | /** | |
395 | * Return the entry for the "newest" mapping. That is, return the Map.Entry | |
396 | * for the key-value pair that was first put into the map when compared to | |
397 | * all the other pairings in the map. The behavior is equivalent to: | |
398 | * | |
399 | * <pre> | |
400 | * Object obj = null; | |
401 | * Iterator iter = entrySet().iterator(); | |
402 | * while(iter.hasNext()) { | |
403 | * obj = iter.next(); | |
404 | * } | |
405 | * return (Map.Entry)obj; | |
406 | * </pre> | |
407 | * | |
408 | * However, the implementation of this method ensures an O(1) lookup of the | |
409 | * last key rather than O(n). | |
410 | * | |
411 | * @return The last entry in the sequence, or <code>null</code> if the map | |
412 | * is empty. | |
413 | **/ | |
414 | public Map.Entry getLast() { | |
415 | // sentinel.prev points to the "last" element of the sequence -- the tail | |
416 | // of the list, which is exactly the entry we need to return. We must test | |
417 | // for an empty list though because we don't want to return the sentinel! | |
418 | 1 | return (isEmpty()) ? null : sentinel.prev; |
419 | } | |
420 | ||
421 | /** | |
422 | * Return the key for the "newest" mapping. That is, return the key for the | |
423 | * mapping that was last put into the map when compared to all the other | |
424 | * objects in the map. This behavior is equivalent to using | |
425 | * <code>getLast().getKey()</code>, but this method provides a slightly | |
426 | * optimized implementation. | |
427 | * | |
428 | * @return The last key in the sequence, or <code>null</code> if the map is | |
429 | * empty. | |
430 | **/ | |
431 | public Object getLastKey() { | |
432 | // sentinel.prev points to the "last" element of the sequence -- the tail | |
433 | // of the list -- and the requisite key is returned from it. An empty list | |
434 | // does not need to be tested. In cases where the list is empty, | |
435 | // sentinel.prev will point to the sentinel itself which has a null key, | |
436 | // which is exactly what we would want to return if the list is empty (a | |
437 | // nice convient way to avoid test for an empty list) | |
438 | 1 | return sentinel.prev.getKey(); |
439 | } | |
440 | ||
441 | /** | |
442 | * Return the value for the "newest" mapping. That is, return the value for | |
443 | * the mapping that was last put into the map when compared to all the other | |
444 | * objects in the map. This behavior is equivalent to using | |
445 | * <code>getLast().getValue()</code>, but this method provides a slightly | |
446 | * optimized implementation. | |
447 | * | |
448 | * @return The last value in the sequence, or <code>null</code> if the map | |
449 | * is empty. | |
450 | **/ | |
451 | public Object getLastValue() { | |
452 | // sentinel.prev points to the "last" element of the sequence -- the tail | |
453 | // of the list -- and the requisite value is returned from it. An empty | |
454 | // list does not need to be tested. In cases where the list is empty, | |
455 | // sentinel.prev will point to the sentinel itself which has a null value, | |
456 | // which is exactly what we would want to return if the list is empty (a | |
457 | // nice convient way to avoid test for an empty list) | |
458 | 1 | return sentinel.prev.getValue(); |
459 | } | |
460 | ||
461 | /** | |
462 | * Implements {@link Map#put(Object, Object)}. | |
463 | */ | |
464 | public Object put(Object key, Object value) { | |
465 | 24172 | modCount++; |
466 | ||
467 | 24172 | Object oldValue = null; |
468 | ||
469 | // lookup the entry for the specified key | |
470 | 24172 | Entry e = (Entry) entries.get(key); |
471 | ||
472 | // check to see if it already exists | |
473 | 24172 | if (e != null) { |
474 | // remove from list so the entry gets "moved" to the end of list | |
475 | 8098 | removeEntry(e); |
476 | ||
477 | // update value in map | |
478 | 8098 | oldValue = e.setValue(value); |
479 | ||
480 | // Note: We do not update the key here because its unnecessary. We only | |
481 | // do comparisons using equals(Object) and we know the specified key and | |
482 | // that in the map are equal in that sense. This may cause a problem if | |
483 | // someone does not implement their hashCode() and/or equals(Object) | |
484 | // method properly and then use it as a key in this map. | |
485 | } else { | |
486 | // add new entry | |
487 | 16074 | e = new Entry(key, value); |
488 | 16074 | entries.put(key, e); |
489 | } | |
490 | // assert(entry in map, but not list) | |
491 | ||
492 | // add to list | |
493 | 24172 | insertEntry(e); |
494 | ||
495 | 24172 | return oldValue; |
496 | } | |
497 | ||
498 | /** | |
499 | * Implements {@link Map#remove(Object)}. | |
500 | */ | |
501 | public Object remove(Object key) { | |
502 | 2 | Entry e = removeImpl(key); |
503 | 2 | return (e == null) ? null : e.getValue(); |
504 | } | |
505 | ||
506 | /** | |
507 | * Fully remove an entry from the map, returning the old entry or null if | |
508 | * there was no such entry with the specified key. | |
509 | **/ | |
510 | private Entry removeImpl(Object key) { | |
511 | 2 | Entry e = (Entry) entries.remove(key); |
512 | 2 | if (e == null) |
513 | 0 | return null; |
514 | 2 | modCount++; |
515 | 2 | removeEntry(e); |
516 | 2 | return e; |
517 | } | |
518 | ||
519 | /** | |
520 | * Adds all the mappings in the specified map to this map, replacing any | |
521 | * mappings that already exist (as per {@link Map#putAll(Map)}). The order | |
522 | * in which the entries are added is determined by the iterator returned | |
523 | * from {@link Map#entrySet()} for the specified map. | |
524 | * | |
525 | * @param t the mappings that should be added to this map. | |
526 | * | |
527 | * @exception NullPointerException if <code>t</code> is <code>null</code> | |
528 | **/ | |
529 | public void putAll(Map t) { | |
530 | 3754 | Iterator iter = t.entrySet().iterator(); |
531 | 15604 | while (iter.hasNext()) { |
532 | 8096 | Map.Entry entry = (Map.Entry) iter.next(); |
533 | 8096 | put(entry.getKey(), entry.getValue()); |
534 | } | |
535 | 3754 | } |
536 | ||
537 | /** | |
538 | * Implements {@link Map#clear()}. | |
539 | */ | |
540 | public void clear() { | |
541 | 2 | modCount++; |
542 | ||
543 | // remove all from the underlying map | |
544 | 2 | entries.clear(); |
545 | ||
546 | // and the list | |
547 | 2 | sentinel.next = sentinel; |
548 | 2 | sentinel.prev = sentinel; |
549 | 2 | } |
550 | ||
551 | /** | |
552 | * Implements {@link Map#equals(Object)}. | |
553 | */ | |
554 | public boolean equals(Object obj) { | |
555 | 8 | if (obj == null) |
556 | 0 | return false; |
557 | 8 | if (obj == this) |
558 | 3 | return true; |
559 | ||
560 | 5 | if (!(obj instanceof Map)) |
561 | 1 | return false; |
562 | ||
563 | 4 | return entrySet().equals(((Map) obj).entrySet()); |
564 | } | |
565 | ||
566 | /** | |
567 | * Implements {@link Map#hashCode()}. | |
568 | */ | |
569 | public int hashCode() { | |
570 | 1 | return entrySet().hashCode(); |
571 | } | |
572 | ||
573 | /** | |
574 | * Provides a string representation of the entries within the map. The | |
575 | * format of the returned string may change with different releases, so this | |
576 | * method is suitable for debugging purposes only. If a specific format is | |
577 | * required, use {@link #entrySet()}.{@link Set#iterator() iterator()} and | |
578 | * iterate over the entries in the map formatting them as appropriate. | |
579 | **/ | |
580 | public String toString() { | |
581 | 1 | StringBuffer buf = new StringBuffer(); |
582 | 1 | buf.append('['); |
583 | 2 | for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) { |
584 | 1 | buf.append(pos.getKey()); |
585 | 1 | buf.append('='); |
586 | 1 | buf.append(pos.getValue()); |
587 | 1 | if (pos.next != sentinel) { |
588 | 0 | buf.append(','); |
589 | } | |
590 | } | |
591 | 1 | buf.append(']'); |
592 | ||
593 | 1 | return buf.toString(); |
594 | } | |
595 | ||
596 | /** | |
597 | * Implements {@link Map#keySet()}. | |
598 | */ | |
599 | public Set keySet() { | |
600 | 9977 | return new AbstractSet() { |
601 | ||
602 | // required impls | |
603 | public Iterator iterator() { | |
604 | return new OrderedIterator(KEY); | |
605 | } | |
606 | public boolean remove(Object o) { | |
607 | Entry e = SequencedHashMap.this.removeImpl(o); | |
608 | return (e != null); | |
609 | } | |
610 | ||
611 | // more efficient impls than abstract set | |
612 | public void clear() { | |
613 | SequencedHashMap.this.clear(); | |
614 | } | |
615 | public int size() { | |
616 | return SequencedHashMap.this.size(); | |
617 | } | |
618 | public boolean isEmpty() { | |
619 | return SequencedHashMap.this.isEmpty(); | |
620 | } | |
621 | public boolean contains(Object o) { | |
622 | return SequencedHashMap.this.containsKey(o); | |
623 | } | |
624 | ||
625 | }; | |
626 | } | |
627 | ||
628 | /** | |
629 | * Implements {@link Map#values()}. | |
630 | */ | |
631 | public Collection values() { | |
632 | 33 | return new AbstractCollection() { |
633 | // required impl | |
634 | public Iterator iterator() { | |
635 | return new OrderedIterator(VALUE); | |
636 | } | |
637 | public boolean remove(Object value) { | |
638 | // do null comparison outside loop so we only need to do it once. This | |
639 | // provides a tighter, more efficient loop at the expense of slight | |
640 | // code duplication. | |
641 | if (value == null) { | |
642 | for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) { | |
643 | if (pos.getValue() == null) { | |
644 | SequencedHashMap.this.removeImpl(pos.getKey()); | |
645 | return true; | |
646 | } | |
647 | } | |
648 | } else { | |
649 | for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) { | |
650 | if (value.equals(pos.getValue())) { | |
651 | SequencedHashMap.this.removeImpl(pos.getKey()); | |
652 | return true; | |
653 | } | |
654 | } | |
655 | } | |
656 | ||
657 | return false; | |
658 | } | |
659 | ||
660 | // more efficient impls than abstract collection | |
661 | public void clear() { | |
662 | SequencedHashMap.this.clear(); | |
663 | } | |
664 | public int size() { | |
665 | return SequencedHashMap.this.size(); | |
666 | } | |
667 | public boolean isEmpty() { | |
668 | return SequencedHashMap.this.isEmpty(); | |
669 | } | |
670 | public boolean contains(Object o) { | |
671 | return SequencedHashMap.this.containsValue(o); | |
672 | } | |
673 | }; | |
674 | } | |
675 | ||
676 | /** | |
677 | * Implements {@link Map#entrySet()}. | |
678 | */ | |
679 | public Set entrySet() { | |
680 | 336 | return new AbstractSet() { |
681 | // helper | |
682 | private Entry findEntry(Object o) { | |
683 | if (o == null) | |
684 | return null; | |
685 | if (!(o instanceof Map.Entry)) | |
686 | return null; | |
687 | ||
688 | Map.Entry e = (Map.Entry) o; | |
689 | Entry entry = (Entry) entries.get(e.getKey()); | |
690 | if (entry != null && entry.equals(e)) | |
691 | return entry; | |
692 | else | |
693 | return null; | |
694 | } | |
695 | ||
696 | // required impl | |
697 | public Iterator iterator() { | |
698 | return new OrderedIterator(ENTRY); | |
699 | } | |
700 | public boolean remove(Object o) { | |
701 | Entry e = findEntry(o); | |
702 | if (e == null) | |
703 | return false; | |
704 | ||
705 | return SequencedHashMap.this.removeImpl(e.getKey()) != null; | |
706 | } | |
707 | ||
708 | // more efficient impls than abstract collection | |
709 | public void clear() { | |
710 | SequencedHashMap.this.clear(); | |
711 | } | |
712 | public int size() { | |
713 | return SequencedHashMap.this.size(); | |
714 | } | |
715 | public boolean isEmpty() { | |
716 | return SequencedHashMap.this.isEmpty(); | |
717 | } | |
718 | public boolean contains(Object o) { | |
719 | return findEntry(o) != null; | |
720 | } | |
721 | }; | |
722 | } | |
723 | ||
724 | // constants to define what the iterator should return on "next" | |
725 | private static final int KEY = 0; | |
726 | private static final int VALUE = 1; | |
727 | private static final int ENTRY = 2; | |
728 | private static final int REMOVED_MASK = 0x80000000; | |
729 | ||
730 | private class OrderedIterator implements Iterator { | |
731 | /** | |
732 | * Holds the type that should be returned from the iterator. The value | |
733 | * should be either {@link #KEY}, {@link #VALUE}, or {@link #ENTRY}. To | |
734 | * save a tiny bit of memory, this field is also used as a marker for when | |
735 | * remove has been called on the current object to prevent a second remove | |
736 | * on the same element. Essientially, if this value is negative (i.e. the | |
737 | * bit specified by {@link #REMOVED_MASK} is set), the current position | |
738 | * has been removed. If positive, remove can still be called. | |
739 | **/ | |
740 | private int returnType; | |
741 | ||
742 | /** | |
743 | * Holds the "current" position in the iterator. When pos.next is the | |
744 | * sentinel, we've reached the end of the list. | |
745 | **/ | |
746 | private Entry pos = sentinel; | |
747 | ||
748 | /** | |
749 | * Holds the expected modification count. If the actual modification | |
750 | * count of the map differs from this value, then a concurrent | |
751 | * modification has occurred. | |
752 | **/ | |
753 | private transient long expectedModCount = modCount; | |
754 | ||
755 | /** | |
756 | * Construct an iterator over the sequenced elements in the order in which | |
757 | * they were added. The {@link #next()} method returns the type specified | |
758 | * by <code>returnType</code> which must be either {@link #KEY}, {@link | |
759 | * #VALUE}, or {@link #ENTRY}. | |
760 | **/ | |
761 | public OrderedIterator(int returnType) { | |
762 | //// Since this is a private inner class, nothing else should have | |
763 | //// access to the constructor. Since we know the rest of the outer | |
764 | //// class uses the iterator correctly, we can leave of the following | |
765 | //// check: | |
766 | //if(returnType >= 0 && returnType <= 2) { | |
767 | // throw new IllegalArgumentException("Invalid iterator type"); | |
768 | //} | |
769 | ||
770 | // Set the "removed" bit so that the iterator starts in a state where | |
771 | // "next" must be called before "remove" will succeed. | |
772 | this.returnType = returnType | REMOVED_MASK; | |
773 | } | |
774 | ||
775 | /** | |
776 | * Returns whether there is any additional elements in the iterator to be | |
777 | * returned. | |
778 | * | |
779 | * @return <code>true</code> if there are more elements left to be | |
780 | * returned from the iterator; <code>false</code> otherwise. | |
781 | **/ | |
782 | public boolean hasNext() { | |
783 | return pos.next != sentinel; | |
784 | } | |
785 | ||
786 | /** | |
787 | * Returns the next element from the iterator. | |
788 | * | |
789 | * @return the next element from the iterator. | |
790 | * | |
791 | * @exception NoSuchElementException if there are no more elements in the | |
792 | * iterator. | |
793 | * | |
794 | * @exception ConcurrentModificationException if a modification occurs in | |
795 | * the underlying map. | |
796 | **/ | |
797 | public Object next() { | |
798 | if (modCount != expectedModCount) { | |
799 | throw new ConcurrentModificationException(); | |
800 | } | |
801 | if (pos.next == sentinel) { | |
802 | throw new NoSuchElementException(); | |
803 | } | |
804 | ||
805 | // clear the "removed" flag | |
806 | returnType = returnType & ~REMOVED_MASK; | |
807 | ||
808 | pos = pos.next; | |
809 | switch (returnType) { | |
810 | case KEY : | |
811 | return pos.getKey(); | |
812 | case VALUE : | |
813 | return pos.getValue(); | |
814 | case ENTRY : | |
815 | return pos; | |
816 | default : | |
817 | // should never happen | |
818 | throw new Error("bad iterator type: " + returnType); | |
819 | } | |
820 | ||
821 | } | |
822 | ||
823 | /** | |
824 | * Removes the last element returned from the {@link #next()} method from | |
825 | * the sequenced map. | |
826 | * | |
827 | * @exception IllegalStateException if there isn't a "last element" to be | |
828 | * removed. That is, if {@link #next()} has never been called, or if | |
829 | * {@link #remove()} was already called on the element. | |
830 | * | |
831 | * @exception ConcurrentModificationException if a modification occurs in | |
832 | * the underlying map. | |
833 | **/ | |
834 | public void remove() { | |
835 | if ((returnType & REMOVED_MASK) != 0) { | |
836 | throw new IllegalStateException("remove() must follow next()"); | |
837 | } | |
838 | if (modCount != expectedModCount) { | |
839 | throw new ConcurrentModificationException(); | |
840 | } | |
841 | ||
842 | SequencedHashMap.this.removeImpl(pos.getKey()); | |
843 | ||
844 | // update the expected mod count for the remove operation | |
845 | expectedModCount++; | |
846 | ||
847 | // set the removed flag | |
848 | returnType = returnType | REMOVED_MASK; | |
849 | } | |
850 | } | |
851 | ||
852 | // APIs maintained from previous version of SequencedHashMap for backwards | |
853 | // compatibility | |
854 | ||
855 | /** | |
856 | * Creates a shallow copy of this object, preserving the internal structure | |
857 | * by copying only references. The keys and values themselves are not | |
858 | * <code>clone()</code>'d. The cloned object maintains the same sequence. | |
859 | * | |
860 | * @return A clone of this instance. | |
861 | * | |
862 | * @exception CloneNotSupportedException if clone is not supported by a | |
863 | * subclass. | |
864 | */ | |
865 | public Object clone() throws CloneNotSupportedException { | |
866 | // yes, calling super.clone() silly since we're just blowing away all | |
867 | // the stuff that super might be doing anyway, but for motivations on | |
868 | // this, see: | |
869 | // http://www.javaworld.com/javaworld/jw-01-1999/jw-01-object.html | |
870 | 1 | SequencedHashMap map = (SequencedHashMap) super.clone(); |
871 | ||
872 | // create new, empty sentinel | |
873 | 1 | map.sentinel = createSentinel(); |
874 | ||
875 | // create a new, empty entry map | |
876 | // note: this does not preserve the initial capacity and load factor. | |
877 | 1 | map.entries = new HashMap(); |
878 | ||
879 | // add all the mappings | |
880 | 1 | map.putAll(this); |
881 | ||
882 | // Note: We cannot just clone the hashmap and sentinel because we must | |
883 | // duplicate our internal structures. Cloning those two will not clone all | |
884 | // the other entries they reference, and so the cloned hash map will not be | |
885 | // able to maintain internal consistency because there are two objects with | |
886 | // the same entries. See discussion in the Entry implementation on why we | |
887 | // cannot implement a clone of the Entry (and thus why we need to recreate | |
888 | // everything). | |
889 | ||
890 | 1 | return map; |
891 | } | |
892 | ||
893 | /** | |
894 | * Returns the Map.Entry at the specified index | |
895 | * | |
896 | * @exception ArrayIndexOutOfBoundsException if the specified index is | |
897 | * <code>< 0</code> or <code>></code> the size of the map. | |
898 | **/ | |
899 | private Map.Entry getEntry(int index) { | |
900 | 5 | Entry pos = sentinel; |
901 | ||
902 | 5 | if (index < 0) { |
903 | 1 | throw new ArrayIndexOutOfBoundsException(index + " < 0"); |
904 | } | |
905 | ||
906 | // loop to one before the position | |
907 | 4 | int i = -1; |
908 | 9 | while (i < (index - 1) && pos.next != sentinel) { |
909 | 1 | i++; |
910 | 1 | pos = pos.next; |
911 | } | |
912 | // pos.next is the requested position | |
913 | ||
914 | // if sentinel is next, past end of list | |
915 | 4 | if (pos.next == sentinel) { |
916 | 1 | throw new ArrayIndexOutOfBoundsException(index + " >= " + (i + 1)); |
917 | } | |
918 | ||
919 | 3 | return pos.next; |
920 | } | |
921 | ||
922 | /** | |
923 | * Returns the key at the specified index. | |
924 | * | |
925 | * @exception ArrayIndexOutOfBoundsException if the <code>index</code> is | |
926 | * <code>< 0</code> or <code>></code> the size of the map. | |
927 | */ | |
928 | public Object get(int index) { | |
929 | 4 | return getEntry(index).getKey(); |
930 | } | |
931 | ||
932 | /** | |
933 | * Returns the value at the specified index. | |
934 | * | |
935 | * @exception ArrayIndexOutOfBoundsException if the <code>index</code> is | |
936 | * <code>< 0</code> or <code>></code> the size of the map. | |
937 | */ | |
938 | public Object getValue(int index) { | |
939 | 1 | return getEntry(index).getValue(); |
940 | } | |
941 | ||
942 | /** | |
943 | * Returns the index of the specified key. | |
944 | */ | |
945 | public int indexOf(Object key) { | |
946 | 2 | Entry e = (Entry) entries.get(key); |
947 | 2 | int pos = 0; |
948 | 4 | while (e.prev != sentinel) { |
949 | 0 | pos++; |
950 | 0 | e = e.prev; |
951 | } | |
952 | 2 | return pos; |
953 | } | |
954 | ||
955 | /** | |
956 | * Returns a key iterator. | |
957 | */ | |
958 | public Iterator iterator() { | |
959 | 1 | return keySet().iterator(); |
960 | } | |
961 | ||
962 | /** | |
963 | * Returns the last index of the specified key. | |
964 | */ | |
965 | public int lastIndexOf(Object key) { | |
966 | // keys in a map are guarunteed to be unique | |
967 | 1 | return indexOf(key); |
968 | } | |
969 | ||
970 | /** | |
971 | * Returns a List view of the keys rather than a set view. The returned | |
972 | * list is unmodifiable. This is required because changes to the values of | |
973 | * the list (using {@link java.util.ListIterator#set(Object)}) will | |
974 | * effectively remove the value from the list and reinsert that value at | |
975 | * the end of the list, which is an unexpected side effect of changing the | |
976 | * value of a list. This occurs because changing the key, changes when the | |
977 | * mapping is added to the map and thus where it appears in the list. | |
978 | * | |
979 | * <P>An alternative to this method is to use {@link #keySet()} | |
980 | * | |
981 | * @see #keySet() | |
982 | * @return The ordered list of keys. | |
983 | */ | |
984 | public List sequence() { | |
985 | 1 | List l = new ArrayList(size()); |
986 | 1 | Iterator iter = keySet().iterator(); |
987 | 3 | while (iter.hasNext()) { |
988 | 1 | l.add(iter.next()); |
989 | } | |
990 | ||
991 | 1 | return Collections.unmodifiableList(l); |
992 | } | |
993 | ||
994 | /** | |
995 | * Removes the element at the specified index. | |
996 | * | |
997 | * @param index The index of the object to remove. | |
998 | * @return The previous value coressponding the <code>key</code>, or | |
999 | * <code>null</code> if none existed. | |
1000 | * | |
1001 | * @exception ArrayIndexOutOfBoundsException if the <code>index</code> is | |
1002 | * <code>< 0</code> or <code>></code> the size of the map. | |
1003 | */ | |
1004 | public Object remove(int index) { | |
1005 | 1 | return remove(get(index)); |
1006 | } | |
1007 | ||
1008 | // per Externalizable.readExternal(ObjectInput) | |
1009 | ||
1010 | /** | |
1011 | * Deserializes this map from the given stream. | |
1012 | * | |
1013 | * @param in the stream to deserialize from | |
1014 | * @throws IOException if the stream raises it | |
1015 | * @throws ClassNotFoundException if the stream raises it | |
1016 | */ | |
1017 | public void readExternal(ObjectInput in) | |
1018 | throws IOException, ClassNotFoundException { | |
1019 | 1 | int size = in.readInt(); |
1020 | 2 | for (int i = 0; i < size; i++) { |
1021 | 1 | Object key = in.readObject(); |
1022 | 1 | Object value = in.readObject(); |
1023 | 1 | put(key, value); |
1024 | } | |
1025 | 1 | } |
1026 | ||
1027 | /** | |
1028 | * Serializes this map to the given stream. | |
1029 | * | |
1030 | * @param out the stream to serialize to | |
1031 | * @throws IOException if the stream raises it | |
1032 | */ | |
1033 | public void writeExternal(ObjectOutput out) throws IOException { | |
1034 | 1 | out.writeInt(size()); |
1035 | 2 | for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) { |
1036 | 1 | out.writeObject(pos.getKey()); |
1037 | 1 | out.writeObject(pos.getValue()); |
1038 | } | |
1039 | 1 | } |
1040 | ||
1041 | // add a serial version uid, so that if we change things in the future | |
1042 | // without changing the format, we can still deserialize properly. | |
1043 | private static final long serialVersionUID = 3380552487888102930L; | |
1044 | ||
1045 | } |
this report was generated by version 1.0.5 of jcoverage. |
copyright © 2003, jcoverage ltd. all rights reserved. |