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
015package org.apache.hivemind.service;
016
017import 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 */
032public 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}