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.plastic; 014 015import org.apache.tapestry5.internal.plastic.PlasticInternalUtils; 016 017import java.lang.reflect.Method; 018import java.lang.reflect.Modifier; 019import java.util.Arrays; 020 021/** 022 * Describes a {@link PlasticMethod} in terms of a method name, a set of modifiers 023 * (public, private, static, final, etc.), a return type, types of method arguments, 024 * and types of checked exceptions. Types are represented as Java source names: 025 * either names of primitives ("void", "byte", "long") or fully qualified class names ("java.lang.Object", 026 * "java.lang.Runnable"). ASM refers to this as "class name". 027 * 028 * MethodDescriptions are immutable, and properly implement equals() and hashCode(); they are often used as keys in 029 * Maps. 030 * 031 * The natural sort order for a MethodDescription is ascending order by method name, then descending order by number of 032 * parameters (for the same name). Sort order is not currently specified for overrides of the same method with the same 033 * number of parameters. 034 * 035 * TODO: Handling generic types. 036 */ 037public class MethodDescription implements Comparable<MethodDescription> 038{ 039 /** 040 * The full set of modifier flags for the method. 041 */ 042 public final int modifiers; 043 044 /** The Java source name for the return type, e.g., "void", "short", "java.util.Map", "java.lang.String[]". */ 045 public final String returnType; 046 047 /** The name of the method. */ 048 public final String methodName; 049 050 public final String genericSignature; 051 052 /** 053 * A non-null array of Java source names for arguments. Do not modify 054 * the contents of this array. 055 */ 056 public final String[] argumentTypes; 057 058 /** A non-null array of Java source names for checked exceptions. Do not modify the contents of this array. */ 059 public final String[] checkedExceptionTypes; 060 061 /** 062 * Convenience constructor for public methods that have no checked exceptions. 063 * 064 * @param returnType 065 * return type as type name 066 * @param methodName 067 * name of method 068 * @param argumentTypes 069 * type names for arguments 070 */ 071 public MethodDescription(String returnType, String methodName, String... argumentTypes) 072 { 073 this(Modifier.PUBLIC, returnType, methodName, argumentTypes, null, null); 074 } 075 076 /** 077 * Convenience constructor for copying a MethodDescription with 078 * different exception types. 079 * @since 5.4.4 080 */ 081 public MethodDescription(MethodDescription description, String[] checkedExceptionTypes) 082 { 083 this.argumentTypes = description.argumentTypes; 084 this.checkedExceptionTypes = checkedExceptionTypes; 085 this.genericSignature = description.genericSignature; 086 this.methodName = description.methodName; 087 this.modifiers = description.modifiers; 088 this.returnType = description.returnType; 089 } 090 091 /** 092 * @param modifiers 093 * @param returnType 094 * Java source name for the return type 095 * @param methodName 096 * @param argumentTypes 097 * may be null 098 * @param genericSignature 099 * TODO 100 * @param checkedExceptionTypes 101 * may be null 102 */ 103 public MethodDescription(int modifiers, String returnType, String methodName, String[] argumentTypes, 104 String genericSignature, String[] checkedExceptionTypes) 105 { 106 assert PlasticInternalUtils.isNonBlank(returnType); 107 assert PlasticInternalUtils.isNonBlank(methodName); 108 109 this.modifiers = modifiers; 110 this.returnType = returnType.intern(); 111 this.methodName = methodName.intern(); 112 this.genericSignature = genericSignature == null ? null : genericSignature.intern(); 113 114 this.argumentTypes = PlasticInternalUtils.orEmpty(argumentTypes); 115 this.checkedExceptionTypes = PlasticInternalUtils.orEmpty(checkedExceptionTypes); 116 } 117 118 public MethodDescription withModifiers(int newModifiers) 119 { 120 return new MethodDescription(newModifiers, returnType, methodName, argumentTypes, genericSignature, 121 checkedExceptionTypes); 122 } 123 124 /** Creates a MethodDescription from a Java Method. The generic signature will be null. */ 125 public MethodDescription(Method method) 126 { 127 this(method.getModifiers(), PlasticUtils.toTypeName(method.getReturnType()), method.getName(), PlasticUtils 128 .toTypeNames(method.getParameterTypes()), null, PlasticUtils.toTypeNames(method.getExceptionTypes())); 129 } 130 131 @Override 132 public int compareTo(MethodDescription o) 133 { 134 int result = methodName.compareTo(o.methodName); 135 136 if (result == 0) 137 result = o.argumentTypes.length - argumentTypes.length; 138 139 return result; 140 } 141 142 @Override 143 public int hashCode() 144 { 145 final int prime = 31; 146 int result = 1; 147 148 result = prime * result + Arrays.hashCode(argumentTypes); 149 result = prime * result + Arrays.hashCode(checkedExceptionTypes); 150 result = prime * result + methodName.hashCode(); 151 result = prime * result + modifiers; 152 result = prime * result + (genericSignature == null ? 0 : genericSignature.hashCode()); 153 154 result = prime * result + returnType.hashCode(); 155 156 return result; 157 } 158 159 @Override 160 public boolean equals(Object obj) 161 { 162 if (this == obj) 163 return true; 164 if (obj == null) 165 return false; 166 if (getClass() != obj.getClass()) 167 return false; 168 169 MethodDescription other = (MethodDescription) obj; 170 171 if (!methodName.equals(other.methodName)) 172 return false; 173 174 // TODO: I think this tripped me up in Tapestry at some point, as 175 // there were modifiers that cause some problem, such as abstract 176 // or deprecated or something. May need a mask of modifiers we 177 // care about for equals()/hashCode() purposes. 178 179 if (modifiers != other.modifiers) 180 return false; 181 182 if (!returnType.equals(other.returnType)) 183 return false; 184 185 if (!Arrays.equals(argumentTypes, other.argumentTypes)) 186 return false; 187 188 if (!PlasticInternalUtils.isEqual(genericSignature, other.genericSignature)) 189 return false; 190 191 if (!Arrays.equals(checkedExceptionTypes, other.checkedExceptionTypes)) 192 return false; 193 194 return true; 195 } 196 197 @Override 198 public String toString() 199 { 200 StringBuilder builder = new StringBuilder(); 201 202 // TODO: Not 100% sure that methodNode.access is exactly the same 203 // as modifiers. We'll have to see. 204 205 if (modifiers != 0) 206 builder.append(Modifier.toString(modifiers)).append(' '); 207 208 builder.append(returnType).append(' ').append(methodName).append('('); 209 210 String sep = ""; 211 212 for (String name : argumentTypes) 213 { 214 builder.append(sep); 215 builder.append(name); 216 217 sep = ", "; 218 } 219 220 builder.append(')'); 221 222 if (genericSignature != null) 223 builder.append(" <").append(genericSignature).append('>'); 224 225 sep = " throws "; 226 227 for (String name : checkedExceptionTypes) 228 { 229 builder.append(sep); 230 builder.append(name); 231 232 sep = ", "; 233 } 234 235 return builder.toString(); 236 } 237 238 /** 239 * A string used to identify the method, containing just the method name and argument types 240 * (but ignoring visibility, return type and thrown exceptions). 241 * 242 * @return method identifier 243 */ 244 public String toShortString() 245 { 246 StringBuilder builder = new StringBuilder(methodName).append('('); 247 248 String sep = ""; 249 250 for (String name : argumentTypes) 251 { 252 builder.append(sep).append(name); 253 254 sep = ", "; 255 } 256 257 return builder.append(')').toString(); 258 } 259}