• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package jdiff;
2 
3 import java.io.*;
4 import java.util.*;
5 
6 /* For SAX XML parsing */
7 import org.xml.sax.Attributes;
8 import org.xml.sax.SAXException;
9 import org.xml.sax.SAXParseException;
10 import org.xml.sax.XMLReader;
11 import org.xml.sax.InputSource;
12 import org.xml.sax.helpers.*;
13 
14 /**
15  * Creates a Comments from an XML file. The Comments object is the internal
16  * representation of the comments for the changes.
17  * All methods in this class for populating a Comments object are static.
18  *
19  * See the file LICENSE.txt for copyright details.
20  * @author Matthew Doar, mdoar@pobox.com
21  */
22 public class Comments {
23 
24     /**
25      * All the possible comments known about, accessible by the commentID.
26      */
27     public static Hashtable allPossibleComments = new Hashtable();
28 
29     /** The old Comments object which is populated from the file read in. */
30     private static Comments oldComments_ = null;
31 
32     /** Default constructor. */
Comments()33     public Comments() {
34         commentsList_ = new ArrayList(); // SingleComment[]
35     }
36 
37     // The list of comments elements associated with this objects
38     public List commentsList_ = null; // SingleComment[]
39 
40     /**
41      * Read the file where the XML for comments about the changes between
42      * the old API and new API is stored and create a Comments object for
43      * it. The Comments object may be null if no file exists.
44      */
readFile(String filename)45     public static Comments readFile(String filename) {
46         // If validation is desired, write out the appropriate comments.xsd
47         // file in the same directory as the comments XML file.
48         if (XMLToAPI.validateXML) {
49             writeXSD(filename);
50         }
51 
52         // If the file does not exist, return null
53         File f = new File(filename);
54         if (!f.exists())
55             return null;
56 
57         // The instance of the Comments object which is populated from the file.
58         oldComments_ = new Comments();
59         try {
60             DefaultHandler handler = new CommentsHandler(oldComments_);
61             XMLReader parser = null;
62             try {
63                 String parserName = System.getProperty("org.xml.sax.driver");
64                 if (parserName == null) {
65                     parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
66                 } else {
67                     // Let the underlying mechanisms try to work out which
68                     // class to instantiate
69                     parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
70                 }
71             } catch (SAXException saxe) {
72                 System.out.println("SAXException: " + saxe);
73                 saxe.printStackTrace();
74                 System.exit(1);
75             }
76 
77             if (XMLToAPI.validateXML) {
78                 parser.setFeature("http://xml.org/sax/features/namespaces", true);
79                 parser.setFeature("http://xml.org/sax/features/validation", true);
80                 parser.setFeature("http://apache.org/xml/features/validation/schema", true);
81             }
82             parser.setContentHandler(handler);
83             parser.setErrorHandler(handler);
84             parser.parse(new InputSource(new FileInputStream(new File(filename))));
85         } catch(org.xml.sax.SAXNotRecognizedException snre) {
86             System.out.println("SAX Parser does not recognize feature: " + snre);
87             snre.printStackTrace();
88             System.exit(1);
89         } catch(org.xml.sax.SAXNotSupportedException snse) {
90             System.out.println("SAX Parser feature is not supported: " + snse);
91             snse.printStackTrace();
92             System.exit(1);
93         } catch(org.xml.sax.SAXException saxe) {
94             System.out.println("SAX Exception parsing file '" + filename + "' : " + saxe);
95             saxe.printStackTrace();
96             System.exit(1);
97         } catch(java.io.IOException ioe) {
98             System.out.println("IOException parsing file '" + filename + "' : " + ioe);
99             ioe.printStackTrace();
100             System.exit(1);
101         }
102 
103         Collections.sort(oldComments_.commentsList_);
104         return oldComments_;
105     } //readFile()
106 
107     /**
108      * Write the XML Schema file used for validation.
109      */
writeXSD(String filename)110     public static void writeXSD(String filename) {
111         String xsdFileName = filename;
112         int idx = xsdFileName.lastIndexOf('\\');
113         int idx2 = xsdFileName.lastIndexOf('/');
114         if (idx == -1 && idx2 == -1) {
115             xsdFileName = "";
116         } else if (idx == -1 && idx2 != -1) {
117             xsdFileName = xsdFileName.substring(0, idx2+1);
118         } else if (idx != -1  && idx2 == -1) {
119             xsdFileName = xsdFileName.substring(0, idx+1);
120         } else if (idx != -1  && idx2 != -1) {
121             int max = idx2 > idx ? idx2 : idx;
122             xsdFileName = xsdFileName.substring(0, max+1);
123         }
124         xsdFileName += "comments.xsd";
125         try {
126             FileOutputStream fos = new FileOutputStream(xsdFileName);
127             PrintWriter xsdFile = new PrintWriter(fos);
128             // The contents of the comments.xsd file
129             xsdFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
130             xsdFile.println("<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">");
131             xsdFile.println();
132             xsdFile.println("<xsd:annotation>");
133             xsdFile.println("  <xsd:documentation>");
134             xsdFile.println("  Schema for JDiff comments.");
135             xsdFile.println("  </xsd:documentation>");
136             xsdFile.println("</xsd:annotation>");
137             xsdFile.println();
138             xsdFile.println("<xsd:element name=\"comments\" type=\"commentsType\"/>");
139             xsdFile.println();
140             xsdFile.println("<xsd:complexType name=\"commentsType\">");
141             xsdFile.println("  <xsd:sequence>");
142             xsdFile.println("    <xsd:element name=\"comment\" type=\"commentType\" minOccurs='0' maxOccurs='unbounded'/>");
143             xsdFile.println("  </xsd:sequence>");
144             xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
145             xsdFile.println("  <xsd:attribute name=\"jdversion\" type=\"xsd:string\"/>");
146             xsdFile.println("</xsd:complexType>");
147             xsdFile.println();
148             xsdFile.println("<xsd:complexType name=\"commentType\">");
149             xsdFile.println("  <xsd:sequence>");
150             xsdFile.println("    <xsd:element name=\"identifier\" type=\"identifierType\" minOccurs='1' maxOccurs='unbounded'/>");
151             xsdFile.println("    <xsd:element name=\"text\" type=\"xsd:string\" minOccurs='1' maxOccurs='1'/>");
152             xsdFile.println("  </xsd:sequence>");
153             xsdFile.println("</xsd:complexType>");
154             xsdFile.println();
155             xsdFile.println("<xsd:complexType name=\"identifierType\">");
156             xsdFile.println("  <xsd:attribute name=\"id\" type=\"xsd:string\"/>");
157             xsdFile.println("</xsd:complexType>");
158             xsdFile.println();
159             xsdFile.println("</xsd:schema>");
160             xsdFile.close();
161         } catch(IOException e) {
162             System.out.println("IO Error while attempting to create " + xsdFileName);
163             System.out.println("Error: " +  e.getMessage());
164             System.exit(1);
165         }
166     }
167 
168 //
169 // Methods to add data to a Comments object. Called by the XML parser and the
170 // report generator.
171 //
172 
173     /**
174      * Add the SingleComment object to the list of comments kept by this
175      * object.
176      */
addComment(SingleComment comment)177     public void addComment(SingleComment comment) {
178         commentsList_.add(comment);
179     }
180 
181 //
182 // Methods to get data from a Comments object. Called by the report generator
183 //
184 
185     /**
186      * The text placed into XML comments file where there is no comment yet.
187      * It never appears in reports.
188      */
189     public static final String placeHolderText = "InsertCommentsHere";
190 
191     /**
192      * Return the comment associated with the given id in the Comment object.
193      * If there is no such comment, return the placeHolderText.
194      */
getComment(Comments comments, String id)195     public static String getComment(Comments comments, String id) {
196         if (comments == null)
197             return placeHolderText;
198         SingleComment key = new SingleComment(id, null);
199         int idx = Collections.binarySearch(comments.commentsList_, key);
200         if (idx < 0) {
201             return placeHolderText;
202         } else {
203             int startIdx = comments.commentsList_.indexOf(key);
204             int endIdx = comments.commentsList_.indexOf(key);
205             int numIdx = endIdx - startIdx + 1;
206             if (numIdx != 1) {
207                 System.out.println("Warning: " + numIdx + " identical ids in the existing comments file. Using the first instance.");
208             }
209             SingleComment singleComment = (SingleComment)(comments.commentsList_.get(idx));
210             // Convert @link tags to links
211             return singleComment.text_;
212         }
213     }
214 
215     /**
216      * Convert @link tags to HTML links.
217      */
convertAtLinks(String text, String currentElement, PackageAPI pkg, ClassAPI cls)218     public static String convertAtLinks(String text, String currentElement,
219                                         PackageAPI pkg, ClassAPI cls) {
220         if (text == null)
221             return null;
222 
223         StringBuffer result = new StringBuffer();
224 
225         int state = -1;
226 
227         final int NORMAL_TEXT = -1;
228         final int IN_LINK = 1;
229         final int IN_LINK_IDENTIFIER = 2;
230         final int IN_LINK_IDENTIFIER_REFERENCE = 3;
231         final int IN_LINK_IDENTIFIER_REFERENCE_PARAMS = 6;
232         final int IN_LINK_LINKTEXT = 4;
233         final int END_OF_LINK = 5;
234 
235         StringBuffer identifier = null;
236         StringBuffer identifierReference = null;
237         StringBuffer linkText = null;
238 
239         // Figure out relative reference if required.
240         String ref = "";
241         if (currentElement.compareTo("class") == 0 ||
242             currentElement.compareTo("interface") == 0) {
243 	    ref = pkg.name_ + "." + cls.name_ + ".";
244         } else if (currentElement.compareTo("package") == 0) {
245 	    ref = pkg.name_ + ".";
246         }
247         ref = ref.replace('.', '/');
248 
249         for (int i=0; i < text.length(); i++) {
250 	    char c = text.charAt( i);
251 	    char nextChar = i < text.length()-1 ? text.charAt( i+1) : (char)-1;
252 	    int remainingChars = text.length() - i;
253 
254 	    switch (state) {
255 	    case NORMAL_TEXT:
256 		if (c == '{' && remainingChars >= 5) {
257 		    if ("{@link".equals(text.substring(i, i + 6))) {
258 			state = IN_LINK;
259 			identifier = null;
260 			identifierReference = null;
261 			linkText = null;
262 			i += 5;
263 			continue;
264 		    }
265 		}
266 		result.append( c);
267 		break;
268 	    case IN_LINK:
269 		if (Character.isWhitespace(nextChar)) continue;
270 		if (nextChar == '}') {
271 		    // End of the link
272 		    state = END_OF_LINK;
273 		} else if (!Character.isWhitespace(nextChar)) {
274 		    state = IN_LINK_IDENTIFIER;
275 		}
276 		break;
277             case IN_LINK_IDENTIFIER:
278 		if (identifier == null) {
279 		    identifier = new StringBuffer();
280 		}
281 
282 		if (c == '#') {
283 		    // We have a reference.
284 		    state = IN_LINK_IDENTIFIER_REFERENCE;
285 		    // Don't append #
286 		    continue;
287 		} else if (Character.isWhitespace(c)) {
288 		    // We hit some whitespace: the next character is the beginning
289 		    // of the link text.
290 		    state = IN_LINK_LINKTEXT;
291 		    continue;
292 		}
293 		identifier.append(c);
294 		// Check for a } that ends the link.
295 		if (nextChar == '}') {
296 		    state = END_OF_LINK;
297 		}
298 		break;
299             case IN_LINK_IDENTIFIER_REFERENCE:
300 		if (identifierReference == null) {
301 		    identifierReference = new StringBuffer();
302 		}
303 		if (Character.isWhitespace(c)) {
304 		    state = IN_LINK_LINKTEXT;
305 		    continue;
306 		}
307 		identifierReference.append(c);
308 
309 		if (c == '(') {
310 		    state = IN_LINK_IDENTIFIER_REFERENCE_PARAMS;
311 		}
312 
313 		if (nextChar == '}') {
314 		    state = END_OF_LINK;
315 		}
316 		break;
317             case IN_LINK_IDENTIFIER_REFERENCE_PARAMS:
318 		// We're inside the parameters of a reference. Spaces are allowed.
319 		if (c == ')') {
320 		    state = IN_LINK_IDENTIFIER_REFERENCE;
321 		}
322 		identifierReference.append(c);
323 		if (nextChar == '}') {
324 		    state = END_OF_LINK;
325 		}
326 		break;
327             case IN_LINK_LINKTEXT:
328 		if (linkText == null) linkText = new StringBuffer();
329 
330 		linkText.append(c);
331 
332 		if (nextChar == '}') {
333 		    state = END_OF_LINK;
334 		}
335 		break;
336             case END_OF_LINK:
337 		if (identifier != null) {
338 		    result.append("<A HREF=\"");
339 		    result.append(HTMLReportGenerator.newDocPrefix);
340 		    result.append(ref);
341 		    result.append(identifier.toString().replace('.', '/'));
342 		    result.append(".html");
343 		    if (identifierReference != null) {
344 			result.append("#");
345 			result.append(identifierReference);
346 		    }
347 		    result.append("\">");   // target=_top?
348 
349 		    result.append("<TT>");
350 		    if (linkText != null) {
351 			result.append(linkText);
352 		    } else {
353 			result.append(identifier);
354 			if (identifierReference != null) {
355 			    result.append(".");
356 			    result.append(identifierReference);
357 			}
358 		    }
359 		    result.append("</TT>");
360 		    result.append("</A>");
361 		}
362 		state = NORMAL_TEXT;
363 		break;
364 	    }
365         }
366         return result.toString();
367     }
368 
369 //
370 // Methods to write a Comments object out to a file.
371 //
372 
373     /**
374      * Write the XML representation of comments to a file.
375      *
376      * @param outputFileName The name of the comments file.
377      * @param oldComments The old comments on the changed APIs.
378      * @param newComments The new comments on the changed APIs.
379      * @return true if no problems encountered
380      */
writeFile(String outputFileName, Comments newComments)381     public static boolean writeFile(String outputFileName,
382                                     Comments newComments) {
383         try {
384             FileOutputStream fos = new FileOutputStream(outputFileName);
385             outputFile = new PrintWriter(fos);
386             newComments.emitXMLHeader(outputFileName);
387             newComments.emitComments();
388             newComments.emitXMLFooter();
389             outputFile.close();
390         } catch(IOException e) {
391             System.out.println("IO Error while attempting to create " + outputFileName);
392             System.out.println("Error: "+ e.getMessage());
393             System.exit(1);
394         }
395         return true;
396     }
397 
398     /**
399      * Write the Comments object out in XML.
400      */
emitComments()401     public void emitComments() {
402         Iterator iter = commentsList_.iterator();
403         while (iter.hasNext()) {
404             SingleComment currComment = (SingleComment)(iter.next());
405             if (!currComment.isUsed_)
406                 outputFile.println("<!-- This comment is no longer used ");
407             outputFile.println("<comment>");
408             outputFile.println("  <identifier id=\"" + currComment.id_ + "\"/>");
409             outputFile.println("  <text>");
410             outputFile.println("    " + currComment.text_);
411             outputFile.println("  </text>");
412             outputFile.println("</comment>");
413             if (!currComment.isUsed_)
414                 outputFile.println("-->");
415         }
416     }
417 
418     /**
419      * Dump the contents of a Comments object out for inspection.
420      */
dump()421     public void dump() {
422         Iterator iter = commentsList_.iterator();
423         int i = 0;
424         while (iter.hasNext()) {
425             i++;
426             SingleComment currComment = (SingleComment)(iter.next());
427             System.out.println("Comment " + i);
428             System.out.println("id = " + currComment.id_);
429             System.out.println("text = \"" + currComment.text_ + "\"");
430             System.out.println("isUsed = " + currComment.isUsed_);
431         }
432     }
433 
434     /**
435      * Emit messages about which comments are now unused and which are new.
436      */
noteDifferences(Comments oldComments, Comments newComments)437     public static void noteDifferences(Comments oldComments, Comments newComments) {
438         if (oldComments == null) {
439             System.out.println("Note: all the comments have been newly generated");
440             return;
441         }
442 
443         // See which comment ids are no longer used and add those entries to
444         // the new comments, marking them as unused.
445         Iterator iter = oldComments.commentsList_.iterator();
446         while (iter.hasNext()) {
447             SingleComment oldComment = (SingleComment)(iter.next());
448             int idx = Collections.binarySearch(newComments.commentsList_, oldComment);
449             if (idx < 0) {
450                 System.out.println("Warning: comment \"" + oldComment.id_ + "\" is no longer used.");
451                 oldComment.isUsed_ = false;
452                 newComments.commentsList_.add(oldComment);
453             }
454         }
455 
456     }
457 
458     /**
459      * Emit the XML header.
460      */
emitXMLHeader(String filename)461     public void emitXMLHeader(String filename) {
462         outputFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
463         outputFile.println("<comments");
464         outputFile.println("  xmlns:xsi='" + RootDocToXML.baseURI + "/2001/XMLSchema-instance'");
465         outputFile.println("  xsi:noNamespaceSchemaLocation='comments.xsd'");
466         // Extract the identifier from the filename by removing the suffix
467         int idx = filename.lastIndexOf('.');
468         String apiIdentifier = filename.substring(0, idx);
469         // Also remove the output directory and directory separator if present
470         if (HTMLReportGenerator.commentsDir != null)
471 	    apiIdentifier = apiIdentifier.substring(HTMLReportGenerator.commentsDir.length()+1);
472         else if (HTMLReportGenerator.outputDir != null)
473             apiIdentifier = apiIdentifier.substring(HTMLReportGenerator.outputDir.length()+1);
474         // Also remove "user_comments_for_"
475         apiIdentifier = apiIdentifier.substring(18);
476         outputFile.println("  name=\"" + apiIdentifier + "\"");
477         outputFile.println("  jdversion=\"" + JDiff.version + "\">");
478         outputFile.println();
479         outputFile.println("<!-- Use this file to enter an API change description. For example, when you remove a class, ");
480         outputFile.println("     you can enter a comment for that class that points developers to the replacement class. ");
481         outputFile.println("     You can also provide a change summary for modified API, to give an overview of the changes ");
482         outputFile.println("     why they were made, workarounds, etc.  -->");
483         outputFile.println();
484         outputFile.println("<!-- When the API diffs report is generated, the comments in this file get added to the tables of ");
485         outputFile.println("     removed, added, and modified packages, classes, methods, and fields. This file does not ship ");
486         outputFile.println("     with the final report. -->");
487         outputFile.println();
488         outputFile.println("<!-- The id attribute in an identifier element identifies the change as noted in the report. ");
489         outputFile.println("     An id has the form package[.class[.[ctor|method|field].signature]], where [] indicates optional ");
490         outputFile.println("     text. A comment element can have multiple identifier elements, which will will cause the same ");
491         outputFile.println("     text to appear at each place in the report, but will be converted to separate comments when the ");
492         outputFile.println("     comments file is used. -->");
493         outputFile.println();
494         outputFile.println("<!-- HTML tags in the text field will appear in the report. You also need to close p HTML elements, ");
495         outputFile.println("     used for paragraphs - see the top-level documentation. -->");
496         outputFile.println();
497         outputFile.println("<!-- You can include standard javadoc links in your change descriptions. You can use the @first command  ");
498         outputFile.println("     to cause jdiff to include the first line of the API documentation. You also need to close p HTML ");
499         outputFile.println("     elements, used for paragraphs - see the top-level documentation. -->");
500         outputFile.println();
501     }
502 
503     /**
504      * Emit the XML footer.
505      */
emitXMLFooter()506     public void emitXMLFooter() {
507         outputFile.println();
508         outputFile.println("</comments>");
509     }
510 
511     private static List oldAPIList = null;
512     private static List newAPIList = null;
513 
514     /**
515      * Return true if the given HTML tag has no separate </tag> end element.
516      *
517      * If you want to be able to use sloppy HTML in your comments, then you can
518      * add the element, e.g. li back into the condition here. However, if you
519      * then become more careful and do provide the closing tag, the output is
520      * generally just the closing tag, which is incorrect.
521      *
522      * tag.equalsIgnoreCase("tr") || // Is sometimes minimized
523      * tag.equalsIgnoreCase("th") || // Is sometimes minimized
524      * tag.equalsIgnoreCase("td") || // Is sometimes minimized
525      * tag.equalsIgnoreCase("dt") || // Is sometimes minimized
526      * tag.equalsIgnoreCase("dd") || // Is sometimes minimized
527      * tag.equalsIgnoreCase("img") || // Is sometimes minimized
528      * tag.equalsIgnoreCase("code") || // Is sometimes minimized (error)
529      * tag.equalsIgnoreCase("font") || // Is sometimes minimized (error)
530      * tag.equalsIgnoreCase("ul") || // Is sometimes minimized
531      * tag.equalsIgnoreCase("ol") || // Is sometimes minimized
532      * tag.equalsIgnoreCase("li") // Is sometimes minimized
533      */
isMinimizedTag(String tag)534     public static boolean isMinimizedTag(String tag) {
535         if (tag.equalsIgnoreCase("p") ||
536             tag.equalsIgnoreCase("br") ||
537             tag.equalsIgnoreCase("hr")
538             ) {
539             return true;
540 	}
541         return false;
542     }
543 
544     /**
545      * The file where the XML representing the new Comments object is stored.
546      */
547     private static PrintWriter outputFile = null;
548 
549 }
550 
551 
552