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 015package org.apache.tapestry.util; 016 017import java.io.Externalizable; 018import java.io.IOException; 019import java.io.ObjectInput; 020import java.io.ObjectOutput; 021 022import 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 032public 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}