001    package biweekly.util;
002    
003    import java.util.ArrayList;
004    import java.util.Collection;
005    import java.util.Collections;
006    import java.util.Iterator;
007    import java.util.LinkedHashMap;
008    import java.util.List;
009    import java.util.Map;
010    import java.util.Set;
011    
012    /*
013     Copyright (c) 2013, Michael Angstadt
014     All rights reserved.
015    
016     Redistribution and use in source and binary forms, with or without
017     modification, are permitted provided that the following conditions are met: 
018    
019     1. Redistributions of source code must retain the above copyright notice, this
020     list of conditions and the following disclaimer. 
021     2. Redistributions in binary form must reproduce the above copyright notice,
022     this list of conditions and the following disclaimer in the documentation
023     and/or other materials provided with the distribution. 
024    
025     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
026     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
027     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
028     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
029     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
030     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
031     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
032     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
033     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
034     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
035     */
036    
037    /**
038     * A multimap that uses {@link List} objects to store its values. The internal
039     * {@link Map} implementation is a {@link LinkedHashMap} that uses
040     * {@link ArrayList} for its values.
041     * @author Michael Angstadt
042     * @param <K> the key
043     * @param <V> the value
044     */
045    public class ListMultimap<K, V> implements Iterable<Map.Entry<K, List<V>>> {
046            private final Map<K, List<V>> map;
047    
048            /**
049             * Creates a new multi map.
050             */
051            public ListMultimap() {
052                    map = new LinkedHashMap<K, List<V>>();
053            }
054    
055            /**
056             * Creates a new multi map.
057             * @param initialCapacity the initial capacity of the underlying map.
058             */
059            public ListMultimap(int initialCapacity) {
060                    map = new LinkedHashMap<K, List<V>>(initialCapacity);
061            }
062    
063            /**
064             * Copy constructor.
065             * @param orig the multimap to copy from
066             */
067            public ListMultimap(ListMultimap<K, V> orig) {
068                    this();
069                    for (Map.Entry<K, List<V>> entry : orig) {
070                            List<V> values = new ArrayList<V>(entry.getValue());
071                            map.put(entry.getKey(), values);
072                    }
073            }
074    
075            /**
076             * Adds a value to the multimap.
077             * @param key the key
078             * @param value the value to add
079             */
080            public void put(K key, V value) {
081                    List<V> values = get(key, true);
082                    values.add(value);
083            }
084    
085            /**
086             * Adds multiple values to the multimap.
087             * @param key the key
088             * @param values the values to add
089             */
090            public void putAll(K key, Collection<V> values) {
091                    List<V> existingValues = get(key, true);
092                    existingValues.addAll(values);
093            }
094    
095            /**
096             * Gets the values associated with the key.
097             * @param key the key
098             * @return the list of values or empty list if the key doesn't exist
099             */
100            public List<V> get(K key) {
101                    return get(key, false);
102            }
103    
104            /**
105             * Gets the values associated with the key.
106             * @param key the key
107             * @param add true to add an empty element to the map if the key doesn't
108             * exist, false not to
109             * @return the list of values or empty list if the key doesn't exist
110             */
111            private List<V> get(K key, boolean add) {
112                    key = sanitizeKey(key);
113                    List<V> values = map.get(key);
114                    if (values == null) {
115                            values = new ArrayList<V>();
116                            if (add) {
117                                    map.put(key, values);
118                            }
119                    }
120                    return values;
121            }
122    
123            /**
124             * Gets the first value that's associated with a key.
125             * @param key the key
126             * @return the first value or null if the key doesn't exist
127             */
128            public V first(K key) {
129                    List<V> values = get(key);
130                    return (values == null || values.isEmpty()) ? null : values.get(0);
131            }
132    
133            /**
134             * Determines whether the given key exists.
135             * @param key the key
136             * @return true if the key exists, false if not
137             */
138            public boolean containsKey(K key) {
139                    return map.containsKey(key);
140            }
141    
142            /**
143             * Removes a particular value.
144             * @param key the key
145             * @param value the value to remove
146             * @return true if the multimap contained the value, false if not
147             */
148            public boolean remove(K key, V value) {
149                    List<V> values = map.get(sanitizeKey(key));
150                    if (values != null) {
151                            return values.remove(value);
152                    }
153                    return false;
154            }
155    
156            /**
157             * Removes all the values associated with a key
158             * @param key the key to remove
159             * @return the removed values or empty list if the key doesn't exist
160             */
161            public List<V> removeAll(K key) {
162                    List<V> removed = map.remove(sanitizeKey(key));
163                    return (removed == null) ? Collections.<V> emptyList() : removed;
164            }
165    
166            /**
167             * Replaces all values with the given value.
168             * @param key the key
169             * @param value the value with which to replace all existing values, or null
170             * will remove all values
171             * @return the values that were replaced
172             */
173            public List<V> replace(K key, V value) {
174                    List<V> replaced = removeAll(key);
175                    if (value != null) {
176                            put(key, value);
177                    }
178                    return replaced;
179            }
180    
181            /**
182             * Replaces all values with the given values.
183             * @param key the key
184             * @param values the values with which to replace all existing values
185             * @return the values that were replaced
186             */
187            public List<V> replace(K key, Collection<V> values) {
188                    List<V> replaced = removeAll(key);
189                    if (values != null && !values.isEmpty()) {
190                            putAll(key, values);
191                    }
192                    return replaced;
193            }
194    
195            /**
196             * Clears all entries from the multimap.
197             */
198            public void clear() {
199                    map.clear();
200            }
201    
202            /**
203             * Returns all the keys.
204             * @return all the keys
205             */
206            public Set<K> keySet() {
207                    return map.keySet();
208            }
209    
210            /**
211             * Returns all the values.
212             * @return all the values
213             */
214            public Collection<V> values() {
215                    Collection<V> list = new ArrayList<V>();
216                    for (List<V> value : map.values()) {
217                            list.addAll(value);
218                    }
219                    return list;
220            }
221    
222            /**
223             * Determines if the multimap is empty or not.
224             * @return true if it's empty, false if not
225             */
226            public boolean isEmpty() {
227                    return size() == 0;
228            }
229    
230            /**
231             * Returns the number of values in the map.
232             * @return the number of values
233             */
234            public int size() {
235                    int size = 0;
236                    for (List<V> value : map.values()) {
237                            size += value.size();
238                    }
239                    return size;
240            }
241    
242            /**
243             * Gets the underlying {@link Map} object.
244             * @return the underlying {@link Map} object
245             */
246            public Map<K, List<V>> getMap() {
247                    return map;
248            }
249    
250            /**
251             * Modifies a given key before it is used to interact with the internal map.
252             * This method is meant to be overridden by child classes if necessary.
253             * @param key the key
254             * @return the modified key (by default, the key is returned as-is)
255             */
256            protected K sanitizeKey(K key) {
257                    return key;
258            }
259    
260            //@Override
261            public Iterator<Map.Entry<K, List<V>>> iterator() {
262                    return map.entrySet().iterator();
263            }
264    
265            @Override
266            public String toString() {
267                    return map.toString();
268            }
269    }