    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.ioc.internal.services;
    014
    015import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    016import org.apache.tapestry5.plastic.PlasticUtils;
    017
    018import java.lang.reflect.Method;
    019import java.util.Arrays;
    020
    021/**
    022 * A representation of a {@link java.lang.reflect.Method}, identifying the name, return type, parameter types and
    023 * exception types. Actual Method objects are tied to a particular class, and don't compare well with other otherwise
    024 * identical Methods from other classes or interface; MethodSignatures are distinct from classes and compare well.
    025 *
    026 * Because the intended purpose is to compare methods from interfaces (which are always public and abstract) we don't
    027 * bother to actually track the modifiers. In addition, at this time, MethodSignature <em>does not distinguish between
    028 * instance and static methods</em>.
    029 */
    030@SuppressWarnings("all")
    031public class MethodSignature
    032{
    033    private int hashCode = -1;
    034
    035    private final Class returnType;
    036
    037    private final String name;
    038
    039    private final Class[] parameterTypes;
    040
    041    private final Class[] exceptionTypes;
    042
    043    private final Method method;
    044
    045    public MethodSignature(Class returnType, String name, Class[] parameterTypes, Class[] exceptionTypes)
    046    {
    047        this(null, returnType, name, parameterTypes, exceptionTypes);
    048    }
    049
    050    private MethodSignature(Method method, Class returnType, String name, Class[] parameterTypes, Class[] exceptionTypes)
    051    {
    052        this.method = method;
    053        assert returnType != null;
    054        this.returnType = returnType;
    055        assert InternalUtils.isNonBlank(name);
    056        this.name = name;
    057
    058        // Can be null!
    059        this.parameterTypes = parameterTypes;
    060        this.exceptionTypes = exceptionTypes;
    061    }
    062
    063    public MethodSignature(Method m)
    064    {
    065        this(m, m.getReturnType(), m.getName(), m.getParameterTypes(), m.getExceptionTypes());
    066    }
    067
    068    /**
    069     * Returns the exceptions for this method. Caution: do not modify the returned array. May return null.
    070     */
    071    public Class[] getExceptionTypes()
    072    {
    073        return exceptionTypes;
    074    }
    075
    076    public String getName()
    077    {
    078        return name;
    079    }
    080
    081    /**
    082     * If this signature was created from a method, return that method.
    083     *
    084     * @since 5.3
    085     */
    086    public Method getMethod()
    087    {
    088        return method;
    089    }
    090
    091    /**
    092     * Returns the parameter types for this method. May return null. Caution: do not modify the returned array.
    093     */
    094    public Class[] getParameterTypes()
    095    {
    096        return parameterTypes;
    097    }
    098
    099    public Class getReturnType()
    100    {
    101        return returnType;
    102    }
    103
    104    @Override
    105    public int hashCode()
    106    {
    107        if (hashCode == -1)
    108        {
    109
    110            hashCode = returnType.hashCode();
    111
    112            hashCode = 31 * hashCode + name.hashCode();
    113
    114            int count = InternalUtils.size(parameterTypes);
    115
    116            for (int i = 0; i < count; i++)
    117                hashCode = 31 * hashCode + parameterTypes[i].hashCode();
    118
    119            count = InternalUtils.size(exceptionTypes);
    120
    121            for (int i = 0; i < count; i++)
    122                hashCode = 31 * hashCode + exceptionTypes[i].hashCode();
    123        }
    124
    125        return hashCode;
    126    }
    127
    128    /**
    129     * Returns true if the other object is an instance of MethodSignature with <em>identical</em> values for return
    130     * type, name, parameter types and exception types.
    131     *
    132     * @see #isOverridingSignatureOf(MethodSignature)
    133     */
    134    @Override
    135    public boolean equals(Object o)
    136    {
    137        if (o == null || !(o instanceof MethodSignature))
    138            return false;
    139
    140        MethodSignature ms = (MethodSignature) o;
    141
    142        if (returnType != ms.returnType)
    143            return false;
    144
    145        if (!name.equals(ms.name))
    146            return false;
    147
    148        if (mismatch(parameterTypes, ms.parameterTypes))
    149            return false;
    150
    151        return !mismatch(exceptionTypes, ms.exceptionTypes);
    152    }
    153
    154    private boolean mismatch(Class[] a1, Class[] a2)
    155    {
    156        int a1Count = InternalUtils.size(a1);
    157        int a2Count = InternalUtils.size(a2);
    158
    159        if (a1Count != a2Count)
    160            return true;
    161
    162        // Hm. What if order is important (for exceptions)? We're really saying here that they
    163        // were derived from the name Method.
    164
    165        for (int i = 0; i < a1Count; i++)
    166        {
    167            if (a1[i] != a2[i])
    168                return true;
    169        }
    170
    171        return false;
    172    }
    173
    174    @Override
    175    public String toString()
    176    {
    177        StringBuilder buffer = new StringBuilder();
    178
    179        buffer.append(PlasticUtils.toTypeName(returnType));
    180        buffer.append(' ');
    181        buffer.append(name);
    182        buffer.append('(');
    183
    184        for (int i = 0; i < InternalUtils.size(parameterTypes); i++)
    185        {
    186            if (i > 0)
    187                buffer.append(", ");
    188
    189            buffer.append(PlasticUtils.toTypeName(parameterTypes[i]));
    190        }
    191
    192        buffer.append(')');
    193
    194        int _exceptionCount = InternalUtils.size(exceptionTypes);
    195        String _exceptionNames[] = new String[_exceptionCount];
    196        for (int i = 0; i < _exceptionCount; i++)
    197        {
    198            _exceptionNames[i] = exceptionTypes[i].getName();
    199        }
    200
    201        Arrays.sort(_exceptionNames);
    202
    203        for (int i = 0; i < _exceptionCount; i++)
    204        {
    205            if (i == 0)
    206                buffer.append(" throws ");
    207            else
    208                buffer.append(", ");
    209
    210            buffer.append(_exceptionNames[i]);
    211        }
    212
    213        return buffer.toString();
    214    }
    215
    216    /**
    217     * Returns a string consisting of the name of the method and its parameter types. This is similar to
    218     * {@link #toString()}, but omits the return type and information about thrown exceptions. A unique id is used by
    219     * {@link org.apache.tapestry5.ioc.internal.services.MethodIterator} to identify overlapping methods (methods with the same name and parameter types but with
    220     * different thrown exceptions).
    221     *
    222     * @see #isOverridingSignatureOf(MethodSignature)
    223     */
    224    public String getUniqueId()
    225    {
    226        StringBuilder buffer = new StringBuilder(name);
    227        buffer.append('(');
    228
    229        for (int i = 0; i < InternalUtils.size(parameterTypes); i++)
    230        {
    231            if (i > 0)
    232                buffer.append(',');
    233
    234            buffer.append(PlasticUtils.toTypeName(parameterTypes[i]));
    235        }
    236
    237        buffer.append(')');
    238
    239        return buffer.toString();
    240    }
    241
    242    /**
    243     * Returns true if this signature has the same return type, name and parameters types as the method signature passed
    244     * in, and this signature's exceptions "trump" (are the same as, or super-implementations of, all exceptions thrown
    245     * by the other method signature).
    246     */
    247
    248    public boolean isOverridingSignatureOf(MethodSignature ms)
    249    {
    250        if (returnType != ms.returnType)
    251            return false;
    252
    253        if (!name.equals(ms.name))
    254            return false;
    255
    256        if (mismatch(parameterTypes, ms.parameterTypes))
    257            return false;
    258
    259        return exceptionsEncompass(ms.exceptionTypes);
    260    }
    261
    262    /**
    263     * The nuts and bolts of checking that another method signature's exceptions are a subset of this signature's.
    264     */
    265
    266    @SuppressWarnings("unchecked")
    267    private boolean exceptionsEncompass(Class[] otherExceptions)
    268    {
    269        int ourCount = InternalUtils.size(exceptionTypes);
    270        int otherCount = InternalUtils.size(otherExceptions);
    271
    272        // If we have no exceptions, then ours encompass theirs only if they
    273        // have no exceptions, either.
    274
    275        if (ourCount == 0)
    276            return otherCount == 0;
    277
    278        boolean[] matched = new boolean[otherCount];
    279        int unmatched = otherCount;
    280
    281        for (int i = 0; i < ourCount && unmatched > 0; i++)
    282        {
    283            for (int j = 0; j < otherCount; j++)
    284            {
    285                // Ignore exceptions that have already been matched
    286
    287                if (matched[j])
    288                    continue;
    289
    290                // When one of our exceptions is a super-class of one of their exceptions,
    291                // then their exceptions is matched.
    292
    293                if (exceptionTypes[i].isAssignableFrom(otherExceptions[j]))
    294                {
    295                    matched[j] = true;
    296                    unmatched--;
    297                }
    298            }
    299        }
    300
    301        return unmatched == 0;
    302    }
    303}