    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}