001package biweekly.util;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.Collections;
006import java.util.Iterator;
007import java.util.LinkedHashMap;
008import java.util.List;
009import java.util.Map;
010import java.util.Set;
011
012/*
013 Copyright (c) 2013-2015, 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 */
045public 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}