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