    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}