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}