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
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 <diff> 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 <removed> 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 <removed> 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 <added> 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 <added> 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 <changed> 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 <classchanged> 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 <classchange> node, followed by a
396 * <from> node, with the old information about the class
397 * followed by a <to> 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 <fieldchange> node, followed by a
422 * <from> node, with the old information about the field
423 * followed by a <to> 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 <methodchange> node, followed by a
448 * <from> node, with the old information about the method
449 * followed by a <to> 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 <classchanged> 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 <changed> 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 <diff> 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 <class> node, which contains information about
521 * what interfaces are implemented each in a <implements> 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 <method> 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 <field> 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
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 <arguments> node containing the
676 * argument types for the method, followed by a <return> 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 <type> 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("<");
771 break;
772 case '&':
773 ret.append("&");
774 break;
775 case '>':
776 ret.append(">");
777 break;
778 default:
779 ret.append(ch);
780 }
781 }
782 return ret.toString();
783 }
784 }