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 *      http://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.ObjectInputStream;
021import java.io.ObjectOutputStream;
022import java.io.Serializable;
023import java.util.AbstractList;
024import java.util.AbstractSet;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.List;
030import java.util.ListIterator;
031import java.util.Map;
032import java.util.NoSuchElementException;
033import java.util.Set;
034
035import org.apache.commons.collections4.OrderedMap;
036import org.apache.commons.collections4.OrderedMapIterator;
037import org.apache.commons.collections4.ResettableIterator;
038import org.apache.commons.collections4.iterators.AbstractUntypedIteratorDecorator;
039import org.apache.commons.collections4.keyvalue.AbstractMapEntry;
040import org.apache.commons.collections4.list.UnmodifiableList;
041
042/**
043 * Decorates a {@code Map} to ensure that the order of addition is retained
044 * using a {@code List} to maintain order.
045 * <p>
046 * The order will be used via the iterators and toArray methods on the views.
047 * The order is also returned by the {@code MapIterator}.
048 * The {@code orderedMapIterator()} method accesses an iterator that can
049 * iterate both forwards and backwards through the map.
050 * In addition, non-interface methods are provided to access the map by index.
051 * </p>
052 * <p>
053 * If an object is added to the Map for a second time, it will remain in the
054 * original position in the iteration.
055 * </p>
056 * <p>
057 * <strong>Note that ListOrderedMap is not synchronized and is not thread-safe.</strong>
058 * If you wish to use this map from multiple threads concurrently, you must use
059 * appropriate synchronization. The simplest approach is to wrap this map
060 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
061 * exceptions when accessed by concurrent threads without synchronization.
062 * </p>
063 * <p>
064 * <strong>Note that ListOrderedMap doesn't work with
065 * {@link java.util.IdentityHashMap IdentityHashMap}, {@link CaseInsensitiveMap},
066 * or similar maps that violate the general contract of {@link java.util.Map}.</strong>
067 * The {@code ListOrderedMap} (or, more precisely, the underlying {@code List})
068 * is relying on {@link Object#equals(Object) equals()}. This is fine, as long as the
069 * decorated {@code Map} is also based on {@link Object#equals(Object) equals()},
070 * and {@link Object#hashCode() hashCode()}, which
071 * {@link java.util.IdentityHashMap IdentityHashMap}, and
072 * {@link CaseInsensitiveMap} don't: The former uses {@code ==}, and
073 * the latter uses {@link Object#equals(Object) equals()} on a lower-cased
074 * key.
075 * </p>
076 * <p>
077 * This class is {@link Serializable} starting with Commons Collections 3.1.
078 * </p>
079 *
080 * @param <K> the type of the keys in this map
081 * @param <V> the type of the values in this map
082 * @since 3.0
083 */
084public class ListOrderedMap<K, V>
085        extends AbstractMapDecorator<K, V>
086        implements OrderedMap<K, V>, Serializable {
087
088    static class EntrySetView<K, V> extends AbstractSet<Map.Entry<K, V>> {
089        private final ListOrderedMap<K, V> parent;
090        private final List<K> insertOrder;
091        private Set<Map.Entry<K, V>> entrySet;
092
093        EntrySetView(final ListOrderedMap<K, V> parent, final List<K> insertOrder) {
094            this.parent = parent;
095            this.insertOrder = insertOrder;
096        }
097
098        @Override
099        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}