1. /*
  2. * Copyright 2003-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.collections.keyvalue;
  17. import java.io.Serializable;
  18. import java.util.Arrays;
  19. /**
  20. * A <code>MultiKey</code> allows multiple map keys to be merged together.
  21. * <p>
  22. * The purpose of this class is to avoid the need to write code to handle
  23. * maps of maps. An example might be the need to lookup a filename by
  24. * key and locale. The typical solution might be nested maps. This class
  25. * can be used instead by creating an instance passing in the key and locale.
  26. * <p>
  27. * Example usage:
  28. * <pre>
  29. * // populate map with data mapping key+locale to localizedText
  30. * Map map = new HashMap();
  31. * MultiKey multiKey = new MultiKey(key, locale);
  32. * map.put(multiKey, localizedText);
  33. *
  34. * // later retireve the localized text
  35. * MultiKey multiKey = new MultiKey(key, locale);
  36. * String localizedText = (String) map.get(multiKey);
  37. * </pre>
  38. *
  39. * @since Commons Collections 3.0
  40. * @version $Revision: 1.5 $ $Date: 2004/03/13 12:43:43 $
  41. *
  42. * @author Howard Lewis Ship
  43. * @author Stephen Colebourne
  44. */
  45. public class MultiKey implements Serializable {
  46. // This class could implement List, but that would confuse it's purpose
  47. /** Serialisation version */
  48. private static final long serialVersionUID = 4465448607415788805L;
  49. /** The individual keys */
  50. private final Object[] keys;
  51. /** The cached hashCode */
  52. private final int hashCode;
  53. /**
  54. * Constructor taking two keys.
  55. * <p>
  56. * The keys should be immutable
  57. * If they are not then they must not be changed after adding to the MultiKey.
  58. *
  59. * @param key1 the first key
  60. * @param key2 the second key
  61. */
  62. public MultiKey(Object key1, Object key2) {
  63. this(new Object[] {key1, key2}, false);
  64. }
  65. /**
  66. * Constructor taking three keys.
  67. * <p>
  68. * The keys should be immutable
  69. * If they are not then they must not be changed after adding to the MultiKey.
  70. *
  71. * @param key1 the first key
  72. * @param key2 the second key
  73. * @param key3 the third key
  74. */
  75. public MultiKey(Object key1, Object key2, Object key3) {
  76. this(new Object[] {key1, key2, key3}, false);
  77. }
  78. /**
  79. * Constructor taking four keys.
  80. * <p>
  81. * The keys should be immutable
  82. * If they are not then they must not be changed after adding to the MultiKey.
  83. *
  84. * @param key1 the first key
  85. * @param key2 the second key
  86. * @param key3 the third key
  87. * @param key4 the fourth key
  88. */
  89. public MultiKey(Object key1, Object key2, Object key3, Object key4) {
  90. this(new Object[] {key1, key2, key3, key4}, false);
  91. }
  92. /**
  93. * Constructor taking five keys.
  94. * <p>
  95. * The keys should be immutable
  96. * If they are not then they must not be changed after adding to the MultiKey.
  97. *
  98. * @param key1 the first key
  99. * @param key2 the second key
  100. * @param key3 the third key
  101. * @param key4 the fourth key
  102. * @param key5 the fifth key
  103. */
  104. public MultiKey(Object key1, Object key2, Object key3, Object key4, Object key5) {
  105. this(new Object[] {key1, key2, key3, key4, key5}, false);
  106. }
  107. /**
  108. * Constructor taking an array of keys which is cloned.
  109. * <p>
  110. * The keys should be immutable
  111. * If they are not then they must not be changed after adding to the MultiKey.
  112. * <p>
  113. * This is equivalent to <code>new MultiKey(keys, true)</code>.
  114. *
  115. * @param keys the array of keys, not null
  116. * @throws IllegalArgumentException if the key array is null
  117. */
  118. public MultiKey(Object[] keys) {
  119. this(keys, true);
  120. }
  121. /**
  122. * Constructor taking an array of keys, optionally choosing whether to clone.
  123. * <p>
  124. * <b>If the array is not cloned, then it must not be modified.</b>
  125. * <p>
  126. * This method is public for performance reasons only, to avoid a clone.
  127. * The hashcode is calculated once here in this method.
  128. * Therefore, changing the array passed in would not change the hashcode but
  129. * would change the equals method, which is a bug.
  130. * <p>
  131. * This is the only fully safe usage of this constructor, as the object array
  132. * is never made available in a variable:
  133. * <pre>
  134. * new MultiKey(new Object[] {...}, false);
  135. * </pre>
  136. * <p>
  137. * The keys should be immutable
  138. * If they are not then they must not be changed after adding to the MultiKey.
  139. *
  140. * @param keys the array of keys, not null
  141. * @param makeClone true to clone the array, false to assign it
  142. * @throws IllegalArgumentException if the key array is null
  143. * @since Commons Collections 3.1
  144. */
  145. public MultiKey(Object[] keys, boolean makeClone) {
  146. super();
  147. if (keys == null) {
  148. throw new IllegalArgumentException("The array of keys must not be null");
  149. }
  150. if (makeClone) {
  151. this.keys = (Object[]) keys.clone();
  152. } else {
  153. this.keys = keys;
  154. }
  155. int total = 0;
  156. for (int i = 0; i < keys.length; i++) {
  157. if (keys[i] != null) {
  158. total ^= keys[i].hashCode();
  159. }
  160. }
  161. hashCode = total;
  162. }
  163. //-----------------------------------------------------------------------
  164. /**
  165. * Gets a clone of the array of keys.
  166. * <p>
  167. * The keys should be immutable
  168. * If they are not then they must not be changed.
  169. *
  170. * @return the individual keys
  171. */
  172. public Object[] getKeys() {
  173. return (Object[]) keys.clone();
  174. }
  175. /**
  176. * Gets the key at the specified index.
  177. * <p>
  178. * The key should be immutable.
  179. * If it is not then it must not be changed.
  180. *
  181. * @param index the index to retrieve
  182. * @return the key at the index
  183. * @throws IndexOutOfBoundsException if the index is invalid
  184. * @since Commons Collections 3.1
  185. */
  186. public Object getKey(int index) {
  187. return keys[index];
  188. }
  189. /**
  190. * Gets the size of the list of keys.
  191. *
  192. * @return the size of the list of keys
  193. * @since Commons Collections 3.1
  194. */
  195. public int size() {
  196. return keys.length;
  197. }
  198. //-----------------------------------------------------------------------
  199. /**
  200. * Compares this object to another.
  201. * <p>
  202. * To be equal, the other object must be a <code>MultiKey</code> with the
  203. * same number of keys which are also equal.
  204. *
  205. * @param other the other object to compare to
  206. * @return true if equal
  207. */
  208. public boolean equals(Object other) {
  209. if (other == this) {
  210. return true;
  211. }
  212. if (other instanceof MultiKey) {
  213. MultiKey otherMulti = (MultiKey) other;
  214. return Arrays.equals(keys, otherMulti.keys);
  215. }
  216. return false;
  217. }
  218. /**
  219. * Gets the combined hash code that is computed from all the keys.
  220. * <p>
  221. * This value is computed once and then cached, so elements should not
  222. * change their hash codes once created (note that this is the same
  223. * constraint that would be used if the individual keys elements were
  224. * themselves {@link java.util.Map Map} keys.
  225. *
  226. * @return the hash code
  227. */
  228. public int hashCode() {
  229. return hashCode;
  230. }
  231. /**
  232. * Gets a debugging string version of the key.
  233. *
  234. * @return a debugging string
  235. */
  236. public String toString() {
  237. return "MultiKey" + Arrays.asList(keys).toString();
  238. }
  239. }