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 }