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.Serializable;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.commons.collections4.CollectionUtils;
026import org.apache.commons.collections4.collection.CompositeCollection;
027import org.apache.commons.collections4.set.CompositeSet;
028
029/**
030 * Decorates a map of other maps to provide a single unified view.
031 * <p>
032 * Changes made to this map will actually be made on the decorated map.
033 * Add and remove operations require the use of a pluggable strategy. If no
034 * strategy is provided then add and remove are unsupported.
035 * </p>
036 * <p>
037 * <strong>Note that CompositeMap is not synchronized and is not thread-safe.</strong>
038 * If you wish to use this map from multiple threads concurrently, you must use
039 * appropriate synchronization. The simplest approach is to wrap this map
040 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
041 * exceptions when accessed by concurrent threads without synchronization.
042 * </p>
043 *
044 * @param <K> The type of the keys in this map
045 * @param <V> The type of the values in this map
046 * @since 3.0
047 */
048public class CompositeMap<K, V> extends AbstractIterableMap<K, V> implements Serializable {
049
050    /**
051     * This interface allows definition for all of the indeterminate
052     * mutators in a CompositeMap, as well as providing a hook for
053     * callbacks on key collisions.
054     *
055     * @param <K> The type of the keys in the map
056     * @param <V> The type of the values in the map
057     */
058    public interface MapMutator<K, V> extends Serializable {
059
060        /**
061         * Called when the CompositeMap.put() method is invoked.
062         *
063         * @param map  The CompositeMap which is being modified
064         * @param composited  array of Maps in the CompositeMap being modified
065         * @param key  key with which the specified value is to be associated.
066         * @param value  value to be associated with the specified key.
067         * @return previous value associated with specified key, or {@code null}
068         *         if there was no mapping for key.  A {@code null} return can
069         *         also indicate that the map previously associated {@code null}
070         *         with the specified key, if the implementation supports
071         *         {@code null} values.
072         *
073         * @throws UnsupportedOperationException if not defined
074         * @throws ClassCastException if the class of the specified key or value
075         *            prevents it from being stored in this map.
076         * @throws IllegalArgumentException if some aspect of this key or value
077         *            prevents it from being stored in this map.
078         * @throws NullPointerException this map does not permit {@code null}
079         *            keys or values, and the specified key or value is
080         *            {@code null}.
081         */
082        V put(CompositeMap<K, V> map, Map<K, V>[] composited, K key, V value);
083
084        /**
085         * Called when the CompositeMap.putAll() method is invoked.
086         *
087         * @param map  The CompositeMap which is being modified
088         * @param composited  array of Maps in the CompositeMap being modified
089         * @param mapToAdd  Mappings to be stored in this CompositeMap
090         * @throws UnsupportedOperationException if not defined
091         * @throws ClassCastException if the class of the specified key or value
092         *            prevents it from being stored in this map.
093         * @throws IllegalArgumentException if some aspect of this key or value
094         *            prevents it from being stored in this map.
095         * @throws NullPointerException this map does not permit {@code null}
096         *            keys or values, and the specified key or value is
097         *            {@code null}.
098         */
099        void putAll(CompositeMap<K, V> map, Map<K, V>[] composited,
100                Map<? extends K, ? extends V> mapToAdd);
101
102        /**
103         * Called when adding a new Composited Map results in a
104         * key collision.
105         *
106         * @param composite  The CompositeMap with the collision
107         * @param existing  The Map already in the composite which contains the
108         *        offending key
109         * @param added  The Map being added
110         * @param intersect  The intersection of the keysets of the existing and added maps
111         */
112        void resolveCollision(CompositeMap<K, V> composite, Map<K, V> existing,
113                Map<K, V> added, Collection<K> intersect);
114    }
115
116    @SuppressWarnings("rawtypes")
117    private static final Map[] EMPTY_MAP_ARRAY = {};
118
119    /** Serialization version */
120    private static final long serialVersionUID = -6096931280583808322L;
121
122    /** Array of all maps in the composite */
123    private Map<K, V>[] composite;
124
125    /** Handle mutation operations */
126    private MapMutator<K, V> mutator;
127
128    /**
129     * Create a new, empty, CompositeMap.
130     */
131    @SuppressWarnings("unchecked")
132    public CompositeMap() {
133        this(new Map[] {}, null);
134    }
135
136    /**
137     * Create a new CompositeMap which composites all of the Map instances in the
138     * argument. It copies the argument array, it does not use it directly.
139     *
140     * @param composite  The Maps to be composited
141     * @throws IllegalArgumentException if there is a key collision
142     */
143    public CompositeMap(final Map<K, V>... composite) {
144        this(composite, null);
145    }
146
147    /**
148     * Create a new CompositeMap with two composited Map instances.
149     *
150     * @param one  The first Map to be composited
151     * @param two  The second Map to be composited
152     * @throws IllegalArgumentException if there is a key collision
153     */
154    @SuppressWarnings("unchecked")
155    public CompositeMap(final Map<K, V> one, final Map<K, V> two) {
156        this(new Map[] { one, two }, null);
157    }
158
159    /**
160     * Create a new CompositeMap with two composited Map instances.
161     *
162     * @param one  The first Map to be composited
163     * @param two  The second Map to be composited
164     * @param mutator  MapMutator to be used for mutation operations
165     */
166    @SuppressWarnings("unchecked")
167    public CompositeMap(final Map<K, V> one, final Map<K, V> two, final MapMutator<K, V> mutator) {
168        this(new Map[] { one, two }, mutator);
169    }
170
171    /**
172     * Create a new CompositeMap which composites all of the Map instances in the
173     * argument. It copies the argument array, it does not use it directly.
174     *
175     * @param composite  Maps to be composited
176     * @param mutator  MapMutator to be used for mutation operations
177     */
178    @SuppressWarnings("unchecked")
179    public CompositeMap(final Map<K, V>[] composite, final MapMutator<K, V> mutator) {
180        this.mutator = mutator;
181        this.composite = EMPTY_MAP_ARRAY;
182        for (int i = composite.length - 1; i >= 0; --i) {
183            this.addComposited(composite[i]);
184        }
185    }
186
187    /**
188     * Add an additional Map to the composite.
189     *
190     * @param map  The Map to be added to the composite
191     * @throws IllegalArgumentException if there is a key collision and there is no
192     *         MapMutator set to handle it.
193     */
194    public synchronized void addComposited(final Map<K, V> map) throws IllegalArgumentException {
195        if (map != null) {
196            for (int i = composite.length - 1; i >= 0; --i) {
197                final Collection<K> intersect = CollectionUtils.intersection(composite[i].keySet(), map.keySet());
198                if (!intersect.isEmpty()) {
199                    if (mutator == null) {
200                        throw new IllegalArgumentException("Key collision adding Map to CompositeMap");
201                    }
202                    mutator.resolveCollision(this, composite[i], map, intersect);
203                }
204            }
205            final Map<K, V>[] temp = Arrays.copyOf(composite, composite.length + 1);
206            temp[temp.length - 1] = map;
207            composite = temp;
208        }
209    }
210
211    /**
212     * Calls {@code clear()} on all composited Maps.
213     *
214     * @throws UnsupportedOperationException if any of the composited Maps do not support clear()
215     */
216    @Override
217    public void clear() {
218        for (int i = composite.length - 1; i >= 0; --i) {
219            composite[i].clear();
220        }
221    }
222
223    /**
224     * Returns {@code true} if this map contains a mapping for the specified
225     * key.  More formally, returns {@code true} if and only if
226     * this map contains at a mapping for a key {@code k} such that
227     * {@code (key==null ? k==null : key.equals(k))}.  (There can be
228     * at most one such mapping.)
229     *
230     * @param key  key whose presence in this map is to be tested.
231     * @return {@code true} if this map contains a mapping for the specified
232     *         key.
233     *
234     * @throws ClassCastException if the key is of an inappropriate type for
235     *         this map (optional).
236     * @throws NullPointerException if the key is {@code null} and this map
237     *            does not permit {@code null} keys (optional).
238     */
239    @Override
240    public boolean containsKey(final Object key) {
241        for (int i = composite.length - 1; i >= 0; --i) {
242            if (composite[i].containsKey(key)) {
243                return true;
244            }
245        }
246        return false;
247    }
248
249    /**
250     * Returns {@code true} if this map maps one or more keys to the
251     * specified value.  More formally, returns {@code true} if and only if
252     * this map contains at least one mapping to a value {@code v} such that
253     * {@code (value==null ? v==null : value.equals(v))}.  This operation
254     * will probably require time linear in the map size for most
255     * implementations of the {@code Map} interface.
256     *
257     * @param value value whose presence in this map is to be tested.
258     * @return {@code true} if this map maps one or more keys to the
259     *         specified value.
260     * @throws ClassCastException if the value is of an inappropriate type for
261     *         this map (optional).
262     * @throws NullPointerException if the value is {@code null} and this map
263     *            does not permit {@code null} values (optional).
264     */
265    @Override
266    public boolean containsValue(final Object value) {
267        for (int i = composite.length - 1; i >= 0; --i) {
268            if (composite[i].containsValue(value)) {
269                return true;
270            }
271        }
272        return false;
273    }
274
275    /**
276     * Returns a set view of the mappings contained in this map.  Each element
277     * in the returned set is a {@code Map.Entry}.  The set is backed by the
278     * map, so changes to the map are reflected in the set, and vice-versa.
279     * If the map is modified while an iteration over the set is in progress,
280     * the results of the iteration are undefined.  The set supports element
281     * removal, which removes the corresponding mapping from the map, via the
282     * {@code Iterator.remove}, {@code Set.remove}, {@code removeAll},
283     * {@code retainAll} and {@code clear} operations.  It does not support
284     * the {@code add} or {@code addAll} operations.
285     * <p>
286     * This implementation returns a {@code CompositeSet} which
287     * composites the entry sets from all of the composited maps.
288     *
289     * @see CompositeSet
290     * @return A set view of the mappings contained in this map.
291     */
292    @Override
293    public Set<Map.Entry<K, V>> entrySet() {
294        final CompositeSet<Map.Entry<K, V>> entries = new CompositeSet<>();
295        for (int i = composite.length - 1; i >= 0; --i) {
296            entries.addComposited(composite[i].entrySet());
297        }
298        return entries;
299    }
300
301    /**
302     * Checks if this Map equals another as per the Map specification.
303     *
304     * @param obj  The object to compare to
305     * @return true if the maps are equal
306     */
307    @Override
308    public boolean equals(final Object obj) {
309        if (obj instanceof Map) {
310            final Map<?, ?> map = (Map<?, ?>) obj;
311            return this.entrySet().equals(map.entrySet());
312        }
313        return false;
314    }
315
316    /**
317     * Gets the value to which this map maps the specified key.  Returns
318     * {@code null} if the map contains no mapping for this key.  A return
319     * value of {@code null} does not <em>necessarily</em> indicate that the
320     * map contains no mapping for the key; it's also possible that the map
321     * explicitly maps the key to {@code null}.  The {@code containsKey}
322     * operation may be used to distinguish these two cases.
323     *
324     * <p>More formally, if this map contains a mapping from a key
325     * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
326     * key.equals(k))}, then this method returns {@code v}; otherwise
327     * it returns {@code null}.  (There can be at most one such mapping.)
328     *
329     * @param key key whose associated value is to be returned.
330     * @return The value to which this map maps the specified key, or
331     *         {@code null} if the map contains no mapping for this key.
332     *
333     * @throws ClassCastException if the key is of an inappropriate type for
334     *         this map (optional).
335     * @throws NullPointerException key is {@code null} and this map does
336     *         not permit {@code null} keys (optional).
337     *
338     * @see #containsKey(Object)
339     */
340    @Override
341    public V get(final Object key) {
342        for (int i = composite.length - 1; i >= 0; --i) {
343            if (composite[i].containsKey(key)) {
344                return composite[i].get(key);
345            }
346        }
347        return null;
348    }
349
350    /**
351     * Gets a hash code for the Map as per the Map specification.
352     * {@inheritDoc}
353     */
354    @Override
355    public int hashCode() {
356        int code = 0;
357        for (final Map.Entry<K, V> entry : entrySet()) {
358            code += entry.hashCode();
359        }
360        return code;
361    }
362
363    /**
364     * Returns {@code true} if this map contains no key-value mappings.
365     *
366     * @return {@code true} if this map contains no key-value mappings.
367     */
368    @Override
369    public boolean isEmpty() {
370        for (int i = composite.length - 1; i >= 0; --i) {
371            if (!composite[i].isEmpty()) {
372                return false;
373            }
374        }
375        return true;
376    }
377
378    /**
379     * Returns a set view of the keys contained in this map.  The set is
380     * backed by the map, so changes to the map are reflected in the set, and
381     * vice-versa.  If the map is modified while an iteration over the set is
382     * in progress, the results of the iteration are undefined.  The set
383     * supports element removal, which removes the corresponding mapping from
384     * the map, via the {@code Iterator.remove}, {@code Set.remove},
385     * {@code removeAll} {@code retainAll}, and {@code clear} operations.
386     * It does not support the add or {@code addAll} operations.
387     * <p>
388     * This implementation returns a {@code CompositeSet} which
389     * composites the key sets from all of the composited maps.
390     * </p>
391     *
392     * @return A set view of the keys contained in this map.
393     */
394    @Override
395    public Set<K> keySet() {
396        final CompositeSet<K> keys = new CompositeSet<>();
397        for (int i = composite.length - 1; i >= 0; --i) {
398            keys.addComposited(composite[i].keySet());
399        }
400        return keys;
401    }
402
403    /**
404     * Associates the specified value with the specified key in this map
405     * (optional operation).  If the map previously contained a mapping for
406     * this key, the old value is replaced by the specified value.  (A map
407     * {@code m} is said to contain a mapping for a key {@code k} if and only
408     * if {@link #containsKey(Object) m.containsKey(k)} would return
409     * {@code true}.))
410     *
411     * @param key key with which the specified value is to be associated.
412     * @param value value to be associated with the specified key.
413     * @return previous value associated with specified key, or {@code null}
414     *         if there was no mapping for key.  A {@code null} return can
415     *         also indicate that the map previously associated {@code null}
416     *         with the specified key, if the implementation supports
417     *         {@code null} values.
418     *
419     * @throws UnsupportedOperationException if no MapMutator has been specified
420     * @throws ClassCastException if the class of the specified key or value
421     *            prevents it from being stored in this map.
422     * @throws IllegalArgumentException if some aspect of this key or value
423     *            prevents it from being stored in this map.
424     * @throws NullPointerException this map does not permit {@code null}
425     *            keys or values, and the specified key or value is
426     *            {@code null}.
427     */
428    @Override
429    public V put(final K key, final V value) {
430        if (mutator == null) {
431            throw new UnsupportedOperationException("No mutator specified");
432        }
433        return mutator.put(this, composite, key, value);
434    }
435
436    /**
437     * Copies all of the mappings from the specified map to this map
438     * (optional operation).  The effect of this call is equivalent to that
439     * of calling {@link #put(Object,Object) put(k, v)} on this map once
440     * for each mapping from key {@code k} to value {@code v} in the
441     * specified map.  The behavior of this operation is unspecified if the
442     * specified map is modified while the operation is in progress.
443     *
444     * @param map Mappings to be stored in this map.
445     * @throws UnsupportedOperationException if the {@code putAll} method is
446     *         not supported by this map.
447     *
448     * @throws ClassCastException if the class of a key or value in the
449     *         specified map prevents it from being stored in this map.
450     *
451     * @throws IllegalArgumentException some aspect of a key or value in the
452     *         specified map prevents it from being stored in this map.
453     * @throws NullPointerException the specified map is {@code null}, or if
454     *         this map does not permit {@code null} keys or values, and the
455     *         specified map contains {@code null} keys or values.
456     */
457    @Override
458    public void putAll(final Map<? extends K, ? extends V> map) {
459        if (mutator == null) {
460            throw new UnsupportedOperationException("No mutator specified");
461        }
462        mutator.putAll(this, composite, map);
463    }
464
465    /**
466     * Removes the mapping for this key from this map if it is present
467     * (optional operation).   More formally, if this map contains a mapping
468     * from key {@code k} to value {@code v} such that
469     * {@code (key==null ?  k==null : key.equals(k))}, that mapping
470     * is removed.  (The map can contain at most one such mapping.)
471     *
472     * <p>Returns the value to which the map previously associated the key, or
473     * {@code null} if the map contained no mapping for this key.  (A
474     * {@code null} return can also indicate that the map previously
475     * associated {@code null} with the specified key if the implementation
476     * supports {@code null} values.)  The map will not contain a mapping for
477     * the specified  key once the call returns.
478     *
479     * @param key key whose mapping is to be removed from the map.
480     * @return previous value associated with specified key, or {@code null}
481     *         if there was no mapping for key.
482     *
483     * @throws ClassCastException if the key is of an inappropriate type for
484     *         the composited map (optional).
485     * @throws NullPointerException if the key is {@code null} and the composited map
486     *            does not permit {@code null} keys (optional).
487     * @throws UnsupportedOperationException if the {@code remove} method is
488     *         not supported by the composited map containing the key
489     */
490    @Override
491    public V remove(final Object key) {
492        for (int i = composite.length - 1; i >= 0; --i) {
493            if (composite[i].containsKey(key)) {
494                return composite[i].remove(key);
495            }
496        }
497        return null;
498    }
499
500    /**
501     * Remove a Map from the composite.
502     *
503     * @param map  The Map to be removed from the composite
504     * @return The removed Map or {@code null} if map is not in the composite
505     */
506    @SuppressWarnings("unchecked")
507    public synchronized Map<K, V> removeComposited(final Map<K, V> map) {
508        final int size = composite.length;
509        for (int i = 0; i < size; ++i) {
510            if (composite[i].equals(map)) {
511                final Map<K, V>[] temp = new Map[size - 1];
512                System.arraycopy(composite, 0, temp, 0, i);
513                System.arraycopy(composite, i + 1, temp, i, size - i - 1);
514                composite = temp;
515                return map;
516            }
517        }
518        return null;
519    }
520
521    /**
522     * Specify the MapMutator to be used by mutation operations.
523     *
524     * @param mutator  The MapMutator to be used for mutation delegation
525     */
526    public void setMutator(final MapMutator<K, V> mutator) {
527        this.mutator = mutator;
528    }
529
530    /**
531     * Returns the number of key-value mappings in this map.  If the
532     * map contains more than {@code Integer.MAX_VALUE} elements, returns
533     * {@code Integer.MAX_VALUE}.
534     *
535     * @return The number of key-value mappings in this map.
536     */
537    @Override
538    public int size() {
539        long size = 0;
540        for (int i = composite.length - 1; i >= 0; --i) {
541            size += composite[i].size();
542        }
543        return (int) Math.min(size, Integer.MAX_VALUE);
544    }
545
546    /**
547     * Returns a collection view of the values contained in this map.  The
548     * collection is backed by the map, so changes to the map are reflected in
549     * the collection, and vice-versa.  If the map is modified while an
550     * iteration over the collection is in progress, the results of the
551     * iteration are undefined.  The collection supports element removal,
552     * which removes the corresponding mapping from the map, via the
553     * {@code Iterator.remove}, {@code Collection.remove},
554     * {@code removeAll}, {@code retainAll} and {@code clear} operations.
555     * It does not support the add or {@code addAll} operations.
556     *
557     * @return A collection view of the values contained in this map.
558     */
559    @Override
560    public Collection<V> values() {
561        final CompositeCollection<V> values = new CompositeCollection<>();
562        for (int i = composite.length - 1; i >= 0; --i) {
563            values.addComposited(composite[i].values());
564        }
565        return values;
566    }
567}