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