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 an empty multimap.
050             */
051            public ListMultimap() {
052                    map = new LinkedHashMap<K, List<V>>();
053            }
054    
055            /**
056             * Creates an empty multimap.
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             * Creates a copy of an existing multimap.
065             * @param orig the multimap to copy from
066             */
067            public ListMultimap(ListMultimap<K, V> orig) {
068                    this(orig.map);
069            }
070    
071            /**
072             * Creates a copy of an existing map.
073             * @param orig the map to copy from
074             */
075            public ListMultimap(Map<K, List<V>> orig) {
076                    this();
077                    for (Map.Entry<K, List<V>> entry : orig.entrySet()) {
078                            List<V> values = new ArrayList<V>(entry.getValue());
079                            map.put(entry.getKey(), values);
080                    }
081            }
082    
083            /**
084             * Adds a value to the multimap.
085             * @param key the key
086             * @param value the value to add
087             */
088            public void put(K key, V value) {
089                    List<V> values = get(key, true);
090                    values.add(value);
091            }
092    
093            /**
094             * Adds multiple values to the multimap.
095             * @param key the key
096             * @param values the values to add
097             */
098            public void putAll(K key, Collection<V> values) {
099                    List<V> existingValues = get(key, true);
100                    existingValues.addAll(values);
101            }
102    
103            /**
104             * Gets the values associated with the key.
105             * @param key the key
106             * @return the list of values or empty list if the key doesn't exist
107             */
108            public List<V> get(K key) {
109                    return get(key, false);
110            }
111    
112            /**
113             * Gets the values associated with the key.
114             * @param key the key
115             * @param add true to add an empty element to the map if the key doesn't
116             * exist, false not to
117             * @return the list of values or empty list if the key doesn't exist
118             */
119            private List<V> get(K key, boolean add) {
120                    key = sanitizeKey(key);
121                    List<V> values = map.get(key);
122                    if (values == null) {
123                            values = new ArrayList<V>();
124                            if (add) {
125                                    map.put(key, values);
126                            }
127                    }
128                    return values;
129            }
130    
131            /**
132             * Gets the first value that's associated with a key.
133             * @param key the key
134             * @return the first value or null if the key doesn't exist
135             */
136            public V first(K key) {
137                    List<V> values = get(key);
138                    return (values == null || values.isEmpty()) ? null : values.get(0);
139            }
140    
141            /**
142             * Determines whether the given key exists.
143             * @param key the key
144             * @return true if the key exists, false if not
145             */
146            public boolean containsKey(K key) {
147                    return map.containsKey(key);
148            }
149    
150            /**
151             * Removes a particular value.
152             * @param key the key
153             * @param value the value to remove
154             * @return true if the multimap contained the value, false if not
155             */
156            public boolean remove(K key, V value) {
157                    List<V> values = map.get(sanitizeKey(key));
158                    if (values != null) {
159                            return values.remove(value);
160                    }
161                    return false;
162            }
163    
164            /**
165             * Removes all the values associated with a key
166             * @param key the key to remove
167             * @return the removed values or empty list if the key doesn't exist
168             */
169            public List<V> removeAll(K key) {
170                    List<V> removed = map.remove(sanitizeKey(key));
171                    return (removed == null) ? Collections.<V> emptyList() : removed;
172            }
173    
174            /**
175             * Replaces all values with the given value.
176             * @param key the key
177             * @param value the value with which to replace all existing values, or null
178             * to remove all values
179             * @return the values that were replaced
180             */
181            public List<V> replace(K key, V value) {
182                    List<V> replaced = removeAll(key);
183                    if (value != null) {
184                            put(key, value);
185                    }
186                    return replaced;
187            }
188    
189            /**
190             * Replaces all values with the given values.
191             * @param key the key
192             * @param values the values with which to replace all existing values
193             * @return the values that were replaced
194             */
195            public List<V> replace(K key, Collection<V> values) {
196                    List<V> replaced = removeAll(key);
197                    if (values != null && !values.isEmpty()) {
198                            putAll(key, values);
199                    }
200                    return replaced;
201            }
202    
203            /**
204             * Clears all entries from the multimap.
205             */
206            public void clear() {
207                    map.clear();
208            }
209    
210            /**
211             * Returns all the keys.
212             * @return all the keys
213             */
214            public Set<K> keySet() {
215                    return map.keySet();
216            }
217    
218            /**
219             * Returns all the values.
220             * @return all the values
221             */
222            public List<V> values() {
223                    List<V> list = new ArrayList<V>();
224                    for (List<V> value : map.values()) {
225                            list.addAll(value);
226                    }
227                    return list;
228            }
229    
230            /**
231             * Determines if the multimap is empty or not.
232             * @return true if it's empty, false if not
233             */
234            public boolean isEmpty() {
235                    return size() == 0;
236            }
237    
238            /**
239             * Returns the number of values in the map.
240             * @return the number of values
241             */
242            public int size() {
243                    int size = 0;
244                    for (List<V> value : map.values()) {
245                            size += value.size();
246                    }
247                    return size;
248            }
249    
250            /**
251             * Gets the underlying {@link Map} object.
252             * @return the underlying {@link Map} object
253             */
254            public Map<K, List<V>> getMap() {
255                    return map;
256            }
257    
258            /**
259             * Modifies a given key before it is used to interact with the internal map.
260             * This method is meant to be overridden by child classes if necessary.
261             * @param key the key
262             * @return the modified key (by default, the key is returned as-is)
263             */
264            protected K sanitizeKey(K key) {
265                    return key;
266            }
267    
268            //@Override
269            public Iterator<Map.Entry<K, List<V>>> iterator() {
270                    return map.entrySet().iterator();
271            }
272    
273            @Override
274            public String toString() {
275                    return map.toString();
276            }
277    
278            @Override
279            public int hashCode() {
280                    return map.hashCode();
281            }
282    
283            @Override
284            public boolean equals(Object obj) {
285                    if (this == obj)
286                            return true;
287                    if (obj == null)
288                            return false;
289                    if (getClass() != obj.getClass())
290                            return false;
291    
292                    ListMultimap<?, ?> other = (ListMultimap<?, ?>) obj;
293                    return map.equals(other.map);
294            }
295    }