1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 }