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 }