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