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