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
015package org.apache.tapestry5.plastic;
016
017import java.lang.reflect.Method;
018import java.lang.reflect.Modifier;
019import java.util.Arrays;
020
021import 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 */
039public 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    @Override
119    public int compareTo(MethodDescription o)
120    {
121        int result = methodName.compareTo(o.methodName);
122
123        if (result == 0)
124            result = o.argumentTypes.length - argumentTypes.length;
125
126        return result;
127    }
128
129    @Override
130    public int hashCode()
131    {
132        final int prime = 31;
133        int result = 1;
134
135        result = prime * result + Arrays.hashCode(argumentTypes);
136        result = prime * result + Arrays.hashCode(checkedExceptionTypes);
137        result = prime * result + methodName.hashCode();
138        result = prime * result + modifiers;
139        result = prime * result + (genericSignature == null ? 0 : genericSignature.hashCode());
140
141        result = prime * result + returnType.hashCode();
142
143        return result;
144    }
145
146    @Override
147    public boolean equals(Object obj)
148    {
149        if (this == obj)
150            return true;
151        if (obj == null)
152            return false;
153        if (getClass() != obj.getClass())
154            return false;
155
156        MethodDescription other = (MethodDescription) obj;
157
158        if (!methodName.equals(other.methodName))
159            return false;
160
161        // TODO: I think this tripped me up in Tapestry at some point, as
162        // there were modifiers that cause some problem, such as abstract
163        // or deprecated or something. May need a mask of modifiers we
164        // care about for equals()/hashCode() purposes.
165
166        if (modifiers != other.modifiers)
167            return false;
168
169        if (!returnType.equals(other.returnType))
170            return false;
171
172        if (!Arrays.equals(argumentTypes, other.argumentTypes))
173            return false;
174
175        if (!PlasticInternalUtils.isEqual(genericSignature, other.genericSignature))
176            return false;
177
178        if (!Arrays.equals(checkedExceptionTypes, other.checkedExceptionTypes))
179            return false;
180
181        return true;
182    }
183
184    @Override
185    public String toString()
186    {
187        StringBuilder builder = new StringBuilder();
188
189        // TODO: Not 100% sure that methodNode.access is exactly the same
190        // as modifiers. We'll have to see.
191
192        if (modifiers != 0)
193            builder.append(Modifier.toString(modifiers)).append(" ");
194
195        builder.append(returnType).append(" ").append(methodName).append("(");
196
197        String sep = "";
198
199        for (String name : argumentTypes)
200        {
201            builder.append(sep);
202            builder.append(name);
203
204            sep = ", ";
205        }
206
207        builder.append(")");
208
209        if (genericSignature != null)
210            builder.append(" <").append(genericSignature).append(">");
211
212        sep = " throws ";
213
214        for (String name : checkedExceptionTypes)
215        {
216            builder.append(sep);
217            builder.append(name);
218
219            sep = ", ";
220        }
221
222        return builder.toString();
223    }
224
225    /**
226     * A string used to identify the method, containing just the method name and argument types
227     * (but ignoring visibility, return type and thrown exceptions).
228     * 
229     * @return method identifier
230     */
231    public String toShortString()
232    {
233        StringBuilder builder = new StringBuilder(methodName).append("(");
234
235        String sep = "";
236
237        for (String name : argumentTypes)
238        {
239            builder.append(sep).append(name);
240
241            sep = ", ";
242        }
243
244        return builder.append(")").toString();
245    }
246}