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 java.util.Comparator;
23  
24  import net.sf.clirr.core.internal.ClassChangeCheck;
25  import net.sf.clirr.core.internal.AbstractDiffReporter;
26  import net.sf.clirr.core.internal.ApiDiffDispatcher;
27  import net.sf.clirr.core.internal.CoIterator;
28  import net.sf.clirr.core.internal.NameComparator;
29  import net.sf.clirr.core.spi.Field;
30  import net.sf.clirr.core.spi.JavaType;
31  import net.sf.clirr.core.spi.Scope;
32  import net.sf.clirr.core.ApiDifference;
33  import net.sf.clirr.core.Severity;
34  import net.sf.clirr.core.ScopeSelector;
35  import net.sf.clirr.core.Message;
36  
37  /***
38   * Checks the fields of a class.
39   *
40   * @author lkuehne
41   */
42  public class FieldSetCheck
43      extends AbstractDiffReporter
44      implements ClassChangeCheck
45  {
46      private static final Message MSG_FIELD_ADDED = new Message(6000);
47      private static final Message MSG_FIELD_REMOVED = new Message(6001);
48      private static final Message MSG_FIELD_NOT_CONSTANT = new Message(6002);
49      private static final Message MSG_FIELD_CONSTANT_CHANGED = new Message(6003);
50      private static final Message MSG_FIELD_TYPE_CHANGED = new Message(6004);
51      private static final Message MSG_FIELD_NOW_NON_FINAL = new Message(6005);
52      private static final Message MSG_FIELD_NOW_FINAL = new Message(6006);
53      private static final Message MSG_FIELD_NOW_NON_STATIC = new Message(6007);
54      private static final Message MSG_FIELD_NOW_STATIC = new Message(6008);
55      private static final Message MSG_FIELD_MORE_ACCESSIBLE = new Message(6009);
56      private static final Message MSG_FIELD_LESS_ACCESSIBLE = new Message(6010);
57      private static final Message MSG_CONSTANT_FIELD_REMOVED = new Message(6011);
58  
59      private static final Comparator COMPARATOR = new NameComparator();
60      private ScopeSelector scopeSelector;
61  
62      public FieldSetCheck(ApiDiffDispatcher dispatcher, ScopeSelector scopeSelector)
63      {
64          super(dispatcher);
65          this.scopeSelector = scopeSelector;
66      }
67  
68      public final boolean check(JavaType baselineClass, JavaType currentClass)
69      {
70          final Field[] baselineFields = baselineClass.getFields();
71          final Field[] currentFields = currentClass.getFields();
72  
73          CoIterator iter = new CoIterator(
74              COMPARATOR, baselineFields, currentFields);
75  
76          while (iter.hasNext())
77          {
78              iter.next();
79  
80              Field bField = (Field) iter.getLeft();
81              Field cField = (Field) iter.getRight();
82  
83              if (bField == null)
84              {
85                  if (scopeSelector.isSelected(cField))
86                  {
87                      String scope = cField.getDeclaredScope().getDesc();
88                      fireDiff(MSG_FIELD_ADDED,
89                          Severity.INFO, currentClass, cField,
90                          new String[]{scope});
91                  }
92              }
93              else if (cField == null)
94              {
95                  if (scopeSelector.isSelected(bField))
96                  {
97                      if ((bField.getConstantValue() != null) && bField.isFinal())
98                      {
99                          // Fields which are compile-time constants will have
100                         // been inlined into callers; even though the field
101                         // has been deleted, the caller will continue to use
102                         // the old value. The result is therefore not
103                         // technically a binary incompatibility, though it is
104                         // a source-code incompatibility.
105                         // See bugtracker #961222
106                         fireDiff(MSG_CONSTANT_FIELD_REMOVED,
107                             getSeverity(baselineClass, bField, Severity.WARNING),
108                             getSeverity(baselineClass, bField, Severity.ERROR),
109                             baselineClass, bField, null);
110                     }
111                     else
112                     {
113                         fireDiff(MSG_FIELD_REMOVED,
114                             getSeverity(baselineClass, bField, Severity.ERROR),
115                             baselineClass, bField, null);
116                     }
117                 }
118             }
119             else if (scopeSelector.isSelected(bField) || scopeSelector.isSelected(cField))
120             {
121                 checkForModifierChange(bField, cField, currentClass);
122                 checkForVisibilityChange(bField, cField, currentClass);
123                 checkForTypeChange(bField, cField, currentClass);
124                 checkForConstantValueChange(bField, cField, currentClass);
125             }
126         }
127 
128         return true;
129     }
130 
131     private void checkForConstantValueChange(Field bField, Field cField, JavaType currentClass)
132     {
133         if (!(bField.isStatic() && bField.isFinal() && cField.isStatic() && cField.isFinal()))
134         {
135             return;
136         }
137 
138         final Object bVal = bField.getConstantValue();
139 
140         if (bVal != null)
141         {
142             final String bValRep = bVal.toString();
143             final Object cVal = cField.getConstantValue();
144             if (cVal == null)
145             {
146                 // TODO: also check whether old field is final. If it's not
147                 // final, then external code cannot have inlined the
148                 // constant, and therefore we can issue an INFO instead
149                 // of a warning. Actually, may be better to introduce a
150                 // different message code rather than issue this code with
151                 // two different severity levels..
152                 fireDiff(MSG_FIELD_NOT_CONSTANT,
153                         getSeverity(currentClass, bField, Severity.WARNING),
154                         currentClass, cField, null);
155                 return;
156             }
157 
158             final String cValRep = String.valueOf(cVal);
159             if (!bValRep.equals(cValRep))
160             {
161                 // TODO: print out old and new value
162                 // How can that be done with BCEL, esp. for boolean values?
163                 //
164                 // TODO: also check whether field is final (see above).
165                 fireDiff(MSG_FIELD_CONSTANT_CHANGED,
166                         getSeverity(currentClass, bField, Severity.WARNING),
167                         currentClass, cField, null);
168             }
169         }
170     }
171 
172     private void checkForTypeChange(Field bField, Field cField, JavaType currentClass)
173     {
174         final String bSig = bField.getType().toString();
175         final String cSig = cField.getType().toString();
176         if (!bSig.equals(cSig))
177         {
178             fireDiff(MSG_FIELD_TYPE_CHANGED,
179                     getSeverity(currentClass, bField, Severity.ERROR),
180                     currentClass, bField,
181                     new String[] {bSig, cSig});
182         }
183     }
184 
185     private void checkForModifierChange(Field bField, Field cField, JavaType clazz)
186     {
187         if (bField.isFinal() && !cField.isFinal())
188         {
189             fireDiff(MSG_FIELD_NOW_NON_FINAL,
190                 Severity.INFO, clazz, cField, null);
191         }
192 
193         if (!bField.isFinal() && cField.isFinal())
194         {
195             fireDiff(MSG_FIELD_NOW_FINAL, Severity.ERROR, clazz, cField, null);
196         }
197 
198         if (bField.isStatic() && !cField.isStatic())
199         {
200             fireDiff(MSG_FIELD_NOW_NON_STATIC,
201                 getSeverity(clazz, bField, Severity.ERROR),
202                 clazz, cField, null);
203         }
204 
205         if (!bField.isStatic() && cField.isStatic())
206         {
207             fireDiff(MSG_FIELD_NOW_STATIC,
208                 getSeverity(clazz, bField, Severity.ERROR),
209                 clazz, cField, null);
210         }
211 
212         // JLS, 13.4.10: Adding or deleting a transient modifier of a field
213         // does not break compatibility with pre-existing binaries
214 
215         // TODO: What about volatile?
216     }
217 
218     private void checkForVisibilityChange(Field bField, Field cField, JavaType clazz)
219     {
220         Scope bScope = bField.getEffectiveScope();
221         Scope cScope = cField.getEffectiveScope();
222 
223         if (cScope.isMoreVisibleThan(bScope))
224         {
225             fireDiff(MSG_FIELD_MORE_ACCESSIBLE,
226                 Severity.INFO, clazz, cField,
227                 new String[] {bScope.getDesc(), cScope.getDesc()});
228         }
229         else if (cScope.isLessVisibleThan(bScope))
230         {
231             fireDiff(MSG_FIELD_LESS_ACCESSIBLE,
232                 getSeverity(clazz, bField, Severity.ERROR),
233                 clazz, cField,
234                 new String[] {bScope.getDesc(), cScope.getDesc()});
235         }
236     }
237 
238     private void fireDiff(
239         Message msg,
240         Severity severity,
241         JavaType clazz,
242         Field field,
243         String[] args)
244     {
245         fireDiff(msg, severity, severity, clazz, field, args);
246     }
247 
248     private void fireDiff(
249         Message msg,
250         Severity binarySeverity,
251         Severity sourceSeverity,
252         JavaType clazz,
253         Field field,
254         String[] args)
255     {
256         final String className = clazz.getName();
257         final ApiDifference diff =
258             new ApiDifference(
259                 msg,
260                 binarySeverity, sourceSeverity,
261                 className, null, field.getName(), args);
262         getApiDiffDispatcher().fireDiff(diff);
263     }
264 }