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