View Javadoc

1   /*
2    * Jour - bytecode instrumentation library
3    *
4    * Copyright (C) 2007-2008 Vlad Skarzhevskyy
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Library General Public
8    * License as published by the Free Software Foundation; either
9    * version 2 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Library General Public License for more details.
15   *
16   * You should have received a copy of the GNU Library General Public
17   * License along with this library; if not, write to the
18   * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19   * Boston, MA  02111-1307, USA.
20   * 
21   * @version $Id: APICompare.java 111 2008-03-05 07:28:30Z vlads $
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   * @author vlads
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  		// ExportClasses.export("target/test-api-classes", classes);
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 			// java.lang.Object in CLDC / javassist will reference same class...
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 		// Constructors
213 		CtConstructor[] refConstructors = refClass.getDeclaredConstructors();
214 		CtConstructor[] implConstructors = implClass.getDeclaredConstructors();
215 		compareConstructors(refConstructors, implConstructors, className);
216 
217 		// Methods
218 		CtMethod[] refMethods = refClass.getDeclaredMethods();
219 		CtMethod[] implMethods = implClass.getDeclaredMethods();
220 		compareMethods(refMethods, implMethods, className);
221 
222 		// all accessible public fields
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 				// System.out.println("ignore " + members[i].getName());
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 				// throw new Error("duplicate member name " + name + " " +
262 				// members[i].getName()+ " = " +
263 				// ((Member)namesMap.get(name)).getName());
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 			// Overloaded Methods should have different names
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 			// Compare value
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 }