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 * @param modifiers 078 * @param returnType 079 * Java source name for the return type 080 * @param methodName 081 * @param argumentTypes 082 * may be null 083 * @param genericSignature 084 * TODO 085 * @param checkedExceptionTypes 086 * may be null 087 */ 088 public MethodDescription(int modifiers, String returnType, String methodName, String[] argumentTypes, 089 String genericSignature, String[] checkedExceptionTypes) 090 { 091 assert PlasticInternalUtils.isNonBlank(returnType); 092 assert PlasticInternalUtils.isNonBlank(methodName); 093 094 this.modifiers = modifiers; 095 this.returnType = returnType; 096 this.methodName = methodName; 097 this.genericSignature = genericSignature; 098 099 this.argumentTypes = PlasticInternalUtils.orEmpty(argumentTypes); 100 this.checkedExceptionTypes = PlasticInternalUtils.orEmpty(checkedExceptionTypes); 101 } 102 103 public MethodDescription withModifiers(int newModifiers) 104 { 105 return new MethodDescription(newModifiers, returnType, methodName, argumentTypes, genericSignature, 106 checkedExceptionTypes); 107 } 108 109 /** Creates a MethodDescription from a Java Method. The generic signature will be null. */ 110 public MethodDescription(Method method) 111 { 112 this(method.getModifiers(), PlasticUtils.toTypeName(method.getReturnType()), method.getName(), PlasticUtils 113 .toTypeNames(method.getParameterTypes()), null, PlasticUtils.toTypeNames(method.getExceptionTypes())); 114 } 115 116 @Override 117 public int compareTo(MethodDescription o) 118 { 119 int result = methodName.compareTo(o.methodName); 120 121 if (result == 0) 122 result = o.argumentTypes.length - argumentTypes.length; 123 124 return result; 125 } 126 127 @Override 128 public int hashCode() 129 { 130 final int prime = 31; 131 int result = 1; 132 133 result = prime * result + Arrays.hashCode(argumentTypes); 134 result = prime * result + Arrays.hashCode(checkedExceptionTypes); 135 result = prime * result + methodName.hashCode(); 136 result = prime * result + modifiers; 137 result = prime * result + (genericSignature == null ? 0 : genericSignature.hashCode()); 138 139 result = prime * result + returnType.hashCode(); 140 141 return result; 142 } 143 144 @Override 145 public boolean equals(Object obj) 146 { 147 if (this == obj) 148 return true; 149 if (obj == null) 150 return false; 151 if (getClass() != obj.getClass()) 152 return false; 153 154 MethodDescription other = (MethodDescription) obj; 155 156 if (!methodName.equals(other.methodName)) 157 return false; 158 159 // TODO: I think this tripped me up in Tapestry at some point, as 160 // there were modifiers that cause some problem, such as abstract 161 // or deprecated or something. May need a mask of modifiers we 162 // care about for equals()/hashCode() purposes. 163 164 if (modifiers != other.modifiers) 165 return false; 166 167 if (!returnType.equals(other.returnType)) 168 return false; 169 170 if (!Arrays.equals(argumentTypes, other.argumentTypes)) 171 return false; 172 173 if (!PlasticInternalUtils.isEqual(genericSignature, other.genericSignature)) 174 return false; 175 176 if (!Arrays.equals(checkedExceptionTypes, other.checkedExceptionTypes)) 177 return false; 178 179 return true; 180 } 181 182 @Override 183 public String toString() 184 { 185 StringBuilder builder = new StringBuilder(); 186 187 // TODO: Not 100% sure that methodNode.access is exactly the same 188 // as modifiers. We'll have to see. 189 190 if (modifiers != 0) 191 builder.append(Modifier.toString(modifiers)).append(' '); 192 193 builder.append(returnType).append(' ').append(methodName).append('('); 194 195 String sep = ""; 196 197 for (String name : argumentTypes) 198 { 199 builder.append(sep); 200 builder.append(name); 201 202 sep = ", "; 203 } 204 205 builder.append(')'); 206 207 if (genericSignature != null) 208 builder.append(" <").append(genericSignature).append('>'); 209 210 sep = " throws "; 211 212 for (String name : checkedExceptionTypes) 213 { 214 builder.append(sep); 215 builder.append(name); 216 217 sep = ", "; 218 } 219 220 return builder.toString(); 221 } 222 223 /** 224 * A string used to identify the method, containing just the method name and argument types 225 * (but ignoring visibility, return type and thrown exceptions). 226 * 227 * @return method identifier 228 */ 229 public String toShortString() 230 { 231 StringBuilder builder = new StringBuilder(methodName).append('('); 232 233 String sep = ""; 234 235 for (String name : argumentTypes) 236 { 237 builder.append(sep).append(name); 238 239 sep = ", "; 240 } 241 242 return builder.append(')').toString(); 243 } 244}