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.Severity;
023: import net.sf.clirr.core.Message;
024: import net.sf.clirr.core.internal.AbstractDiffReporter;
025: import net.sf.clirr.core.internal.ApiDiffDispatcher;
026: import net.sf.clirr.core.internal.ClassChangeCheck;
027: import net.sf.clirr.core.spi.JavaType;
028: import net.sf.clirr.core.spi.Method;
029: import net.sf.clirr.core.spi.Scope;
030:
031: /**
032: * Detects changes in class modifiers (abstract, final).
033: *
034: * @author lkuehne
035: */
036: public final class ClassModifierCheck extends AbstractDiffReporter
037: implements ClassChangeCheck {
038: private static final Message MSG_MODIFIER_UNABLE_TO_DETERMINE_CLASS_SCOPE = new Message(
039: 3000);
040: private static final Message MSG_MODIFIER_REMOVED_FINAL = new Message(
041: 3001);
042: private static final Message MSG_MODIFIER_ADDED_FINAL_TO_EFFECTIVE_FINAL = new Message(
043: 3002);
044: private static final Message MSG_MODIFIER_ADDED_FINAL = new Message(
045: 3003);
046: private static final Message MSG_MODIFIER_REMOVED_ABSTRACT = new Message(
047: 3004);
048: private static final Message MSG_MODIFIER_ADDED_ABSTRACT = new Message(
049: 3005);
050:
051: /**
052: * Create a new instance of this check.
053: * @param dispatcher the diff dispatcher that distributes the detected changes to the listeners.
054: */
055: public ClassModifierCheck(ApiDiffDispatcher dispatcher) {
056: super (dispatcher);
057: }
058:
059: /** {@inheritDoc} */
060: public boolean check(JavaType compatBaseLine,
061: JavaType currentVersion) {
062: final String className = compatBaseLine.getName();
063:
064: Scope currentScope = currentVersion.getEffectiveScope();
065: if (currentScope.isLessVisibleThan(Scope.PACKAGE)) {
066: // for private classes, we don't care if they are now final,
067: // or now abstract, or now an interface.
068: return true;
069: }
070:
071: final boolean currentIsFinal = currentVersion.isFinal();
072: final boolean compatIsFinal = compatBaseLine.isFinal();
073: final boolean currentIsAbstract = currentVersion.isAbstract();
074: final boolean compatIsAbstract = compatBaseLine.isAbstract();
075: final boolean currentIsInterface = currentVersion.isInterface();
076: final boolean compatIsInterface = compatBaseLine.isInterface();
077:
078: if (compatIsFinal && !currentIsFinal) {
079: log(MSG_MODIFIER_REMOVED_FINAL, Severity.INFO, className,
080: null, null, null);
081: } else if (!compatIsFinal && currentIsFinal) {
082: if (isEffectivelyFinal(compatBaseLine)) {
083: log(MSG_MODIFIER_ADDED_FINAL_TO_EFFECTIVE_FINAL,
084: Severity.INFO, className, null, null, null);
085: } else {
086: log(MSG_MODIFIER_ADDED_FINAL, getSeverity(
087: compatBaseLine, Severity.ERROR), className,
088: null, null, null);
089: }
090: }
091:
092: // interfaces are always abstract, don't report gender change here
093: if (compatIsAbstract && !currentIsAbstract
094: && !compatIsInterface) {
095: log(MSG_MODIFIER_REMOVED_ABSTRACT, Severity.INFO,
096: className, null, null, null);
097: } else if (!compatIsAbstract && currentIsAbstract
098: && !currentIsInterface) {
099: log(MSG_MODIFIER_ADDED_ABSTRACT, getSeverity(
100: compatBaseLine, Severity.ERROR), className, null,
101: null, null);
102: }
103:
104: return true;
105: }
106:
107: /**
108: * There are cases where nonfinal classes are effectively final
109: * because they do not have public or protected ctors. For such
110: * classes we should not emit errors when a final modifier is
111: * introduced.
112: */
113: private boolean isEffectivelyFinal(JavaType clazz) {
114: if (clazz.isFinal()) {
115: return true;
116: }
117:
118: // iterate over all constructors, and detect whether any are
119: // public or protected. If so, return false.
120: Method[] methods = clazz.getMethods();
121: for (int i = 0; i < methods.length; ++i) {
122: Method method = methods[i];
123: final String methodName = method.getName();
124: if (methodName.equals("<init>")) {
125: if (method.getEffectiveScope().isMoreVisibleThan(
126: Scope.PACKAGE)) {
127: return false;
128: }
129: }
130: }
131:
132: // no public or protected constructor found
133: return true;
134: }
135: }
|