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