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