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