1 /* 2 * Copyright (C) 2007 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.collect; 18 19 import com.google.common.annotations.GwtCompatible; 20 import com.google.common.annotations.VisibleForTesting; 21 import com.google.common.base.Preconditions; 22 23 import java.io.IOException; 24 import java.io.ObjectInputStream; 25 import java.io.ObjectOutputStream; 26 import java.util.Collection; 27 import java.util.Iterator; 28 import java.util.LinkedHashMap; 29 import java.util.LinkedHashSet; 30 import java.util.Map; 31 import java.util.Set; 32 33 import javax.annotation.Nullable; 34 35 /** 36 * Implementation of {@code Multimap} that does not allow duplicate key-value 37 * entries and that returns collections whose iterators follow the ordering in 38 * which the data was added to the multimap. 39 * 40 * <p>The collections returned by {@code keySet}, {@code keys}, and {@code 41 * asMap} iterate through the keys in the order they were first added to the 42 * multimap. Similarly, {@code get}, {@code removeAll}, and {@code 43 * replaceValues} return collections that iterate through the values in the 44 * order they were added. The collections generated by {@code entries} and 45 * {@code values} iterate across the key-value mappings in the order they were 46 * added to the multimap. 47 * 48 * <p>The iteration ordering of the collections generated by {@code keySet}, 49 * {@code keys}, and {@code asMap} has a few subtleties. As long as the set of 50 * keys remains unchanged, adding or removing mappings does not affect the key 51 * iteration order. However, if you remove all values associated with a key and 52 * then add the key back to the multimap, that key will come last in the key 53 * iteration order. 54 * 55 * <p>The multimap does not store duplicate key-value pairs. Adding a new 56 * key-value pair equal to an existing key-value pair has no effect. 57 * 58 * <p>Keys and values may be null. All optional multimap methods are supported, 59 * and all returned views are modifiable. 60 * 61 * <p>This class is not threadsafe when any concurrent operations update the 62 * multimap. Concurrent read operations will work correctly. To allow concurrent 63 * update operations, wrap your multimap with a call to {@link 64 * Multimaps#synchronizedSetMultimap}. 65 * 66 * @author Jared Levy 67 * @since 2010.01.04 <b>stable</b> (imported from Google Collections Library) 68 */ 69 @GwtCompatible(serializable = true) 70 public final class LinkedHashMultimap<K, V> extends AbstractSetMultimap<K, V> { 71 private static final int DEFAULT_VALUES_PER_KEY = 8; 72 73 @VisibleForTesting 74 transient int expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; 75 76 /** 77 * Map entries with an iteration order corresponding to the order in which the 78 * key-value pairs were added to the multimap. 79 */ 80 // package-private for GWT deserialization 81 transient Collection<Map.Entry<K, V>> linkedEntries; 82 83 /** 84 * Creates a new, empty {@code LinkedHashMultimap} with the default initial 85 * capacities. 86 */ create()87 public static <K, V> LinkedHashMultimap<K, V> create() { 88 return new LinkedHashMultimap<K, V>(); 89 } 90 91 /** 92 * Constructs an empty {@code LinkedHashMultimap} with enough capacity to hold 93 * the specified numbers of keys and values without rehashing. 94 * 95 * @param expectedKeys the expected number of distinct keys 96 * @param expectedValuesPerKey the expected average number of values per key 97 * @throws IllegalArgumentException if {@code expectedKeys} or {@code 98 * expectedValuesPerKey} is negative 99 */ create( int expectedKeys, int expectedValuesPerKey)100 public static <K, V> LinkedHashMultimap<K, V> create( 101 int expectedKeys, int expectedValuesPerKey) { 102 return new LinkedHashMultimap<K, V>(expectedKeys, expectedValuesPerKey); 103 } 104 105 /** 106 * Constructs a {@code LinkedHashMultimap} with the same mappings as the 107 * specified multimap. If a key-value mapping appears multiple times in the 108 * input multimap, it only appears once in the constructed multimap. The new 109 * multimap has the same {@link Multimap#entries()} iteration order as the 110 * input multimap, except for excluding duplicate mappings. 111 * 112 * @param multimap the multimap whose contents are copied to this multimap 113 */ create( Multimap<? extends K, ? extends V> multimap)114 public static <K, V> LinkedHashMultimap<K, V> create( 115 Multimap<? extends K, ? extends V> multimap) { 116 return new LinkedHashMultimap<K, V>(multimap); 117 } 118 LinkedHashMultimap()119 private LinkedHashMultimap() { 120 super(new LinkedHashMap<K, Collection<V>>()); 121 linkedEntries = Sets.newLinkedHashSet(); 122 } 123 LinkedHashMultimap(int expectedKeys, int expectedValuesPerKey)124 private LinkedHashMultimap(int expectedKeys, int expectedValuesPerKey) { 125 super(new LinkedHashMap<K, Collection<V>>(expectedKeys)); 126 Preconditions.checkArgument(expectedValuesPerKey >= 0); 127 this.expectedValuesPerKey = expectedValuesPerKey; 128 linkedEntries = new LinkedHashSet<Map.Entry<K, V>>( 129 expectedKeys * expectedValuesPerKey); 130 } 131 LinkedHashMultimap(Multimap<? extends K, ? extends V> multimap)132 private LinkedHashMultimap(Multimap<? extends K, ? extends V> multimap) { 133 super(new LinkedHashMap<K, Collection<V>>( 134 Maps.capacity(multimap.keySet().size()))); 135 linkedEntries 136 = new LinkedHashSet<Map.Entry<K, V>>(Maps.capacity(multimap.size())); 137 putAll(multimap); 138 } 139 140 /** 141 * {@inheritDoc} 142 * 143 * <p>Creates an empty {@code LinkedHashSet} for a collection of values for 144 * one key. 145 * 146 * @return a new {@code LinkedHashSet} containing a collection of values for 147 * one key 148 */ createCollection()149 @Override Set<V> createCollection() { 150 return new LinkedHashSet<V>(Maps.capacity(expectedValuesPerKey)); 151 } 152 153 /** 154 * {@inheritDoc} 155 * 156 * <p>Creates a decorated {@code LinkedHashSet} that also keeps track of the 157 * order in which key-value pairs are added to the multimap. 158 * 159 * @param key key to associate with values in the collection 160 * @return a new decorated {@code LinkedHashSet} containing a collection of 161 * values for one key 162 */ createCollection(@ullable K key)163 @Override Collection<V> createCollection(@Nullable K key) { 164 return new SetDecorator(key, createCollection()); 165 } 166 167 private class SetDecorator extends ForwardingSet<V> { 168 final Set<V> delegate; 169 final K key; 170 SetDecorator(@ullable K key, Set<V> delegate)171 SetDecorator(@Nullable K key, Set<V> delegate) { 172 this.delegate = delegate; 173 this.key = key; 174 } 175 delegate()176 @Override protected Set<V> delegate() { 177 return delegate; 178 } 179 createEntry(@ullable E value)180 <E> Map.Entry<K, E> createEntry(@Nullable E value) { 181 return Maps.immutableEntry(key, value); 182 } 183 createEntries(Collection<E> values)184 <E> Collection<Map.Entry<K, E>> createEntries(Collection<E> values) { 185 // converts a collection of values into a list of key/value map entries 186 Collection<Map.Entry<K, E>> entries 187 = Lists.newArrayListWithExpectedSize(values.size()); 188 for (E value : values) { 189 entries.add(createEntry(value)); 190 } 191 return entries; 192 } 193 add(@ullable V value)194 @Override public boolean add(@Nullable V value) { 195 boolean changed = delegate.add(value); 196 if (changed) { 197 linkedEntries.add(createEntry(value)); 198 } 199 return changed; 200 } 201 addAll(Collection<? extends V> values)202 @Override public boolean addAll(Collection<? extends V> values) { 203 boolean changed = delegate.addAll(values); 204 if (changed) { 205 linkedEntries.addAll(createEntries(delegate())); 206 } 207 return changed; 208 } 209 clear()210 @Override public void clear() { 211 linkedEntries.removeAll(createEntries(delegate())); 212 delegate.clear(); 213 } 214 iterator()215 @Override public Iterator<V> iterator() { 216 final Iterator<V> delegateIterator = delegate.iterator(); 217 return new Iterator<V>() { 218 V value; 219 220 public boolean hasNext() { 221 return delegateIterator.hasNext(); 222 } 223 public V next() { 224 value = delegateIterator.next(); 225 return value; 226 } 227 public void remove() { 228 delegateIterator.remove(); 229 linkedEntries.remove(createEntry(value)); 230 } 231 }; 232 } 233 remove(@ullable Object value)234 @Override public boolean remove(@Nullable Object value) { 235 boolean changed = delegate.remove(value); 236 if (changed) { 237 /* 238 * linkedEntries.remove() will return false when this method is called 239 * by entries().iterator().remove() 240 */ 241 linkedEntries.remove(createEntry(value)); 242 } 243 return changed; 244 } 245 removeAll(Collection<?> values)246 @Override public boolean removeAll(Collection<?> values) { 247 boolean changed = delegate.removeAll(values); 248 if (changed) { 249 linkedEntries.removeAll(createEntries(values)); 250 } 251 return changed; 252 } 253 retainAll(Collection<?> values)254 @Override public boolean retainAll(Collection<?> values) { 255 /* 256 * Calling linkedEntries.retainAll() would incorrectly remove values 257 * with other keys. 258 */ 259 boolean changed = false; 260 Iterator<V> iterator = delegate.iterator(); 261 while (iterator.hasNext()) { 262 V value = iterator.next(); 263 if (!values.contains(value)) { 264 iterator.remove(); 265 linkedEntries.remove(Maps.immutableEntry(key, value)); 266 changed = true; 267 } 268 } 269 return changed; 270 } 271 } 272 273 /** 274 * {@inheritDoc} 275 * 276 * <p>Generates an iterator across map entries that follows the ordering in 277 * which the key-value pairs were added to the multimap. 278 * 279 * @return a key-value iterator with the correct ordering 280 */ createEntryIterator()281 @Override Iterator<Map.Entry<K, V>> createEntryIterator() { 282 final Iterator<Map.Entry<K, V>> delegateIterator = linkedEntries.iterator(); 283 284 return new Iterator<Map.Entry<K, V>>() { 285 Map.Entry<K, V> entry; 286 287 public boolean hasNext() { 288 return delegateIterator.hasNext(); 289 } 290 291 public Map.Entry<K, V> next() { 292 entry = delegateIterator.next(); 293 return entry; 294 } 295 296 public void remove() { 297 // Remove from iterator first to keep iterator valid. 298 delegateIterator.remove(); 299 LinkedHashMultimap.this.remove(entry.getKey(), entry.getValue()); 300 } 301 }; 302 } 303 304 /** 305 * {@inheritDoc} 306 * 307 * <p>If {@code values} is not empty and the multimap already contains a 308 * mapping for {@code key}, the {@code keySet()} ordering is unchanged. 309 * However, the provided values always come last in the {@link #entries()} and 310 * {@link #values()} iteration orderings. 311 */ 312 @Override public Set<V> replaceValues( 313 @Nullable K key, Iterable<? extends V> values) { 314 return super.replaceValues(key, values); 315 } 316 317 /** 318 * Returns a set of all key-value pairs. Changes to the returned set will 319 * update the underlying multimap, and vice versa. The entries set does not 320 * support the {@code add} or {@code addAll} operations. 321 * 322 * <p>The iterator generated by the returned set traverses the entries in the 323 * order they were added to the multimap. 324 * 325 * <p>Each entry is an immutable snapshot of a key-value mapping in the 326 * multimap, taken at the time the entry is returned by a method call to the 327 * collection or its iterator. 328 */ 329 @Override public Set<Map.Entry<K, V>> entries() { 330 return super.entries(); 331 } 332 333 /** 334 * Returns a collection of all values in the multimap. Changes to the returned 335 * collection will update the underlying multimap, and vice versa. 336 * 337 * <p>The iterator generated by the returned collection traverses the values 338 * in the order they were added to the multimap. 339 */ 340 @Override public Collection<V> values() { 341 return super.values(); 342 } 343 344 // Unfortunately, the entries() ordering does not determine the key ordering; 345 // see the example in the LinkedListMultimap class Javadoc. 346 347 /** 348 * @serialData the number of distinct keys, and then for each distinct key: 349 * the first key, the number of values for that key, and the key's values, 350 * followed by successive keys and values from the entries() ordering 351 */ 352 private void writeObject(ObjectOutputStream stream) throws IOException { 353 stream.defaultWriteObject(); 354 stream.writeInt(expectedValuesPerKey); 355 Serialization.writeMultimap(this, stream); 356 for (Map.Entry<K, V> entry : linkedEntries) { 357 stream.writeObject(entry.getKey()); 358 stream.writeObject(entry.getValue()); 359 } 360 } 361 362 private void readObject(ObjectInputStream stream) 363 throws IOException, ClassNotFoundException { 364 stream.defaultReadObject(); 365 expectedValuesPerKey = stream.readInt(); 366 int distinctKeys = Serialization.readCount(stream); 367 setMap(new LinkedHashMap<K, Collection<V>>(Maps.capacity(distinctKeys))); 368 linkedEntries = new LinkedHashSet<Map.Entry<K, V>>( 369 distinctKeys * expectedValuesPerKey); 370 Serialization.populateMultimap(this, stream, distinctKeys); 371 linkedEntries.clear(); // will clear and repopulate entries 372 for (int i = 0; i < size(); i++) { 373 @SuppressWarnings("unchecked") // reading data stored by writeObject 374 K key = (K) stream.readObject(); 375 @SuppressWarnings("unchecked") // reading data stored by writeObject 376 V value = (V) stream.readObject(); 377 linkedEntries.add(Maps.immutableEntry(key, value)); 378 } 379 } 380 381 private static final long serialVersionUID = 0; 382 } 383