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    
015    package org.apache.tapestry5.ioc.services;
016    
017    import java.lang.reflect.Method;
018    import java.util.Arrays;
019    
020    import 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")
037    public 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    }