1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.collections4.map;
18
19 import java.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.io.ObjectOutputStream;
22 import java.io.Serializable;
23 import java.util.AbstractList;
24 import java.util.AbstractSet;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.ListIterator;
31 import java.util.Map;
32 import java.util.NoSuchElementException;
33 import java.util.Set;
34
35 import org.apache.commons.collections4.OrderedMap;
36 import org.apache.commons.collections4.OrderedMapIterator;
37 import org.apache.commons.collections4.ResettableIterator;
38 import org.apache.commons.collections4.iterators.AbstractUntypedIteratorDecorator;
39 import org.apache.commons.collections4.keyvalue.AbstractMapEntry;
40 import org.apache.commons.collections4.list.UnmodifiableList;
41
42 /**
43 * Decorates a {@code Map} to ensure that the order of addition is retained
44 * using a {@code List} to maintain order.
45 * <p>
46 * The order will be used via the iterators and toArray methods on the views.
47 * The order is also returned by the {@code MapIterator}.
48 * The {@code orderedMapIterator()} method accesses an iterator that can
49 * iterate both forwards and backwards through the map.
50 * In addition, non-interface methods are provided to access the map by index.
51 * </p>
52 * <p>
53 * If an object is added to the Map for a second time, it will remain in the
54 * original position in the iteration.
55 * </p>
56 * <p>
57 * <strong>Note that ListOrderedMap is not synchronized and is not thread-safe.</strong>
58 * If you wish to use this map from multiple threads concurrently, you must use
59 * appropriate synchronization. The simplest approach is to wrap this map
60 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
61 * exceptions when accessed by concurrent threads without synchronization.
62 * </p>
63 * <p>
64 * <strong>Note that ListOrderedMap doesn't work with
65 * {@link java.util.IdentityHashMap IdentityHashMap}, {@link CaseInsensitiveMap},
66 * or similar maps that violate the general contract of {@link java.util.Map}.</strong>
67 * The {@code ListOrderedMap} (or, more precisely, the underlying {@code List})
68 * is relying on {@link Object#equals(Object) equals()}. This is fine, as long as the
69 * decorated {@code Map} is also based on {@link Object#equals(Object) equals()},
70 * and {@link Object#hashCode() hashCode()}, which
71 * {@link java.util.IdentityHashMap IdentityHashMap}, and
72 * {@link CaseInsensitiveMap} don't: The former uses {@code ==}, and
73 * the latter uses {@link Object#equals(Object) equals()} on a lower-cased
74 * key.
75 * </p>
76 * <p>
77 * This class is {@link Serializable} starting with Commons Collections 3.1.
78 * </p>
79 *
80 * @param <K> the type of the keys in this map
81 * @param <V> the type of the values in this map
82 * @since 3.0
83 */
84 public class ListOrderedMap<K, V>
85 extends AbstractMapDecorator<K, V>
86 implements OrderedMap<K, V>, Serializable {
87
88 static class EntrySetView<K, V> extends AbstractSet<Map.Entry<K, V>> {
89 private final ListOrderedMap<K, V> parent;
90 private final List<K> insertOrder;
91 private Set<Map.Entry<K, V>> entrySet;
92
93 EntrySetView(final ListOrderedMap<K, V> parent, final List<K> insertOrder) {
94 this.parent = parent;
95 this.insertOrder = insertOrder;
96 }
97
98 @Override
99 public void clear() {
100 parent.clear();
101 }
102
103 @Override
104 public boolean contains(final Object obj) {
105 return getEntrySet().contains(obj);
106 }
107 @Override
108 public boolean containsAll(final Collection<?> coll) {
109 return getEntrySet().containsAll(coll);
110 }
111
112 @Override
113 public boolean equals(final Object obj) {
114 if (obj == this) {
115 return true;
116 }
117 return getEntrySet().equals(obj);
118 }
119
120 private Set<Map.Entry<K, V>> getEntrySet() {
121 if (entrySet == null) {
122 entrySet = parent.decorated().entrySet();
123 }
124 return entrySet;
125 }
126
127 @Override
128 public int hashCode() {
129 return getEntrySet().hashCode();
130 }
131
132 @Override
133 public boolean isEmpty() {
134 return parent.isEmpty();
135 }
136
137 @Override
138 public Iterator<Map.Entry<K, V>> iterator() {
139 return new ListOrderedIterator<>(parent, insertOrder);
140 }
141
142 @Override
143 @SuppressWarnings("unchecked")
144 public boolean remove(final Object obj) {
145 if (!(obj instanceof Map.Entry)) {
146 return false;
147 }
148 if (getEntrySet().contains(obj)) {
149 final Object key = ((Map.Entry<K, V>) obj).getKey();
150 parent.remove(key);
151 return true;
152 }
153 return false;
154 }
155
156 @Override
157 public int size() {
158 return parent.size();
159 }
160
161 @Override
162 public String toString() {
163 return getEntrySet().toString();
164 }
165 }
166
167 static class KeySetView<K> extends AbstractSet<K> {
168 private final ListOrderedMap<K, Object> parent;
169
170 @SuppressWarnings("unchecked")
171 KeySetView(final ListOrderedMap<K, ?> parent) {
172 this.parent = (ListOrderedMap<K, Object>) parent;
173 }
174
175 @Override
176 public void clear() {
177 parent.clear();
178 }
179
180 @Override
181 public boolean contains(final Object value) {
182 return parent.containsKey(value);
183 }
184
185 @Override
186 public Iterator<K> iterator() {
187 return new AbstractUntypedIteratorDecorator<Map.Entry<K, Object>, K>(parent.entrySet().iterator()) {
188 @Override
189 public K next() {
190 return getIterator().next().getKey();
191 }
192 };
193 }
194
195 @Override
196 public int size() {
197 return parent.size();
198 }
199 }
200
201 static class ListOrderedIterator<K, V> extends AbstractUntypedIteratorDecorator<K, Map.Entry<K, V>> {
202 private final ListOrderedMap<K, V> parent;
203 private K last;
204
205 ListOrderedIterator(final ListOrderedMap<K, V> parent, final List<K> insertOrder) {
206 super(insertOrder.iterator());
207 this.parent = parent;
208 }
209
210 @Override
211 public Map.Entry<K, V> next() {
212 last = getIterator().next();
213 return new ListOrderedMapEntry<>(parent, last);
214 }
215
216 @Override
217 public void remove() {
218 super.remove();
219 parent.decorated().remove(last);
220 }
221 }
222
223 static class ListOrderedMapEntry<K, V> extends AbstractMapEntry<K, V> {
224 private final ListOrderedMap<K, V> parent;
225
226 ListOrderedMapEntry(final ListOrderedMap<K, V> parent, final K key) {
227 super(key, null);
228 this.parent = parent;
229 }
230
231 @Override
232 public V getValue() {
233 return parent.get(getKey());
234 }
235
236 @Override
237 public V setValue(final V value) {
238 return parent.decorated().put(getKey(), value);
239 }
240 }
241
242 static class ListOrderedMapIterator<K, V> implements OrderedMapIterator<K, V>, ResettableIterator<K> {
243 private final ListOrderedMap<K, V> parent;
244 private ListIterator<K> iterator;
245 private K last;
246 private boolean readable;
247
248 ListOrderedMapIterator(final ListOrderedMap<K, V> parent) {
249 this.parent = parent;
250 this.iterator = parent.insertOrder.listIterator();
251 }
252
253 @Override
254 public K getKey() {
255 if (!readable) {
256 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
257 }
258 return last;
259 }
260
261 @Override
262 public V getValue() {
263 if (!readable) {
264 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
265 }
266 return parent.get(last);
267 }
268
269 @Override
270 public boolean hasNext() {
271 return iterator.hasNext();
272 }
273
274 @Override
275 public boolean hasPrevious() {
276 return iterator.hasPrevious();
277 }
278
279 @Override
280 public K next() {
281 last = iterator.next();
282 readable = true;
283 return last;
284 }
285
286 @Override
287 public K previous() {
288 last = iterator.previous();
289 readable = true;
290 return last;
291 }
292
293 @Override
294 public void remove() {
295 if (!readable) {
296 throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
297 }
298 iterator.remove();
299 parent.map.remove(last);
300 readable = false;
301 }
302
303 @Override
304 public void reset() {
305 iterator = parent.insertOrder.listIterator();
306 last = null;
307 readable = false;
308 }
309
310 @Override
311 public V setValue(final V value) {
312 if (!readable) {
313 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
314 }
315 return parent.map.put(last, value);
316 }
317
318 @Override
319 public String toString() {
320 if (readable) {
321 return "Iterator[" + getKey() + "=" + getValue() + "]";
322 }
323 return "Iterator[]";
324 }
325 }
326
327 static class ValuesView<V> extends AbstractList<V> {
328 private final ListOrderedMap<Object, V> parent;
329
330 @SuppressWarnings("unchecked")
331 ValuesView(final ListOrderedMap<?, V> parent) {
332 this.parent = (ListOrderedMap<Object, V>) parent;
333 }
334
335 @Override
336 public void clear() {
337 parent.clear();
338 }
339
340 @Override
341 public boolean contains(final Object value) {
342 return parent.containsValue(value);
343 }
344
345 @Override
346 public V get(final int index) {
347 return parent.getValue(index);
348 }
349
350 @Override
351 public Iterator<V> iterator() {
352 return new AbstractUntypedIteratorDecorator<Map.Entry<Object, V>, V>(parent.entrySet().iterator()) {
353 @Override
354 public V next() {
355 return getIterator().next().getValue();
356 }
357 };
358 }
359
360 @Override
361 public V remove(final int index) {
362 return parent.remove(index);
363 }
364
365 @Override
366 public V set(final int index, final V value) {
367 return parent.setValue(index, value);
368 }
369
370 @Override
371 public int size() {
372 return parent.size();
373 }
374 }
375
376 /** Serialization version */
377 private static final long serialVersionUID = 2728177751851003750L;
378
379 /**
380 * Factory method to create an ordered map.
381 * <p>
382 * An {@code ArrayList} is used to retain order.
383 * </p>
384 *
385 * @param <K> the key type
386 * @param <V> the value type
387 * @param map the map to decorate, must not be null
388 * @return a new list ordered map
389 * @throws NullPointerException if map is null
390 * @since 4.0
391 */
392 public static <K, V> ListOrderedMap<K, V> listOrderedMap(final Map<K, V> map) {
393 return new ListOrderedMap<>(map);
394 }
395
396 /** Internal list to hold the sequence of objects */
397 private final List<K> insertOrder = new ArrayList<>();
398
399 /**
400 * Constructs a new empty {@code ListOrderedMap} that decorates
401 * a {@code HashMap}.
402 *
403 * @since 3.1
404 */
405 public ListOrderedMap() {
406 this(new HashMap<>());
407 }
408
409 /**
410 * Constructor that wraps (not copies).
411 *
412 * @param map the map to decorate, must not be null
413 * @throws NullPointerException if map is null
414 */
415 protected ListOrderedMap(final Map<K, V> map) {
416 super(map);
417 insertOrder.addAll(decorated().keySet());
418 }
419
420 /**
421 * Gets an unmodifiable List view of the keys which changes as the map changes.
422 * <p>
423 * The returned list is unmodifiable because changes to the values of
424 * the list (using {@link java.util.ListIterator#set(Object)}) will
425 * effectively remove the value from the list and reinsert that value at
426 * the end of the list, which is an unexpected side effect of changing the
427 * value of a list. This occurs because changing the key, changes when the
428 * mapping is added to the map and thus where it appears in the list.
429 * </p>
430 * <p>
431 * An alternative to this method is to use the better named
432 * {@link #keyList()} or {@link #keySet()}.
433 * </p>
434 *
435 * @see #keyList()
436 * @see #keySet()
437 * @return The ordered list of keys.
438 */
439 public List<K> asList() {
440 return keyList();
441 }
442
443 @Override
444 public void clear() {
445 decorated().clear();
446 insertOrder.clear();
447 }
448
449 /**
450 * Gets a view over the entries in the map.
451 * <p>
452 * The Set will be ordered by object insertion into the map.
453 * </p>
454 *
455 * @return the fully modifiable set view over the entries
456 */
457 @Override
458 public Set<Map.Entry<K, V>> entrySet() {
459 return new EntrySetView<>(this, insertOrder);
460 }
461
462 /**
463 * Gets the first key in this map by insert order.
464 *
465 * @return the first key currently in this map
466 * @throws NoSuchElementException if this map is empty
467 */
468 @Override
469 public K firstKey() {
470 if (isEmpty()) {
471 throw new NoSuchElementException("Map is empty");
472 }
473 return insertOrder.get(0);
474 }
475
476 /**
477 * Gets the key at the specified index.
478 *
479 * @param index the index to retrieve
480 * @return the key at the specified index
481 * @throws IndexOutOfBoundsException if the index is invalid
482 */
483 public K get(final int index) {
484 return insertOrder.get(index);
485 }
486
487 /**
488 * Gets the value at the specified index.
489 *
490 * @param index the index to retrieve
491 * @return the key at the specified index
492 * @throws IndexOutOfBoundsException if the index is invalid
493 */
494 public V getValue(final int index) {
495 return get(insertOrder.get(index));
496 }
497
498 /**
499 * Gets the index of the specified key.
500 *
501 * @param key the key to find the index of
502 * @return the index, or -1 if not found
503 */
504 public int indexOf(final Object key) {
505 return insertOrder.indexOf(key);
506 }
507
508 /**
509 * Gets a view over the keys in the map as a List.
510 * <p>
511 * The List will be ordered by object insertion into the map.
512 * The List is unmodifiable.
513 * </p>
514 *
515 * @see #keySet()
516 * @return the unmodifiable list view over the keys
517 * @since 3.2
518 */
519 public List<K> keyList() {
520 return UnmodifiableList.unmodifiableList(insertOrder);
521 }
522
523 /**
524 * Gets a view over the keys in the map.
525 * <p>
526 * The Collection will be ordered by object insertion into the map.
527 * </p>
528 *
529 * @see #keyList()
530 * @return the fully modifiable collection view over the keys
531 */
532 @Override
533 public Set<K> keySet() {
534 return new KeySetView<>(this);
535 }
536
537 /**
538 * Gets the last key in this map by insert order.
539 *
540 * @return the last key currently in this map
541 * @throws NoSuchElementException if this map is empty
542 */
543 @Override
544 public K lastKey() {
545 if (isEmpty()) {
546 throw new NoSuchElementException("Map is empty");
547 }
548 return insertOrder.get(size() - 1);
549 }
550
551 @Override
552 public OrderedMapIterator<K, V> mapIterator() {
553 return new ListOrderedMapIterator<>(this);
554 }
555
556 /**
557 * Gets the next key to the one specified using insert order.
558 * This method performs a list search to find the key and is O(n).
559 *
560 * @param key the key to find previous for
561 * @return the next key, null if no match or at start
562 */
563 @Override
564 public K nextKey(final Object key) {
565 final int index = insertOrder.indexOf(key);
566 if (index >= 0 && index < size() - 1) {
567 return insertOrder.get(index + 1);
568 }
569 return null;
570 }
571
572 /**
573 * Gets the previous key to the one specified using insert order.
574 * This method performs a list search to find the key and is O(n).
575 *
576 * @param key the key to find previous for
577 * @return the previous key, null if no match or at start
578 */
579 @Override
580 public K previousKey(final Object key) {
581 final int index = insertOrder.indexOf(key);
582 if (index > 0) {
583 return insertOrder.get(index - 1);
584 }
585 return null;
586 }
587
588 /**
589 * Puts a key-value mapping into the map at the specified index.
590 * <p>
591 * If the map already contains the key, then the original mapping
592 * is removed and the new mapping added at the specified index.
593 * The remove may change the effect of the index. The index is
594 * always calculated relative to the original state of the map.
595 * </p>
596 * <p>
597 * Thus, the steps are: (1) remove the existing key-value mapping,
598 * then (2) insert the new key-value mapping at the position it
599 * would have been inserted had the remove not occurred.
600 * </p>
601 *
602 * @param index the index at which the mapping should be inserted
603 * @param key the key
604 * @param value the value
605 * @return the value previously mapped to the key
606 * @throws IndexOutOfBoundsException if the index is out of range [0, size]
607 * @since 3.2
608 */
609 public V put(int index, final K key, final V value) {
610 if (index < 0 || index > insertOrder.size()) {
611 throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size());
612 }
613
614 final Map<K, V> m = decorated();
615 if (m.containsKey(key)) {
616 final V result = m.remove(key);
617 final int pos = insertOrder.indexOf(key);
618 insertOrder.remove(pos);
619 if (pos < index) {
620 index--;
621 }
622 insertOrder.add(index, key);
623 m.put(key, value);
624 return result;
625 }
626 insertOrder.add(index, key);
627 m.put(key, value);
628 return null;
629 }
630
631 @Override
632 public V put(final K key, final V value) {
633 if (decorated().containsKey(key)) {
634 // re-adding doesn't change order
635 return decorated().put(key, value);
636 }
637 // first add, so add to both map and list
638 final V result = decorated().put(key, value);
639 insertOrder.add(key);
640 return result;
641 }
642
643 /**
644 * Puts the values contained in a supplied Map into the Map starting at
645 * the specified index.
646 *
647 * @param index the index in the Map to start at.
648 * @param map the Map containing the entries to be added.
649 * @throws IndexOutOfBoundsException if the index is out of range [0, size]
650 */
651 public void putAll(int index, final Map<? extends K, ? extends V> map) {
652 if (index < 0 || index > insertOrder.size()) {
653 throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size());
654 }
655 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
656 final K key = entry.getKey();
657 final boolean contains = containsKey(key);
658 // The return value of put is null if the key did not exist OR the value was null
659 // so it cannot be used to determine whether the key was added
660 put(index, entry.getKey(), entry.getValue());
661 if (!contains) {
662 // if no key was replaced, increment the index
663 index++;
664 } else {
665 // otherwise put the next item after the currently inserted key
666 index = indexOf(entry.getKey()) + 1;
667 }
668 }
669 }
670
671 @Override
672 public void putAll(final Map<? extends K, ? extends V> map) {
673 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
674 put(entry.getKey(), entry.getValue());
675 }
676 }
677
678 /**
679 * Deserializes the map in using a custom routine.
680 *
681 * @param in the input stream
682 * @throws IOException if an error occurs while reading from the stream
683 * @throws ClassNotFoundException if an object read from the stream cannot be loaded
684 * @since 3.1
685 */
686 @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
687 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
688 in.defaultReadObject();
689 map = (Map<K, V>) in.readObject(); // (1)
690 }
691
692 /**
693 * Removes the element at the specified index.
694 *
695 * @param index the index of the object to remove
696 * @return the removed value, or {@code null} if none existed
697 * @throws IndexOutOfBoundsException if the index is invalid
698 */
699 public V remove(final int index) {
700 return remove(get(index));
701 }
702
703 @Override
704 public V remove(final Object key) {
705 V result = null;
706 if (decorated().containsKey(key)) {
707 result = decorated().remove(key);
708 insertOrder.remove(key);
709 }
710 return result;
711 }
712
713 /**
714 * Sets the value at the specified index.
715 *
716 * @param index the index of the value to set
717 * @param value the new value to set
718 * @return the previous value at that index
719 * @throws IndexOutOfBoundsException if the index is invalid
720 * @since 3.2
721 */
722 public V setValue(final int index, final V value) {
723 final K key = insertOrder.get(index);
724 return put(key, value);
725 }
726
727 /**
728 * Returns the Map as a string.
729 *
730 * @return the Map as a String
731 */
732 @Override
733 public String toString() {
734 if (isEmpty()) {
735 return "{}";
736 }
737 final StringBuilder buf = new StringBuilder();
738 buf.append('{');
739 boolean first = true;
740 for (final Map.Entry<K, V> entry : entrySet()) {
741 final K key = entry.getKey();
742 final V value = entry.getValue();
743 if (first) {
744 first = false;
745 } else {
746 buf.append(", ");
747 }
748 buf.append(key == this ? "(this Map)" : key);
749 buf.append('=');
750 buf.append(value == this ? "(this Map)" : value);
751 }
752 buf.append('}');
753 return buf.toString();
754 }
755
756 /**
757 * Gets a view over the values in the map as a List.
758 * <p>
759 * The List will be ordered by object insertion into the map.
760 * The List supports remove and set, but does not support add.
761 * </p>
762 *
763 * @see #values()
764 * @return the partially modifiable list view over the values
765 * @since 3.2
766 */
767 public List<V> valueList() {
768 return new ValuesView<>(this);
769 }
770
771 /**
772 * Gets a view over the values in the map.
773 * <p>
774 * The Collection will be ordered by object insertion into the map.
775 * </p>
776 * <p>
777 * From Commons Collections 3.2, this Collection can be cast
778 * to a list, see {@link #valueList()}
779 * </p>
780 *
781 * @see #valueList()
782 * @return the fully modifiable collection view over the values
783 */
784 @Override
785 public Collection<V> values() {
786 return new ValuesView<>(this);
787 }
788
789 /**
790 * Serializes this object to an ObjectOutputStream.
791 *
792 * @param out the target ObjectOutputStream.
793 * @throws IOException thrown when an I/O errors occur writing to the target stream.
794 * @since 3.1
795 */
796 private void writeObject(final ObjectOutputStream out) throws IOException {
797 out.defaultWriteObject();
798 out.writeObject(map);
799 }
800
801 }