CompositeMap.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.collections4.map;

  18. import java.io.Serializable;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Map;
  22. import java.util.Set;

  23. import org.apache.commons.collections4.CollectionUtils;
  24. import org.apache.commons.collections4.collection.CompositeCollection;
  25. import org.apache.commons.collections4.set.CompositeSet;

  26. /**
  27.  * Decorates a map of other maps to provide a single unified view.
  28.  * <p>
  29.  * Changes made to this map will actually be made on the decorated map.
  30.  * Add and remove operations require the use of a pluggable strategy. If no
  31.  * strategy is provided then add and remove are unsupported.
  32.  * </p>
  33.  * <p>
  34.  * <strong>Note that CompositeMap is not synchronized and is not thread-safe.</strong>
  35.  * If you wish to use this map from multiple threads concurrently, you must use
  36.  * appropriate synchronization. The simplest approach is to wrap this map
  37.  * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
  38.  * exceptions when accessed by concurrent threads without synchronization.
  39.  * </p>
  40.  *
  41.  * @param <K> the type of the keys in this map
  42.  * @param <V> the type of the values in this map
  43.  * @since 3.0
  44.  */
  45. public class CompositeMap<K, V> extends AbstractIterableMap<K, V> implements Serializable {

  46.     /**
  47.      * This interface allows definition for all of the indeterminate
  48.      * mutators in a CompositeMap, as well as providing a hook for
  49.      * callbacks on key collisions.
  50.      *
  51.      * @param <K> the type of the keys in the map
  52.      * @param <V> the type of the values in the map
  53.      */
  54.     public interface MapMutator<K, V> extends Serializable {
  55.         /**
  56.          * Called when the CompositeMap.put() method is invoked.
  57.          *
  58.          * @param map  the CompositeMap which is being modified
  59.          * @param composited  array of Maps in the CompositeMap being modified
  60.          * @param key  key with which the specified value is to be associated.
  61.          * @param value  value to be associated with the specified key.
  62.          * @return previous value associated with specified key, or {@code null}
  63.          *         if there was no mapping for key.  A {@code null} return can
  64.          *         also indicate that the map previously associated {@code null}
  65.          *         with the specified key, if the implementation supports
  66.          *         {@code null} values.
  67.          *
  68.          * @throws UnsupportedOperationException if not defined
  69.          * @throws ClassCastException if the class of the specified key or value
  70.          *            prevents it from being stored in this map.
  71.          * @throws IllegalArgumentException if some aspect of this key or value
  72.          *            prevents it from being stored in this map.
  73.          * @throws NullPointerException this map does not permit {@code null}
  74.          *            keys or values, and the specified key or value is
  75.          *            {@code null}.
  76.          */
  77.         V put(CompositeMap<K, V> map, Map<K, V>[] composited, K key, V value);

  78.         /**
  79.          * Called when the CompositeMap.putAll() method is invoked.
  80.          *
  81.          * @param map  the CompositeMap which is being modified
  82.          * @param composited  array of Maps in the CompositeMap being modified
  83.          * @param mapToAdd  Mappings to be stored in this CompositeMap
  84.          * @throws UnsupportedOperationException if not defined
  85.          * @throws ClassCastException if the class of the specified key or value
  86.          *            prevents it from being stored in this map.
  87.          * @throws IllegalArgumentException if some aspect of this key or value
  88.          *            prevents it from being stored in this map.
  89.          * @throws NullPointerException this map does not permit {@code null}
  90.          *            keys or values, and the specified key or value is
  91.          *            {@code null}.
  92.          */
  93.         void putAll(CompositeMap<K, V> map, Map<K, V>[] composited,
  94.                 Map<? extends K, ? extends V> mapToAdd);

  95.         /**
  96.          * Called when adding a new Composited Map results in a
  97.          * key collision.
  98.          *
  99.          * @param composite  the CompositeMap with the collision
  100.          * @param existing  the Map already in the composite which contains the
  101.          *        offending key
  102.          * @param added  the Map being added
  103.          * @param intersect  the intersection of the keysets of the existing and added maps
  104.          */
  105.         void resolveCollision(CompositeMap<K, V> composite, Map<K, V> existing,
  106.                 Map<K, V> added, Collection<K> intersect);
  107.     }

  108.     @SuppressWarnings("rawtypes")
  109.     private static final Map[] EMPTY_MAP_ARRAY = {};

  110.     /** Serialization version */
  111.     private static final long serialVersionUID = -6096931280583808322L;

  112.     /** Array of all maps in the composite */
  113.     private Map<K, V>[] composite;

  114.     /** Handle mutation operations */
  115.     private MapMutator<K, V> mutator;

  116.     /**
  117.      * Create a new, empty, CompositeMap.
  118.      */
  119.     @SuppressWarnings("unchecked")
  120.     public CompositeMap() {
  121.         this(new Map[] {}, null);
  122.     }

  123.     /**
  124.      * Create a new CompositeMap which composites all of the Map instances in the
  125.      * argument. It copies the argument array, it does not use it directly.
  126.      *
  127.      * @param composite  the Maps to be composited
  128.      * @throws IllegalArgumentException if there is a key collision
  129.      */
  130.     public CompositeMap(final Map<K, V>... composite) {
  131.         this(composite, null);
  132.     }

  133.     /**
  134.      * Create a new CompositeMap with two composited Map instances.
  135.      *
  136.      * @param one  the first Map to be composited
  137.      * @param two  the second Map to be composited
  138.      * @throws IllegalArgumentException if there is a key collision
  139.      */
  140.     @SuppressWarnings("unchecked")
  141.     public CompositeMap(final Map<K, V> one, final Map<K, V> two) {
  142.         this(new Map[] { one, two }, null);
  143.     }

  144.     /**
  145.      * Create a new CompositeMap with two composited Map instances.
  146.      *
  147.      * @param one  the first Map to be composited
  148.      * @param two  the second Map to be composited
  149.      * @param mutator  MapMutator to be used for mutation operations
  150.      */
  151.     @SuppressWarnings("unchecked")
  152.     public CompositeMap(final Map<K, V> one, final Map<K, V> two, final MapMutator<K, V> mutator) {
  153.         this(new Map[] { one, two }, mutator);
  154.     }

  155.     /**
  156.      * Create a new CompositeMap which composites all of the Map instances in the
  157.      * argument. It copies the argument array, it does not use it directly.
  158.      *
  159.      * @param composite  Maps to be composited
  160.      * @param mutator  MapMutator to be used for mutation operations
  161.      */
  162.     @SuppressWarnings("unchecked")
  163.     public CompositeMap(final Map<K, V>[] composite, final MapMutator<K, V> mutator) {
  164.         this.mutator = mutator;
  165.         this.composite = EMPTY_MAP_ARRAY;
  166.         for (int i = composite.length - 1; i >= 0; --i) {
  167.             this.addComposited(composite[i]);
  168.         }
  169.     }

  170.     /**
  171.      * Add an additional Map to the composite.
  172.      *
  173.      * @param map  the Map to be added to the composite
  174.      * @throws IllegalArgumentException if there is a key collision and there is no
  175.      *         MapMutator set to handle it.
  176.      */
  177.     public synchronized void addComposited(final Map<K, V> map) throws IllegalArgumentException {
  178.         if (map != null) {
  179.             for (int i = composite.length - 1; i >= 0; --i) {
  180.                 final Collection<K> intersect = CollectionUtils.intersection(composite[i].keySet(), map.keySet());
  181.                 if (!intersect.isEmpty()) {
  182.                     if (mutator == null) {
  183.                         throw new IllegalArgumentException("Key collision adding Map to CompositeMap");
  184.                     }
  185.                     mutator.resolveCollision(this, composite[i], map, intersect);
  186.                 }
  187.             }
  188.             final Map<K, V>[] temp = Arrays.copyOf(composite, composite.length + 1);
  189.             temp[temp.length - 1] = map;
  190.             composite = temp;
  191.         }
  192.     }

  193.     /**
  194.      * Calls {@code clear()} on all composited Maps.
  195.      *
  196.      * @throws UnsupportedOperationException if any of the composited Maps do not support clear()
  197.      */
  198.     @Override
  199.     public void clear() {
  200.         for (int i = composite.length - 1; i >= 0; --i) {
  201.             composite[i].clear();
  202.         }
  203.     }

  204.     /**
  205.      * Returns {@code true} if this map contains a mapping for the specified
  206.      * key.  More formally, returns {@code true} if and only if
  207.      * this map contains at a mapping for a key {@code k} such that
  208.      * {@code (key==null ? k==null : key.equals(k))}.  (There can be
  209.      * at most one such mapping.)
  210.      *
  211.      * @param key  key whose presence in this map is to be tested.
  212.      * @return {@code true} if this map contains a mapping for the specified
  213.      *         key.
  214.      *
  215.      * @throws ClassCastException if the key is of an inappropriate type for
  216.      *         this map (optional).
  217.      * @throws NullPointerException if the key is {@code null} and this map
  218.      *            does not permit {@code null} keys (optional).
  219.      */
  220.     @Override
  221.     public boolean containsKey(final Object key) {
  222.         for (int i = composite.length - 1; i >= 0; --i) {
  223.             if (composite[i].containsKey(key)) {
  224.                 return true;
  225.             }
  226.         }
  227.         return false;
  228.     }

  229.     /**
  230.      * Returns {@code true} if this map maps one or more keys to the
  231.      * specified value.  More formally, returns {@code true} if and only if
  232.      * this map contains at least one mapping to a value {@code v} such that
  233.      * {@code (value==null ? v==null : value.equals(v))}.  This operation
  234.      * will probably require time linear in the map size for most
  235.      * implementations of the {@code Map} interface.
  236.      *
  237.      * @param value value whose presence in this map is to be tested.
  238.      * @return {@code true} if this map maps one or more keys to the
  239.      *         specified value.
  240.      * @throws ClassCastException if the value is of an inappropriate type for
  241.      *         this map (optional).
  242.      * @throws NullPointerException if the value is {@code null} and this map
  243.      *            does not permit {@code null} values (optional).
  244.      */
  245.     @Override
  246.     public boolean containsValue(final Object value) {
  247.         for (int i = composite.length - 1; i >= 0; --i) {
  248.             if (composite[i].containsValue(value)) {
  249.                 return true;
  250.             }
  251.         }
  252.         return false;
  253.     }

  254.     /**
  255.      * Returns a set view of the mappings contained in this map.  Each element
  256.      * in the returned set is a {@code Map.Entry}.  The set is backed by the
  257.      * map, so changes to the map are reflected in the set, and vice-versa.
  258.      * If the map is modified while an iteration over the set is in progress,
  259.      * the results of the iteration are undefined.  The set supports element
  260.      * removal, which removes the corresponding mapping from the map, via the
  261.      * {@code Iterator.remove}, {@code Set.remove}, {@code removeAll},
  262.      * {@code retainAll} and {@code clear} operations.  It does not support
  263.      * the {@code add} or {@code addAll} operations.
  264.      * <p>
  265.      * This implementation returns a {@code CompositeSet} which
  266.      * composites the entry sets from all of the composited maps.
  267.      *
  268.      * @see CompositeSet
  269.      * @return a set view of the mappings contained in this map.
  270.      */
  271.     @Override
  272.     public Set<Map.Entry<K, V>> entrySet() {
  273.         final CompositeSet<Map.Entry<K, V>> entries = new CompositeSet<>();
  274.         for (int i = composite.length - 1; i >= 0; --i) {
  275.             entries.addComposited(composite[i].entrySet());
  276.         }
  277.         return entries;
  278.     }

  279.     /**
  280.      * Checks if this Map equals another as per the Map specification.
  281.      *
  282.      * @param obj  the object to compare to
  283.      * @return true if the maps are equal
  284.      */
  285.     @Override
  286.     public boolean equals(final Object obj) {
  287.         if (obj instanceof Map) {
  288.             final Map<?, ?> map = (Map<?, ?>) obj;
  289.             return this.entrySet().equals(map.entrySet());
  290.         }
  291.         return false;
  292.     }

  293.     /**
  294.      * Gets the value to which this map maps the specified key.  Returns
  295.      * {@code null} if the map contains no mapping for this key.  A return
  296.      * value of {@code null} does not <em>necessarily</em> indicate that the
  297.      * map contains no mapping for the key; it's also possible that the map
  298.      * explicitly maps the key to {@code null}.  The {@code containsKey}
  299.      * operation may be used to distinguish these two cases.
  300.      *
  301.      * <p>More formally, if this map contains a mapping from a key
  302.      * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
  303.      * key.equals(k))}, then this method returns {@code v}; otherwise
  304.      * it returns {@code null}.  (There can be at most one such mapping.)
  305.      *
  306.      * @param key key whose associated value is to be returned.
  307.      * @return the value to which this map maps the specified key, or
  308.      *         {@code null} if the map contains no mapping for this key.
  309.      *
  310.      * @throws ClassCastException if the key is of an inappropriate type for
  311.      *         this map (optional).
  312.      * @throws NullPointerException key is {@code null} and this map does
  313.      *         not permit {@code null} keys (optional).
  314.      *
  315.      * @see #containsKey(Object)
  316.      */
  317.     @Override
  318.     public V get(final Object key) {
  319.         for (int i = composite.length - 1; i >= 0; --i) {
  320.             if (composite[i].containsKey(key)) {
  321.                 return composite[i].get(key);
  322.             }
  323.         }
  324.         return null;
  325.     }

  326.     /**
  327.      * Gets a hash code for the Map as per the Map specification.
  328.      * {@inheritDoc}
  329.      */
  330.     @Override
  331.     public int hashCode() {
  332.         int code = 0;
  333.         for (final Map.Entry<K, V> entry : entrySet()) {
  334.             code += entry.hashCode();
  335.         }
  336.         return code;
  337.     }

  338.     /**
  339.      * Returns {@code true} if this map contains no key-value mappings.
  340.      *
  341.      * @return {@code true} if this map contains no key-value mappings.
  342.      */
  343.     @Override
  344.     public boolean isEmpty() {
  345.         for (int i = composite.length - 1; i >= 0; --i) {
  346.             if (!composite[i].isEmpty()) {
  347.                 return false;
  348.             }
  349.         }
  350.         return true;
  351.     }

  352.     /**
  353.      * Returns a set view of the keys contained in this map.  The set is
  354.      * backed by the map, so changes to the map are reflected in the set, and
  355.      * vice-versa.  If the map is modified while an iteration over the set is
  356.      * in progress, the results of the iteration are undefined.  The set
  357.      * supports element removal, which removes the corresponding mapping from
  358.      * the map, via the {@code Iterator.remove}, {@code Set.remove},
  359.      * {@code removeAll} {@code retainAll}, and {@code clear} operations.
  360.      * It does not support the add or {@code addAll} operations.
  361.      * <p>
  362.      * This implementation returns a {@code CompositeSet} which
  363.      * composites the key sets from all of the composited maps.
  364.      * </p>
  365.      *
  366.      * @return a set view of the keys contained in this map.
  367.      */
  368.     @Override
  369.     public Set<K> keySet() {
  370.         final CompositeSet<K> keys = new CompositeSet<>();
  371.         for (int i = composite.length - 1; i >= 0; --i) {
  372.             keys.addComposited(composite[i].keySet());
  373.         }
  374.         return keys;
  375.     }

  376.     /**
  377.      * Associates the specified value with the specified key in this map
  378.      * (optional operation).  If the map previously contained a mapping for
  379.      * this key, the old value is replaced by the specified value.  (A map
  380.      * {@code m} is said to contain a mapping for a key {@code k} if and only
  381.      * if {@link #containsKey(Object) m.containsKey(k)} would return
  382.      * {@code true}.))
  383.      *
  384.      * @param key key with which the specified value is to be associated.
  385.      * @param value value to be associated with the specified key.
  386.      * @return previous value associated with specified key, or {@code null}
  387.      *         if there was no mapping for key.  A {@code null} return can
  388.      *         also indicate that the map previously associated {@code null}
  389.      *         with the specified key, if the implementation supports
  390.      *         {@code null} values.
  391.      *
  392.      * @throws UnsupportedOperationException if no MapMutator has been specified
  393.      * @throws ClassCastException if the class of the specified key or value
  394.      *            prevents it from being stored in this map.
  395.      * @throws IllegalArgumentException if some aspect of this key or value
  396.      *            prevents it from being stored in this map.
  397.      * @throws NullPointerException this map does not permit {@code null}
  398.      *            keys or values, and the specified key or value is
  399.      *            {@code null}.
  400.      */
  401.     @Override
  402.     public V put(final K key, final V value) {
  403.         if (mutator == null) {
  404.             throw new UnsupportedOperationException("No mutator specified");
  405.         }
  406.         return mutator.put(this, composite, key, value);
  407.     }

  408.     /**
  409.      * Copies all of the mappings from the specified map to this map
  410.      * (optional operation).  The effect of this call is equivalent to that
  411.      * of calling {@link #put(Object,Object) put(k, v)} on this map once
  412.      * for each mapping from key {@code k} to value {@code v} in the
  413.      * specified map.  The behavior of this operation is unspecified if the
  414.      * specified map is modified while the operation is in progress.
  415.      *
  416.      * @param map Mappings to be stored in this map.
  417.      * @throws UnsupportedOperationException if the {@code putAll} method is
  418.      *         not supported by this map.
  419.      *
  420.      * @throws ClassCastException if the class of a key or value in the
  421.      *         specified map prevents it from being stored in this map.
  422.      *
  423.      * @throws IllegalArgumentException some aspect of a key or value in the
  424.      *         specified map prevents it from being stored in this map.
  425.      * @throws NullPointerException the specified map is {@code null}, or if
  426.      *         this map does not permit {@code null} keys or values, and the
  427.      *         specified map contains {@code null} keys or values.
  428.      */
  429.     @Override
  430.     public void putAll(final Map<? extends K, ? extends V> map) {
  431.         if (mutator == null) {
  432.             throw new UnsupportedOperationException("No mutator specified");
  433.         }
  434.         mutator.putAll(this, composite, map);
  435.     }

  436.     /**
  437.      * Removes the mapping for this key from this map if it is present
  438.      * (optional operation).   More formally, if this map contains a mapping
  439.      * from key {@code k} to value {@code v} such that
  440.      * {@code (key==null ?  k==null : key.equals(k))}, that mapping
  441.      * is removed.  (The map can contain at most one such mapping.)
  442.      *
  443.      * <p>Returns the value to which the map previously associated the key, or
  444.      * {@code null} if the map contained no mapping for this key.  (A
  445.      * {@code null} return can also indicate that the map previously
  446.      * associated {@code null} with the specified key if the implementation
  447.      * supports {@code null} values.)  The map will not contain a mapping for
  448.      * the specified  key once the call returns.
  449.      *
  450.      * @param key key whose mapping is to be removed from the map.
  451.      * @return previous value associated with specified key, or {@code null}
  452.      *         if there was no mapping for key.
  453.      *
  454.      * @throws ClassCastException if the key is of an inappropriate type for
  455.      *         the composited map (optional).
  456.      * @throws NullPointerException if the key is {@code null} and the composited map
  457.      *            does not permit {@code null} keys (optional).
  458.      * @throws UnsupportedOperationException if the {@code remove} method is
  459.      *         not supported by the composited map containing the key
  460.      */
  461.     @Override
  462.     public V remove(final Object key) {
  463.         for (int i = composite.length - 1; i >= 0; --i) {
  464.             if (composite[i].containsKey(key)) {
  465.                 return composite[i].remove(key);
  466.             }
  467.         }
  468.         return null;
  469.     }

  470.     /**
  471.      * Remove a Map from the composite.
  472.      *
  473.      * @param map  the Map to be removed from the composite
  474.      * @return The removed Map or {@code null} if map is not in the composite
  475.      */
  476.     @SuppressWarnings("unchecked")
  477.     public synchronized Map<K, V> removeComposited(final Map<K, V> map) {
  478.         final int size = composite.length;
  479.         for (int i = 0; i < size; ++i) {
  480.             if (composite[i].equals(map)) {
  481.                 final Map<K, V>[] temp = new Map[size - 1];
  482.                 System.arraycopy(composite, 0, temp, 0, i);
  483.                 System.arraycopy(composite, i + 1, temp, i, size - i - 1);
  484.                 composite = temp;
  485.                 return map;
  486.             }
  487.         }
  488.         return null;
  489.     }

  490.     /**
  491.      * Specify the MapMutator to be used by mutation operations.
  492.      *
  493.      * @param mutator  the MapMutator to be used for mutation delegation
  494.      */
  495.     public void setMutator(final MapMutator<K, V> mutator) {
  496.         this.mutator = mutator;
  497.     }

  498.     /**
  499.      * Returns the number of key-value mappings in this map.  If the
  500.      * map contains more than {@code Integer.MAX_VALUE} elements, returns
  501.      * {@code Integer.MAX_VALUE}.
  502.      *
  503.      * @return the number of key-value mappings in this map.
  504.      */
  505.     @Override
  506.     public int size() {
  507.         int size = 0;
  508.         for (int i = composite.length - 1; i >= 0; --i) {
  509.             size += composite[i].size();
  510.         }
  511.         return size;
  512.     }

  513.     /**
  514.      * Returns a collection view of the values contained in this map.  The
  515.      * collection is backed by the map, so changes to the map are reflected in
  516.      * the collection, and vice-versa.  If the map is modified while an
  517.      * iteration over the collection is in progress, the results of the
  518.      * iteration are undefined.  The collection supports element removal,
  519.      * which removes the corresponding mapping from the map, via the
  520.      * {@code Iterator.remove}, {@code Collection.remove},
  521.      * {@code removeAll}, {@code retainAll} and {@code clear} operations.
  522.      * It does not support the add or {@code addAll} operations.
  523.      *
  524.      * @return a collection view of the values contained in this map.
  525.      */
  526.     @Override
  527.     public Collection<V> values() {
  528.         final CompositeCollection<V> values = new CompositeCollection<>();
  529.         for (int i = composite.length - 1; i >= 0; --i) {
  530.             values.addComposited(composite[i].values());
  531.         }
  532.         return values;
  533.     }
  534. }