001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.map;
018
019import java.io.IOException;
020import java.io.InvalidObjectException;
021import java.io.ObjectInputStream;
022import java.io.ObjectOutputStream;
023import java.io.Serializable;
024import java.util.AbstractList;
025import java.util.AbstractSet;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.IdentityHashMap;
031import java.util.Iterator;
032import java.util.List;
033import java.util.ListIterator;
034import java.util.Map;
035import java.util.NoSuchElementException;
036import java.util.Set;
037
038import org.apache.commons.collections4.OrderedMap;
039import org.apache.commons.collections4.OrderedMapIterator;
040import org.apache.commons.collections4.ResettableIterator;
041import org.apache.commons.collections4.iterators.AbstractUntypedIteratorDecorator;
042import org.apache.commons.collections4.keyvalue.AbstractMapEntry;
043import org.apache.commons.collections4.list.UnmodifiableList;
044
045/**
046 * Decorates a {@code Map} to ensure that the order of addition is retained
047 * using a {@code List} to maintain order.
048 * <p>
049 * The order will be used via the iterators and toArray methods on the views.
050 * The order is also returned by the {@code MapIterator}.
051 * The {@code orderedMapIterator()} method accesses an iterator that can
052 * iterate both forwards and backwards through the map.
053 * In addition, non-interface methods are provided to access the map by index.
054 * </p>
055 * <p>
056 * If an object is added to the Map for a second time, it will remain in the
057 * original position in the iteration.
058 * </p>
059 * <p>
060 * <strong>Note that ListOrderedMap is not synchronized and is not thread-safe.</strong>
061 * If you wish to use this map from multiple threads concurrently, you must use
062 * appropriate synchronization. The simplest approach is to wrap this map
063 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
064 * exceptions when accessed by concurrent threads without synchronization.
065 * </p>
066 * <p>
067 * <strong>Note that ListOrderedMap doesn't work with
068 * {@link IdentityHashMap IdentityHashMap}, {@link CaseInsensitiveMap},
069 * or similar maps that violate the general contract of {@link Map}.</strong>
070 * The {@code ListOrderedMap} (or, more precisely, the underlying {@code List})
071 * is relying on {@link Object#equals(Object) equals()}. This is fine, as long as the
072 * decorated {@code Map} is also based on {@link Object#equals(Object) equals()},
073 * and {@link Object#hashCode() hashCode()}, which
074 * {@link IdentityHashMap IdentityHashMap}, and
075 * {@link CaseInsensitiveMap} don't: The former uses {@code ==}, and
076 * the latter uses {@link Object#equals(Object) equals()} on a lower-cased
077 * key.
078 * </p>
079 * <p>
080 * This class is {@link Serializable} starting with Commons Collections 3.1.
081 * </p>
082 *
083 * @param <K> The type of the keys in this map
084 * @param <V> The type of the values in this map
085 * @since 3.0
086 */
087public class ListOrderedMap<K, V>
088        extends AbstractMapDecorator<K, V>
089        implements OrderedMap<K, V>, Serializable {
090
091    static class EntrySetView<K, V> extends AbstractSet<Map.Entry<K, V>> {
092        private final ListOrderedMap<K, V> parent;
093        private final List<K> insertOrder;
094        private Set<Map.Entry<K, V>> entrySet;
095
096        EntrySetView(final ListOrderedMap<K, V> parent, final List<K> insertOrder) {
097            this.parent = parent;
098            this.insertOrder = insertOrder;
099        }
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}