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 }