001: /*
002: * Copyright 2003-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.collections.keyvalue;
017:
018: import java.io.Serializable;
019: import java.util.Arrays;
020:
021: /**
022: * A <code>MultiKey</code> allows multiple map keys to be merged together.
023: * <p>
024: * The purpose of this class is to avoid the need to write code to handle
025: * maps of maps. An example might be the need to lookup a filename by
026: * key and locale. The typical solution might be nested maps. This class
027: * can be used instead by creating an instance passing in the key and locale.
028: * <p>
029: * Example usage:
030: * <pre>
031: * // populate map with data mapping key+locale to localizedText
032: * Map map = new HashMap();
033: * MultiKey multiKey = new MultiKey(key, locale);
034: * map.put(multiKey, localizedText);
035: *
036: * // later retireve the localized text
037: * MultiKey multiKey = new MultiKey(key, locale);
038: * String localizedText = (String) map.get(multiKey);
039: * </pre>
040: *
041: * @since Commons Collections 3.0
042: * @version $Revision: 155406 $ $Date: 2005-02-26 12:55:26 +0000 (Sat, 26 Feb 2005) $
043: *
044: * @author Howard Lewis Ship
045: * @author Stephen Colebourne
046: */
047: public class MultiKey implements Serializable {
048: // This class could implement List, but that would confuse it's purpose
049:
050: /** Serialisation version */
051: private static final long serialVersionUID = 4465448607415788805L;
052:
053: /** The individual keys */
054: private final Object[] keys;
055: /** The cached hashCode */
056: private final int hashCode;
057:
058: /**
059: * Constructor taking two keys.
060: * <p>
061: * The keys should be immutable
062: * If they are not then they must not be changed after adding to the MultiKey.
063: *
064: * @param key1 the first key
065: * @param key2 the second key
066: */
067: public MultiKey(Object key1, Object key2) {
068: this (new Object[] { key1, key2 }, false);
069: }
070:
071: /**
072: * Constructor taking three keys.
073: * <p>
074: * The keys should be immutable
075: * If they are not then they must not be changed after adding to the MultiKey.
076: *
077: * @param key1 the first key
078: * @param key2 the second key
079: * @param key3 the third key
080: */
081: public MultiKey(Object key1, Object key2, Object key3) {
082: this (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: public MultiKey(Object key1, Object key2, Object key3, Object key4) {
097: this (new Object[] { key1, key2, key3, key4 }, false);
098: }
099:
100: /**
101: * Constructor taking five keys.
102: * <p>
103: * The keys should be immutable
104: * If they are not then they must not be changed after adding to the MultiKey.
105: *
106: * @param key1 the first key
107: * @param key2 the second key
108: * @param key3 the third key
109: * @param key4 the fourth key
110: * @param key5 the fifth key
111: */
112: public MultiKey(Object key1, Object key2, Object key3, Object key4,
113: 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(
160: "The array of keys must not be null");
161: }
162: if (makeClone) {
163: this .keys = (Object[]) keys.clone();
164: } else {
165: this .keys = keys;
166: }
167:
168: int total = 0;
169: for (int i = 0; i < keys.length; i++) {
170: if (keys[i] != null) {
171: total ^= keys[i].hashCode();
172: }
173: }
174: hashCode = total;
175: }
176:
177: //-----------------------------------------------------------------------
178: /**
179: * Gets a clone of the array of keys.
180: * <p>
181: * The keys should be immutable
182: * If they are not then they must not be changed.
183: *
184: * @return the individual keys
185: */
186: public Object[] getKeys() {
187: return (Object[]) keys.clone();
188: }
189:
190: /**
191: * Gets the key at the specified index.
192: * <p>
193: * The key should be immutable.
194: * If it is not then it must not be changed.
195: *
196: * @param index the index to retrieve
197: * @return the key at the index
198: * @throws IndexOutOfBoundsException if the index is invalid
199: * @since Commons Collections 3.1
200: */
201: public Object getKey(int index) {
202: return keys[index];
203: }
204:
205: /**
206: * Gets the size of the list of keys.
207: *
208: * @return the size of the list of keys
209: * @since Commons Collections 3.1
210: */
211: public int size() {
212: return keys.length;
213: }
214:
215: //-----------------------------------------------------------------------
216: /**
217: * Compares this object to another.
218: * <p>
219: * To be equal, the other object must be a <code>MultiKey</code> with the
220: * same number of keys which are also equal.
221: *
222: * @param other the other object to compare to
223: * @return true if equal
224: */
225: public boolean equals(Object other) {
226: if (other == this ) {
227: return true;
228: }
229: if (other instanceof MultiKey) {
230: MultiKey otherMulti = (MultiKey) other;
231: return Arrays.equals(keys, otherMulti.keys);
232: }
233: return false;
234: }
235:
236: /**
237: * Gets the combined hash code that is computed from all the keys.
238: * <p>
239: * This value is computed once and then cached, so elements should not
240: * change their hash codes once created (note that this is the same
241: * constraint that would be used if the individual keys elements were
242: * themselves {@link java.util.Map Map} keys.
243: *
244: * @return the hash code
245: */
246: public int hashCode() {
247: return hashCode;
248: }
249:
250: /**
251: * Gets a debugging string version of the key.
252: *
253: * @return a debugging string
254: */
255: public String toString() {
256: return "MultiKey" + Arrays.asList(keys).toString();
257: }
258:
259: }
|