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}