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