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 }