MultiKeyMap.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.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.ObjectOutputStream;
  21. import java.io.Serializable;
  22. import java.util.Map;
  23. import java.util.Objects;

  24. import org.apache.commons.collections4.MapIterator;
  25. import org.apache.commons.collections4.keyvalue.MultiKey;
  26. import org.apache.commons.collections4.map.AbstractHashedMap.HashEntry;

  27. /**
  28.  * A {@code Map} implementation that uses multiple keys to map the value.
  29.  * <p>
  30.  * This class is the most efficient way to uses multiple keys to map to a value.
  31.  * The best way to use this class is via the additional map-style methods.
  32.  * These provide {@code get}, {@code containsKey}, {@code put} and
  33.  * {@code remove} for individual keys which operate without extra object creation.
  34.  * </p>
  35.  * <p>
  36.  * The additional methods are the main interface of this map.
  37.  * As such, you will not normally hold this map in a variable of type {@code Map}.
  38.  * </p>
  39.  * <p>
  40.  * The normal map methods take in and return a {@link MultiKey}.
  41.  * If you try to use {@code put()} with any other object type a
  42.  * {@code ClassCastException} is thrown. If you try to use {@code null} as
  43.  * the key in {@code put()} a {@code NullPointerException} is thrown.
  44.  * </p>
  45.  * <p>
  46.  * This map is implemented as a decorator of a {@code AbstractHashedMap} which
  47.  * enables extra behavior to be added easily.
  48.  * </p>
  49.  * <ul>
  50.  * <li>{@code MultiKeyMap.decorate(new LinkedMap())} creates an ordered map.
  51.  * <li>{@code MultiKeyMap.decorate(new LRUMap())} creates an least recently used map.
  52.  * <li>{@code MultiKeyMap.decorate(new ReferenceMap())} creates a garbage collector sensitive map.
  53.  * </ul>
  54.  * <p>
  55.  * Note that {@code IdentityMap} and {@code ReferenceIdentityMap} are unsuitable
  56.  * for use as the key comparison would work on the whole MultiKey, not the elements within.
  57.  * </p>
  58.  * <p>
  59.  * As an example, consider a least recently used cache that uses a String airline code
  60.  * and a Locale to lookup the airline's name:
  61.  * </p>
  62.  * <pre>
  63.  * private MultiKeyMap cache = MultiKeyMap.multiKeyMap(new LRUMap(50));
  64.  *
  65.  * public String getAirlineName(String code, String locale) {
  66.  *   String name = (String) cache.get(code, locale);
  67.  *   if (name == null) {
  68.  *     name = getAirlineNameFromDB(code, locale);
  69.  *     cache.put(code, locale, name);
  70.  *   }
  71.  *   return name;
  72.  * }
  73.  * </pre>
  74.  * <p>
  75.  * <strong>Note that MultiKeyMap is not synchronized and is not thread-safe.</strong>
  76.  * If you wish to use this map from multiple threads concurrently, you must use
  77.  * appropriate synchronization. This class may throw exceptions when accessed
  78.  * by concurrent threads without synchronization.
  79.  * </p>
  80.  *
  81.  * @param <K> the type of the keys in this map
  82.  * @param <V> the type of the values in this map
  83.  * @since 3.1
  84.  */
  85. public class MultiKeyMap<K, V> extends AbstractMapDecorator<MultiKey<? extends K>, V>
  86.         implements Serializable, Cloneable {

  87.     /** Serialization version */
  88.     private static final long serialVersionUID = -1788199231038721040L;

  89.     /**
  90.      * Decorates the specified map to add the MultiKeyMap API and fast query.
  91.      * The map must not be null and must be empty.
  92.      *
  93.      * @param <K>  the key type
  94.      * @param <V>  the value type
  95.      * @param map  the map to decorate, not null
  96.      * @return a new multi key map
  97.      * @throws NullPointerException if map is null
  98.      * @throws IllegalArgumentException if the map is not empty
  99.      * @since 4.0
  100.      */
  101.     public static <K, V> MultiKeyMap<K, V> multiKeyMap(final AbstractHashedMap<MultiKey<? extends K>, V> map) {
  102.         Objects.requireNonNull(map, "map");
  103.         if (map.isEmpty()) {
  104.             return new MultiKeyMap<>(map);
  105.         }
  106.         throw new IllegalArgumentException("Map must be empty");
  107.     }

  108.     /**
  109.      * Constructs a new MultiKeyMap that decorates a {@code HashedMap}.
  110.      */
  111.     public MultiKeyMap() {
  112.         this(new HashedMap<>());
  113.     }

  114.     /**
  115.      * Constructor that decorates the specified map and is called from
  116.      * {@link #multiKeyMap(AbstractHashedMap)}.
  117.      * The map must not be null and should be empty or only contain valid keys.
  118.      * This constructor performs no validation.
  119.      *
  120.      * @param map  the map to decorate
  121.      */
  122.     protected MultiKeyMap(final AbstractHashedMap<MultiKey<? extends K>, V> map) {
  123.         super(map);
  124.         this.map = map;
  125.     }

  126.     /**
  127.      * Check to ensure that input keys are valid MultiKey objects.
  128.      *
  129.      * @param key  the key to check
  130.      */
  131.     protected void checkKey(final MultiKey<?> key) {
  132.         Objects.requireNonNull(key, "key");
  133.     }

  134.     /**
  135.      * Clones the map without cloning the keys or values.
  136.      *
  137.      * @return a shallow clone
  138.      */
  139.     @SuppressWarnings("unchecked")
  140.     @Override
  141.     public MultiKeyMap<K, V> clone() {
  142.         try {
  143.             return (MultiKeyMap<K, V>) super.clone();
  144.         } catch (final CloneNotSupportedException e) {
  145.             throw new UnsupportedOperationException(e);
  146.         }
  147.     }

  148.     /**
  149.      * Checks whether the map contains the specified multi-key.
  150.      *
  151.      * @param key1  the first key
  152.      * @param key2  the second key
  153.      * @return true if the map contains the key
  154.      */
  155.     public boolean containsKey(final Object key1, final Object key2) {
  156.         final int hashCode = hash(key1, key2);
  157.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decoratedHashEntry(hashCode);
  158.         while (entry != null) {
  159.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
  160.                 return true;
  161.             }
  162.             entry = entry.next;
  163.         }
  164.         return false;
  165.     }

  166.     /**
  167.      * Checks whether the map contains the specified multi-key.
  168.      *
  169.      * @param key1  the first key
  170.      * @param key2  the second key
  171.      * @param key3  the third key
  172.      * @return true if the map contains the key
  173.      */
  174.     public boolean containsKey(final Object key1, final Object key2, final Object key3) {
  175.         final int hashCode = hash(key1, key2, key3);
  176.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decoratedHashEntry(hashCode);
  177.         while (entry != null) {
  178.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
  179.                 return true;
  180.             }
  181.             entry = entry.next;
  182.         }
  183.         return false;
  184.     }

  185.     /**
  186.      * Checks whether the map contains the specified multi-key.
  187.      *
  188.      * @param key1  the first key
  189.      * @param key2  the second key
  190.      * @param key3  the third key
  191.      * @param key4  the fourth key
  192.      * @return true if the map contains the key
  193.      */
  194.     public boolean containsKey(final Object key1, final Object key2, final Object key3, final Object key4) {
  195.         final int hashCode = hash(key1, key2, key3, key4);
  196.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decoratedHashEntry(hashCode);
  197.         while (entry != null) {
  198.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
  199.                 return true;
  200.             }
  201.             entry = entry.next;
  202.         }
  203.         return false;
  204.     }

  205.     /**
  206.      * Checks whether the map contains the specified multi-key.
  207.      *
  208.      * @param key1  the first key
  209.      * @param key2  the second key
  210.      * @param key3  the third key
  211.      * @param key4  the fourth key
  212.      * @param key5  the fifth key
  213.      * @return true if the map contains the key
  214.      */
  215.     public boolean containsKey(final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) {
  216.         final int hashCode = hash(key1, key2, key3, key4, key5);
  217.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decoratedHashEntry(hashCode);
  218.         while (entry != null) {
  219.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
  220.                 return true;
  221.             }
  222.             entry = entry.next;
  223.         }
  224.         return false;
  225.     }

  226.     /**
  227.      * {@inheritDoc}
  228.      */
  229.     @Override
  230.     protected AbstractHashedMap<MultiKey<? extends K>, V> decorated() {
  231.         return (AbstractHashedMap<MultiKey<? extends K>, V>) super.decorated();
  232.     }

  233.     HashEntry<MultiKey<? extends K>, V> decoratedHashEntry(final int hashCode) {
  234.         return decorated().data[decoratedHashIndex(hashCode)];
  235.     }

  236.     int decoratedHashIndex(final int hashCode) {
  237.         return decorated().hashIndex(hashCode, decorated().data.length);
  238.     }

  239.     /**
  240.      * Gets the value mapped to the specified multi-key.
  241.      *
  242.      * @param key1  the first key
  243.      * @param key2  the second key
  244.      * @return the mapped value, null if no match
  245.      */
  246.     public V get(final Object key1, final Object key2) {
  247.         final int hashCode = hash(key1, key2);
  248.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decoratedHashEntry(hashCode);
  249.         while (entry != null) {
  250.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
  251.                 return entry.getValue();
  252.             }
  253.             entry = entry.next;
  254.         }
  255.         return null;
  256.     }

  257.     /**
  258.      * Gets the value mapped to the specified multi-key.
  259.      *
  260.      * @param key1  the first key
  261.      * @param key2  the second key
  262.      * @param key3  the third key
  263.      * @return the mapped value, null if no match
  264.      */
  265.     public V get(final Object key1, final Object key2, final Object key3) {
  266.         final int hashCode = hash(key1, key2, key3);
  267.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decoratedHashEntry(hashCode);
  268.         while (entry != null) {
  269.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
  270.                 return entry.getValue();
  271.             }
  272.             entry = entry.next;
  273.         }
  274.         return null;
  275.     }

  276.     /**
  277.      * Gets the value mapped to the specified multi-key.
  278.      *
  279.      * @param key1  the first key
  280.      * @param key2  the second key
  281.      * @param key3  the third key
  282.      * @param key4  the fourth key
  283.      * @return the mapped value, null if no match
  284.      */
  285.     public V get(final Object key1, final Object key2, final Object key3, final Object key4) {
  286.         final int hashCode = hash(key1, key2, key3, key4);
  287.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decoratedHashEntry(hashCode);
  288.         while (entry != null) {
  289.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
  290.                 return entry.getValue();
  291.             }
  292.             entry = entry.next;
  293.         }
  294.         return null;
  295.     }

  296.     /**
  297.      * Gets the value mapped to the specified multi-key.
  298.      *
  299.      * @param key1  the first key
  300.      * @param key2  the second key
  301.      * @param key3  the third key
  302.      * @param key4  the fourth key
  303.      * @param key5  the fifth key
  304.      * @return the mapped value, null if no match
  305.      */
  306.     public V get(final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) {
  307.         final int hashCode = hash(key1, key2, key3, key4, key5);
  308.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decoratedHashEntry(hashCode);
  309.         while (entry != null) {
  310.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
  311.                 return entry.getValue();
  312.             }
  313.             entry = entry.next;
  314.         }
  315.         return null;
  316.     }

  317.     /**
  318.      * Gets the hash code for the specified multi-key.
  319.      *
  320.      * @param key1  the first key
  321.      * @param key2  the second key
  322.      * @return the hash code
  323.      */
  324.     protected int hash(final Object key1, final Object key2) {
  325.         int h = 0;
  326.         if (key1 != null) {
  327.             h ^= key1.hashCode();
  328.         }
  329.         if (key2 != null) {
  330.             h ^= key2.hashCode();
  331.         }
  332.         h += ~(h << 9);
  333.         h ^=  h >>> 14;
  334.         h +=  h << 4;
  335.         h ^=  h >>> 10;
  336.         return h;
  337.     }

  338.     /**
  339.      * Gets the hash code for the specified multi-key.
  340.      *
  341.      * @param key1  the first key
  342.      * @param key2  the second key
  343.      * @param key3  the third key
  344.      * @return the hash code
  345.      */
  346.     protected int hash(final Object key1, final Object key2, final Object key3) {
  347.         int h = 0;
  348.         if (key1 != null) {
  349.             h ^= key1.hashCode();
  350.         }
  351.         if (key2 != null) {
  352.             h ^= key2.hashCode();
  353.         }
  354.         if (key3 != null) {
  355.             h ^= key3.hashCode();
  356.         }
  357.         h += ~(h << 9);
  358.         h ^=  h >>> 14;
  359.         h +=  h << 4;
  360.         h ^=  h >>> 10;
  361.         return h;
  362.     }

  363.     /**
  364.      * Gets the hash code for the specified multi-key.
  365.      *
  366.      * @param key1  the first key
  367.      * @param key2  the second key
  368.      * @param key3  the third key
  369.      * @param key4  the fourth key
  370.      * @return the hash code
  371.      */
  372.     protected int hash(final Object key1, final Object key2, final Object key3, final Object key4) {
  373.         int h = 0;
  374.         if (key1 != null) {
  375.             h ^= key1.hashCode();
  376.         }
  377.         if (key2 != null) {
  378.             h ^= key2.hashCode();
  379.         }
  380.         if (key3 != null) {
  381.             h ^= key3.hashCode();
  382.         }
  383.         if (key4 != null) {
  384.             h ^= key4.hashCode();
  385.         }
  386.         h += ~(h << 9);
  387.         h ^=  h >>> 14;
  388.         h +=  h << 4;
  389.         h ^=  h >>> 10;
  390.         return h;
  391.     }

  392.     /**
  393.      * Gets the hash code for the specified multi-key.
  394.      *
  395.      * @param key1  the first key
  396.      * @param key2  the second key
  397.      * @param key3  the third key
  398.      * @param key4  the fourth key
  399.      * @param key5  the fifth key
  400.      * @return the hash code
  401.      */
  402.     protected int hash(final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) {
  403.         int h = 0;
  404.         if (key1 != null) {
  405.             h ^= key1.hashCode();
  406.         }
  407.         if (key2 != null) {
  408.             h ^= key2.hashCode();
  409.         }
  410.         if (key3 != null) {
  411.             h ^= key3.hashCode();
  412.         }
  413.         if (key4 != null) {
  414.             h ^= key4.hashCode();
  415.         }
  416.         if (key5 != null) {
  417.             h ^= key5.hashCode();
  418.         }
  419.         h += ~(h << 9);
  420.         h ^=  h >>> 14;
  421.         h +=  h << 4;
  422.         h ^=  h >>> 10;
  423.         return h;
  424.     }

  425.     /**
  426.      * Is the key equal to the combined key.
  427.      *
  428.      * @param entry  the entry to compare to
  429.      * @param key1  the first key
  430.      * @param key2  the second key
  431.      * @return true if the key matches
  432.      */
  433.     protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry,
  434.             final Object key1, final Object key2) {
  435.         final MultiKey<? extends K> multi = entry.getKey();
  436.         return
  437.             multi.size() == 2 &&
  438.             Objects.equals(key1, multi.getKey(0)) &&
  439.             Objects.equals(key2, multi.getKey(1));
  440.     }

  441.     /**
  442.      * Is the key equal to the combined key.
  443.      *
  444.      * @param entry  the entry to compare to
  445.      * @param key1  the first key
  446.      * @param key2  the second key
  447.      * @param key3  the third key
  448.      * @return true if the key matches
  449.      */
  450.     protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry,
  451.                                  final Object key1, final Object key2, final Object key3) {
  452.         final MultiKey<? extends K> multi = entry.getKey();
  453.         return
  454.             multi.size() == 3 &&
  455.             Objects.equals(key1, multi.getKey(0)) &&
  456.             Objects.equals(key2, multi.getKey(1)) &&
  457.             Objects.equals(key3, multi.getKey(2));
  458.     }

  459.     /**
  460.      * Is the key equal to the combined key.
  461.      *
  462.      * @param entry  the entry to compare to
  463.      * @param key1  the first key
  464.      * @param key2  the second key
  465.      * @param key3  the third key
  466.      * @param key4  the fourth key
  467.      * @return true if the key matches
  468.      */
  469.     protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry,
  470.                                  final Object key1, final Object key2, final Object key3, final Object key4) {
  471.         final MultiKey<? extends K> multi = entry.getKey();
  472.         return
  473.             multi.size() == 4 &&
  474.             Objects.equals(key1, multi.getKey(0)) &&
  475.             Objects.equals(key2, multi.getKey(1)) &&
  476.             Objects.equals(key3, multi.getKey(2)) &&
  477.             Objects.equals(key4, multi.getKey(3));
  478.     }

  479.     /**
  480.      * Is the key equal to the combined key.
  481.      *
  482.      * @param entry  the entry to compare to
  483.      * @param key1  the first key
  484.      * @param key2  the second key
  485.      * @param key3  the third key
  486.      * @param key4  the fourth key
  487.      * @param key5  the fifth key
  488.      * @return true if the key matches
  489.      */
  490.     protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry,
  491.             final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) {
  492.         final MultiKey<? extends K> multi = entry.getKey();
  493.         return
  494.             multi.size() == 5 &&
  495.             Objects.equals(key1, multi.getKey(0)) &&
  496.             Objects.equals(key2, multi.getKey(1)) &&
  497.             Objects.equals(key3, multi.getKey(2)) &&
  498.             Objects.equals(key4, multi.getKey(3)) &&
  499.             Objects.equals(key5, multi.getKey(4));
  500.     }

  501.     @Override
  502.     public MapIterator<MultiKey<? extends K>, V> mapIterator() {
  503.         return decorated().mapIterator();
  504.     }

  505.     /**
  506.      * Associates the specified value with the specified keys in this map.
  507.      *
  508.      * @param key1  the first key
  509.      * @param key2  the second key
  510.      * @param key3  the third key
  511.      * @param key4  the fourth key
  512.      * @param key5  the fifth key
  513.      * @param value  the value to store
  514.      * @return the value previously mapped to this combined key, null if none
  515.      */
  516.     public V put(final K key1, final K key2, final K key3, final K key4, final K key5, final V value) {
  517.         final int hashCode = hash(key1, key2, key3, key4, key5);
  518.         final int index = decoratedHashIndex(hashCode);
  519.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
  520.         while (entry != null) {
  521.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
  522.                 final V oldValue = entry.getValue();
  523.                 decorated().updateEntry(entry, value);
  524.                 return oldValue;
  525.             }
  526.             entry = entry.next;
  527.         }
  528.         decorated().addMapping(index, hashCode, new MultiKey<>(key1, key2, key3, key4, key5), value);
  529.         return null;
  530.     }

  531.     /**
  532.      * Associates the specified value with the specified keys in this map.
  533.      *
  534.      * @param key1  the first key
  535.      * @param key2  the second key
  536.      * @param key3  the third key
  537.      * @param key4  the fourth key
  538.      * @param value  the value to store
  539.      * @return the value previously mapped to this combined key, null if none
  540.      */
  541.     public V put(final K key1, final K key2, final K key3, final K key4, final V value) {
  542.         final int hashCode = hash(key1, key2, key3, key4);
  543.         final int index = decoratedHashIndex(hashCode);
  544.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
  545.         while (entry != null) {
  546.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
  547.                 final V oldValue = entry.getValue();
  548.                 decorated().updateEntry(entry, value);
  549.                 return oldValue;
  550.             }
  551.             entry = entry.next;
  552.         }
  553.         decorated().addMapping(index, hashCode, new MultiKey<>(key1, key2, key3, key4), value);
  554.         return null;
  555.     }

  556.     /**
  557.      * Associates the specified value with the specified keys in this map.
  558.      *
  559.      * @param key1  the first key
  560.      * @param key2  the second key
  561.      * @param key3  the third key
  562.      * @param value  the value to store
  563.      * @return the value previously mapped to this combined key, null if none
  564.      */
  565.     public V put(final K key1, final K key2, final K key3, final V value) {
  566.         final int hashCode = hash(key1, key2, key3);
  567.         final int index = decoratedHashIndex(hashCode);
  568.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
  569.         while (entry != null) {
  570.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
  571.                 final V oldValue = entry.getValue();
  572.                 decorated().updateEntry(entry, value);
  573.                 return oldValue;
  574.             }
  575.             entry = entry.next;
  576.         }
  577.         decorated().addMapping(index, hashCode, new MultiKey<>(key1, key2, key3), value);
  578.         return null;
  579.     }

  580.     /**
  581.      * Associates the specified value with the specified keys in this map.
  582.      *
  583.      * @param key1  the first key
  584.      * @param key2  the second key
  585.      * @param value  the value to store
  586.      * @return the value previously mapped to this combined key, null if none
  587.      */
  588.     public V put(final K key1, final K key2, final V value) {
  589.         final int hashCode = hash(key1, key2);
  590.         final int index = decoratedHashIndex(hashCode);
  591.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
  592.         while (entry != null) {
  593.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
  594.                 final V oldValue = entry.getValue();
  595.                 decorated().updateEntry(entry, value);
  596.                 return oldValue;
  597.             }
  598.             entry = entry.next;
  599.         }
  600.         decorated().addMapping(index, hashCode, new MultiKey<>(key1, key2), value);
  601.         return null;
  602.     }

  603.     /**
  604.      * Puts the key and value into the map, where the key must be a non-null
  605.      * MultiKey object.
  606.      *
  607.      * @param key  the non-null MultiKey object
  608.      * @param value  the value to store
  609.      * @return the previous value for the key
  610.      * @throws NullPointerException if the key is null
  611.      * @throws ClassCastException if the key is not a MultiKey
  612.      */
  613.     @Override
  614.     public V put(final MultiKey<? extends K> key, final V value) {
  615.         checkKey(key);
  616.         return super.put(key, value);
  617.     }

  618.     /**
  619.      * Copies all of the keys and values from the specified map to this map.
  620.      * Each key must be non-null and a MultiKey object.
  621.      *
  622.      * @param mapToCopy  to this map
  623.      * @throws NullPointerException if the mapToCopy or any key within is null
  624.      * @throws ClassCastException if any key in mapToCopy is not a MultiKey
  625.      */
  626.     @Override
  627.     public void putAll(final Map<? extends MultiKey<? extends K>, ? extends V> mapToCopy) {
  628.         for (final MultiKey<? extends K> key : mapToCopy.keySet()) {
  629.             checkKey(key);
  630.         }
  631.         super.putAll(mapToCopy);
  632.     }

  633.     /**
  634.      * Deserializes the map in using a custom routine.
  635.      *
  636.      * @param in  the input stream
  637.      * @throws IOException if an error occurs while reading from the stream
  638.      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
  639.      */
  640.     @SuppressWarnings("unchecked")
  641.     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
  642.         in.defaultReadObject();
  643.         map = (Map<MultiKey<? extends K>, V>) in.readObject();
  644.     }

  645.     /**
  646.      * Removes all mappings where the first key is that specified.
  647.      * <p>
  648.      * This method removes all the mappings where the {@code MultiKey}
  649.      * has one or more keys, and the first matches that specified.
  650.      * </p>
  651.      *
  652.      * @param key1  the first key
  653.      * @return true if any elements were removed
  654.      */
  655.     public boolean removeAll(final Object key1) {
  656.         boolean modified = false;
  657.         final MapIterator<MultiKey<? extends K>, V> it = mapIterator();
  658.         while (it.hasNext()) {
  659.             final MultiKey<? extends K> multi = it.next();
  660.             if (multi.size() >= 1 &&
  661.                 Objects.equals(key1, multi.getKey(0))) {
  662.                 it.remove();
  663.                 modified = true;
  664.             }
  665.         }
  666.         return modified;
  667.     }

  668.     /**
  669.      * Removes all mappings where the first two keys are those specified.
  670.      * <p>
  671.      * This method removes all the mappings where the {@code MultiKey}
  672.      * has two or more keys, and the first two match those specified.
  673.      * </p>
  674.      *
  675.      * @param key1  the first key
  676.      * @param key2  the second key
  677.      * @return true if any elements were removed
  678.      */
  679.     public boolean removeAll(final Object key1, final Object key2) {
  680.         boolean modified = false;
  681.         final MapIterator<MultiKey<? extends K>, V> it = mapIterator();
  682.         while (it.hasNext()) {
  683.             final MultiKey<? extends K> multi = it.next();
  684.             if (multi.size() >= 2 &&
  685.                 Objects.equals(key1, multi.getKey(0)) &&
  686.                 Objects.equals(key2, multi.getKey(1))) {
  687.                 it.remove();
  688.                 modified = true;
  689.             }
  690.         }
  691.         return modified;
  692.     }

  693.     /**
  694.      * Removes all mappings where the first three keys are those specified.
  695.      * <p>
  696.      * This method removes all the mappings where the {@code MultiKey}
  697.      * has three or more keys, and the first three match those specified.
  698.      * </p>
  699.      *
  700.      * @param key1  the first key
  701.      * @param key2  the second key
  702.      * @param key3  the third key
  703.      * @return true if any elements were removed
  704.      */
  705.     public boolean removeAll(final Object key1, final Object key2, final Object key3) {
  706.         boolean modified = false;
  707.         final MapIterator<MultiKey<? extends K>, V> it = mapIterator();
  708.         while (it.hasNext()) {
  709.             final MultiKey<? extends K> multi = it.next();
  710.             if (multi.size() >= 3 &&
  711.                 Objects.equals(key1, multi.getKey(0)) &&
  712.                 Objects.equals(key2, multi.getKey(1)) &&
  713.                 Objects.equals(key3, multi.getKey(2))) {
  714.                 it.remove();
  715.                 modified = true;
  716.             }
  717.         }
  718.         return modified;
  719.     }

  720.     /**
  721.      * Removes all mappings where the first four keys are those specified.
  722.      * <p>
  723.      * This method removes all the mappings where the {@code MultiKey}
  724.      * has four or more keys, and the first four match those specified.
  725.      * </p>
  726.      *
  727.      * @param key1  the first key
  728.      * @param key2  the second key
  729.      * @param key3  the third key
  730.      * @param key4  the fourth key
  731.      * @return true if any elements were removed
  732.      */
  733.     public boolean removeAll(final Object key1, final Object key2, final Object key3, final Object key4) {
  734.         boolean modified = false;
  735.         final MapIterator<MultiKey<? extends K>, V> it = mapIterator();
  736.         while (it.hasNext()) {
  737.             final MultiKey<? extends K> multi = it.next();
  738.             if (multi.size() >= 4 &&
  739.                 Objects.equals(key1, multi.getKey(0)) &&
  740.                 Objects.equals(key2, multi.getKey(1)) &&
  741.                 Objects.equals(key3, multi.getKey(2)) &&
  742.                 Objects.equals(key4, multi.getKey(3))) {
  743.                 it.remove();
  744.                 modified = true;
  745.             }
  746.         }
  747.         return modified;
  748.     }

  749.     /**
  750.      * Removes the specified multi-key from this map.
  751.      *
  752.      * @param key1  the first key
  753.      * @param key2  the second key
  754.      * @return the value mapped to the removed key, null if key not in map
  755.      * @since 4.0 (previous name: remove(Object, Object))
  756.      */
  757.     public V removeMultiKey(final Object key1, final Object key2) {
  758.         final int hashCode = hash(key1, key2);
  759.         final int index = decoratedHashIndex(hashCode);
  760.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
  761.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null;
  762.         while (entry != null) {
  763.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
  764.                 final V oldValue = entry.getValue();
  765.                 decorated().removeMapping(entry, index, previous);
  766.                 return oldValue;
  767.             }
  768.             previous = entry;
  769.             entry = entry.next;
  770.         }
  771.         return null;
  772.     }

  773.     /**
  774.      * Removes the specified multi-key from this map.
  775.      *
  776.      * @param key1  the first key
  777.      * @param key2  the second key
  778.      * @param key3  the third key
  779.      * @return the value mapped to the removed key, null if key not in map
  780.      * @since 4.0 (previous name: remove(Object, Object, Object))
  781.      */
  782.     public V removeMultiKey(final Object key1, final Object key2, final Object key3) {
  783.         final int hashCode = hash(key1, key2, key3);
  784.         final int index = decoratedHashIndex(hashCode);
  785.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
  786.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null;
  787.         while (entry != null) {
  788.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
  789.                 final V oldValue = entry.getValue();
  790.                 decorated().removeMapping(entry, index, previous);
  791.                 return oldValue;
  792.             }
  793.             previous = entry;
  794.             entry = entry.next;
  795.         }
  796.         return null;
  797.     }

  798.     /**
  799.      * Removes the specified multi-key from this map.
  800.      *
  801.      * @param key1  the first key
  802.      * @param key2  the second key
  803.      * @param key3  the third key
  804.      * @param key4  the fourth key
  805.      * @return the value mapped to the removed key, null if key not in map
  806.      * @since 4.0 (previous name: remove(Object, Object, Object, Object))
  807.      */
  808.     public V removeMultiKey(final Object key1, final Object key2, final Object key3, final Object key4) {
  809.         final int hashCode = hash(key1, key2, key3, key4);
  810.         final int index = decoratedHashIndex(hashCode);
  811.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
  812.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null;
  813.         while (entry != null) {
  814.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
  815.                 final V oldValue = entry.getValue();
  816.                 decorated().removeMapping(entry, index, previous);
  817.                 return oldValue;
  818.             }
  819.             previous = entry;
  820.             entry = entry.next;
  821.         }
  822.         return null;
  823.     }

  824.     /**
  825.      * Removes the specified multi-key from this map.
  826.      *
  827.      * @param key1  the first key
  828.      * @param key2  the second key
  829.      * @param key3  the third key
  830.      * @param key4  the fourth key
  831.      * @param key5  the fifth key
  832.      * @return the value mapped to the removed key, null if key not in map
  833.      * @since 4.0 (previous name: remove(Object, Object, Object, Object, Object))
  834.      */
  835.     public V removeMultiKey(final Object key1, final Object key2, final Object key3,
  836.                             final Object key4, final Object key5) {
  837.         final int hashCode = hash(key1, key2, key3, key4, key5);
  838.         final int index = decoratedHashIndex(hashCode);
  839.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
  840.         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null;
  841.         while (entry != null) {
  842.             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
  843.                 final V oldValue = entry.getValue();
  844.                 decorated().removeMapping(entry, index, previous);
  845.                 return oldValue;
  846.             }
  847.             previous = entry;
  848.             entry = entry.next;
  849.         }
  850.         return null;
  851.     }

  852.     /**
  853.      * Serializes this object to an ObjectOutputStream.
  854.      *
  855.      * @param out the target ObjectOutputStream.
  856.      * @throws IOException thrown when an I/O errors occur writing to the target stream.
  857.      */
  858.     private void writeObject(final ObjectOutputStream out) throws IOException {
  859.         out.defaultWriteObject();
  860.         out.writeObject(map);
  861.     }

  862. }