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 */ 017 package org.apache.commons.collections.keyvalue; 018 019 import java.io.Serializable; 020 import 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 lookup a filename 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 retireve the localized text 038 * MultiKey multiKey = new MultiKey(key, locale); 039 * String localizedText = (String) map.get(multiKey); 040 * </pre> 041 * 042 * @since Commons Collections 3.0 043 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $ 044 * 045 * @author Howard Lewis Ship 046 * @author Stephen Colebourne 047 */ 048 public class MultiKey implements Serializable { 049 // This class could implement List, but that would confuse it's purpose 050 051 /** Serialisation version */ 052 private static final long serialVersionUID = 4465448607415788805L; 053 054 /** The individual keys */ 055 private final Object[] keys; 056 /** The cached hashCode */ 057 private final int hashCode; 058 059 /** 060 * Constructor taking two keys. 061 * <p> 062 * The keys should be immutable 063 * If they are not then they must not be changed after adding to the MultiKey. 064 * 065 * @param key1 the first key 066 * @param key2 the second key 067 */ 068 public MultiKey(Object key1, Object key2) { 069 this(new Object[] {key1, key2}, false); 070 } 071 072 /** 073 * Constructor taking three keys. 074 * <p> 075 * The keys should be immutable 076 * If they are not then they must not be changed after adding to the MultiKey. 077 * 078 * @param key1 the first key 079 * @param key2 the second key 080 * @param key3 the third key 081 */ 082 public MultiKey(Object key1, Object key2, Object key3) { 083 this(new Object[] {key1, key2, key3}, false); 084 } 085 086 /** 087 * Constructor taking four keys. 088 * <p> 089 * The keys should be immutable 090 * If they are not then they must not be changed after adding to the MultiKey. 091 * 092 * @param key1 the first key 093 * @param key2 the second key 094 * @param key3 the third key 095 * @param key4 the fourth key 096 */ 097 public MultiKey(Object key1, Object key2, Object key3, Object key4) { 098 this(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 public MultiKey(Object key1, Object key2, Object key3, Object key4, Object key5) { 114 this(new Object[] {key1, key2, key3, key4, key5}, false); 115 } 116 117 /** 118 * Constructor taking an array of keys which is cloned. 119 * <p> 120 * The keys should be immutable 121 * If they are not then they must not be changed after adding to the MultiKey. 122 * <p> 123 * This is equivalent to <code>new MultiKey(keys, true)</code>. 124 * 125 * @param keys the array of keys, not null 126 * @throws IllegalArgumentException if the key array is null 127 */ 128 public MultiKey(Object[] keys) { 129 this(keys, true); 130 } 131 132 /** 133 * Constructor taking an array of keys, optionally choosing whether to clone. 134 * <p> 135 * <b>If the array is not cloned, then it must not be modified.</b> 136 * <p> 137 * This method is public for performance reasons only, to avoid a clone. 138 * The hashcode is calculated once here in this method. 139 * Therefore, changing the array passed in would not change the hashcode but 140 * would change the equals method, which is a bug. 141 * <p> 142 * This is the only fully safe usage of this constructor, as the object array 143 * is never made available in a variable: 144 * <pre> 145 * new MultiKey(new Object[] {...}, false); 146 * </pre> 147 * <p> 148 * The keys should be immutable 149 * If they are not then they must not be changed after adding to the MultiKey. 150 * 151 * @param keys the array of keys, not null 152 * @param makeClone true to clone the array, false to assign it 153 * @throws IllegalArgumentException if the key array is null 154 * @since Commons Collections 3.1 155 */ 156 public MultiKey(Object[] keys, boolean makeClone) { 157 super(); 158 if (keys == null) { 159 throw new IllegalArgumentException("The array of keys must not be null"); 160 } 161 if (makeClone) { 162 this.keys = (Object[]) keys.clone(); 163 } else { 164 this.keys = keys; 165 } 166 167 int total = 0; 168 for (int i = 0; i < keys.length; i++) { 169 if (keys[i] != null) { 170 total ^= keys[i].hashCode(); 171 } 172 } 173 hashCode = total; 174 } 175 176 //----------------------------------------------------------------------- 177 /** 178 * Gets a clone of the array of keys. 179 * <p> 180 * The keys should be immutable 181 * If they are not then they must not be changed. 182 * 183 * @return the individual keys 184 */ 185 public Object[] getKeys() { 186 return (Object[]) keys.clone(); 187 } 188 189 /** 190 * Gets the key at the specified index. 191 * <p> 192 * The key should be immutable. 193 * If it is not then it must not be changed. 194 * 195 * @param index the index to retrieve 196 * @return the key at the index 197 * @throws IndexOutOfBoundsException if the index is invalid 198 * @since Commons Collections 3.1 199 */ 200 public Object getKey(int index) { 201 return keys[index]; 202 } 203 204 /** 205 * Gets the size of the list of keys. 206 * 207 * @return the size of the list of keys 208 * @since Commons Collections 3.1 209 */ 210 public int size() { 211 return keys.length; 212 } 213 214 //----------------------------------------------------------------------- 215 /** 216 * Compares this object to another. 217 * <p> 218 * To be equal, the other object must be a <code>MultiKey</code> with the 219 * same number of keys which are also equal. 220 * 221 * @param other the other object to compare to 222 * @return true if equal 223 */ 224 public boolean equals(Object other) { 225 if (other == this) { 226 return true; 227 } 228 if (other instanceof MultiKey) { 229 MultiKey otherMulti = (MultiKey) other; 230 return Arrays.equals(keys, otherMulti.keys); 231 } 232 return false; 233 } 234 235 /** 236 * Gets the combined hash code that is computed from all the keys. 237 * <p> 238 * This value is computed once and then cached, so elements should not 239 * change their hash codes once created (note that this is the same 240 * constraint that would be used if the individual keys elements were 241 * themselves {@link java.util.Map Map} keys. 242 * 243 * @return the hash code 244 */ 245 public int hashCode() { 246 return hashCode; 247 } 248 249 /** 250 * Gets a debugging string version of the key. 251 * 252 * @return a debugging string 253 */ 254 public String toString() { 255 return "MultiKey" + Arrays.asList(keys).toString(); 256 } 257 258 }