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