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            this.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 this.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 this.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            this.parent.clear();
178        }
179
180        @Override
181        public boolean contains(final Object value) {
182            return this.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 this.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            this.parent.clear();
338        }
339
340        @Override
341        public boolean contains(final Object value) {
342            return this.parent.containsValue(value);
343        }
344
345        @Override
346        public V get(final int index) {
347            return this.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 this.parent.remove(index);
363        }
364
365        @Override
366        public V set(final int index, final V value) {
367            return this.parent.setValue(index, value);
368        }
369
370        @Override
371        public int size() {
372            return this.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     *
384     * @param <K>  the key type
385     * @param <V>  the value type
386     * @param map  the map to decorate, must not be null
387     * @return a new list ordered map
388     * @throws NullPointerException if map is null
389     * @since 4.0
390     */
391    public static <K, V> ListOrderedMap<K, V> listOrderedMap(final Map<K, V> map) {
392        return new ListOrderedMap<>(map);
393    }
394
395    /** Internal list to hold the sequence of objects */
396    private final List<K> insertOrder = new ArrayList<>();
397
398    /**
399     * Constructs a new empty {@code ListOrderedMap} that decorates
400     * a {@code HashMap}.
401     *
402     * @since 3.1
403     */
404    public ListOrderedMap() {
405        this(new HashMap<>());
406    }
407
408    /**
409     * Constructor that wraps (not copies).
410     *
411     * @param map  the map to decorate, must not be null
412     * @throws NullPointerException if map is null
413     */
414    protected ListOrderedMap(final Map<K, V> map) {
415        super(map);
416        insertOrder.addAll(decorated().keySet());
417    }
418
419    /**
420     * Gets an unmodifiable List view of the keys which changes as the map changes.
421     * <p>
422     * The returned list is unmodifiable because changes to the values of
423     * the list (using {@link java.util.ListIterator#set(Object)}) will
424     * effectively remove the value from the list and reinsert that value at
425     * the end of the list, which is an unexpected side effect of changing the
426     * value of a list.  This occurs because changing the key, changes when the
427     * mapping is added to the map and thus where it appears in the list.
428     * <p>
429     * An alternative to this method is to use the better named
430     * {@link #keyList()} or {@link #keySet()}.
431     *
432     * @see #keyList()
433     * @see #keySet()
434     * @return The ordered list of keys.
435     */
436    public List<K> asList() {
437        return keyList();
438    }
439
440    @Override
441    public void clear() {
442        decorated().clear();
443        insertOrder.clear();
444    }
445
446    /**
447     * Gets a view over the entries in the map.
448     * <p>
449     * The Set will be ordered by object insertion into the map.
450     *
451     * @return the fully modifiable set view over the entries
452     */
453    @Override
454    public Set<Map.Entry<K, V>> entrySet() {
455        return new EntrySetView<>(this, this.insertOrder);
456    }
457
458    /**
459     * Gets the first key in this map by insert order.
460     *
461     * @return the first key currently in this map
462     * @throws NoSuchElementException if this map is empty
463     */
464    @Override
465    public K firstKey() {
466        if (isEmpty()) {
467            throw new NoSuchElementException("Map is empty");
468        }
469        return insertOrder.get(0);
470    }
471
472    /**
473     * Gets the key at the specified index.
474     *
475     * @param index  the index to retrieve
476     * @return the key at the specified index
477     * @throws IndexOutOfBoundsException if the index is invalid
478     */
479    public K get(final int index) {
480        return insertOrder.get(index);
481    }
482
483    /**
484     * Gets the value at the specified index.
485     *
486     * @param index  the index to retrieve
487     * @return the key at the specified index
488     * @throws IndexOutOfBoundsException if the index is invalid
489     */
490    public V getValue(final int index) {
491        return get(insertOrder.get(index));
492    }
493
494    /**
495     * Gets the index of the specified key.
496     *
497     * @param key  the key to find the index of
498     * @return the index, or -1 if not found
499     */
500    public int indexOf(final Object key) {
501        return insertOrder.indexOf(key);
502    }
503
504    /**
505     * Gets a view over the keys in the map as a List.
506     * <p>
507     * The List will be ordered by object insertion into the map.
508     * The List is unmodifiable.
509     *
510     * @see #keySet()
511     * @return the unmodifiable list view over the keys
512     * @since 3.2
513     */
514    public List<K> keyList() {
515        return UnmodifiableList.unmodifiableList(insertOrder);
516    }
517
518    /**
519     * Gets a view over the keys in the map.
520     * <p>
521     * The Collection will be ordered by object insertion into the map.
522     *
523     * @see #keyList()
524     * @return the fully modifiable collection view over the keys
525     */
526    @Override
527    public Set<K> keySet() {
528        return new KeySetView<>(this);
529    }
530
531    /**
532     * Gets the last key in this map by insert order.
533     *
534     * @return the last key currently in this map
535     * @throws NoSuchElementException if this map is empty
536     */
537    @Override
538    public K lastKey() {
539        if (isEmpty()) {
540            throw new NoSuchElementException("Map is empty");
541        }
542        return insertOrder.get(size() - 1);
543    }
544
545    // Implement OrderedMap
546    @Override
547    public OrderedMapIterator<K, V> mapIterator() {
548        return new ListOrderedMapIterator<>(this);
549    }
550
551    /**
552     * Gets the next key to the one specified using insert order.
553     * This method performs a list search to find the key and is O(n).
554     *
555     * @param key  the key to find previous for
556     * @return the next key, null if no match or at start
557     */
558    @Override
559    public K nextKey(final Object key) {
560        final int index = insertOrder.indexOf(key);
561        if (index >= 0 && index < size() - 1) {
562            return insertOrder.get(index + 1);
563        }
564        return null;
565    }
566
567    /**
568     * Gets the previous key to the one specified using insert order.
569     * This method performs a list search to find the key and is O(n).
570     *
571     * @param key  the key to find previous for
572     * @return the previous key, null if no match or at start
573     */
574    @Override
575    public K previousKey(final Object key) {
576        final int index = insertOrder.indexOf(key);
577        if (index > 0) {
578            return insertOrder.get(index - 1);
579        }
580        return null;
581    }
582
583    /**
584     * Puts a key-value mapping into the map at the specified index.
585     * <p>
586     * If the map already contains the key, then the original mapping
587     * is removed and the new mapping added at the specified index.
588     * The remove may change the effect of the index. The index is
589     * always calculated relative to the original state of the map.
590     * <p>
591     * Thus, the steps are: (1) remove the existing key-value mapping,
592     * then (2) insert the new key-value mapping at the position it
593     * would have been inserted had the remove not occurred.
594     *
595     * @param index  the index at which the mapping should be inserted
596     * @param key  the key
597     * @param value  the value
598     * @return the value previously mapped to the key
599     * @throws IndexOutOfBoundsException if the index is out of range [0, size]
600     * @since 3.2
601     */
602    public V put(int index, final K key, final V value) {
603        if (index < 0 || index > insertOrder.size()) {
604            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size());
605        }
606
607        final Map<K, V> m = decorated();
608        if (m.containsKey(key)) {
609            final V result = m.remove(key);
610            final int pos = insertOrder.indexOf(key);
611            insertOrder.remove(pos);
612            if (pos < index) {
613                index--;
614            }
615            insertOrder.add(index, key);
616            m.put(key, value);
617            return result;
618        }
619        insertOrder.add(index, key);
620        m.put(key, value);
621        return null;
622    }
623
624    @Override
625    public V put(final K key, final V value) {
626        if (decorated().containsKey(key)) {
627            // re-adding doesn't change order
628            return decorated().put(key, value);
629        }
630        // first add, so add to both map and list
631        final V result = decorated().put(key, value);
632        insertOrder.add(key);
633        return result;
634    }
635
636    /**
637     * Puts the values contained in a supplied Map into the Map starting at
638     * the specified index.
639     *
640     * @param index the index in the Map to start at.
641     * @param map the Map containing the entries to be added.
642     * @throws IndexOutOfBoundsException if the index is out of range [0, size]
643     */
644    public void putAll(int index, final Map<? extends K, ? extends V> map) {
645        if (index < 0 || index > insertOrder.size()) {
646            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size());
647        }
648        for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
649            final K key = entry.getKey();
650            final boolean contains = containsKey(key);
651            // The return value of put is null if the key did not exist OR the value was null
652            // so it cannot be used to determine whether the key was added
653            put(index, entry.getKey(), entry.getValue());
654            if (!contains) {
655                // if no key was replaced, increment the index
656                index++;
657            } else {
658                // otherwise put the next item after the currently inserted key
659                index = indexOf(entry.getKey()) + 1;
660            }
661        }
662    }
663
664    @Override
665    public void putAll(final Map<? extends K, ? extends V> map) {
666        for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
667            put(entry.getKey(), entry.getValue());
668        }
669    }
670
671    /**
672     * Read the map in using a custom routine.
673     *
674     * @param in  the input stream
675     * @throws IOException if an error occurs while reading from the stream
676     * @throws ClassNotFoundException if an object read from the stream can not be loaded
677     * @since 3.1
678     */
679    @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
680    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
681        in.defaultReadObject();
682        map = (Map<K, V>) in.readObject(); // (1)
683    }
684
685    /**
686     * Removes the element at the specified index.
687     *
688     * @param index  the index of the object to remove
689     * @return the removed value, or {@code null} if none existed
690     * @throws IndexOutOfBoundsException if the index is invalid
691     */
692    public V remove(final int index) {
693        return remove(get(index));
694    }
695
696    @Override
697    public V remove(final Object key) {
698        V result = null;
699        if (decorated().containsKey(key)) {
700            result = decorated().remove(key);
701            insertOrder.remove(key);
702        }
703        return result;
704    }
705
706    /**
707     * Sets the value at the specified index.
708     *
709     * @param index  the index of the value to set
710     * @param value  the new value to set
711     * @return the previous value at that index
712     * @throws IndexOutOfBoundsException if the index is invalid
713     * @since 3.2
714     */
715    public V setValue(final int index, final V value) {
716        final K key = insertOrder.get(index);
717        return put(key, value);
718    }
719
720    /**
721     * Returns the Map as a string.
722     *
723     * @return the Map as a String
724     */
725    @Override
726    public String toString() {
727        if (isEmpty()) {
728            return "{}";
729        }
730        final StringBuilder buf = new StringBuilder();
731        buf.append('{');
732        boolean first = true;
733        for (final Map.Entry<K, V> entry : entrySet()) {
734            final K key = entry.getKey();
735            final V value = entry.getValue();
736            if (first) {
737                first = false;
738            } else {
739                buf.append(", ");
740            }
741            buf.append(key == this ? "(this Map)" : key);
742            buf.append('=');
743            buf.append(value == this ? "(this Map)" : value);
744        }
745        buf.append('}');
746        return buf.toString();
747    }
748
749    /**
750     * Gets a view over the values in the map as a List.
751     * <p>
752     * The List will be ordered by object insertion into the map.
753     * The List supports remove and set, but does not support add.
754     *
755     * @see #values()
756     * @return the partially modifiable list view over the values
757     * @since 3.2
758     */
759    public List<V> valueList() {
760        return new ValuesView<>(this);
761    }
762
763    /**
764     * Gets a view over the values in the map.
765     * <p>
766     * The Collection will be ordered by object insertion into the map.
767     * <p>
768     * From Commons Collections 3.2, this Collection can be cast
769     * to a list, see {@link #valueList()}
770     *
771     * @see #valueList()
772     * @return the fully modifiable collection view over the values
773     */
774    @Override
775    public Collection<V> values() {
776        return new ValuesView<>(this);
777    }
778
779    /**
780     * Write the map out using a custom routine.
781     *
782     * @param out  the output stream
783     * @throws IOException if an error occurs while writing to the stream
784     * @since 3.1
785     */
786    private void writeObject(final ObjectOutputStream out) throws IOException {
787        out.defaultWriteObject();
788        out.writeObject(map);
789    }
790
791}