1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 package org.osjava.jardiff;
40 import java.io.File;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.OutputStream;
45 import java.net.URL;
46 import java.net.URLClassLoader;
47 import java.util.Enumeration;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.Map;
52 import java.util.Set;
53 import java.util.TreeSet;
54 import java.util.TreeMap;
55 import java.util.jar.JarEntry;
56 import java.util.jar.JarFile;
57
58
59
60
61
62
63
64
65
66
67
68 import org.objectweb.asm.ClassReader;
69
70 /***
71 * A class to perform a diff between two jar files.
72 *
73 * @author <a href="mailto:antony@cyberiantiger.org">Antony Riley</a>
74 */
75 public class JarDiff
76 {
77 /***
78 * A map containing information about classes which are dependencies.
79 * Keys are internal class names.
80 * Values are instances of ClassInfo.
81 */
82 protected Map depClassInfo = new HashMap();
83
84 /***
85 * A map containing information about classes in the old jar file.
86 * Keys are internal class names.
87 * Values are instances of ClassInfo.
88 */
89 protected Map oldClassInfo = new TreeMap();
90
91 /***
92 * A map containing information about classes in the new jar file.
93 * Keys are internal class names.
94 * Values are instances of ClassInfo.
95 */
96 protected Map newClassInfo = new TreeMap();
97
98 /***
99 * An array of dependencies which are jar files, or urls.
100 */
101 private URL[] deps;
102
103 /***
104 * A class loader used for loading dependency classes.
105 */
106 private URLClassLoader depLoader;
107
108 /***
109 * The name of the old version.
110 */
111 private String oldVersion;
112
113 /***
114 * The name of the new version.
115 */
116 private String newVersion;
117
118 /***
119 * Class info visitor, used to load information about classes.
120 */
121 private ClassInfoVisitor infoVisitor = new ClassInfoVisitor();
122
123 /***
124 * Create a new JarDiff object.
125 */
126 public JarDiff() {
127 }
128
129 /***
130 * Set the name of the old version.
131 *
132 * @param oldVersion the name
133 */
134 public void setOldVersion(String oldVersion) {
135 this.oldVersion = oldVersion;
136 }
137
138 /***
139 * Get the name of the old version.
140 *
141 * @return the name
142 */
143 public String getOldVersion() {
144 return oldVersion;
145 }
146
147 /***
148 * Set the name of the new version.
149 *
150 * @param newVersion
151 */
152 public void setNewVersion(String newVersion) {
153 this.newVersion = newVersion;
154 }
155
156 /***
157 * Get the name of the new version.
158 *
159 * @return the name
160 */
161 public String getNewVersion() {
162 return newVersion;
163 }
164
165 /***
166 * Set the dependencies.
167 *
168 * @param deps an array of urls pointing to jar files or directories
169 * containing classes which are required dependencies.
170 */
171 public void setDependencies(URL[] deps) {
172 this.deps = deps;
173 }
174
175 /***
176 * Get the dependencies.
177 *
178 * @return the dependencies as an array of URLs
179 */
180 public URL[] getDependencies() {
181 return deps;
182 }
183
184 /***
185 * Load classinfo given a ClassReader.
186 *
187 * @param reader the ClassReader
188 * @return the ClassInfo
189 */
190 private synchronized ClassInfo loadClassInfo(ClassReader reader)
191 throws IOException
192 {
193 infoVisitor.reset();
194 reader.accept(infoVisitor, false);
195 return infoVisitor.getClassInfo();
196 }
197
198 /***
199 * Load all the classes from the specified URL and store information
200 * about them in the specified map.
201 * This currently only works for jar files, <b>not</b> directories
202 * which contain classes in subdirectories or in the current directory.
203 *
204 * @param infoMap the map to store the ClassInfo in.
205 * @throws DiffException if there is an exception reading info about a
206 * class.
207 */
208 private void loadClasses(Map infoMap, URL path) throws DiffException {
209 try {
210 File jarFile = null;
211 if(!"file".equals(path.getProtocol()) || path.getHost() != null) {
212
213
214 jarFile = File.createTempFile("jardiff","jar");
215
216 jarFile.deleteOnExit();
217 InputStream in = path.openStream();
218 OutputStream out = new FileOutputStream(jarFile);
219 byte[] buffer = new byte[4096];
220 int i;
221 while( (i = in.read(buffer,0,buffer.length)) != -1) {
222 out.write(buffer, 0, i);
223 }
224 in.close();
225 out.close();
226 } else {
227
228 jarFile = new File(path.getPath());
229 }
230 loadClasses(infoMap, jarFile);
231 } catch (IOException ioe) {
232 throw new DiffException(ioe);
233 }
234 }
235
236 /***
237 * Load all the classes from the specified URL and store information
238 * about them in the specified map.
239 * This currently only works for jar files, <b>not</b> directories
240 * which contain classes in subdirectories or in the current directory.
241 *
242 * @param infoMap the map to store the ClassInfo in.
243 * @param file the jarfile to load classes from.
244 * @throws IOException if there is an IOException reading info about a
245 * class.
246 */
247 private void loadClasses(Map infoMap, File file) throws DiffException {
248 try {
249 JarFile jar = new JarFile(file);
250 Enumeration e = jar.entries();
251 while (e.hasMoreElements()) {
252 JarEntry entry = (JarEntry) e.nextElement();
253 String name = entry.getName();
254 if (!entry.isDirectory() && name.endsWith(".class")) {
255 ClassReader reader
256 = new ClassReader(jar.getInputStream(entry));
257 ClassInfo ci = loadClassInfo(reader);
258 infoMap.put(ci.getName(), ci);
259 }
260 }
261 } catch (IOException ioe) {
262 throw new DiffException(ioe);
263 }
264 }
265
266 /***
267 * Load old classes from the specified URL.
268 *
269 * @param loc The location of a jar file to load classes from.
270 * @throws DiffException if there is an IOException.
271 */
272 public void loadOldClasses(URL loc) throws DiffException {
273 loadClasses(oldClassInfo, loc);
274 }
275
276 /***
277 * Load new classes from the specified URL.
278 *
279 * @param loc The location of a jar file to load classes from.
280 * @throws DiffException if there is an IOException.
281 */
282 public void loadNewClasses(URL loc) throws DiffException {
283 loadClasses(newClassInfo, loc);
284 }
285
286 /***
287 * Load old classes from the specified File.
288 *
289 * @param file The location of a jar file to load classes from.
290 * @throws DiffException if there is an IOException
291 */
292 public void loadOldClasses(File file) throws DiffException {
293 loadClasses(oldClassInfo, file);
294 }
295
296 /***
297 * Load new classes from the specified File.
298 *
299 * @param file The location of a jar file to load classes from.
300 * @throws DiffExeption if there is an IOException
301 */
302 public void loadNewClasses(File file) throws DiffException {
303 loadClasses(newClassInfo, file);
304 }
305
306 /***
307 * Perform a diff sending the output to the specified handler, using
308 * the specified criteria to select diffs.
309 *
310 * @param handler The handler to receive and handle differences.
311 * @param criteria The criteria we use to select differences.
312 * @throws DiffException when there is an underlying exception, e.g.
313 * writing to a file caused an IOException
314 */
315 public void diff(DiffHandler handler, DiffCriteria criteria)
316 throws DiffException
317 {
318
319 handler.startDiff(oldVersion, newVersion);
320 Iterator i;
321
322 handler.startOldContents();
323 i = oldClassInfo.entrySet().iterator();
324 while(i.hasNext()) {
325 Map.Entry entry = (Map.Entry) i.next();
326 ClassInfo ci = (ClassInfo) entry.getValue();
327 if(criteria.validClass(ci)) {
328 handler.contains(ci);
329 }
330 }
331 handler.endOldContents();
332
333 handler.startNewContents();
334 i = newClassInfo.entrySet().iterator();
335 while(i.hasNext()) {
336 Map.Entry entry = (Map.Entry) i.next();
337 ClassInfo ci = (ClassInfo) entry.getValue();
338 if(criteria.validClass(ci)) {
339 handler.contains(ci);
340 }
341 }
342 handler.endNewContents();
343
344 java.util.Set onlyOld = new TreeSet(oldClassInfo.keySet());
345 java.util.Set onlyNew = new TreeSet(newClassInfo.keySet());
346 java.util.Set both = new TreeSet(oldClassInfo.keySet());
347 onlyOld.removeAll(newClassInfo.keySet());
348 onlyNew.removeAll(oldClassInfo.keySet());
349 both.retainAll(newClassInfo.keySet());
350 handler.startRemoved();
351 i = onlyOld.iterator();
352 while (i.hasNext()) {
353 String s = (String) i.next();
354 ClassInfo ci = (ClassInfo) oldClassInfo.get(s);
355 if (criteria.validClass(ci))
356 handler.classRemoved(ci);
357 }
358 handler.endRemoved();
359 handler.startAdded();
360 i = onlyNew.iterator();
361 while (i.hasNext()) {
362 String s = (String) i.next();
363 ClassInfo ci = (ClassInfo) newClassInfo.get(s);
364 if (criteria.validClass(ci))
365 handler.classAdded(ci);
366 }
367 handler.endAdded();
368 java.util.Set removedMethods = new TreeSet();
369 java.util.Set removedFields = new TreeSet();
370 java.util.Set addedMethods = new TreeSet();
371 java.util.Set addedFields = new TreeSet();
372 java.util.Set changedMethods = new TreeSet();
373 java.util.Set changedFields = new TreeSet();
374 handler.startChanged();
375 i = both.iterator();
376 while (i.hasNext()) {
377 String s = (String) i.next();
378 ClassInfo oci = (ClassInfo) oldClassInfo.get(s);
379 ClassInfo nci = (ClassInfo) newClassInfo.get(s);
380 if (criteria.validClass(oci) || criteria.validClass(nci)) {
381 Map oldMethods = oci.getMethodMap();
382 Map oldFields = oci.getFieldMap();
383 Map newMethods = nci.getMethodMap();
384 Map newFields = nci.getFieldMap();
385 Iterator j = oldMethods.entrySet().iterator();
386 while (j.hasNext()) {
387 Map.Entry entry = (Map.Entry) j.next();
388 if (criteria.validMethod((MethodInfo) entry.getValue()))
389 removedMethods.add(entry.getKey());
390 }
391 j = oldFields.entrySet().iterator();
392 while (j.hasNext()) {
393 Map.Entry entry = (Map.Entry) j.next();
394 if (criteria.validField((FieldInfo) entry.getValue()))
395 removedFields.add(entry.getKey());
396 }
397 j = newMethods.entrySet().iterator();
398 while (j.hasNext()) {
399 Map.Entry entry = (Map.Entry) j.next();
400 if (criteria.validMethod((MethodInfo) entry.getValue()))
401 addedMethods.add(entry.getKey());
402 }
403 j = newFields.entrySet().iterator();
404 while (j.hasNext()) {
405 Map.Entry entry = (Map.Entry) j.next();
406 if (criteria.validField((FieldInfo) entry.getValue()))
407 addedFields.add(entry.getKey());
408 }
409 changedMethods.addAll(removedMethods);
410 changedMethods.retainAll(addedMethods);
411 removedMethods.removeAll(changedMethods);
412 addedMethods.removeAll(changedMethods);
413 changedFields.addAll(removedFields);
414 changedFields.retainAll(addedFields);
415 removedFields.removeAll(changedFields);
416 addedFields.removeAll(changedFields);
417 j = changedMethods.iterator();
418 while (j.hasNext()) {
419 String desc = (String) j.next();
420 MethodInfo oldInfo = (MethodInfo) oldMethods.get(desc);
421 MethodInfo newInfo = (MethodInfo) newMethods.get(desc);
422 if (!criteria.differs(oldInfo, newInfo))
423 j.remove();
424 }
425 j = changedFields.iterator();
426 while (j.hasNext()) {
427 String desc = (String) j.next();
428 FieldInfo oldInfo = (FieldInfo) oldFields.get(desc);
429 FieldInfo newInfo = (FieldInfo) newFields.get(desc);
430 if (!criteria.differs(oldInfo, newInfo))
431 j.remove();
432 }
433 boolean classchanged = criteria.differs(oci, nci);
434 if (classchanged || !removedMethods.isEmpty()
435 || !removedFields.isEmpty() || !addedMethods.isEmpty()
436 || !addedFields.isEmpty() || !changedMethods.isEmpty()
437 || !changedFields.isEmpty()) {
438 handler.startClassChanged(s);
439 handler.startRemoved();
440 j = removedFields.iterator();
441 while (j.hasNext())
442 handler
443 .fieldRemoved((FieldInfo) oldFields.get(j.next()));
444 j = removedMethods.iterator();
445 while (j.hasNext())
446 handler.methodRemoved((MethodInfo)
447 oldMethods.get(j.next()));
448 handler.endRemoved();
449 handler.startAdded();
450 j = addedFields.iterator();
451 while (j.hasNext())
452 handler
453 .fieldAdded((FieldInfo) newFields.get(j.next()));
454 j = addedMethods.iterator();
455 while (j.hasNext())
456 handler.methodAdded((MethodInfo)
457 newMethods.get(j.next()));
458 handler.endAdded();
459 handler.startChanged();
460 if (classchanged)
461 handler.classChanged(oci, nci);
462 j = changedFields.iterator();
463 while (j.hasNext()) {
464 Object tmp = j.next();
465 handler.fieldChanged((FieldInfo) oldFields.get(tmp),
466 (FieldInfo) newFields.get(tmp));
467 }
468 j = changedMethods.iterator();
469 while (j.hasNext()) {
470 Object tmp = j.next();
471 handler.methodChanged((MethodInfo) oldMethods.get(tmp),
472 ((MethodInfo)
473 newMethods.get(tmp)));
474 }
475 handler.endChanged();
476 handler.endClassChanged();
477 removedMethods.clear();
478 removedFields.clear();
479 addedMethods.clear();
480 addedFields.clear();
481 changedMethods.clear();
482 changedFields.clear();
483 }
484 }
485 }
486 handler.endChanged();
487 handler.endDiff();
488 }
489 }