001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.map; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.io.Serializable; 023import java.util.AbstractCollection; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.Map; 029import java.util.Set; 030 031import org.apache.commons.collections4.CollectionUtils; 032import org.apache.commons.collections4.Factory; 033import org.apache.commons.collections4.FunctorException; 034import org.apache.commons.collections4.MultiMap; 035import org.apache.commons.collections4.MultiValuedMap; 036import org.apache.commons.collections4.Transformer; 037import org.apache.commons.collections4.iterators.EmptyIterator; 038import org.apache.commons.collections4.iterators.IteratorChain; 039import org.apache.commons.collections4.iterators.LazyIteratorChain; 040import org.apache.commons.collections4.iterators.TransformIterator; 041 042/** 043 * A MultiValueMap decorates another map, allowing it to have 044 * more than one value for a key. 045 * <p> 046 * A {@code MultiMap} is a Map with slightly different semantics. 047 * Putting a value into the map will add the value to a Collection at that key. 048 * Getting a value will return a Collection, holding all the values put to that key. 049 * </p> 050 * <p> 051 * This implementation is a decorator, allowing any Map implementation 052 * to be used as the base. 053 * </p> 054 * <p> 055 * In addition, this implementation allows the type of collection used 056 * for the values to be controlled. By default, an {@code ArrayList} 057 * is used, however a {@code Class} to instantiate may be specified, 058 * or a factory that returns a {@code Collection} instance. 059 * </p> 060 * <p> 061 * <strong>Note that MultiValueMap is not synchronized and is not thread-safe.</strong> 062 * If you wish to use this map from multiple threads concurrently, you must use 063 * appropriate synchronization. This class may throw exceptions when accessed 064 * by concurrent threads without synchronization. 065 * </p> 066 * 067 * @param <K> The type of the keys in this map 068 * @param <V> The type of the values in this map 069 * @since 3.2 070 * @deprecated Since 4.1, use {@link MultiValuedMap MultiValuedMap} instead 071 */ 072@Deprecated 073public class MultiValueMap<K, V> extends AbstractMapDecorator<K, Object> implements MultiMap<K, V>, Serializable { 074 075 /** 076 * Inner class that provides a simple reflection factory. 077 * 078 * @param <T> The type of results supplied by this supplier. 079 */ 080 private static final class ReflectionFactory<T extends Collection<?>> implements Factory<T>, Serializable { 081 082 /** Serialization version */ 083 private static final long serialVersionUID = 2986114157496788874L; 084 085 private final Class<T> clazz; 086 087 ReflectionFactory(final Class<T> clazz) { 088 this.clazz = clazz; 089 } 090 091 @Override 092 public T create() { 093 try { 094 return clazz.getDeclaredConstructor().newInstance(); 095 } catch (final Exception ex) { 096 throw new FunctorException("Cannot instantiate class: " + clazz, ex); 097 } 098 } 099 100 /** 101 * Deserializes an instance from an ObjectInputStream. 102 * 103 * @param in The source ObjectInputStream. 104 * @throws IOException Any of the usual Input/Output related exceptions. 105 * @throws ClassNotFoundException A class of a serialized object cannot be found. 106 */ 107 private void readObject(final ObjectInputStream is) throws IOException, ClassNotFoundException { 108 is.defaultReadObject(); 109 // ensure that the de-serialized class is a Collection, COLLECTIONS-580 110 if (clazz != null && !Collection.class.isAssignableFrom(clazz)) { 111 throw new UnsupportedOperationException(); 112 } 113 } 114 } 115 116 /** 117 * Inner class that provides the values view. 118 */ 119 private final class Values extends AbstractCollection<V> { 120 @Override 121 public void clear() { 122 MultiValueMap.this.clear(); 123 } 124 125 @Override 126 public Iterator<V> iterator() { 127 final IteratorChain<V> chain = new IteratorChain<>(); 128 for (final K k : keySet()) { 129 chain.addIterator(new ValuesIterator(k)); 130 } 131 return chain; 132 } 133 134 @Override 135 public int size() { 136 return totalSize(); 137 } 138 } 139 140 /** 141 * Inner class that provides the values iterator. 142 */ 143 private final class ValuesIterator implements Iterator<V> { 144 private final Object key; 145 private final Collection<V> values; 146 private final Iterator<V> iterator; 147 148 ValuesIterator(final Object key) { 149 this.key = key; 150 this.values = getCollection(key); 151 this.iterator = values.iterator(); 152 } 153 154 @Override 155 public boolean hasNext() { 156 return iterator.hasNext(); 157 } 158 159 @Override 160 public V next() { 161 return iterator.next(); 162 } 163 164 @Override 165 public void remove() { 166 iterator.remove(); 167 if (values.isEmpty()) { 168 MultiValueMap.this.remove(key); 169 } 170 } 171 } 172 173 /** Serialization version */ 174 private static final long serialVersionUID = -2214159910087182007L; 175 176 /** 177 * Creates a map which decorates the given {@code map} and 178 * maps keys to collections of type {@code collectionClass}. 179 * 180 * @param <K> the key type 181 * @param <V> the value type 182 * @param <C> the collection class type 183 * @param map The map to wrap 184 * @param collectionClass The type of the collection class 185 * @return A new multi-value map 186 * @since 4.0 187 */ 188 public static <K, V, C extends Collection<V>> MultiValueMap<K, V> multiValueMap(final Map<K, ? super C> map, 189 final Class<C> collectionClass) { 190 return new MultiValueMap<>(map, new ReflectionFactory<>(collectionClass)); 191 } 192 193 /** 194 * Creates a map which decorates the given {@code map} and 195 * creates the value collections using the supplied {@code collectionFactory}. 196 * 197 * @param <K> the key type 198 * @param <V> the value type 199 * @param <C> the collection class type 200 * @param map The map to decorate 201 * @param collectionFactory The collection factory (must return a Collection object). 202 * @return A new multi-value map 203 * @since 4.0 204 */ 205 public static <K, V, C extends Collection<V>> MultiValueMap<K, V> multiValueMap(final Map<K, ? super C> map, 206 final Factory<C> collectionFactory) { 207 return new MultiValueMap<>(map, collectionFactory); 208 } 209 210 /** 211 * Creates a map which wraps the given map and 212 * maps keys to ArrayLists. 213 * 214 * @param <K> the key type 215 * @param <V> the value type 216 * @param map The map to wrap 217 * @return A new multi-value map 218 * @since 4.0 219 */ 220 @SuppressWarnings({ "unchecked", "rawtypes" }) 221 public static <K, V> MultiValueMap<K, V> multiValueMap(final Map<K, ? super Collection<V>> map) { 222 return MultiValueMap.<K, V, ArrayList>multiValueMap((Map<K, ? super Collection>) map, ArrayList.class); 223 } 224 225 /** The factory for creating value collections. */ 226 private final Factory<? extends Collection<V>> collectionFactory; 227 228 /** The cached values. */ 229 private transient Collection<V> valuesView; 230 231 /** 232 * Creates a MultiValueMap based on a {@code HashMap} and 233 * storing the multiple values in an {@code ArrayList}. 234 */ 235 @SuppressWarnings({ "unchecked", "rawtypes" }) 236 public MultiValueMap() { 237 this(new HashMap<>(), new ReflectionFactory(ArrayList.class)); 238 } 239 240 /** 241 * Creates a MultiValueMap which decorates the given {@code map} and 242 * creates the value collections using the supplied {@code collectionFactory}. 243 * 244 * @param <C> the collection class type 245 * @param map The map to decorate 246 * @param collectionFactory The collection factory which must return a Collection instance 247 */ 248 @SuppressWarnings("unchecked") 249 protected <C extends Collection<V>> MultiValueMap(final Map<K, ? super C> map, 250 final Factory<C> collectionFactory) { 251 super((Map<K, Object>) map); 252 if (collectionFactory == null) { 253 throw new IllegalArgumentException("The factory must not be null"); 254 } 255 this.collectionFactory = collectionFactory; 256 } 257 258 /** 259 * Clear the map. 260 */ 261 @Override 262 public void clear() { 263 // If you believe that you have GC issues here, try uncommenting this code 264// Set pairs = getMap().entrySet(); 265// Iterator pairsIterator = pairs.iterator(); 266// while (pairsIterator.hasNext()) { 267// Map.Entry keyValuePair = (Map.Entry) pairsIterator.next(); 268// Collection coll = (Collection) keyValuePair.getValue(); 269// coll.clear(); 270// } 271 decorated().clear(); 272 } 273 274 /** 275 * Checks whether the map contains the value specified. 276 * <p> 277 * This checks all collections against all keys for the value, and thus could be slow. 278 * </p> 279 * 280 * @param value The value to search for 281 * @return true if the map contains the value 282 */ 283 @Override 284 @SuppressWarnings("unchecked") 285 public boolean containsValue(final Object value) { 286 final Set<Map.Entry<K, Object>> pairs = decorated().entrySet(); 287 if (pairs != null) { 288 for (final Map.Entry<K, Object> entry : pairs) { 289 if (((Collection<V>) entry.getValue()).contains(value)) { 290 return true; 291 } 292 } 293 } 294 return false; 295 } 296 297 /** 298 * Checks whether the collection at the specified key contains the value. 299 * 300 * @param key The key to search for 301 * @param value The value to search for 302 * @return true if the map contains the value 303 */ 304 public boolean containsValue(final Object key, final Object value) { 305 final Collection<V> coll = getCollection(key); 306 if (coll == null) { 307 return false; 308 } 309 return coll.contains(value); 310 } 311 312 /** 313 * Creates a new instance of the map value Collection container 314 * using the factory. 315 * <p> 316 * This method can be overridden to perform your own processing 317 * instead of using the factory. 318 * </p> 319 * 320 * @param size The collection size that is about to be added 321 * @return The new collection 322 */ 323 protected Collection<V> createCollection(final int size) { 324 return collectionFactory.get(); 325 } 326 327 /** 328 * {@inheritDoc} 329 * <p> 330 * Note: the returned Entry objects will contain as value a {@link Collection} 331 * of all values that are mapped to the given key. To get a "flattened" version 332 * of all mappings contained in this map, use {@link #iterator()}. 333 * </p> 334 * 335 * @see #iterator() 336 */ 337 @Override 338 public Set<Entry<K, Object>> entrySet() { // NOPMD 339 return super.entrySet(); 340 } 341 342 /** 343 * Gets the collection mapped to the specified key. 344 * This method is a convenience method to typecast the result of {@code get(key)}. 345 * 346 * @param key The key to retrieve 347 * @return The collection mapped to the key, null if no mapping 348 */ 349 @SuppressWarnings("unchecked") 350 public Collection<V> getCollection(final Object key) { 351 return (Collection<V>) decorated().get(key); 352 } 353 354 /** 355 * Gets an iterator for all mappings stored in this {@link MultiValueMap}. 356 * <p> 357 * The iterator will return multiple Entry objects with the same key 358 * if there are multiple values mapped to this key. 359 * </p> 360 * <p> 361 * Note: calling {@link java.util.Map.Entry#setValue(Object)} on any of the returned 362 * elements will result in a {@link UnsupportedOperationException}. 363 * </p> 364 * 365 * @return The iterator of all mappings in this map 366 * @since 4.0 367 */ 368 public Iterator<Entry<K, V>> iterator() { 369 final Collection<K> allKeys = new ArrayList<>(keySet()); 370 final Iterator<K> keyIterator = allKeys.iterator(); 371 372 return new LazyIteratorChain<Entry<K, V>>() { 373 @Override 374 protected Iterator<? extends Entry<K, V>> nextIterator(final int count) { 375 if (!keyIterator.hasNext()) { 376 return null; 377 } 378 final K key = keyIterator.next(); 379 final Transformer<V, Entry<K, V>> transformer = input -> new Entry<K, V>() { 380 @Override 381 public K getKey() { 382 return key; 383 } 384 385 @Override 386 public V getValue() { 387 return input; 388 } 389 390 @Override 391 public V setValue(final V value) { 392 throw new UnsupportedOperationException(); 393 } 394 }; 395 return new TransformIterator<>(new ValuesIterator(key), transformer); 396 } 397 }; 398 } 399 400 /** 401 * Gets an iterator for the collection mapped to the specified key. 402 * 403 * @param key The key to get an iterator for 404 * @return The iterator of the collection at the key, empty iterator if key not in map 405 */ 406 public Iterator<V> iterator(final Object key) { 407 if (!containsKey(key)) { 408 return EmptyIterator.<V>emptyIterator(); 409 } 410 return new ValuesIterator(key); 411 } 412 413 /** 414 * Adds the value to the collection associated with the specified key. 415 * <p> 416 * Unlike a normal {@code Map} the previous value is not replaced. 417 * Instead, the new value is added to the collection stored against the key. 418 * </p> 419 * 420 * @param key The key to store against 421 * @param value The value to add to the collection at the key 422 * @return The value added if the map changed and null if the map did not change 423 */ 424 @Override 425 @SuppressWarnings("unchecked") 426 public Object put(final K key, final Object value) { 427 boolean result = false; 428 Collection<V> coll = getCollection(key); 429 if (coll == null) { 430 coll = createCollection(1); // might produce a non-empty collection 431 coll.add((V) value); 432 if (!coll.isEmpty()) { 433 // only add if non-zero size to maintain class state 434 decorated().put(key, coll); 435 result = true; // map definitely changed 436 } 437 } else { 438 result = coll.add((V) value); 439 } 440 return result ? value : null; 441 } 442 443 /** 444 * Adds a collection of values to the collection associated with 445 * the specified key. 446 * 447 * @param key The key to store against 448 * @param values The values to add to the collection at the key, null ignored 449 * @return true if this map changed 450 */ 451 public boolean putAll(final K key, final Collection<V> values) { 452 if (values == null || values.isEmpty()) { 453 return false; 454 } 455 boolean result = false; 456 Collection<V> coll = getCollection(key); 457 if (coll == null) { 458 coll = createCollection(values.size()); // might produce a non-empty collection 459 coll.addAll(values); 460 if (!coll.isEmpty()) { 461 // only add if non-zero size to maintain class state 462 decorated().put(key, coll); 463 result = true; // map definitely changed 464 } 465 } else { 466 result = coll.addAll(values); 467 } 468 return result; 469 } 470 471 /** 472 * Override superclass to ensure that MultiMap instances are 473 * correctly handled. 474 * <p> 475 * If you call this method with a normal map, each entry is 476 * added using {@code put(Object, Object)}. 477 * If you call this method with a multi map, each entry is 478 * added using {@code putAll(Object, Collection)}. 479 * </p> 480 * 481 * @param map The map to copy (either a normal or multi map) 482 */ 483 @Override 484 @SuppressWarnings("unchecked") 485 public void putAll(final Map<? extends K, ?> map) { 486 if (map instanceof MultiMap) { 487 for (final Map.Entry<? extends K, Object> entry : ((MultiMap<? extends K, V>) map).entrySet()) { 488 putAll(entry.getKey(), (Collection<V>) entry.getValue()); 489 } 490 } else { 491 for (final Map.Entry<? extends K, ?> entry : map.entrySet()) { 492 put(entry.getKey(), entry.getValue()); 493 } 494 } 495 } 496 497 /** 498 * Deserializes the map in using a custom routine. 499 * 500 * @param in The input stream 501 * @throws IOException Thrown if an error occurs while reading from the stream 502 * @throws ClassNotFoundException if an object read from the stream cannot be loaded 503 * @since 4.0 504 */ 505 @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect 506 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 507 in.defaultReadObject(); 508 map = (Map<K, Object>) in.readObject(); // (1) 509 } 510 511 /** 512 * Removes a specific value from map. 513 * <p> 514 * The item is removed from the collection mapped to the specified key. 515 * Other values attached to that key are unaffected. 516 * </p> 517 * <p> 518 * If the last value for a key is removed, {@code null} will be returned 519 * from a subsequent {@code get(key)}. 520 * </p> 521 * 522 * @param key The key to remove from 523 * @param value The value to remove 524 * @return {@code true} if the mapping was removed, {@code false} otherwise 525 */ 526 @Override 527 public boolean removeMapping(final Object key, final Object value) { 528 final Collection<V> valuesForKey = getCollection(key); 529 if (valuesForKey == null) { 530 return false; 531 } 532 final boolean removed = valuesForKey.remove(value); 533 if (!removed) { 534 return false; 535 } 536 if (valuesForKey.isEmpty()) { 537 remove(key); 538 } 539 return true; 540 } 541 542 /** 543 * Gets the size of the collection mapped to the specified key. 544 * 545 * @param key The key to get size for 546 * @return The size of the collection at the key, zero if key not in map 547 */ 548 public int size(final Object key) { 549 final Collection<V> coll = getCollection(key); 550 if (coll == null) { 551 return 0; 552 } 553 return coll.size(); 554 } 555 556 /** 557 * Gets the total size of the map by counting all the values. 558 * 559 * @return The total size of the map counting all values 560 */ 561 public int totalSize() { 562 int total = 0; 563 for (final Object v : decorated().values()) { 564 total += CollectionUtils.size(v); 565 } 566 return total; 567 } 568 569 /** 570 * Gets a collection containing all the values in the map. 571 * <p> 572 * This returns a collection containing the combination of values from all keys. 573 * </p> 574 * 575 * @return A collection view of the values contained in this map 576 */ 577 @Override 578 @SuppressWarnings("unchecked") 579 public Collection<Object> values() { 580 final Collection<V> vs = valuesView; 581 return (Collection<Object>) (vs != null ? vs : (valuesView = new Values())); 582 } 583 584 /** 585 * Serializes this object to an ObjectOutputStream. 586 * 587 * @param out The target ObjectOutputStream. 588 * @throws IOException thrown when an I/O errors occur writing to the target stream. 589 * @since 4.0 590 */ 591 private void writeObject(final ObjectOutputStream out) throws IOException { 592 out.defaultWriteObject(); 593 out.writeObject(map); 594 } 595 596}