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    }