001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry.util;
016
017 import java.io.Externalizable;
018 import java.io.IOException;
019 import java.io.ObjectInput;
020 import java.io.ObjectOutput;
021
022 import org.apache.tapestry.Tapestry;
023
024 /**
025 * A complex key that may be used as an alternative to nested
026 * {@link java.util.Map}s.
027 *
028 * @author Howard Lewis Ship
029 *
030 **/
031
032 public class MultiKey implements Externalizable
033 {
034 /**
035 * @since 2.0.4
036 *
037 **/
038
039 private static final long serialVersionUID = 4465448607415788806L;
040
041 private static final int HASH_CODE_UNSET = -1;
042
043 private transient int hashCode = HASH_CODE_UNSET;
044
045 private Object[] keys;
046
047 /**
048 * Public no-arguments constructor needed to be compatible with
049 * {@link Externalizable}; this leaves the new MultiKey in a
050 * non-usable state and shouldn't be used by user code.
051 *
052 **/
053
054 public MultiKey()
055 {
056 }
057
058 /**
059 * Builds a <code>MultiKey</code> from an array of keys. If the array is not
060 * copied, then it must not be modified.
061 *
062 * @param keys The components of the key.
063 * @param makeCopy If true, a copy of the keys is created. If false,
064 * the keys are simple retained by the <code>MultiKey</code>.
065 *
066 * @throws IllegalArgumentException if keys is null, of if the
067 * first element of keys is null.
068 *
069 **/
070
071 public MultiKey(Object[] keys, boolean makeCopy)
072 {
073 super();
074
075 if (keys == null || keys.length == 0)
076 throw new IllegalArgumentException(Tapestry.getMessage("MultiKey.null-keys"));
077
078 if (keys[0] == null)
079 throw new IllegalArgumentException(Tapestry.getMessage("MultiKey.first-element-may-not-be-null"));
080
081 if (makeCopy)
082 {
083 this.keys = new Object[keys.length];
084 System.arraycopy(keys, 0, this.keys, 0, keys.length);
085 }
086 else
087 this.keys = keys;
088 }
089
090 /**
091 * Returns true if:
092 * <ul>
093 * <li>The other object is a <code>MultiKey</code>
094 * <li>They have the same number of key elements
095 * <li>Every element is an exact match or is equal
096 * </ul>
097 *
098 **/
099
100 public boolean equals(Object other)
101 {
102 int i;
103
104 if (other == null)
105 return false;
106
107 if (keys == null)
108 throw new IllegalStateException(Tapestry.getMessage("MultiKey.no-keys"));
109
110 // Would a hashCode check be worthwhile here?
111
112 try
113 {
114 MultiKey otherMulti = (MultiKey) other;
115
116 if (keys.length != otherMulti.keys.length)
117 return false;
118
119 for (i = 0; i < keys.length; i++)
120 {
121 // On an exact match, continue. This means that null matches
122 // null.
123
124 if (keys[i] == otherMulti.keys[i])
125 continue;
126
127 // If either is null, but not both, then
128 // not a match.
129
130 if (keys[i] == null || otherMulti.keys[i] == null)
131 return false;
132
133 if (!keys[i].equals(otherMulti.keys[i]))
134 return false;
135
136 }
137
138 // Every key equal. A match.
139
140 return true;
141 }
142 catch (ClassCastException e)
143 {
144 }
145
146 return false;
147 }
148
149 /**
150 * Returns the hash code of the receiver, which is computed from all the
151 * non-null key elements. This value is computed once and
152 * then cached, so elements should not change their hash codes
153 * once created (note that this
154 * is the same constraint that would be used if the individual
155 * key elements were
156 * themselves {@link java.util.Map} keys.
157 *
158 *
159 **/
160
161 public int hashCode()
162 {
163 if (hashCode == HASH_CODE_UNSET)
164 {
165 hashCode = keys[0].hashCode();
166
167 for (int i = 1; i < keys.length; i++)
168 {
169 if (keys[i] != null)
170 hashCode ^= keys[i].hashCode();
171 }
172 }
173
174 return hashCode;
175 }
176
177 /**
178 * Identifies all the keys stored by this <code>MultiKey</code>.
179 *
180 **/
181
182 public String toString()
183 {
184 StringBuffer buffer;
185 int i;
186
187 buffer = new StringBuffer("MultiKey[");
188
189 for (i = 0; i < keys.length; i++)
190 {
191 if (i > 0)
192 buffer.append(", ");
193
194 if (keys[i] == null)
195 buffer.append("<null>");
196 else
197 buffer.append(keys[i]);
198 }
199
200 buffer.append(']');
201
202 return buffer.toString();
203 }
204
205 /**
206 * Writes a count of the keys, then writes each individual key.
207 *
208 **/
209
210 public void writeExternal(ObjectOutput out) throws IOException
211 {
212 out.writeInt(keys.length);
213
214 for (int i = 0; i < keys.length; i++)
215 out.writeObject(keys[i]);
216 }
217
218 /**
219 * Reads the state previously written by {@link #writeExternal(ObjectOutput)}.
220 *
221 **/
222
223 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
224 {
225 int count;
226
227 count = in.readInt();
228 keys = new Object[count];
229
230 for (int i = 0; i < count; i++)
231 keys[i] = in.readObject();
232 }
233 }