1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package net.sf.jour.signature;
25
26 import java.util.Hashtable;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Vector;
31
32 import javassist.ClassPool;
33 import javassist.CtBehavior;
34 import javassist.CtClass;
35 import javassist.CtConstructor;
36 import javassist.CtField;
37 import javassist.CtMember;
38 import javassist.CtMethod;
39 import javassist.Modifier;
40 import javassist.NotFoundException;
41
42
43
44
45
46 public class APICompare extends APICompareChangeHelper {
47
48 private APIFilter filter;
49
50 private APICompareConfig config;
51
52 static ThreadLocal counters = new ThreadLocal();
53
54 private List changesMissing;
55
56 private List changesExtra;
57
58 private List changesChanges;
59
60 public APICompare() {
61 filter = new APIFilter(APIFilter.PROTECTED);
62 config = new APICompareConfig();
63 changesMissing = new Vector();
64 changesExtra = new Vector();
65 changesChanges = new Vector();
66 }
67
68 public static void compare(String classpath, String signatureFileName, APICompareConfig config,
69 boolean useSystemClassPath, String supportingJars) throws ChangeDetectedException {
70 List changes = listChanges(classpath, signatureFileName, config, useSystemClassPath, supportingJars);
71 if (changes.size() > 0) {
72 throw new ChangeDetectedException(changes);
73 }
74 }
75
76 public static List listChanges(String classpath, String signatureFileName, APICompareConfig config,
77 boolean useSystemClassPath, String supportingJars) {
78 counters.set(new Long(0));
79 SignatureImport im = new SignatureImport(useSystemClassPath, supportingJars);
80 im.load(signatureFileName);
81
82 ClassPool classPool = new ClassPool();
83 try {
84 classPool.appendPathList(classpath);
85 if (supportingJars != null) {
86 classPool.appendPathList(supportingJars);
87 }
88 } catch (NotFoundException e) {
89 throw new RuntimeException(e);
90 }
91 if (useSystemClassPath) {
92 classPool.appendSystemPath();
93 }
94
95 List classes = im.getClasses();
96
97
98
99 APICompare cmp = new APICompare();
100 if (config != null) {
101 cmp.config = config;
102 cmp.filter = new APIFilter(config.apiLevel);
103 }
104
105 int classesCount = 0;
106
107 for (Iterator iterator = classes.iterator(); iterator.hasNext();) {
108 CtClass refClass = (CtClass) iterator.next();
109 if (!cmp.filter.isAPIClass(refClass)) {
110 continue;
111 }
112 classesCount++;
113 counters.set(new Long(classesCount));
114 CtClass implClass = null;
115 try {
116 implClass = classPool.get(refClass.getName());
117 } catch (NotFoundException e) {
118 cmp.fail(refClass.getName() + " is missing");
119 cmp.addMissing(refClass);
120 }
121 if (implClass != null) {
122 try {
123 cmp.compareClasses(refClass, implClass);
124 } catch (NotFoundException e) {
125 throw new RuntimeException(e);
126 }
127 }
128 }
129 return cmp.changes;
130 }
131
132 public static long getClassesCount() {
133 return ((Long) counters.get()).longValue();
134 }
135
136 public static void compare(CtClass refClass, CtClass implClass) throws ChangeDetectedException {
137 APICompare cmp = new APICompare();
138 List diff;
139 try {
140 diff = cmp.compareClasses(refClass, implClass);
141 } catch (NotFoundException e) {
142 throw new RuntimeException(e);
143 }
144 if (diff.size() > 0) {
145 throw new ChangeDetectedException(diff);
146 }
147 }
148
149 private String className(CtClass klass) {
150 if (klass == null) {
151 return null;
152 } else {
153 return klass.getName();
154 }
155 }
156
157 private void addMissing(Object member) {
158 changesMissing.add(member);
159 }
160
161 private void addExtra(Object member) {
162 changesExtra.add(member);
163 }
164
165 private void addChanges(Object member) {
166 changesChanges.add(member);
167 }
168
169 public Iterator getChangesMissing() {
170 return changesMissing.iterator();
171 }
172
173 public Iterator getChangesExtra() {
174 return changesExtra.iterator();
175 }
176
177 public Iterator getChangesChanges() {
178 return changesChanges.iterator();
179 }
180
181 public List compareClasses(CtClass refClass, CtClass implClass) throws NotFoundException {
182
183 String className = refClass.getName();
184
185 boolean ch = false;
186
187 ch |= assertEquals(className + " isInterface", refClass.isInterface(), implClass.isInterface());
188 ch |= assertEquals(className + " getModifiers", refClass.getModifiers(), implClass.getModifiers());
189
190 CtClass[] refInterfaces = refClass.getInterfaces();
191 CtClass[] implInterfaces = implClass.getInterfaces();
192
193 ch = ch | assertEquals(className + " interfaces implemented", refInterfaces.length, implInterfaces.length);
194 ch = ch | compareInterfaces(refInterfaces, implInterfaces, className);
195
196 if (implClass.getSuperclass() == null) {
197
198 if (refClass.getSuperclass() != null) {
199 ch |= assertEquals(className + " Superclass ", "java.lang.Object", refClass.getSuperclass().getName());
200 }
201 } else if (refClass.getSuperclass() != null) {
202 ch |= assertEquals(className + " Superclass", refClass.getSuperclass().getName(), implClass.getSuperclass()
203 .getName());
204 } else {
205 ch |= assertNull(className + " Superclass " + className(implClass.getSuperclass()), implClass
206 .getSuperclass());
207 }
208 if (ch) {
209 addChanges(implClass);
210 }
211
212
213 CtConstructor[] refConstructors = refClass.getDeclaredConstructors();
214 CtConstructor[] implConstructors = implClass.getDeclaredConstructors();
215 compareConstructors(refConstructors, implConstructors, className);
216
217
218 CtMethod[] refMethods = refClass.getDeclaredMethods();
219 CtMethod[] implMethods = implClass.getDeclaredMethods();
220 compareMethods(refMethods, implMethods, className);
221
222
223 CtField[] refFields = refClass.getDeclaredFields();
224 CtField[] implFields = implClass.getDeclaredFields();
225 compareFields(refFields, implFields, className, refClass, implClass);
226
227 return changes;
228 }
229
230 private boolean isAPIMember(CtMember member) {
231 return filter.isAPIMember(member);
232 }
233
234 private boolean compareInterfaces(CtClass[] refInterfaces, CtClass[] implInterfacess, String className) {
235 List implNames = new Vector();
236 for (int i = 0; i < implInterfacess.length; i++) {
237 implNames.add(implInterfacess[i].getName());
238 }
239 boolean ch = false;
240 for (int i = 0; i < refInterfaces.length; i++) {
241 String interfaceName = refInterfaces[i].getName();
242 ch |= assertTrue(className + " should implement interface " + interfaceName, implNames
243 .contains(interfaceName));
244 }
245 return ch;
246 }
247
248 private Map buildNameMap(CtMember[] members, String className) throws NotFoundException {
249 Map namesMap = new Hashtable();
250 for (int i = 0; i < members.length; i++) {
251 if (!isAPIMember(members[i])) {
252
253 continue;
254 }
255 String name = getName4Map(members[i]);
256 if (namesMap.containsKey(name)) {
257 CtMember exists = (CtMember) namesMap.get(name);
258 if (exists.getDeclaringClass().getName().equals(className)) {
259 continue;
260 }
261
262
263
264 }
265 namesMap.put(name, members[i]);
266 }
267 return namesMap;
268 }
269
270 private int getModifiers(CtMember member) {
271 int mod = member.getModifiers();
272 if (Modifier.isNative(mod)) {
273 mod = mod - Modifier.NATIVE;
274 }
275 if (Modifier.isSynchronized(mod)) {
276 mod = mod - Modifier.SYNCHRONIZED;
277 }
278 if (Modifier.isStrict(mod)) {
279 mod = mod - Modifier.STRICT;
280 }
281 return mod;
282 }
283
284 private void compareConstructors(CtConstructor[] refConstructors, CtConstructor[] implConstructors, String className)
285 throws NotFoundException {
286 Map implNames = buildNameMap(implConstructors, className);
287 int compared = 0;
288 int expectedConstructors = 0;
289 for (int i = 0; i < refConstructors.length; i++) {
290 if (!isAPIMember(refConstructors[i])) {
291 implNames.remove(getName4Map(refConstructors[i]));
292 continue;
293 }
294 expectedConstructors++;
295 CtConstructor implConstructor = (CtConstructor) implNames.get(getName4Map(refConstructors[i]));
296 compareConstructor(refConstructors[i], implConstructor, className);
297 implNames.remove(getName4Map(refConstructors[i]));
298 if (implConstructor != null) {
299 compared++;
300 }
301 }
302 if (config.allowAPIextension) {
303 return;
304 }
305 StringBuffer extra = new StringBuffer();
306 for (Iterator i = implNames.keySet().iterator(); i.hasNext();) {
307 if (extra.length() > 0) {
308 extra.append(", ");
309 } else {
310 extra.append(", Extra constructor(s) [");
311 }
312 CtConstructor cx = (CtConstructor) implNames.get(i.next());
313 addExtra(cx);
314 extra.append(cx.getSignature());
315 }
316 if (extra.length() > 0) {
317 extra.append("]");
318 }
319 assertEquals(className + " number of Constructors" + extra.toString(), expectedConstructors, implNames.size()
320 + compared);
321 }
322
323 private boolean compareThrows(CtBehavior refMethod, CtBehavior implMethod, String className)
324 throws NotFoundException {
325 List refNames = new Vector();
326 CtClass[] refExceptions = refMethod.getExceptionTypes();
327 for (int i = 0; i < refExceptions.length; i++) {
328 refNames.add(refExceptions[i].getName());
329 }
330
331 boolean ch = false;
332 List implNames = new Vector();
333 CtClass[] implExceptions = implMethod.getExceptionTypes();
334 for (int i = 0; i < implExceptions.length; i++) {
335 implNames.add(implExceptions[i].getName());
336 String exceptionName = implExceptions[i].getName();
337 ch |= assertTrue(className + " " + refMethod.getName() + refMethod.getSignature() + " should not throw "
338 + exceptionName, refNames.contains(exceptionName));
339
340 }
341
342 if (!config.allowThrowsLess) {
343 for (int i = 0; i < refExceptions.length; i++) {
344 String exceptionName = refExceptions[i].getName();
345 ch |= assertTrue(className + " " + refMethod.getName() + refMethod.getSignature() + " should throw "
346 + exceptionName, implNames.contains(exceptionName));
347 }
348 }
349 return ch;
350 }
351
352 private void compareConstructor(CtConstructor refConstructor, CtConstructor implConstructor, String className)
353 throws NotFoundException {
354 String name = refConstructor.getSignature();
355 assertNotNull(className + " Constructor " + name + " is Missing", implConstructor);
356 if (implConstructor == null) {
357 addMissing(refConstructor);
358 return;
359 }
360 boolean ch = assertEquals(className + ". Constructor " + name + " modifiers", Modifier
361 .toString(getModifiers(refConstructor)), Modifier.toString(getModifiers(implConstructor)));
362 ch |= compareThrows(refConstructor, implConstructor, className);
363 if (ch) {
364 addChanges(implConstructor);
365 }
366 }
367
368 private boolean compareMember(CtMember refMember, CtMember implMember, String className, String signature) {
369 String name = refMember.getName();
370 assertNotNull(className + "." + name + signature + " is Missing", implMember);
371 if (implMember == null) {
372 addMissing(refMember);
373 return true;
374 }
375 return assertEquals(className + "." + name + " modifiers", Modifier.toString(getModifiers(refMember)), Modifier
376 .toString(getModifiers(implMember)));
377 }
378
379 private String getName4Map(CtMember member) throws NotFoundException {
380 StringBuffer name = new StringBuffer();
381 name.append(member.getName());
382 if ((member instanceof CtMethod) || (member instanceof CtConstructor)) {
383
384 CtClass[] param;
385 if (member instanceof CtMethod) {
386 param = ((CtMethod) member).getParameterTypes();
387 } else if (member instanceof CtConstructor) {
388 param = ((CtConstructor) member).getParameterTypes();
389 } else {
390 throw new Error("intenal test error");
391 }
392 name.append("(");
393 for (int i = 0; i < param.length; i++) {
394 if (i != 0) {
395 name.append(" ,");
396 }
397 name.append(param[i].getName());
398 }
399 name.append(")");
400 }
401 return name.toString();
402 }
403
404 private void compareMethods(CtMethod[] refMethods, CtMethod[] implMethods, String className)
405 throws NotFoundException {
406 Map implNames = buildNameMap(implMethods, className);
407 int compared = 0;
408 int expectedMethods = 0;
409 for (int i = 0; i < refMethods.length; i++) {
410 if (!isAPIMember(refMethods[i])) {
411 implNames.remove(getName4Map(refMethods[i]));
412 continue;
413 }
414 expectedMethods++;
415 CtMethod implMethod = (CtMethod) implNames.get(getName4Map(refMethods[i]));
416 compareMethod(refMethods[i], implMethod, className);
417 implNames.remove(getName4Map(refMethods[i]));
418 if (implMethod != null) {
419 compared++;
420 }
421 }
422 if (config.allowAPIextension) {
423 return;
424 }
425 StringBuffer extra = new StringBuffer();
426 for (Iterator i = implNames.keySet().iterator(); i.hasNext();) {
427 if (extra.length() > 0) {
428 extra.append(", ");
429 } else {
430 extra.append(", Extra method(s) [");
431 }
432 String extName = (String) i.next();
433 CtMethod mx = (CtMethod) implNames.get(extName);
434 addExtra(mx);
435 extra.append(extName);
436 }
437 if (extra.length() > 0) {
438 extra.append("]");
439 }
440 assertEquals(className + " number of Methods" + extra.toString(), expectedMethods, implNames.size() + compared);
441 }
442
443 private void compareMethod(CtMethod refMethod, CtMethod implMethod, String className) throws NotFoundException {
444 boolean ch = compareMember(refMethod, implMethod, className, refMethod.getSignature());
445 if (implMethod == null) {
446 return;
447 }
448 String name = refMethod.getName();
449 ch |= assertEquals(className + "." + name + " returnType", refMethod.getReturnType().getName(), implMethod
450 .getReturnType().getName());
451 ch |= compareThrows(refMethod, implMethod, className);
452 if (ch) {
453 addChanges(implMethod);
454 }
455 }
456
457 private void compareFields(CtField[] refFields, CtField[] implFields, String className, CtClass refClass,
458 CtClass implClass) throws NotFoundException {
459 Map implNames = buildNameMap(implFields, className);
460 int compared = 0;
461 int expectedFields = 0;
462 for (int i = 0; i < refFields.length; i++) {
463 String name = getName4Map(refFields[i]);
464 if (!isAPIMember(refFields[i])) {
465 implNames.remove(name);
466 continue;
467 }
468 expectedFields++;
469 CtField impl = (CtField) implNames.get(name);
470 compareField(refFields[i], impl, className, refClass, implClass);
471 implNames.remove(name);
472 if (impl != null) {
473 compared++;
474 }
475 }
476 if (config.allowAPIextension) {
477 return;
478 }
479 StringBuffer extra = new StringBuffer();
480 for (Iterator i = implNames.keySet().iterator(); i.hasNext();) {
481 if (extra.length() > 0) {
482 extra.append(", ");
483 } else {
484 extra.append(", Extra field(s) [");
485 }
486 String extName = (String) i.next();
487 CtField fx = (CtField) implNames.get(extName);
488 addExtra(fx);
489 extra.append(extName);
490 }
491 if (extra.length() > 0) {
492 extra.append("]");
493 }
494 assertEquals(className + " number of Fields" + extra.toString(), expectedFields, implNames.size() + compared);
495 }
496
497 private void compareField(CtField refField, CtField implField, String className, CtClass refClass, CtClass implClass)
498 throws NotFoundException {
499 String name = refField.getName();
500 boolean ch = compareMember(refField, implField, className, "");
501 if (implField == null) {
502 return;
503 }
504 ch |= assertEquals(className + "." + name + " type", refField.getType().getName(), implField.getType()
505 .getName());
506 if ((Modifier.isFinal(refField.getModifiers())) && (Modifier.isStatic(refField.getModifiers()))) {
507
508 Object refConstValue = refField.getConstantValue();
509 Object implConstValue = implField.getConstantValue();
510 if ((refConstValue != null) || (implConstValue != null)) {
511
512 String refValue = null;
513 if (refConstValue != null) {
514 refValue = refConstValue.toString();
515 } else if (refField.getType() == CtClass.booleanType) {
516 refValue = "false";
517 }
518
519 String implValue = null;
520 if (implConstValue != null) {
521 implValue = implConstValue.toString();
522 } else if (implField.getType() == CtClass.booleanType) {
523 implValue = "false";
524 }
525 ch |= assertEquals(className + "." + name + " value ", refValue, implValue);
526 }
527 }
528 if (ch) {
529 addChanges(implField);
530 }
531 }
532
533 }