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.keyvalue; 018 019import java.io.Serializable; 020import java.util.Arrays; 021 022/** 023 * A <code>MultiKey</code> allows multiple map keys to be merged together. 024 * <p> 025 * The purpose of this class is to avoid the need to write code to handle 026 * maps of maps. An example might be the need to look up a file name by 027 * key and locale. The typical solution might be nested maps. This class 028 * can be used instead by creating an instance passing in the key and locale. 029 * <p> 030 * Example usage: 031 * <pre> 032 * // populate map with data mapping key+locale to localizedText 033 * Map map = new HashMap(); 034 * MultiKey multiKey = new MultiKey(key, locale); 035 * map.put(multiKey, localizedText); 036 * 037 * // later retrieve the localized text 038 * MultiKey multiKey = new MultiKey(key, locale); 039 * String localizedText = (String) map.get(multiKey); 040 * </pre> 041 * 042 * @since 3.0 043 * @version $Id: MultiKey.html 972421 2015-11-14 20:00:04Z tn $ 044 */ 045public class MultiKey<K> implements Serializable { 046 // This class could implement List, but that would confuse it's purpose 047 048 /** Serialisation version */ 049 private static final long serialVersionUID = 4465448607415788805L; 050 051 /** The individual keys */ 052 private final K[] keys; 053 /** The cached hashCode */ 054 private transient int hashCode; 055 056 /** 057 * Constructor taking two keys. 058 * <p> 059 * The keys should be immutable 060 * If they are not then they must not be changed after adding to the MultiKey. 061 * 062 * @param key1 the first key 063 * @param key2 the second key 064 */ 065 @SuppressWarnings("unchecked") 066 public MultiKey(final K key1, final K key2) { 067 this((K[]) new Object[] { key1, key2 }, false); 068 } 069 070 /** 071 * Constructor taking three keys. 072 * <p> 073 * The keys should be immutable 074 * If they are not then they must not be changed after adding to the MultiKey. 075 * 076 * @param key1 the first key 077 * @param key2 the second key 078 * @param key3 the third key 079 */ 080 @SuppressWarnings("unchecked") 081 public MultiKey(final K key1, final K key2, final K key3) { 082 this((K[]) new Object[] {key1, key2, key3}, false); 083 } 084 085 /** 086 * Constructor taking four keys. 087 * <p> 088 * The keys should be immutable 089 * If they are not then they must not be changed after adding to the MultiKey. 090 * 091 * @param key1 the first key 092 * @param key2 the second key 093 * @param key3 the third key 094 * @param key4 the fourth key 095 */ 096 @SuppressWarnings("unchecked") 097 public MultiKey(final K key1, final K key2, final K key3, final K key4) { 098 this((K[]) new Object[] {key1, key2, key3, key4}, false); 099 } 100 101 /** 102 * Constructor taking five keys. 103 * <p> 104 * The keys should be immutable 105 * If they are not then they must not be changed after adding to the MultiKey. 106 * 107 * @param key1 the first key 108 * @param key2 the second key 109 * @param key3 the third key 110 * @param key4 the fourth key 111 * @param key5 the fifth key 112 */ 113 @SuppressWarnings("unchecked") 114 public MultiKey(final K key1, final K key2, final K key3, final K key4, final K key5) { 115 this((K[]) new Object[] {key1, key2, key3, key4, key5}, false); 116 } 117 118 /** 119 * Constructor taking an array of keys which is cloned. 120 * <p> 121 * The keys should be immutable 122 * If they are not then they must not be changed after adding to the MultiKey. 123 * <p> 124 * This is equivalent to <code>new MultiKey(keys, true)</code>. 125 * 126 * @param keys the array of keys, not null 127 * @throws IllegalArgumentException if the key array is null 128 */ 129 public MultiKey(final K[] keys) { 130 this(keys, true); 131 } 132 133 /** 134 * Constructor taking an array of keys, optionally choosing whether to clone. 135 * <p> 136 * <b>If the array is not cloned, then it must not be modified.</b> 137 * <p> 138 * This method is public for performance reasons only, to avoid a clone. 139 * The hashcode is calculated once here in this method. 140 * Therefore, changing the array passed in would not change the hashcode but 141 * would change the equals method, which is a bug. 142 * <p> 143 * This is the only fully safe usage of this constructor, as the object array 144 * is never made available in a variable: 145 * <pre> 146 * new MultiKey(new Object[] {...}, false); 147 * </pre> 148 * <p> 149 * The keys should be immutable 150 * If they are not then they must not be changed after adding to the MultiKey. 151 * 152 * @param keys the array of keys, not null 153 * @param makeClone true to clone the array, false to assign it 154 * @throws IllegalArgumentException if the key array is null 155 * @since 3.1 156 */ 157 public MultiKey(final K[] keys, final boolean makeClone) { 158 super(); 159 if (keys == null) { 160 throw new IllegalArgumentException("The array of keys must not be null"); 161 } 162 if (makeClone) { 163 this.keys = keys.clone(); 164 } else { 165 this.keys = keys; 166 } 167 168 calculateHashCode(keys); 169 } 170 171 //----------------------------------------------------------------------- 172 /** 173 * Gets a clone of the array of keys. 174 * <p> 175 * The keys should be immutable 176 * If they are not then they must not be changed. 177 * 178 * @return the individual keys 179 */ 180 public K[] getKeys() { 181 return keys.clone(); 182 } 183 184 /** 185 * Gets the key at the specified index. 186 * <p> 187 * The key should be immutable. 188 * If it is not then it must not be changed. 189 * 190 * @param index the index to retrieve 191 * @return the key at the index 192 * @throws IndexOutOfBoundsException if the index is invalid 193 * @since 3.1 194 */ 195 public K getKey(final int index) { 196 return keys[index]; 197 } 198 199 /** 200 * Gets the size of the list of keys. 201 * 202 * @return the size of the list of keys 203 * @since 3.1 204 */ 205 public int size() { 206 return keys.length; 207 } 208 209 //----------------------------------------------------------------------- 210 /** 211 * Compares this object to another. 212 * <p> 213 * To be equal, the other object must be a <code>MultiKey</code> with the 214 * same number of keys which are also equal. 215 * 216 * @param other the other object to compare to 217 * @return true if equal 218 */ 219 @Override 220 public boolean equals(final Object other) { 221 if (other == this) { 222 return true; 223 } 224 if (other instanceof MultiKey) { 225 final MultiKey<?> otherMulti = (MultiKey<?>) other; 226 return Arrays.equals(keys, otherMulti.keys); 227 } 228 return false; 229 } 230 231 /** 232 * Gets the combined hash code that is computed from all the keys. 233 * <p> 234 * This value is computed once and then cached, so elements should not 235 * change their hash codes once created (note that this is the same 236 * constraint that would be used if the individual keys elements were 237 * themselves {@link java.util.Map Map} keys. 238 * 239 * @return the hash code 240 */ 241 @Override 242 public int hashCode() { 243 return hashCode; 244 } 245 246 /** 247 * Gets a debugging string version of the key. 248 * 249 * @return a debugging string 250 */ 251 @Override 252 public String toString() { 253 return "MultiKey" + Arrays.toString(keys); 254 } 255 256 /** 257 * Calculate the hash code of the instance using the provided keys. 258 * @param keys the keys to calculate the hash code for 259 */ 260 private void calculateHashCode(final Object[] keys) 261 { 262 int total = 0; 263 for (final Object key : keys) { 264 if (key != null) { 265 total ^= key.hashCode(); 266 } 267 } 268 hashCode = total; 269 } 270 271 /** 272 * Recalculate the hash code after deserialization. The hash code of some 273 * keys might have change (hash codes based on the system hash code are 274 * only stable for the same process). 275 * @return the instance with recalculated hash code 276 */ 277 private Object readResolve() { 278 calculateHashCode(keys); 279 return this; 280 } 281}