View Javadoc

1   /*
2    * org.osjava.jardiff.StreamDiffHandler
3    *
4    * $Id: IOThread.java 1952 2005-08-28 18:03:41Z cybertiger $
5    * $URL: https://svn.osjava.org/svn/osjava/trunk/jardiff/src/ava/org/osjava/jardiff/DOMDiffHandler.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  
41  import java.io.BufferedOutputStream;
42  import java.io.BufferedWriter;
43  import java.io.IOException;
44  import java.io.OutputStream;
45  import java.io.OutputStreamWriter;
46  import org.objectweb.asm.Type;
47  
48  /***
49   * A specific type of DiffHandler which uses an OutputStream to create an 
50   * XML document describing the changes in the diff.
51   * This is needed for java 1.2 compatibility for the ant task.
52   *
53   * @author <a href="mailto:antony@cyberiantiger.org">Antony Riley</a>
54   */
55  public class StreamDiffHandler implements DiffHandler
56  {
57      /***
58       * The XML namespace used.
59       */
60      public static final String XML_URI = "http://www.osjava.org/jardiff/0.1";
61  
62      /***
63       * The javax.xml.transform.sax.Transformer used to convert
64       * the DOM to text.
65       */
66      private final BufferedWriter out;
67  
68      /***
69       * Create a new StreamDiffHandler which writes to System.out
70       *
71       * @throws DiffException when there is an underlying exception, e.g.
72       *                       writing to a file caused an IOException
73       */
74      public StreamDiffHandler() throws DiffException {
75          try {
76              out = new BufferedWriter(
77                      new OutputStreamWriter(System.out, "UTF-8")
78                      );
79          } catch (IOException ioe) {
80              throw new DiffException(ioe);
81          }
82      }
83      
84      /***
85       * Create a new StreamDiffHandler with the specified OutputStream.
86       *
87       * @param out Where to write output.
88       */
89      public StreamDiffHandler(OutputStream out)
90          throws DiffException
91      {
92          try {
93              this.out = new BufferedWriter(
94                      new OutputStreamWriter(out, "UTF-8")
95                      );
96          } catch (IOException ioe) {
97              throw new DiffException(ioe);
98          }
99      }
100     
101     /***
102      * Start the diff.
103      * This writes out the start of a &lt;diff&gt; node.
104      *
105      * @param oldJar name of old jar file.
106      * @param newJar name of new jar file.
107      * @throws DiffException when there is an underlying exception, e.g.
108      *                       writing to a file caused an IOException
109      */
110     public void startDiff(String oldJar, String newJar) throws DiffException {
111         try {
112             out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
113             out.write("<diff xmlns=\"");
114             out.write(xmlEscape(XML_URI));
115             out.write("\" old=\"");
116             out.write(xmlEscape(oldJar));
117             out.write("\" new=\"");
118             out.write(xmlEscape(newJar));
119             out.write("\">");
120         } catch (IOException ioe) {
121             throw new DiffException(ioe);
122         }
123     }
124 
125     /***
126      * Start the list of old contents.
127      *
128      * @throws DiffException when there is an underlying exception, e.g.
129      *                       writing to a file caused an IOException
130      */
131     public void startOldContents() throws DiffException {
132         try {
133             out.write("<oldcontents>");
134         } catch (IOException ioe) {
135             throw new DiffException(ioe);
136         }
137     }
138 
139     /***
140      * Start the list of old contents.
141      *
142      * @throws DiffException when there is an underlying exception, e.g.
143      *                       writing to a file caused an IOException
144      */
145     public void startNewContents() throws DiffException {
146         try {
147             out.write("<newcontents>");
148         } catch (IOException ioe) {
149             throw new DiffException(ioe);
150         }
151     }
152 
153     /***
154      * Add a contained class.
155      *
156      * @param info information about a class
157      * @throws DiffException when there is an underlying exception, e.g.
158      *                       writing to a file caused an IOException
159      */
160     public void contains(ClassInfo info) throws DiffException {
161         try {
162             out.write("<class name=\"");
163             out.write(xmlEscape(info.getName()));
164             out.write("\"/>");
165         } catch (IOException ioe) {
166             throw new DiffException(ioe);
167         }
168     }
169 
170     /***
171      * End the list of old contents.
172      *
173      * @throws DiffException when there is an underlying exception, e.g.
174      *                       writing to a file caused an IOException
175      */
176     public void endOldContents() throws DiffException {
177         try {
178             out.write("</oldcontents>");
179         } catch (IOException ioe) {
180             throw new DiffException(ioe);
181         }
182     }
183 
184     /***
185      * End the list of new contents.
186      *
187      * @throws DiffException when there is an underlying exception, e.g.
188      *                       writing to a file caused an IOException
189      */
190     public void endNewContents() throws DiffException {
191         try {
192             out.write("</newcontents>");
193         } catch (IOException ioe) {
194             throw new DiffException(ioe);
195         }
196     }
197     
198     /***
199      * Start the removed node.
200      * This writes out a &lt;removed&gt; node.
201      *
202      * @throws DiffException when there is an underlying exception, e.g.
203      *                       writing to a file caused an IOException
204      */
205     public void startRemoved() throws DiffException {
206         try {
207             out.write("<removed>");
208         } catch (IOException ioe) {
209             throw new DiffException(ioe);
210         }
211     }
212     
213     /***
214      * Write out class info for a removed class.
215      * This writes out the nodes describing a class
216      *
217      * @param info The info to write out.
218      * @throws DiffException when there is an underlying exception, e.g.
219      *                       writing to a file caused an IOException
220      */
221     public void classRemoved(ClassInfo info) throws DiffException {
222         try {
223             writeClassInfo(info);
224         } catch (IOException ioe) {
225             throw new DiffException(ioe);
226         }
227     }
228     
229     /***
230      * End the removed section.
231      * This closes the &lt;removed&gt; tag.
232      *
233      * @throws DiffException when there is an underlying exception, e.g.
234      *                       writing to a file caused an IOException
235      */
236     public void endRemoved() throws DiffException {
237         try {
238             out.write("</removed>");
239         } catch (IOException ioe) {
240             throw new DiffException(ioe);
241         }
242     }
243     
244     /***
245      * Start the added section.
246      * This opens the &lt;added&gt; tag.
247      *
248      * @throws DiffException when there is an underlying exception, e.g.
249      *                       writing to a file caused an IOException
250      */
251     public void startAdded() throws DiffException {
252         try {
253             out.write("<added>");
254         } catch (IOException ioe) {
255             throw new DiffException(ioe);
256         }
257     }
258     
259     /***
260      * Write out the class info for an added class.
261      * This writes out the nodes describing an added class.
262      *
263      * @param info The class info describing the added class.
264      * @throws DiffException when there is an underlying exception, e.g.
265      *                       writing to a file caused an IOException
266      */
267     public void classAdded(ClassInfo info) throws DiffException {
268         try {
269             writeClassInfo(info);
270         } catch (IOException ioe) {
271             throw new DiffException(ioe);
272         }
273     }
274     
275     /***
276      * End the added section.
277      * This closes the &lt;added&gt; tag.
278      *
279      * @throws DiffException when there is an underlying exception, e.g.
280      *                       writing to a file caused an IOException
281      */
282     public void endAdded() throws DiffException {
283         try {
284             out.write("</added>");
285         } catch (IOException ioe) {
286             throw new DiffException(ioe);
287         }
288     }
289     
290     /***
291      * Start the changed section.
292      * This writes out the &lt;changed&gt; node.
293      *
294      * @throws DiffException when there is an underlying exception, e.g.
295      *                       writing to a file caused an IOException
296      */
297     public void startChanged() throws DiffException {
298         try {
299             out.write("<changed>");
300         } catch (IOException ioe) {
301             throw new DiffException(ioe);
302         }
303     }
304     
305     /***
306      * Start a changed section for an individual class.
307      * This writes out an &lt;classchanged&gt; node with the real class
308      * name as the name attribute.
309      *
310      * @param internalName the internal name of the class that has changed.
311      * @throws DiffException when there is an underlying exception, e.g.
312      *                       writing to a file caused an IOException
313      */
314     public void startClassChanged(String internalName) throws DiffException 
315     {
316         try {
317             out.write("<classchanged name=\"");
318             out.write(xmlEscape(internalName));
319             out.write("\">");
320         } catch (IOException ioe) {
321             throw new DiffException(ioe);
322         }
323     }
324     
325     /***
326      * Write out info about a removed field.
327      * This just writes out the field info, it will be inside a start/end
328      * removed section.
329      *
330      * @param info Info about the field that's been removed.
331      * @throws DiffException when there is an underlying exception, e.g.
332      *                       writing to a file caused an IOException
333      */
334     public void fieldRemoved(FieldInfo info) throws DiffException {
335         try {
336             writeFieldInfo(info);
337         } catch (IOException ioe) {
338             throw new DiffException(ioe);
339         }
340     }
341     
342     /***
343      * Write out info about a removed method.
344      * This just writes out the method info, it will be inside a start/end 
345      * removed section.
346      *
347      * @param info Info about the method that's been removed.
348      * @throws DiffException when there is an underlying exception, e.g.
349      *                       writing to a file caused an IOException
350      */
351     public void methodRemoved(MethodInfo info) throws DiffException {
352         try {
353             writeMethodInfo(info);
354         } catch (IOException ioe) {
355             throw new DiffException(ioe);
356         }
357     }
358     
359     /***
360      * Write out info about an added field.
361      * This just writes out the field info, it will be inside a start/end 
362      * added section.
363      *
364      * @param info Info about the added field.
365      * @throws DiffException when there is an underlying exception, e.g.
366      *                       writing to a file caused an IOException
367      */
368     public void fieldAdded(FieldInfo info) throws DiffException {
369         try {
370             writeFieldInfo(info);
371         } catch (IOException ioe) {
372             throw new DiffException(ioe);
373         }
374     }
375     
376     /***
377      * Write out info about a added method.
378      * This just writes out the method info, it will be inside a start/end
379      * added section.
380      *
381      * @param info Info about the added method.
382      * @throws DiffException when there is an underlying exception, e.g.
383      *                       writing to a file caused an IOException
384      */
385     public void methodAdded(MethodInfo info) throws DiffException {
386         try {
387             writeMethodInfo(info);
388         } catch (IOException ioe) {
389             throw new DiffException(ioe);
390         }
391     }
392     
393     /***
394      * Write out info aboout a changed class.
395      * This writes out a &lt;classchange&gt; node, followed by a 
396      * &lt;from&gt; node, with the old information about the class
397      * followed by a &lt;to&gt; node with the new information about the
398      * class.
399      *
400      * @param oldInfo Info about the old class.
401      * @param newInfo Info about the new class.
402      * @throws DiffException when there is an underlying exception, e.g.
403      *                       writing to a file caused an IOException
404      */
405     public void classChanged(ClassInfo oldInfo, ClassInfo newInfo)
406         throws DiffException 
407     {
408         try {
409             out.write("<classchange><from>");
410             writeClassInfo(oldInfo);
411             out.write("</from><to>");
412             writeClassInfo(newInfo);
413             out.write("</to></classchange>");
414         } catch (IOException ioe) {
415             throw new DiffException(ioe);
416         }
417     }
418     
419     /***
420      * Write out info aboout a changed field.
421      * This writes out a &lt;fieldchange&gt; node, followed by a 
422      * &lt;from&gt; node, with the old information about the field
423      * followed by a &lt;to&gt; node with the new information about the
424      * field.
425      *
426      * @param oldInfo Info about the old field.
427      * @param newInfo Info about the new field.
428      * @throws DiffException when there is an underlying exception, e.g.
429      *                       writing to a file caused an IOException
430      */
431     public void fieldChanged(FieldInfo oldInfo, FieldInfo newInfo)
432         throws DiffException 
433     {
434         try {
435             out.write("<fieldchange><from>");
436             writeFieldInfo(oldInfo);
437             out.write("</from><to>");
438             writeFieldInfo(newInfo);
439             out.write("</to></fieldchange>");
440         } catch (IOException ioe) {
441             throw new DiffException(ioe);
442         }
443     }
444     
445     /***
446      * Write out info aboout a changed method.
447      * This writes out a &lt;methodchange&gt; node, followed by a 
448      * &lt;from&gt; node, with the old information about the method
449      * followed by a &lt;to&gt; node with the new information about the
450      * method.
451      *
452      * @param oldInfo Info about the old method.
453      * @param newInfo Info about the new method.
454      * @throws DiffException when there is an underlying exception, e.g.
455      *                       writing to a file caused an IOException
456      */
457     public void methodChanged(MethodInfo oldInfo, MethodInfo newInfo)
458         throws DiffException
459     {
460         try {
461             out.write("<methodchange><from>");
462             writeMethodInfo(oldInfo);
463             out.write("</from><to>");
464             writeMethodInfo(newInfo);
465             out.write("</to></methodchange>");
466         } catch (IOException ioe) {
467             throw new DiffException(ioe);
468         }
469     }
470     
471     /***
472      * End the changed section for an individual class.
473      * This closes the &lt;classchanged&gt; node.
474      *
475      * @throws DiffException when there is an underlying exception, e.g.
476      *                       writing to a file caused an IOException
477      */
478     public void endClassChanged() throws DiffException {
479         try {
480             out.write("</classchanged>");
481         } catch (IOException ioe) {
482             throw new DiffException(ioe);
483         }
484     }
485     
486     /***
487      * End the changed section.
488      * This closes the &lt;changed&gt; node.
489      *
490      * @throws DiffException when there is an underlying exception, e.g.
491      *                       writing to a file caused an IOException
492      */
493     public void endChanged() throws DiffException {
494         try {
495             out.write("</changed>");
496         } catch (IOException ioe) {
497             throw new DiffException(ioe);
498         }
499     }
500     
501     /***
502      * End the diff.
503      * This closes the &lt;diff&gt; node.
504      *
505      * @throws DiffException when there is an underlying exception, e.g.
506      *                       writing to a file caused an IOException
507      */
508     public void endDiff() throws DiffException {
509         try {
510             out.write("</diff>");
511             out.newLine();
512             out.close();
513         } catch (IOException ioe) {
514             throw new DiffException(ioe);
515         }
516     }
517     
518     /***
519      * Write out information about a class.
520      * This writes out a &lt;class&gt; node, which contains information about
521      * what interfaces are implemented each in a &lt;implements&gt; node.
522      *
523      * @param info Info about the class to write out.
524      * @throws IOException when there is an underlying IOException.
525      */
526     protected void writeClassInfo(ClassInfo info) throws IOException {
527         out.write("<class");
528         addAccessFlags(info);
529         if(info.getName() != null) {
530             out.write(" name=\"");
531             out.write(xmlEscape(info.getName()));
532             out.write("\"");
533         }
534         if(info.getSignature() != null) {
535             out.write(" signature=\"");
536             out.write(xmlEscape(info.getSignature()));
537             out.write("\"");
538         }
539         if(info.getSupername() != null) {
540             out.write(" superclass=\"");
541             out.write(xmlEscape(info.getSupername()));
542             out.write("\">");
543         }
544         String[] interfaces = info.getInterfaces();
545         for (int i = 0; i < interfaces.length; i++) {
546             out.write("<implements name=\"");
547             out.write(xmlEscape(interfaces[i]));
548             out.write("\"/>");
549         }
550         out.write("</class>");
551     }
552     
553     /***
554      * Write out information about a method.
555      * This writes out a &lt;method&gt; node which contains information about
556      * the arguments, the return type, and the exceptions thrown by the 
557      * method.
558      *
559      * @param info Info about the method.
560      * @throws IOException when there is an underlying IOException.
561      */
562     protected void writeMethodInfo(MethodInfo info) throws IOException {
563         out.write("<method");
564 
565         addAccessFlags(info);
566 
567         if (info.getName() != null) {
568             out.write(" name=\"");
569             out.write(xmlEscape(info.getName()));
570             out.write("\"");
571         }
572         if (info.getSignature() != null) {
573             out.write(" signature=\"");
574             out.write(xmlEscape(info.getSignature()));
575             out.write("\"");
576         }
577         out.write(">");
578         if (info.getDesc() != null) {
579             addMethodNodes(info.getDesc());
580         }
581         String[] exceptions = info.getExceptions();
582         if (exceptions != null) {
583             for (int i = 0; i < exceptions.length; i++) {
584                 out.write("<exception name=\"");
585                 out.write(xmlEscape(exceptions[i]));
586                 out.write("\"/>");
587             }
588         }
589         out.write("</method>");
590     }
591     
592     /***
593      * Write out information about a field.
594      * This writes out a &lt;field&gt; node with attributes describing the
595      * field.
596      *
597      * @param info Info about the field.
598      * @throws IOException when there is an underlying IOException.
599      */
600     protected void writeFieldInfo(FieldInfo info) throws IOException {
601         out.write("<field");
602 
603         addAccessFlags(info);
604 
605         if(info.getName() != null) {
606             out.write(" name=\"");
607             out.write(xmlEscape(info.getName()));
608             out.write("\"");
609         }
610         if (info.getSignature() != null) {
611             out.write(" signature=\"");
612             out.write(xmlEscape(info.getSignature()));
613             out.write("\"");
614         }
615         if (info.getValue() != null) {
616             out.write(" value=\"");
617             out.write(xmlEscape(info.getValue().toString()));
618             out.write("\"");
619         }
620         out.write(">");
621         if (info.getDesc() != null) {
622             addTypeNode(info.getDesc());
623         }
624         out.write("</field>");
625     }
626     
627     /***
628      * Add attributes describing some access flags.
629      * This adds the attributes to the attr field.
630      *
631      * @param info Info describing the access flags.
632      * @throws IOException when there is an underlying IOException.
633      */
634     protected void addAccessFlags(AbstractInfo info) throws IOException {
635         out.write(" access=\"");
636         // Doesn't need escaping.
637         out.write(info.getAccessType());
638         out.write("\"");
639         if (info.isAbstract())
640             out.write(" abstract=\"yes\"");
641         if (info.isAnnotation())
642             out.write(" annotation=\"yes\"");
643         if (info.isBridge())
644             out.write(" bridge=\"yes\"");
645         if (info.isDeprecated())
646             out.write(" deprecated=\"yes\"");
647         if (info.isEnum())
648             out.write(" enum=\"yes\"");
649         if (info.isFinal())
650             out.write(" final=\"yes\"");
651         if (info.isInterface())
652             out.write(" interface=\"yes\"");
653         if (info.isNative())
654             out.write(" native=\"yes\"");
655         if (info.isStatic())
656             out.write(" static=\"yes\"");
657         if (info.isStrict())
658             out.write(" strict=\"yes\"");
659         if (info.isSuper())
660             out.write(" super=\"yes\"");
661         if (info.isSynchronized())
662             out.write(" synchronized=\"yes\"");
663         if (info.isSynthetic())
664             out.write(" synthetic=\"yes\"");
665         if (info.isTransient())
666             out.write(" transient=\"yes\"");
667         if (info.isVarargs())
668             out.write(" varargs=\"yes\"");
669         if (info.isVolatile())
670             out.write(" volatile=\"yes\"");
671     }
672     
673     /***
674      * Add the method nodes for the method descriptor.
675      * This writes out an &lt;arguments&gt; node containing the 
676      * argument types for the method, followed by a &lt;return&gt; node
677      * containing the return type.
678      *
679      * @param desc The descriptor for the method to write out.
680      * @throws IOException when there is an underlying IOException.
681      */
682     protected void addMethodNodes(String desc) throws IOException {
683         Type[] args = Type.getArgumentTypes(desc);
684         Type ret = Type.getReturnType(desc);
685         out.write("<arguments>");
686         for (int i = 0; i < args.length; i++)
687             addTypeNode(args[i]);
688         out.write("</arguments>");
689         out.write("<return>");
690         addTypeNode(ret);
691         out.write("</return>");
692     }
693     
694     /***
695      * Add a type node for the specified descriptor.
696      *
697      * @param desc A type descriptor.
698      * @throws IOException when there is an underlying IOException.
699      */
700     protected void addTypeNode(String desc) throws IOException {
701         addTypeNode(Type.getType(desc));
702     }
703     
704     /***
705      * Add a type node for the specified type.
706      * This writes out a &lt;type&gt; node with attributes describing
707      * the type.
708      *
709      * @param type The type to describe.
710      * @throws IOException when there is an underlying IOException.
711      */
712     protected void addTypeNode(Type type) throws IOException {
713         out.write("<type");
714         int i = type.getSort();
715         if (i == Type.ARRAY) {
716             out.write(" array=\"yes\" dimensions=\"");
717             out.write(""+type.getDimensions());
718             out.write("\"");
719             type = type.getElementType();
720             i = type.getSort();
721         }
722         switch (i) {
723         case Type.BOOLEAN:
724             out.write(" primitive=\"yes\" name=\"boolean\"/>");
725             break;
726         case Type.BYTE:
727             out.write(" primitive=\"yes\" name=\"byte\"/>");
728             break;
729         case Type.CHAR:
730             out.write(" primitive=\"yes\" name=\"char\"/>");
731             break;
732         case Type.DOUBLE:
733             out.write(" primitive=\"yes\" name=\"double\"/>");
734             break;
735         case Type.FLOAT:
736             out.write(" primitive=\"yes\" name=\"float\"/>");
737             break;
738         case Type.INT:
739             out.write(" primitive=\"yes\" name=\"int\"/>");
740             break;
741         case Type.LONG:
742             out.write(" primitive=\"yes\" name=\"long\"/>");
743             break;
744         case Type.OBJECT:
745             out.write(" name=\"");
746             out.write(xmlEscape(type.getInternalName()));
747             out.write("\"/>");
748             break;
749         case Type.SHORT:
750             out.write(" primitive=\"yes\" name=\"short\"/>");
751             break;
752         case Type.VOID:
753             out.write(" primitive=\"yes\" name=\"void\"/>");
754             break;
755         }
756     }
757 
758     /***
759      * Escape some text into a format suitable for output as xml.
760      *
761      * @param str the text to format
762      * @return the formatted text
763      */
764     private final String xmlEscape(final String str) {
765         StringBuffer ret = new StringBuffer(str.length());
766         for(int i=0;i<str.length();i++) {
767             char ch = str.charAt(i);
768             switch(ch) {
769                 case '<':
770                     ret.append("&lt;");
771                     break;
772                 case '&':
773                     ret.append("&amp;");
774                     break;
775                 case '>':
776                     ret.append("&gt;");
777                     break;
778                 default:
779                     ret.append(ch);
780             }
781         }
782         return ret.toString();
783     }
784 }