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