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    *      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 }