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