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 }