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}