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