001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.ioc.internal.services; 014 015import org.apache.tapestry5.ioc.internal.util.InternalUtils; 016import org.apache.tapestry5.plastic.PlasticUtils; 017 018import java.lang.reflect.Method; 019import java.util.Arrays; 020 021/** 022 * A representation of a {@link java.lang.reflect.Method}, identifying the name, return type, parameter types and 023 * exception types. Actual Method objects are tied to a particular class, and don't compare well with other otherwise 024 * identical Methods from other classes or interface; MethodSignatures are distinct from classes and compare well. 025 * 026 * Because the intended purpose is to compare methods from interfaces (which are always public and abstract) we don't 027 * bother to actually track the modifiers. In addition, at this time, MethodSignature <em>does not distinguish between 028 * instance and static methods</em>. 029 */ 030@SuppressWarnings("all") 031public class MethodSignature 032{ 033 private int hashCode = -1; 034 035 private final Class returnType; 036 037 private final String name; 038 039 private final Class[] parameterTypes; 040 041 private final Class[] exceptionTypes; 042 043 private final Method method; 044 045 public MethodSignature(Class returnType, String name, Class[] parameterTypes, Class[] exceptionTypes) 046 { 047 this(null, returnType, name, parameterTypes, exceptionTypes); 048 } 049 050 private MethodSignature(Method method, Class returnType, String name, Class[] parameterTypes, Class[] exceptionTypes) 051 { 052 this.method = method; 053 assert returnType != null; 054 this.returnType = returnType; 055 assert InternalUtils.isNonBlank(name); 056 this.name = name; 057 058 // Can be null! 059 this.parameterTypes = parameterTypes; 060 this.exceptionTypes = exceptionTypes; 061 } 062 063 public MethodSignature(Method m) 064 { 065 this(m, m.getReturnType(), m.getName(), m.getParameterTypes(), m.getExceptionTypes()); 066 } 067 068 /** 069 * Returns the exceptions for this method. Caution: do not modify the returned array. May return null. 070 */ 071 public Class[] getExceptionTypes() 072 { 073 return exceptionTypes; 074 } 075 076 public String getName() 077 { 078 return name; 079 } 080 081 /** 082 * If this signature was created from a method, return that method. 083 * 084 * @since 5.3 085 */ 086 public Method getMethod() 087 { 088 return method; 089 } 090 091 /** 092 * Returns the parameter types for this method. May return null. Caution: do not modify the returned array. 093 */ 094 public Class[] getParameterTypes() 095 { 096 return parameterTypes; 097 } 098 099 public Class getReturnType() 100 { 101 return returnType; 102 } 103 104 @Override 105 public int hashCode() 106 { 107 if (hashCode == -1) 108 { 109 110 hashCode = returnType.hashCode(); 111 112 hashCode = 31 * hashCode + name.hashCode(); 113 114 int count = InternalUtils.size(parameterTypes); 115 116 for (int i = 0; i < count; i++) 117 hashCode = 31 * hashCode + parameterTypes[i].hashCode(); 118 119 count = InternalUtils.size(exceptionTypes); 120 121 for (int i = 0; i < count; i++) 122 hashCode = 31 * hashCode + exceptionTypes[i].hashCode(); 123 } 124 125 return hashCode; 126 } 127 128 /** 129 * Returns true if the other object is an instance of MethodSignature with <em>identical</em> values for return 130 * type, name, parameter types and exception types. 131 * 132 * @see #isOverridingSignatureOf(MethodSignature) 133 */ 134 @Override 135 public boolean equals(Object o) 136 { 137 if (o == null || !(o instanceof MethodSignature)) 138 return false; 139 140 MethodSignature ms = (MethodSignature) o; 141 142 if (returnType != ms.returnType) 143 return false; 144 145 if (!name.equals(ms.name)) 146 return false; 147 148 if (mismatch(parameterTypes, ms.parameterTypes)) 149 return false; 150 151 return !mismatch(exceptionTypes, ms.exceptionTypes); 152 } 153 154 private boolean mismatch(Class[] a1, Class[] a2) 155 { 156 int a1Count = InternalUtils.size(a1); 157 int a2Count = InternalUtils.size(a2); 158 159 if (a1Count != a2Count) 160 return true; 161 162 // Hm. What if order is important (for exceptions)? We're really saying here that they 163 // were derived from the name Method. 164 165 for (int i = 0; i < a1Count; i++) 166 { 167 if (a1[i] != a2[i]) 168 return true; 169 } 170 171 return false; 172 } 173 174 @Override 175 public String toString() 176 { 177 StringBuilder buffer = new StringBuilder(); 178 179 buffer.append(PlasticUtils.toTypeName(returnType)); 180 buffer.append(' '); 181 buffer.append(name); 182 buffer.append('('); 183 184 for (int i = 0; i < InternalUtils.size(parameterTypes); i++) 185 { 186 if (i > 0) 187 buffer.append(", "); 188 189 buffer.append(PlasticUtils.toTypeName(parameterTypes[i])); 190 } 191 192 buffer.append(')'); 193 194 int _exceptionCount = InternalUtils.size(exceptionTypes); 195 String _exceptionNames[] = new String[_exceptionCount]; 196 for (int i = 0; i < _exceptionCount; i++) 197 { 198 _exceptionNames[i] = exceptionTypes[i].getName(); 199 } 200 201 Arrays.sort(_exceptionNames); 202 203 for (int i = 0; i < _exceptionCount; i++) 204 { 205 if (i == 0) 206 buffer.append(" throws "); 207 else 208 buffer.append(", "); 209 210 buffer.append(_exceptionNames[i]); 211 } 212 213 return buffer.toString(); 214 } 215 216 /** 217 * Returns a string consisting of the name of the method and its parameter types. This is similar to 218 * {@link #toString()}, but omits the return type and information about thrown exceptions. A unique id is used by 219 * {@link org.apache.tapestry5.ioc.internal.services.MethodIterator} to identify overlapping methods (methods with the same name and parameter types but with 220 * different thrown exceptions). 221 * 222 * @see #isOverridingSignatureOf(MethodSignature) 223 */ 224 public String getUniqueId() 225 { 226 StringBuilder buffer = new StringBuilder(name); 227 buffer.append('('); 228 229 for (int i = 0; i < InternalUtils.size(parameterTypes); i++) 230 { 231 if (i > 0) 232 buffer.append(','); 233 234 buffer.append(PlasticUtils.toTypeName(parameterTypes[i])); 235 } 236 237 buffer.append(')'); 238 239 return buffer.toString(); 240 } 241 242 /** 243 * Returns true if this signature has the same return type, name and parameters types as the method signature passed 244 * in, and this signature's exceptions "trump" (are the same as, or super-implementations of, all exceptions thrown 245 * by the other method signature). 246 */ 247 248 public boolean isOverridingSignatureOf(MethodSignature ms) 249 { 250 if (returnType != ms.returnType) 251 return false; 252 253 if (!name.equals(ms.name)) 254 return false; 255 256 if (mismatch(parameterTypes, ms.parameterTypes)) 257 return false; 258 259 return exceptionsEncompass(ms.exceptionTypes); 260 } 261 262 /** 263 * The nuts and bolts of checking that another method signature's exceptions are a subset of this signature's. 264 */ 265 266 @SuppressWarnings("unchecked") 267 private boolean exceptionsEncompass(Class[] otherExceptions) 268 { 269 int ourCount = InternalUtils.size(exceptionTypes); 270 int otherCount = InternalUtils.size(otherExceptions); 271 272 // If we have no exceptions, then ours encompass theirs only if they 273 // have no exceptions, either. 274 275 if (ourCount == 0) 276 return otherCount == 0; 277 278 boolean[] matched = new boolean[otherCount]; 279 int unmatched = otherCount; 280 281 for (int i = 0; i < ourCount && unmatched > 0; i++) 282 { 283 for (int j = 0; j < otherCount; j++) 284 { 285 // Ignore exceptions that have already been matched 286 287 if (matched[j]) 288 continue; 289 290 // When one of our exceptions is a super-class of one of their exceptions, 291 // then their exceptions is matched. 292 293 if (exceptionTypes[i].isAssignableFrom(otherExceptions[j])) 294 { 295 matched[j] = true; 296 unmatched--; 297 } 298 } 299 } 300 301 return unmatched == 0; 302 } 303}