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;
21  
22  
23  /***
24   * Describes an API change.
25   *
26   * @author Lars
27   */
28  public final class ApiDifference
29  {
30      private static final int HASHCODE_MAGIC = 29;
31  
32      /***
33       * Object representing the message text to be output (or null if
34       * the constructor which takes a message string directly is used).
35       */
36      private Message message = null;
37  
38      /*** human readable change report. */
39      private String report;
40  
41      /***
42       * severity of the change in terms of binary compatibility,
43       * as determined by clirr.
44       */
45      private Severity binaryCompatibilitySeverity;
46  
47      /***
48       * severity of the change in terms of source compatibility,
49       * as determined by clirr.
50       */
51      private Severity sourceCompatibilitySeverity;
52  
53      /*** The fully qualified class name that is affected by the API change. */
54      private String affectedClass;
55  
56      /***
57       * The method that is affected, if any.
58       * <p/>
59       * The content is the method name plus the fully qualified
60       * parameter types separated by comma and space and enclosed in
61       * brackets, e.g. "doStuff(java.lang.String, int)".
62       * <p/>
63       * This value is <code>null</code> if no single method is
64       * affected, i.e. if the
65       * api change affects a field or is global
66       * (like "class is now final").
67       */
68      private String affectedMethod;
69  
70      /***
71       * The field that is affected, if any.
72       * <p/>
73       * The content is the field name, e.g. "someValue".
74       * Type information for the field is not available.
75       * <p/>
76       * This value is <code>null</code> if no single field is
77       * affected, i.e. if the
78       * api change affects a method or is global
79       * (like "class is now final").
80       */
81      private String affectedField;
82  
83      /***
84       * The set of additional parameters that are available for use
85       * when building the actual message description. These vary depending
86       * upon the actual difference being reported.
87       */
88      private String[] extraInfo;
89  
90      /***
91       * Invokes the two-severity-level version of this constructor.
92       */
93      public ApiDifference(
94          Message message,
95          Severity severity,
96          String clazz,
97          String method,
98          String field,
99          String[] args)
100     {
101         this(message, severity, severity, clazz, method, field, args);
102     }
103 
104     /***
105      * Create a new API difference representation.
106      *
107      * @param message is the key of a human readable string describing the
108      * change that was made.
109      *
110      * @param binarySeverity the severity in terms of binary compatibility,
111      * must be non-null.
112      *
113      * @param sourceSeverity the severity in terms of source code compatibility,
114      * must be non-null.
115      *
116      * @param clazz is the fully-qualified name of the class in which the
117      * change occurred, must be non-null.
118      *
119      * @param method the method signature of the method that changed,
120      * <code>null</code> if no method was affected.
121      *
122      * @param field the field name where the change occured, <code>null</code>
123      * if no field was affected.
124      *
125      * @param args is a set of additional change-specific strings which are
126      * made available for the message description string to reference via
127      * the standard {n} syntax.
128      */
129     public ApiDifference(
130         Message message,
131         Severity binarySeverity, Severity sourceSeverity,
132         String clazz, String method, String field,
133         String[] args)
134     {
135         checkNonNull(message);
136         checkNonNull(binarySeverity);
137         checkNonNull(sourceSeverity);
138         checkNonNull(clazz);
139 
140         this.message = message;
141         this.binaryCompatibilitySeverity = binarySeverity;
142         this.sourceCompatibilitySeverity = sourceSeverity;
143         this.affectedClass = clazz;
144         this.affectedField = field;
145         this.affectedMethod = method;
146         this.extraInfo = args;
147     }
148 
149     /***
150      * Trivial utility method to verify that a specific object is non-null.
151      */
152     private void checkNonNull(Object o)
153     {
154         if (o == null)
155         {
156             throw new IllegalArgumentException();
157         }
158     }
159 
160     /***
161      * Return the message object (if any) associated with this difference.
162      * <p>
163      * Checks which support the "new" message API will provide ApiDifference
164      * objects with non-null message objects.
165      */
166     public Message getMessage()
167     {
168         return message;
169     }
170 
171     /***
172      * The Severity of the API difference in terms of binary compatibility.
173      * ERROR means that clients will definitely break, WARNING means that
174      * clients may break, depending on how they use the library.
175      * See the eclipse paper for further explanation.
176      *
177      * @return the severity of the API difference in terms of binary compatibility.
178      */
179     public Severity getBinaryCompatibilitySeverity()
180     {
181         return binaryCompatibilitySeverity;
182     }
183 
184     /***
185      * The Severity of the API difference in terms of source compatibility.
186      * Sometimes this is different than {@link #getBinaryCompatibilitySeverity
187      * binary compatibility severity}, for example adding a checked exception
188      * to a method signature is binary compatible but not source compatible.
189      * ERROR means that clients will definitely break, WARNING means that
190      * clients may break, depending on how they use the library.
191      * See the eclipse paper for further explanation.
192      *
193      * @return the severity of the API difference in terms of source code
194      * compatibility.
195      */
196     public Severity getSourceCompatibilitySeverity()
197     {
198         return sourceCompatibilitySeverity;
199     }
200 
201     /***
202      * Return the maximum of the binary and source compatibility severities.
203      */
204     public Severity getMaximumSeverity()
205     {
206         final Severity src = getSourceCompatibilitySeverity();
207         final Severity bin = getBinaryCompatibilitySeverity();
208         return src.compareTo(bin) < 0 ? bin : src;
209     }
210 
211     /***
212      * Human readable api change description.
213      *
214      * @return a human readable description of this API difference.
215      */
216     public String getReport(MessageTranslator translator)
217     {
218         if (report != null)
219         {
220             return report;
221         }
222 
223         String desc = translator.getDesc(message);
224         int nArgs = 0;
225         if (extraInfo != null)
226         {
227             nArgs = extraInfo.length;
228         }
229         String[] strings = new String[nArgs + 3];
230         strings[0] = affectedClass;
231         strings[1] = affectedMethod;
232         strings[2] = affectedField;
233         for (int i = 0; i < nArgs; ++i)
234         {
235             strings[i + 3] = extraInfo[i];
236         }
237 
238         return java.text.MessageFormat.format(desc, strings);
239     }
240 
241     /***
242      * The fully qualified class name of the class that has changed.
243      * @return fully qualified class name of the class that has changed.
244      */
245     public String getAffectedClass()
246     {
247         return affectedClass;
248     }
249 
250     /***
251      * Method signature of the method that has changed, if any.
252      * @return method signature or <code>null</code> if no method is affected.
253      */
254     public String getAffectedMethod()
255     {
256         return affectedMethod;
257     }
258 
259     /***
260      * Field name of the field that has changed, if any.
261      * @return field name or <code>null</code> if no field is affected.
262      */
263     public String getAffectedField()
264     {
265         return affectedField;
266     }
267 
268     /***
269      * {@inheritDoc}
270      */
271     public String toString()
272     {
273         StringBuffer buf = new StringBuffer();
274         buf.append(message.getId());
275         appendCommonData(buf);
276         return buf.toString();
277     }
278 
279     /***
280      * Get a human-readable description of this object. Intended for use by
281      * the unit tests.
282      */
283     public String toString(MessageTranslator translator)
284     {
285         StringBuffer buf = new StringBuffer();
286         buf.append(getReport(translator));
287         appendCommonData(buf);
288         return buf.toString();
289     }
290 
291     /***
292      * Build a string containing a string representation of most of the
293      * fields in this object, but not the message-id or the string
294      * translation thereof.
295      */
296     private void appendCommonData(StringBuffer buf)
297     {
298         buf.append(" (");
299         buf.append(binaryCompatibilitySeverity);
300 
301         if (sourceCompatibilitySeverity != binaryCompatibilitySeverity)
302         {
303             buf.append(",");
304             buf.append(sourceCompatibilitySeverity);
305         }
306 
307         buf.append(") - ");
308         buf.append(affectedClass);
309         buf.append("[");
310         buf.append(affectedField);
311         buf.append("/");
312         buf.append(affectedMethod);
313         buf.append("]");
314     }
315 }