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.Severity;
23  import net.sf.clirr.core.Message;
24  import net.sf.clirr.core.internal.AbstractDiffReporter;
25  import net.sf.clirr.core.internal.ApiDiffDispatcher;
26  import net.sf.clirr.core.internal.ClassChangeCheck;
27  import net.sf.clirr.core.spi.JavaType;
28  import net.sf.clirr.core.spi.Method;
29  import net.sf.clirr.core.spi.Scope;
30  
31  /***
32   * Detects changes in class modifiers (abstract, final).
33   *
34   * @author lkuehne
35   */
36  public final class ClassModifierCheck
37      extends AbstractDiffReporter
38      implements ClassChangeCheck
39  {
40      private static final Message MSG_MODIFIER_UNABLE_TO_DETERMINE_CLASS_SCOPE = new Message(3000);
41      private static final Message MSG_MODIFIER_REMOVED_FINAL = new Message(3001);
42      private static final Message MSG_MODIFIER_ADDED_FINAL_TO_EFFECTIVE_FINAL = new Message(3002);
43      private static final Message MSG_MODIFIER_ADDED_FINAL = new Message(3003);
44      private static final Message MSG_MODIFIER_REMOVED_ABSTRACT = new Message(3004);
45      private static final Message MSG_MODIFIER_ADDED_ABSTRACT = new Message(3005);
46  
47      /***
48       * Create a new instance of this check.
49       * @param dispatcher the diff dispatcher that distributes the detected changes to the listeners.
50       */
51      public ClassModifierCheck(ApiDiffDispatcher dispatcher)
52      {
53          super(dispatcher);
54      }
55  
56      /*** {@inheritDoc} */
57      public boolean check(JavaType compatBaseLine, JavaType currentVersion)
58      {
59          final String className = compatBaseLine.getName();
60  
61          Scope currentScope = currentVersion.getEffectiveScope();
62          if (currentScope.isLessVisibleThan(Scope.PACKAGE))
63          {
64              // for private classes, we don't care if they are now final,
65              // or now abstract, or now an interface.
66              return true;
67          }
68  
69          final boolean currentIsFinal = currentVersion.isFinal();
70          final boolean compatIsFinal = compatBaseLine.isFinal();
71          final boolean currentIsAbstract = currentVersion.isAbstract();
72          final boolean compatIsAbstract = compatBaseLine.isAbstract();
73          final boolean currentIsInterface = currentVersion.isInterface();
74          final boolean compatIsInterface = compatBaseLine.isInterface();
75  
76          if (compatIsFinal && !currentIsFinal)
77          {
78              log(MSG_MODIFIER_REMOVED_FINAL,
79                      Severity.INFO, className, null, null, null);
80          }
81          else if (!compatIsFinal && currentIsFinal)
82          {
83              if (isEffectivelyFinal(compatBaseLine))
84              {
85                  log(MSG_MODIFIER_ADDED_FINAL_TO_EFFECTIVE_FINAL,
86                          Severity.INFO, className, null, null, null);
87              }
88              else
89              {
90                  log(MSG_MODIFIER_ADDED_FINAL,
91                          getSeverity(compatBaseLine, Severity.ERROR),
92                          className, null, null, null);
93              }
94          }
95  
96          // interfaces are always abstract, don't report gender change here
97          if (compatIsAbstract && !currentIsAbstract && !compatIsInterface)
98          {
99              log(MSG_MODIFIER_REMOVED_ABSTRACT,
100                     Severity.INFO, className, null, null, null);
101         }
102         else if (!compatIsAbstract && currentIsAbstract && !currentIsInterface)
103         {
104             log(MSG_MODIFIER_ADDED_ABSTRACT,
105                     getSeverity(compatBaseLine, Severity.ERROR),
106                     className, null, null, null);
107         }
108 
109         return true;
110     }
111 
112     /***
113      * There are cases where nonfinal classes are effectively final
114      * because they do not have public or protected ctors. For such
115      * classes we should not emit errors when a final modifier is
116      * introduced.
117      */
118     private boolean isEffectivelyFinal(JavaType clazz)
119     {
120         if (clazz.isFinal())
121         {
122             return true;
123         }
124 
125         // iterate over all constructors, and detect whether any are
126         // public or protected. If so, return false.
127         Method[] methods = clazz.getMethods();
128         for (int i = 0; i < methods.length; ++i)
129         {
130             Method method = methods[i];
131             final String methodName = method.getName();
132             if (methodName.equals("<init>"))
133             {
134                 if (method.getEffectiveScope().isMoreVisibleThan(Scope.PACKAGE))
135                 {
136                     return false;
137                 }
138             }
139         }
140 
141         // no public or protected constructor found
142         return true;
143     }
144 }