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 }