    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}