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.Collection;
025import java.util.Iterator;
026import java.util.List;
027import java.util.ListIterator;
028import java.util.Map;
029import java.util.function.Predicate;
030
031import org.apache.commons.collections4.iterators.UnmodifiableIterator;
032import org.apache.commons.collections4.iterators.UnmodifiableListIterator;
033import org.apache.commons.collections4.list.UnmodifiableList;
034
035/**
036 * A <code>Map</code> implementation that maintains the order of the entries.
037 * In this implementation order is maintained by original insertion.
038 * <p>
039 * This implementation improves on the JDK1.4 LinkedHashMap by adding the
040 * {@link org.apache.commons.collections4.MapIterator MapIterator}
041 * functionality, additional convenience methods and allowing
042 * bidirectional iteration. It also implements <code>OrderedMap</code>.
043 * In addition, non-interface methods are provided to access the map by index.
044 * </p>
045 * <p>
046 * The <code>orderedMapIterator()</code> method provides direct access to a
047 * bidirectional iterator. The iterators from the other views can also be cast
048 * to <code>OrderedIterator</code> if required.
049 * </p>
050 * <p>
051 * All the available iterators can be reset back to the start by casting to
052 * <code>ResettableIterator</code> and calling <code>reset()</code>.
053 * </p>
054 * <p>
055 * The implementation is also designed to be subclassed, with lots of useful
056 * methods exposed.
057 * </p>
058 * <p>
059 * <strong>Note that LinkedMap is not synchronized and is not thread-safe.</strong>
060 * If you wish to use this map from multiple threads concurrently, you must use
061 * appropriate synchronization. The simplest approach is to wrap this map
062 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
063 * exceptions when accessed by concurrent threads without synchronization.
064 * </p>
065 *
066 * @param <K> the type of the keys in this map
067 * @param <V> the type of the values in this map
068 * @since 3.0
069 */
070public class LinkedMap<K, V> extends AbstractLinkedMap<K, V> implements Serializable, Cloneable {
071
072    /** Serialisation version */
073    private static final long serialVersionUID = 9077234323521161066L;
074
075    /**
076     * Constructs a new empty map with default size and load factor.
077     */
078    public LinkedMap() {
079        super(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_THRESHOLD);
080    }
081
082    /**
083     * Constructs a new, empty map with the specified initial capacity.
084     *
085     * @param initialCapacity  the initial capacity
086     * @throws IllegalArgumentException if the initial capacity is negative
087     */
088    public LinkedMap(final int initialCapacity) {
089        super(initialCapacity);
090    }
091
092    /**
093     * Constructs a new, empty map with the specified initial capacity and
094     * load factor.
095     *
096     * @param initialCapacity  the initial capacity
097     * @param loadFactor  the load factor
098     * @throws IllegalArgumentException if the initial capacity is negative
099     * @throws IllegalArgumentException if the load factor is less than zero
100     */
101    public LinkedMap(final int initialCapacity, final float loadFactor) {
102        super(initialCapacity, loadFactor);
103    }
104
105    /**
106     * Constructor copying elements from another map.
107     *
108     * @param map  the map to copy
109     * @throws NullPointerException if the map is null
110     */
111    public LinkedMap(final Map<? extends K, ? extends V> map) {
112        super(map);
113    }
114
115    //-----------------------------------------------------------------------
116    /**
117     * Clones the map without cloning the keys or values.
118     *
119     * @return a shallow clone
120     */
121    @Override
122    public LinkedMap<K, V> clone() {
123        return (LinkedMap<K, V>) super.clone();
124    }
125
126    /**
127     * Write the map out using a custom routine.
128     *
129     * @param out  the output stream
130     * @throws IOException if an error occurs while writing to the stream
131     */
132    private void writeObject(final ObjectOutputStream out) throws IOException {
133        out.defaultWriteObject();
134        doWriteObject(out);
135    }
136
137    /**
138     * Read the map in using a custom routine.
139     *
140     * @param in the input stream
141     * @throws IOException if an error occurs while reading from the stream
142     * @throws ClassNotFoundException if an object read from the stream can not be loaded
143     */
144    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
145        in.defaultReadObject();
146        doReadObject(in);
147    }
148
149    //-----------------------------------------------------------------------
150    /**
151     * Gets the key at the specified index.
152     *
153     * @param index  the index to retrieve
154     * @return the key at the specified index
155     * @throws IndexOutOfBoundsException if the index is invalid
156     */
157    public K get(final int index) {
158        return getEntry(index).getKey();
159    }
160
161    /**
162     * Gets the value at the specified index.
163     *
164     * @param index  the index to retrieve
165     * @return the value at the specified index
166     * @throws IndexOutOfBoundsException if the index is invalid
167     */
168    public V getValue(final int index) {
169        return getEntry(index).getValue();
170    }
171
172    /**
173     * Gets the index of the specified key.
174     *
175     * @param key  the key to find the index of
176     * @return the index, or -1 if not found
177     */
178    public int indexOf(Object key) {
179        key = convertKey(key);
180        int i = 0;
181        for (LinkEntry<K, V> entry = header.after; entry != header; entry = entry.after, i++) {
182            if (isEqualKey(key, entry.key)) {
183                return i;
184            }
185        }
186        return -1;
187    }
188
189    /**
190     * Removes the element at the specified index.
191     *
192     * @param index  the index of the object to remove
193     * @return the previous value corresponding the <code>key</code>,
194     *  or <code>null</code> if none existed
195     * @throws IndexOutOfBoundsException if the index is invalid
196     */
197    public V remove(final int index) {
198        return remove(get(index));
199    }
200
201    /**
202     * Gets an unmodifiable List view of the keys.
203     * <p>
204     * The returned list is unmodifiable because changes to the values of
205     * the list (using {@link java.util.ListIterator#set(Object)}) will
206     * effectively remove the value from the list and reinsert that value at
207     * the end of the list, which is an unexpected side effect of changing the
208     * value of a list.  This occurs because changing the key, changes when the
209     * mapping is added to the map and thus where it appears in the list.
210     * <p>
211     * An alternative to this method is to use {@link #keySet()}.
212     *
213     * @see #keySet()
214     * @return The ordered list of keys.
215     */
216    public List<K> asList() {
217        return new LinkedMapList<>(this);
218    }
219
220    /**
221     * List view of map.
222     */
223    static class LinkedMapList<K> extends AbstractList<K> {
224
225        private final LinkedMap<K, ?> parent;
226
227        LinkedMapList(final LinkedMap<K, ?> parent) {
228            this.parent = parent;
229        }
230
231        @Override
232        public int size() {
233            return parent.size();
234        }
235
236        @Override
237        public K get(final int index) {
238            return parent.get(index);
239        }
240
241        @Override
242        public boolean contains(final Object obj) {
243            return parent.containsKey(obj);
244        }
245
246        @Override
247        public int indexOf(final Object obj) {
248            return parent.indexOf(obj);
249        }
250
251        @Override
252        public int lastIndexOf(final Object obj) {
253            return parent.indexOf(obj);
254        }
255
256        @Override
257        public boolean containsAll(final Collection<?> coll) {
258            return parent.keySet().containsAll(coll);
259        }
260
261        @Override
262        public K remove(final int index) {
263            throw new UnsupportedOperationException();
264        }
265
266        @Override
267        public boolean remove(final Object obj) {
268            throw new UnsupportedOperationException();
269        }
270
271        /**
272         * @since 4.4
273         */
274        @Override
275        public boolean removeIf(final Predicate<? super K> filter) {
276            throw new UnsupportedOperationException();
277        }
278
279        @Override
280        public boolean removeAll(final Collection<?> coll) {
281            throw new UnsupportedOperationException();
282        }
283
284        @Override
285        public boolean retainAll(final Collection<?> coll) {
286            throw new UnsupportedOperationException();
287        }
288
289        @Override
290        public void clear() {
291            throw new UnsupportedOperationException();
292        }
293
294        @Override
295        public Object[] toArray() {
296            return parent.keySet().toArray();
297        }
298
299        @Override
300        public <T> T[] toArray(final T[] array) {
301            return parent.keySet().toArray(array);
302        }
303
304        @Override
305        public Iterator<K> iterator() {
306            return UnmodifiableIterator.unmodifiableIterator(parent.keySet().iterator());
307        }
308
309        @Override
310        public ListIterator<K> listIterator() {
311            return UnmodifiableListIterator.umodifiableListIterator(super.listIterator());
312        }
313
314        @Override
315        public ListIterator<K> listIterator(final int fromIndex) {
316            return UnmodifiableListIterator.umodifiableListIterator(super.listIterator(fromIndex));
317        }
318
319        @Override
320        public List<K> subList(final int fromIndexInclusive, final int toIndexExclusive) {
321            return UnmodifiableList.unmodifiableList(super.subList(fromIndexInclusive, toIndexExclusive));
322        }
323    }
324
325}