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