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    }