View Javadoc

1   /*
2    * org.osjava.jardiff.JarDiff
3    *
4    * $Id: IOThread.java 1952 2005-08-28 18:03:41Z cybertiger $
5    * $URL: https://svn.osjava.org/svn/osjava/trunk/osjava-nio/src/java/org/osjava/nio/IOThread.java $
6    * $Rev: 1952 $
7    * $Date: 2005-08-28 18:03:41 +0000 (Sun, 28 Aug 2005) $
8    * $Author: cybertiger $
9    *
10   * Copyright (c) 2005, Antony Riley
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or without
14   * modification, are permitted provided that the following conditions are met:
15   *
16   * + Redistributions of source code must retain the above copyright notice,
17   *   this list of conditions and the following disclaimer.
18   *
19   * + Redistributions in binary form must reproduce the above copyright notice,
20   *   this list of conditions and the following disclaimer in the documentation
21   *   and/or other materials provided with the distribution.
22   *
23   * + Neither the name JarDiff nor the names of its contributors may
24   *   be used to endorse or promote products derived from this software without
25   *   specific prior written permission.
26   *
27   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30   * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
31   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37   * POSSIBILITY OF SUCH DAMAGE.
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  import javax.xml.transform.ErrorListener;
59  import javax.xml.transform.Transformer;
60  import javax.xml.transform.TransformerException;
61  import javax.xml.transform.TransformerFactory;
62  import javax.xml.transform.sax.SAXTransformerFactory;
63  import javax.xml.transform.sax.TransformerHandler;
64  import javax.xml.transform.stream.StreamResult;
65  import javax.xml.transform.stream.StreamSource;
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                 // If it's not a local file, store it as a temporary jar file.
213                 // java.util.jar.JarFile requires a local file handle.
214                 jarFile = File.createTempFile("jardiff","jar");
215                 // Mark it to be deleted on exit.
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                 // Else it's a local file, nothing special to do.
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         // TODO: Build the name from the MANIFEST rather than the filename
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 }