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