View Javadoc

1   //////////////////////////////////////////////////////////////////////////////
2   // Clirr: compares two versions of a java library for binary compatibility
3   // Copyright (C) 2003 - 2005  Lars Kühne
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  //////////////////////////////////////////////////////////////////////////////
19  
20  package net.sf.clirr.core.internal.checks;
21  
22  import net.sf.clirr.core.ApiDifference;
23  import net.sf.clirr.core.Message;
24  import net.sf.clirr.core.Severity;
25  import net.sf.clirr.core.ScopeSelector;
26  import net.sf.clirr.core.internal.AbstractDiffReporter;
27  import net.sf.clirr.core.internal.ApiDiffDispatcher;
28  import net.sf.clirr.core.internal.ClassChangeCheck;
29  import net.sf.clirr.core.internal.CoIterator;
30  import net.sf.clirr.core.spi.JavaType;
31  import net.sf.clirr.core.spi.Method;
32  import net.sf.clirr.core.spi.Scope;
33  
34  import java.util.ArrayList;
35  import java.util.HashMap;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Map;
39  
40  /***
41   * Checks the methods of a class.
42   *
43   * @author lkuehne
44   */
45  public class MethodSetCheck
46      extends AbstractDiffReporter
47      implements ClassChangeCheck
48  {
49      private static final Message MSG_METHOD_NOW_IN_SUPERCLASS = new Message(7000);
50      private static final Message MSG_METHOD_NOW_IN_INTERFACE = new Message(7001);
51      private static final Message MSG_METHOD_REMOVED = new Message(7002);
52      private static final Message MSG_METHOD_OVERRIDE_REMOVED = new Message(7003);
53      private static final Message MSG_METHOD_ARGCOUNT_CHANGED = new Message(7004);
54      private static final Message MSG_METHOD_PARAMTYPE_CHANGED = new Message(7005);
55      private static final Message MSG_METHOD_RETURNTYPE_CHANGED = new Message(7006);
56      private static final Message MSG_METHOD_DEPRECATED = new Message(7007);
57      private static final Message MSG_METHOD_UNDEPRECATED = new Message(7008);
58      private static final Message MSG_METHOD_LESS_ACCESSIBLE = new Message(7009);
59      private static final Message MSG_METHOD_MORE_ACCESSIBLE = new Message(7010);
60      private static final Message MSG_METHOD_ADDED = new Message(7011);
61      private static final Message MSG_METHOD_ADDED_TO_INTERFACE = new Message(7012);
62      private static final Message MSG_ABSTRACT_METHOD_ADDED = new Message(7013);
63      private static final Message MSG_METHOD_NOW_FINAL = new Message(7014);
64      private static final Message MSG_METHOD_NOW_NONFINAL = new Message(7015);
65  
66      private ScopeSelector scopeSelector;
67  
68      /***
69       * Instantiates the check.
70       *
71       * @param dispatcher the dispatcher where detected differences shoudl be reported.
72       * @param scopeSelector defines the scopes to look at when searching for differences.
73       */
74      public MethodSetCheck(ApiDiffDispatcher dispatcher, ScopeSelector scopeSelector)
75      {
76          super(dispatcher);
77          this.scopeSelector = scopeSelector;
78      }
79  
80      public final boolean check(JavaType compatBaseline, JavaType currentVersion)
81      {
82          // Dont't report method problems when gender has changed, as
83          // really the whole API is a pile of crap then - let GenderChange check
84          // do it's job, and that's it
85          if (compatBaseline.isInterface() ^ currentVersion.isInterface())
86          {
87              return true;
88          }
89  
90          Map bNameToMethod = buildNameToMethodMap(compatBaseline);
91          Map cNameToMethod = buildNameToMethodMap(currentVersion);
92  
93          CoIterator iter = new CoIterator(null, bNameToMethod.keySet(), cNameToMethod.keySet());
94  
95          while (iter.hasNext())
96          {
97              iter.next();
98  
99              String baselineMethodName = (String) iter.getLeft();
100             String currentMethodName = (String) iter.getRight();
101 
102             if (baselineMethodName == null)
103             {
104                 // a new method name has been added in the new version
105                 List currentMethods = (List) cNameToMethod.get(currentMethodName);
106                 reportMethodsAdded(currentVersion, currentMethods);
107             }
108             else if (currentMethodName == null)
109             {
110                 // all methods with name x have been removed from the old version
111                 List baselineMethods = (List) bNameToMethod.get(baselineMethodName);
112                 reportMethodsRemoved(compatBaseline, baselineMethods, currentVersion);
113             }
114             else
115             {
116                 // assert baselineMethodName equals currentMethodName
117 
118                 List baselineMethods = (List) bNameToMethod.get(baselineMethodName);
119                 List currentMethods = (List) cNameToMethod.get(currentMethodName);
120 
121                 filterSoftMatchedMethods(
122                     compatBaseline, baselineMethods,
123                     currentVersion, currentMethods);
124 
125                 filterChangedMethods(
126                     baselineMethodName,
127                     compatBaseline, baselineMethods,
128                     currentVersion, currentMethods);
129 
130                 // if any methods are left, they have no matching method in
131                 // the other version, so report as removed or added respectively.
132 
133                 if (!baselineMethods.isEmpty())
134                 {
135                     reportMethodsRemoved(compatBaseline, baselineMethods, currentVersion);
136                 }
137 
138                 if (!currentMethods.isEmpty())
139                 {
140                     reportMethodsAdded(currentVersion, currentMethods);
141                 }
142             }
143         }
144 
145         return true;
146     }
147 
148     /***
149      * Given a list of old and new methods for a particular method name,
150      * find the (old, new) method pairs which have identical argument lists.
151      * <p>
152      * For these:
153      * <ul>
154      *  <li>report on changes in accessibility, return type, etc
155      *  <li>remove from the list
156      * </ul>
157      *
158      * On return from this method, the old and new method lists contain only
159      * methods whose argument lists have changed between versions [or possibly,
160      * methods which have been deleted while one or more new methods of the
161      * same name have been added, depending on how you view it]. All other
162      * situations have been dealt with.
163      * <p>
164      * Note that one or both method lists may be empty on return from
165      * this method.
166      */
167     private void filterSoftMatchedMethods(
168             JavaType compatBaseline,
169             List baselineMethods,
170             JavaType currentVersion,
171             List currentMethods)
172     {
173         for (Iterator bIter = baselineMethods.iterator(); bIter.hasNext();)
174         {
175             Method bMethod = (Method) bIter.next();
176 
177             for (Iterator cIter = currentMethods.iterator(); cIter.hasNext();)
178             {
179                 Method cMethod = (Method) cIter.next();
180 
181                 if (isSoftMatch(bMethod, cMethod))
182                 {
183                     check(compatBaseline, bMethod, cMethod);
184                     bIter.remove();
185                     cIter.remove();
186                     break;
187                 }
188             }
189         }
190     }
191 
192     /***
193      * Two methods are a "soft" match if they have the same name and argument
194      * list. No two methods on the same class are ever a "soft match" for
195      * each other, because the compiler requires distinct parameter lists for
196      * overloaded methods. This also implies that for a given method on an "old"
197      * class version, there are either zero or one "soft matches" on the new
198      * version.
199      * <p>
200      * However a "soft match" is not sufficient to ensure binary compatibility.
201      * A change in the method return type will result in a link error when used
202      * with code compiled against the previous version of the class.
203      * <p>
204      * There may also be other differences between methods that are regarded
205      * as "soft matches": the exceptions thrown, the deprecation status of the
206      * methods, their accessibility, etc.
207      */
208     private boolean isSoftMatch(Method oldMethod, Method newMethod)
209     {
210         String oldName = oldMethod.getName();
211         String newName = newMethod.getName();
212 
213         if (!oldName.equals(newName))
214         {
215             return false;
216         }
217 
218         StringBuffer buf = new StringBuffer();
219         appendHumanReadableArgTypeList(oldMethod, buf);
220         String oldArgs = buf.toString();
221 
222         buf.setLength(0);
223         appendHumanReadableArgTypeList(newMethod, buf);
224         String newArgs = buf.toString();
225 
226         return (oldArgs.equals(newArgs));
227     }
228 
229     /***
230      * For each method in the baselineMethods list, find the "best match"
231      * in the currentMethods list, report the changes between this method
232      * pair, then remove both methods from the lists.
233      * <p>
234      * On return, at least one of the method lists will be empty.
235      */
236     private void filterChangedMethods(
237             String methodName,
238             JavaType compatBaseline,
239             List baselineMethods,
240             JavaType currentVersion,
241             List currentMethods)
242     {
243         // ok, we now have to deal with the tricky cases, where it is not
244         // immediately obvious which old methods correspond to which new
245         // methods.
246         //
247         // Here we build a similarity table, i.e. for new method i and old
248         // method j we have number that charaterizes how similar the method
249         // signatures are (0 means equal, higher number means more different)
250 
251         while (!baselineMethods.isEmpty() && !currentMethods.isEmpty())
252         {
253             int[][] similarityTable = buildSimilarityTable(baselineMethods, currentMethods);
254 
255             int min = Integer.MAX_VALUE;
256             int iMin = baselineMethods.size();
257             int jMin = currentMethods.size();
258             for (int i = 0; i < baselineMethods.size(); i++)
259             {
260                 for (int j = 0; j < currentMethods.size(); j++)
261                 {
262                     final int tableEntry = similarityTable[i][j];
263                     if (tableEntry < min)
264                     {
265                         min = tableEntry;
266                         iMin = i;
267                         jMin = j;
268                     }
269                 }
270             }
271             Method iMethod = (Method) baselineMethods.remove(iMin);
272             Method jMethod = (Method) currentMethods.remove(jMin);
273             check(compatBaseline, iMethod, jMethod);
274         }
275     }
276 
277     private int[][] buildSimilarityTable(List baselineMethods, List currentMethods)
278     {
279         int[][] similarityTable = new int[baselineMethods.size()][currentMethods.size()];
280         for (int i = 0; i < baselineMethods.size(); i++)
281         {
282             for (int j = 0; j < currentMethods.size(); j++)
283             {
284                 final Method iMethod = (Method) baselineMethods.get(i);
285                 final Method jMethod = (Method) currentMethods.get(j);
286                 similarityTable[i][j] = distance(iMethod, jMethod);
287             }
288         }
289         return similarityTable;
290     }
291 
292     private int distance(Method m1, Method m2)
293     {
294         final JavaType[] m1Args = m1.getArgumentTypes();
295         final JavaType[] m2Args = m2.getArgumentTypes();
296 
297         if (m1Args.length != m2Args.length)
298         {
299             return 1000 * Math.abs(m1Args.length - m2Args.length);
300         }
301 
302         int retVal = 0;
303         for (int i = 0; i < m1Args.length; i++)
304         {
305             if (!m1Args[i].toString().equals(m2Args[i].toString()))
306             {
307                 retVal += 1;
308             }
309         }
310         return retVal;
311     }
312 
313     /***
314      * Searches the class hierarchy for a method that has a certain signature.
315      * @param methodSignature the sig we're looking for
316      * @param clazz class where search starts
317      * @return class name of a superclass of clazz, might be null
318      */
319     private String findSuperClassWithSignature(String methodSignature, JavaType clazz)
320     {
321         final JavaType[] superClasses = clazz.getSuperClasses();
322         for (int i = 0; i < superClasses.length; i++)
323         {
324             JavaType superClass = superClasses[i];
325             final Method[] superMethods = superClass.getMethods();
326             for (int j = 0; j < superMethods.length; j++)
327             {
328                 Method superMethod = superMethods[j];
329                 final String superMethodSignature = getMethodId(superClass, superMethod);
330                 if (methodSignature.equals(superMethodSignature))
331                 {
332                     return superClass.getName();
333                 }
334             }
335 
336         }
337         return null;
338     }
339 
340     /***
341      * Searches the class hierarchy for a method that has a certtain signature.
342      * @param methodSignature the sig we're looking for
343      * @param clazz class where search starts
344      * @return class name of a superinterface of clazz, might be null
345      */
346     private String findSuperInterfaceWithSignature(String methodSignature, JavaType clazz)
347     {
348         final JavaType[] superClasses = clazz.getAllInterfaces();
349         for (int i = 0; i < superClasses.length; i++)
350         {
351             JavaType superClass = superClasses[i];
352             final Method[] superMethods = superClass.getMethods();
353             for (int j = 0; j < superMethods.length; j++)
354             {
355                 Method superMethod = superMethods[j];
356                 final String superMethodSignature = getMethodId(superClass, superMethod);
357                 if (methodSignature.equals(superMethodSignature))
358                 {
359                     return superClass.getName();
360                 }
361             }
362 
363         }
364         return null;
365     }
366 
367     /***
368      * Given a list of methods, report each one as being removed.
369      */
370     private void reportMethodsRemoved(
371             JavaType baselineClass,
372             List baselineMethods,
373             JavaType currentClass)
374     {
375         for (Iterator i = baselineMethods.iterator(); i.hasNext();)
376         {
377             Method method = (Method) i.next();
378             reportMethodRemoved(baselineClass, method, currentClass);
379         }
380     }
381 
382     /***
383      * Report that a method has been removed from a class.
384      * @param oldClass the class where the method was available
385      * @param oldMethod the method that has been removed
386      * @param currentClass the superclass where the method is now available, might be null
387      */
388     private void reportMethodRemoved(
389             JavaType oldClass,
390             Method oldMethod,
391             JavaType currentClass)
392     {
393         if (!scopeSelector.isSelected(oldMethod))
394         {
395             return;
396         }
397 
398         String signature = getMethodId(oldClass, oldMethod);
399 
400         String oldBaseClassForMethod = findSuperClassWithSignature(signature, oldClass);
401         String oldInterfaceForMethod = findSuperInterfaceWithSignature(signature, oldClass);
402 
403         String newBaseClassForMethod = findSuperClassWithSignature(signature, currentClass);
404         String newInterfaceForMethod = findSuperInterfaceWithSignature(signature, currentClass);
405 
406         boolean oldInheritedMethod = (oldBaseClassForMethod != null) || (oldInterfaceForMethod != null);
407         boolean newInheritedMethod = (newBaseClassForMethod != null) || (newInterfaceForMethod != null);
408 
409         if (oldInheritedMethod && newInheritedMethod)
410         {
411             // Previously, this method overrode an inherited definition.
412             // The current version of the class doesn't have this
413             // method, but a parent class or interface still does, so this
414             // does not cause an incompatibility.
415             fireDiff(MSG_METHOD_OVERRIDE_REMOVED,
416                     Severity.INFO,
417                     oldClass, oldMethod, null);
418         }
419         else if (oldInheritedMethod)
420         {
421             // Previously, this method override an inherited definition.
422             // It isn't present in the current class, though, and neither is
423             // it present in the new class' ancestors. Best to just
424             // report it as removed...
425             fireDiff(MSG_METHOD_REMOVED,
426                     getSeverity(oldClass, oldMethod, Severity.ERROR),
427                     oldClass, oldMethod, null);
428         }
429         else if (newBaseClassForMethod != null)
430         {
431             // Previously, this method didn't override anything. The current
432             // version of this class doesn't have this method any more,
433             // but an ancestor class now *does*. This is an instance
434             // of the pull-up refactoring pattern, where a method is moved
435             // to an ancestor class.
436             fireDiff(MSG_METHOD_NOW_IN_SUPERCLASS,
437                     Severity.INFO, oldClass, oldMethod,
438                     new String[] {newBaseClassForMethod});
439         }
440         else if (newInterfaceForMethod != null)
441         {
442             // Previously, this method didn't override anything. The current
443             // version of this class doesn't have this method any more,
444             // but one of the implemented interfaces now *does*. This is an
445             // instance of the pull-up refactoring pattern, where a method is
446             // moved to an interface.
447             fireDiff(MSG_METHOD_NOW_IN_INTERFACE,
448                     Severity.INFO, oldClass, oldMethod,
449                     new String[] {newInterfaceForMethod});
450         }
451         else
452         {
453             // This method wasn't anything special in the old class, and
454             // it isn't present in the new class either directly or via
455             // inheritance.
456             fireDiff(MSG_METHOD_REMOVED,
457                     getSeverity(oldClass, oldMethod, Severity.ERROR),
458                     oldClass, oldMethod, null);
459         }
460     }
461 
462     /***
463      * Given a list of methods, report each one as being added.
464      */
465     private void reportMethodsAdded(
466             JavaType currentClass,
467             List currentMethods)
468     {
469         for (Iterator i = currentMethods.iterator(); i.hasNext();)
470         {
471             Method method = (Method) i.next();
472             reportMethodAdded(currentClass, method);
473         }
474     }
475 
476     /***
477      * Report that a method has been added to a class.
478      */
479     private void reportMethodAdded(JavaType newClass, Method newMethod)
480     {
481         if (!scopeSelector.isSelected(newMethod))
482         {
483             return;
484         }
485 
486         if (newClass.isInterface())
487         {
488             // TODO: this is not an incompatibility if the new method
489             // actually already exists on a parent interface of the
490             // old interface. In that case, any class implementing the
491             // old version of this interface must already have an
492             // implementation of this method. See bugtracker #961217
493             fireDiff(MSG_METHOD_ADDED_TO_INTERFACE,
494                     getSeverity(newClass, newMethod, Severity.ERROR),
495                     newClass, newMethod, null);
496         }
497         else if (newMethod.isAbstract())
498         {
499             // TODO: this is not an incompatibility if the new method
500             // actually already exists on a parent interface of the
501             // old interface and was abstract. In that case, any class
502             // extending the old version of this class must already
503             // have an implementation of this method.
504             //
505             // Note that abstract methods can never be package or private
506             // scope, so we don't need to use the getSeverity method.
507             fireDiff(MSG_ABSTRACT_METHOD_ADDED,
508                     Severity.ERROR, newClass, newMethod, null);
509         }
510         else
511         {
512             // TODO:
513             //. (a) check whether this method exists on a parent of the
514             //  new class. If so, indicate that this new method is overriding
515             //  some inherited method.
516             //  (b) if not a, then check whether this method exists on a parent
517             //  of the old class. If so, then report that the method has
518             //  been moved from the parent to the child class. This is
519             //  potentially useful info for the user.
520             //
521             // See bugtracker #959225
522             fireDiff(MSG_METHOD_ADDED,
523                     Severity.INFO, newClass, newMethod, null);
524         }
525     }
526 
527     /***
528      * Builds a map from a method name to a List of methods.
529      */
530     private Map buildNameToMethodMap(JavaType clazz)
531     {
532         Method[] methods = clazz.getMethods();
533         Map retVal = new HashMap();
534         for (int i = 0; i < methods.length; i++)
535         {
536             Method method = methods[i];
537 
538             final String name = method.getName();
539             List set = (List) retVal.get(name);
540             if (set == null)
541             {
542                 set = new ArrayList();
543                 retVal.put(name, set);
544             }
545             set.add(method);
546         }
547         return retVal;
548     }
549 
550     private void check(JavaType compatBaseline, Method baselineMethod, Method currentMethod)
551     {
552         if (!scopeSelector.isSelected(baselineMethod) && !scopeSelector.isSelected(currentMethod))
553         {
554             return;
555         }
556 
557         checkParameterTypes(compatBaseline, baselineMethod, currentMethod);
558         checkReturnType(compatBaseline, baselineMethod, currentMethod);
559         checkDeclaredExceptions(compatBaseline, baselineMethod, currentMethod);
560         checkDeprecated(compatBaseline, baselineMethod, currentMethod);
561         checkVisibility(compatBaseline, baselineMethod, currentMethod);
562         checkFinal(compatBaseline, baselineMethod, currentMethod);
563     }
564 
565     private void checkParameterTypes(JavaType compatBaseline, Method baselineMethod, Method currentMethod)
566     {
567         JavaType[] bArgs = baselineMethod.getArgumentTypes();
568         JavaType[] cArgs = currentMethod.getArgumentTypes();
569 
570         if (bArgs.length != cArgs.length)
571         {
572             fireDiff(MSG_METHOD_ARGCOUNT_CHANGED,
573                     getSeverity(compatBaseline, baselineMethod, Severity.ERROR),
574                     compatBaseline, baselineMethod, null);
575             return;
576         }
577 
578         //System.out.println("baselineMethod = " + getMethodId(compatBaseline, baselineMethod));
579         for (int i = 0; i < bArgs.length; i++)
580         {
581             JavaType bArg = bArgs[i];
582             JavaType cArg = cArgs[i];
583 
584             if (bArg.getName().equals(cArg.getName()))
585             {
586                 continue;
587             }
588 
589             // TODO: Check assignability...
590             String[] args =
591             {
592                 "" + (i + 1),
593                 cArg.toString()
594             };
595             fireDiff(MSG_METHOD_PARAMTYPE_CHANGED,
596                     getSeverity(compatBaseline, baselineMethod, Severity.ERROR),
597                     compatBaseline, baselineMethod, args);
598         }
599     }
600 
601     private void checkReturnType(JavaType compatBaseline, Method baselineMethod, Method currentMethod)
602     {
603         JavaType bReturnType = baselineMethod.getReturnType();
604         JavaType cReturnType = currentMethod.getReturnType();
605 
606         // TODO: Check assignability. If the new return type is
607         // assignable to the old type, then the code is source-code
608         // compatible even when binary-incompatible.
609         if (!bReturnType.toString().equals(cReturnType.toString()))
610         {
611             fireDiff(MSG_METHOD_RETURNTYPE_CHANGED,
612                     getSeverity(compatBaseline, baselineMethod, Severity.ERROR),
613                     compatBaseline, baselineMethod,
614                     new String[] {cReturnType.toString()});
615         }
616     }
617 
618     private void checkDeclaredExceptions(
619             JavaType compatBaseline,
620             Method baselineMethod, Method currentMethod)
621     {
622         // TODO
623     }
624 
625     private void checkDeprecated(
626             JavaType compatBaseline,
627             Method baselineMethod, Method currentMethod)
628     {
629         boolean bIsDeprecated = baselineMethod.isDeprecated();
630         boolean cIsDeprecated = currentMethod.isDeprecated();
631 
632         if (bIsDeprecated && !cIsDeprecated)
633         {
634             fireDiff(MSG_METHOD_UNDEPRECATED,
635                     Severity.INFO, compatBaseline, baselineMethod, null);
636         }
637         else if (!bIsDeprecated && cIsDeprecated)
638         {
639             fireDiff(MSG_METHOD_DEPRECATED,
640                     Severity.INFO, compatBaseline, baselineMethod, null);
641         }
642     }
643 
644     /***
645      * Report changes in the declared accessibility of a method
646      * (public/protected/etc).
647      */
648     private void checkVisibility(JavaType compatBaseline, Method baselineMethod, Method currentMethod)
649     {
650         Scope bScope = baselineMethod.getEffectiveScope();
651         Scope cScope = currentMethod.getEffectiveScope();
652 
653         if (cScope.isLessVisibleThan(bScope))
654         {
655             String[] args = {bScope.getDesc(), cScope.getDesc()};
656             fireDiff(MSG_METHOD_LESS_ACCESSIBLE,
657                     getSeverity(compatBaseline, baselineMethod, Severity.ERROR),
658                     compatBaseline, baselineMethod, args);
659         }
660         else if (cScope.isMoreVisibleThan(bScope))
661         {
662             String[] args = {bScope.getDesc(), cScope.getDesc()};
663             fireDiff(MSG_METHOD_MORE_ACCESSIBLE,
664                     Severity.INFO, compatBaseline, baselineMethod, args);
665         }
666     }
667 
668     private void checkFinal(
669             JavaType compatBaseline,
670             Method baselineMethod, Method currentMethod)
671     {
672         boolean bIsFinal = baselineMethod.isFinal();
673         boolean cIsFinal = currentMethod.isFinal();
674 
675         if (bIsFinal && !cIsFinal)
676         {
677             fireDiff(MSG_METHOD_NOW_NONFINAL,
678                     Severity.INFO, compatBaseline, baselineMethod, null);
679         }
680         else if (!bIsFinal && cIsFinal)
681         {
682             fireDiff(MSG_METHOD_NOW_FINAL,
683                     Severity.ERROR, compatBaseline, baselineMethod, null);
684         }
685     }
686 
687     /***
688      * Creates a human readable String that is similar to the method signature
689      * and identifies the method within a class.
690      * @param clazz the container of the method
691      * @param method the method to identify.
692      * @return a human readable id, for example "public void print(java.lang.String)"
693      */
694     private String getMethodId(JavaType clazz, Method method)
695     {
696         StringBuffer buf = new StringBuffer();
697 
698         final String scopeDecl = method.getDeclaredScope().getDecl();
699         if (scopeDecl.length() > 0)
700         {
701             buf.append(scopeDecl);
702             buf.append(" ");
703         }
704 
705         String name = method.getName();
706         if ("<init>".equals(name))
707         {
708             final String className = clazz.getName();
709             int idx = className.lastIndexOf('.');
710             name = className.substring(idx + 1);
711         }
712         else
713         {
714             buf.append(method.getReturnType());
715             buf.append(' ');
716         }
717         buf.append(name);
718         buf.append('(');
719         appendHumanReadableArgTypeList(method, buf);
720         buf.append(')');
721         return buf.toString();
722     }
723 
724     private void appendHumanReadableArgTypeList(Method method, StringBuffer buf)
725     {
726         JavaType[] argTypes = method.getArgumentTypes();
727         String argSeparator = "";
728         for (int i = 0; i < argTypes.length; i++)
729         {
730             buf.append(argSeparator);
731             buf.append(argTypes[i].getName());
732             argSeparator = ", ";
733         }
734     }
735 
736     private void fireDiff(Message msg, Severity severity, JavaType clazz, Method method, String[] args)
737     {
738         final String className = clazz.getName();
739         final ApiDifference diff =
740             new ApiDifference(
741                 msg, severity, className, getMethodId(clazz, method), null, args);
742         getApiDiffDispatcher().fireDiff(diff);
743     }
744 
745 }