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}