aboutsummaryrefslogtreecommitdiff
path: root/src/jdiff/Comments.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jdiff/Comments.java')
-rwxr-xr-xsrc/jdiff/Comments.java551
1 files changed, 551 insertions, 0 deletions
diff --git a/src/jdiff/Comments.java b/src/jdiff/Comments.java
new file mode 100755
index 0000000..066e4f0
--- /dev/null
+++ b/src/jdiff/Comments.java
@@ -0,0 +1,551 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/* For SAX XML parsing */
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.*;
+
+/**
+ * Creates a Comments from an XML file. The Comments object is the internal
+ * representation of the comments for the changes.
+ * All methods in this class for populating a Comments object are static.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+public class Comments {
+
+ /**
+ * All the possible comments known about, accessible by the commentID.
+ */
+ public static Hashtable allPossibleComments = new Hashtable();
+
+ /** The old Comments object which is populated from the file read in. */
+ private static Comments oldComments_ = null;
+
+ /** Default constructor. */
+ public Comments() {
+ commentsList_ = new ArrayList(); // SingleComment[]
+ }
+
+ // The list of comments elements associated with this objects
+ public List commentsList_ = null; // SingleComment[]
+
+ /**
+ * Read the file where the XML for comments about the changes between
+ * the old API and new API is stored and create a Comments object for
+ * it. The Comments object may be null if no file exists.
+ */
+ public static Comments readFile(String filename) {
+ // If validation is desired, write out the appropriate comments.xsd
+ // file in the same directory as the comments XML file.
+ if (XMLToAPI.validateXML) {
+ writeXSD(filename);
+ }
+
+ // If the file does not exist, return null
+ File f = new File(filename);
+ if (!f.exists())
+ return null;
+
+ // The instance of the Comments object which is populated from the file.
+ oldComments_ = new Comments();
+ try {
+ DefaultHandler handler = new CommentsHandler(oldComments_);
+ XMLReader parser = null;
+ try {
+ String parserName = System.getProperty("org.xml.sax.driver");
+ if (parserName == null) {
+ parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
+ } else {
+ // Let the underlying mechanisms try to work out which
+ // class to instantiate
+ parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
+ }
+ } catch (SAXException saxe) {
+ System.out.println("SAXException: " + saxe);
+ saxe.printStackTrace();
+ System.exit(1);
+ }
+
+ if (XMLToAPI.validateXML) {
+ parser.setFeature("http://xml.org/sax/features/namespaces", true);
+ parser.setFeature("http://xml.org/sax/features/validation", true);
+ parser.setFeature("http://apache.org/xml/features/validation/schema", true);
+ }
+ parser.setContentHandler(handler);
+ parser.setErrorHandler(handler);
+ parser.parse(new InputSource(new FileInputStream(new File(filename))));
+ } catch(org.xml.sax.SAXNotRecognizedException snre) {
+ System.out.println("SAX Parser does not recognize feature: " + snre);
+ snre.printStackTrace();
+ System.exit(1);
+ } catch(org.xml.sax.SAXNotSupportedException snse) {
+ System.out.println("SAX Parser feature is not supported: " + snse);
+ snse.printStackTrace();
+ System.exit(1);
+ } catch(org.xml.sax.SAXException saxe) {
+ System.out.println("SAX Exception parsing file '" + filename + "' : " + saxe);
+ saxe.printStackTrace();
+ System.exit(1);
+ } catch(java.io.IOException ioe) {
+ System.out.println("IOException parsing file '" + filename + "' : " + ioe);
+ ioe.printStackTrace();
+ System.exit(1);
+ }
+
+ Collections.sort(oldComments_.commentsList_);
+ return oldComments_;
+ } //readFile()
+
+ /**
+ * Write the XML Schema file used for validation.
+ */
+ public static void writeXSD(String filename) {
+ String xsdFileName = filename;
+ int idx = xsdFileName.lastIndexOf('\\');
+ int idx2 = xsdFileName.lastIndexOf('/');
+ if (idx == -1 && idx2 == -1) {
+ xsdFileName = "";
+ } else if (idx == -1 && idx2 != -1) {
+ xsdFileName = xsdFileName.substring(0, idx2+1);
+ } else if (idx != -1 && idx2 == -1) {
+ xsdFileName = xsdFileName.substring(0, idx+1);
+ } else if (idx != -1 && idx2 != -1) {
+ int max = idx2 > idx ? idx2 : idx;
+ xsdFileName = xsdFileName.substring(0, max+1);
+ }
+ xsdFileName += "comments.xsd";
+ try {
+ FileOutputStream fos = new FileOutputStream(xsdFileName);
+ PrintWriter xsdFile = new PrintWriter(fos);
+ // The contents of the comments.xsd file
+ xsdFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
+ xsdFile.println("<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">");
+ xsdFile.println();
+ xsdFile.println("<xsd:annotation>");
+ xsdFile.println(" <xsd:documentation>");
+ xsdFile.println(" Schema for JDiff comments.");
+ xsdFile.println(" </xsd:documentation>");
+ xsdFile.println("</xsd:annotation>");
+ xsdFile.println();
+ xsdFile.println("<xsd:element name=\"comments\" type=\"commentsType\"/>");
+ xsdFile.println();
+ xsdFile.println("<xsd:complexType name=\"commentsType\">");
+ xsdFile.println(" <xsd:sequence>");
+ xsdFile.println(" <xsd:element name=\"comment\" type=\"commentType\" minOccurs='0' maxOccurs='unbounded'/>");
+ xsdFile.println(" </xsd:sequence>");
+ xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+ xsdFile.println(" <xsd:attribute name=\"jdversion\" type=\"xsd:string\"/>");
+ xsdFile.println("</xsd:complexType>");
+ xsdFile.println();
+ xsdFile.println("<xsd:complexType name=\"commentType\">");
+ xsdFile.println(" <xsd:sequence>");
+ xsdFile.println(" <xsd:element name=\"identifier\" type=\"identifierType\" minOccurs='1' maxOccurs='unbounded'/>");
+ xsdFile.println(" <xsd:element name=\"text\" type=\"xsd:string\" minOccurs='1' maxOccurs='1'/>");
+ xsdFile.println(" </xsd:sequence>");
+ xsdFile.println("</xsd:complexType>");
+ xsdFile.println();
+ xsdFile.println("<xsd:complexType name=\"identifierType\">");
+ xsdFile.println(" <xsd:attribute name=\"id\" type=\"xsd:string\"/>");
+ xsdFile.println("</xsd:complexType>");
+ xsdFile.println();
+ xsdFile.println("</xsd:schema>");
+ xsdFile.close();
+ } catch(IOException e) {
+ System.out.println("IO Error while attempting to create " + xsdFileName);
+ System.out.println("Error: " + e.getMessage());
+ System.exit(1);
+ }
+ }
+
+//
+// Methods to add data to a Comments object. Called by the XML parser and the
+// report generator.
+//
+
+ /**
+ * Add the SingleComment object to the list of comments kept by this
+ * object.
+ */
+ public void addComment(SingleComment comment) {
+ commentsList_.add(comment);
+ }
+
+//
+// Methods to get data from a Comments object. Called by the report generator
+//
+
+ /**
+ * The text placed into XML comments file where there is no comment yet.
+ * It never appears in reports.
+ */
+ public static final String placeHolderText = "InsertCommentsHere";
+
+ /**
+ * Return the comment associated with the given id in the Comment object.
+ * If there is no such comment, return the placeHolderText.
+ */
+ public static String getComment(Comments comments, String id) {
+ if (comments == null)
+ return placeHolderText;
+ SingleComment key = new SingleComment(id, null);
+ int idx = Collections.binarySearch(comments.commentsList_, key);
+ if (idx < 0) {
+ return placeHolderText;
+ } else {
+ int startIdx = comments.commentsList_.indexOf(key);
+ int endIdx = comments.commentsList_.indexOf(key);
+ int numIdx = endIdx - startIdx + 1;
+ if (numIdx != 1) {
+ System.out.println("Warning: " + numIdx + " identical ids in the existing comments file. Using the first instance.");
+ }
+ SingleComment singleComment = (SingleComment)(comments.commentsList_.get(idx));
+ // Convert @link tags to links
+ return singleComment.text_;
+ }
+ }
+
+ /**
+ * Convert @link tags to HTML links.
+ */
+ public static String convertAtLinks(String text, String currentElement,
+ PackageAPI pkg, ClassAPI cls) {
+ if (text == null)
+ return null;
+
+ StringBuffer result = new StringBuffer();
+
+ int state = -1;
+
+ final int NORMAL_TEXT = -1;
+ final int IN_LINK = 1;
+ final int IN_LINK_IDENTIFIER = 2;
+ final int IN_LINK_IDENTIFIER_REFERENCE = 3;
+ final int IN_LINK_IDENTIFIER_REFERENCE_PARAMS = 6;
+ final int IN_LINK_LINKTEXT = 4;
+ final int END_OF_LINK = 5;
+
+ StringBuffer identifier = null;
+ StringBuffer identifierReference = null;
+ StringBuffer linkText = null;
+
+ // Figure out relative reference if required.
+ String ref = "";
+ if (currentElement.compareTo("class") == 0 ||
+ currentElement.compareTo("interface") == 0) {
+ ref = pkg.name_ + "." + cls.name_ + ".";
+ } else if (currentElement.compareTo("package") == 0) {
+ ref = pkg.name_ + ".";
+ }
+ ref = ref.replace('.', '/');
+
+ for (int i=0; i < text.length(); i++) {
+ char c = text.charAt( i);
+ char nextChar = i < text.length()-1 ? text.charAt( i+1) : (char)-1;
+ int remainingChars = text.length() - i;
+
+ switch (state) {
+ case NORMAL_TEXT:
+ if (c == '{' && remainingChars >= 5) {
+ if ("{@link".equals(text.substring(i, i + 6))) {
+ state = IN_LINK;
+ identifier = null;
+ identifierReference = null;
+ linkText = null;
+ i += 5;
+ continue;
+ }
+ }
+ result.append( c);
+ break;
+ case IN_LINK:
+ if (Character.isWhitespace(nextChar)) continue;
+ if (nextChar == '}') {
+ // End of the link
+ state = END_OF_LINK;
+ } else if (!Character.isWhitespace(nextChar)) {
+ state = IN_LINK_IDENTIFIER;
+ }
+ break;
+ case IN_LINK_IDENTIFIER:
+ if (identifier == null) {
+ identifier = new StringBuffer();
+ }
+
+ if (c == '#') {
+ // We have a reference.
+ state = IN_LINK_IDENTIFIER_REFERENCE;
+ // Don't append #
+ continue;
+ } else if (Character.isWhitespace(c)) {
+ // We hit some whitespace: the next character is the beginning
+ // of the link text.
+ state = IN_LINK_LINKTEXT;
+ continue;
+ }
+ identifier.append(c);
+ // Check for a } that ends the link.
+ if (nextChar == '}') {
+ state = END_OF_LINK;
+ }
+ break;
+ case IN_LINK_IDENTIFIER_REFERENCE:
+ if (identifierReference == null) {
+ identifierReference = new StringBuffer();
+ }
+ if (Character.isWhitespace(c)) {
+ state = IN_LINK_LINKTEXT;
+ continue;
+ }
+ identifierReference.append(c);
+
+ if (c == '(') {
+ state = IN_LINK_IDENTIFIER_REFERENCE_PARAMS;
+ }
+
+ if (nextChar == '}') {
+ state = END_OF_LINK;
+ }
+ break;
+ case IN_LINK_IDENTIFIER_REFERENCE_PARAMS:
+ // We're inside the parameters of a reference. Spaces are allowed.
+ if (c == ')') {
+ state = IN_LINK_IDENTIFIER_REFERENCE;
+ }
+ identifierReference.append(c);
+ if (nextChar == '}') {
+ state = END_OF_LINK;
+ }
+ break;
+ case IN_LINK_LINKTEXT:
+ if (linkText == null) linkText = new StringBuffer();
+
+ linkText.append(c);
+
+ if (nextChar == '}') {
+ state = END_OF_LINK;
+ }
+ break;
+ case END_OF_LINK:
+ if (identifier != null) {
+ result.append("<A HREF=\"");
+ result.append(HTMLReportGenerator.newDocPrefix);
+ result.append(ref);
+ result.append(identifier.toString().replace('.', '/'));
+ result.append(".html");
+ if (identifierReference != null) {
+ result.append("#");
+ result.append(identifierReference);
+ }
+ result.append("\">"); // target=_top?
+
+ result.append("<TT>");
+ if (linkText != null) {
+ result.append(linkText);
+ } else {
+ result.append(identifier);
+ if (identifierReference != null) {
+ result.append(".");
+ result.append(identifierReference);
+ }
+ }
+ result.append("</TT>");
+ result.append("</A>");
+ }
+ state = NORMAL_TEXT;
+ break;
+ }
+ }
+ return result.toString();
+ }
+
+//
+// Methods to write a Comments object out to a file.
+//
+
+ /**
+ * Write the XML representation of comments to a file.
+ *
+ * @param outputFileName The name of the comments file.
+ * @param oldComments The old comments on the changed APIs.
+ * @param newComments The new comments on the changed APIs.
+ * @return true if no problems encountered
+ */
+ public static boolean writeFile(String outputFileName,
+ Comments newComments) {
+ try {
+ FileOutputStream fos = new FileOutputStream(outputFileName);
+ outputFile = new PrintWriter(fos);
+ newComments.emitXMLHeader(outputFileName);
+ newComments.emitComments();
+ newComments.emitXMLFooter();
+ outputFile.close();
+ } catch(IOException e) {
+ System.out.println("IO Error while attempting to create " + outputFileName);
+ System.out.println("Error: "+ e.getMessage());
+ System.exit(1);
+ }
+ return true;
+ }
+
+ /**
+ * Write the Comments object out in XML.
+ */
+ public void emitComments() {
+ Iterator iter = commentsList_.iterator();
+ while (iter.hasNext()) {
+ SingleComment currComment = (SingleComment)(iter.next());
+ if (!currComment.isUsed_)
+ outputFile.println("<!-- This comment is no longer used ");
+ outputFile.println("<comment>");
+ outputFile.println(" <identifier id=\"" + currComment.id_ + "\"/>");
+ outputFile.println(" <text>");
+ outputFile.println(" " + currComment.text_);
+ outputFile.println(" </text>");
+ outputFile.println("</comment>");
+ if (!currComment.isUsed_)
+ outputFile.println("-->");
+ }
+ }
+
+ /**
+ * Dump the contents of a Comments object out for inspection.
+ */
+ public void dump() {
+ Iterator iter = commentsList_.iterator();
+ int i = 0;
+ while (iter.hasNext()) {
+ i++;
+ SingleComment currComment = (SingleComment)(iter.next());
+ System.out.println("Comment " + i);
+ System.out.println("id = " + currComment.id_);
+ System.out.println("text = \"" + currComment.text_ + "\"");
+ System.out.println("isUsed = " + currComment.isUsed_);
+ }
+ }
+
+ /**
+ * Emit messages about which comments are now unused and which are new.
+ */
+ public static void noteDifferences(Comments oldComments, Comments newComments) {
+ if (oldComments == null) {
+ System.out.println("Note: all the comments have been newly generated");
+ return;
+ }
+
+ // See which comment ids are no longer used and add those entries to
+ // the new comments, marking them as unused.
+ Iterator iter = oldComments.commentsList_.iterator();
+ while (iter.hasNext()) {
+ SingleComment oldComment = (SingleComment)(iter.next());
+ int idx = Collections.binarySearch(newComments.commentsList_, oldComment);
+ if (idx < 0) {
+ System.out.println("Warning: comment \"" + oldComment.id_ + "\" is no longer used.");
+ oldComment.isUsed_ = false;
+ newComments.commentsList_.add(oldComment);
+ }
+ }
+
+ }
+
+ /**
+ * Emit the XML header.
+ */
+ public void emitXMLHeader(String filename) {
+ outputFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
+ outputFile.println("<comments");
+ outputFile.println(" xmlns:xsi='" + RootDocToXML.baseURI + "/2001/XMLSchema-instance'");
+ outputFile.println(" xsi:noNamespaceSchemaLocation='comments.xsd'");
+ // Extract the identifier from the filename by removing the suffix
+ int idx = filename.lastIndexOf('.');
+ String apiIdentifier = filename.substring(0, idx);
+ // Also remove the output directory and directory separator if present
+ if (HTMLReportGenerator.commentsDir != null)
+ apiIdentifier = apiIdentifier.substring(HTMLReportGenerator.commentsDir.length()+1);
+ else if (HTMLReportGenerator.outputDir != null)
+ apiIdentifier = apiIdentifier.substring(HTMLReportGenerator.outputDir.length()+1);
+ // Also remove "user_comments_for_"
+ apiIdentifier = apiIdentifier.substring(18);
+ outputFile.println(" name=\"" + apiIdentifier + "\"");
+ outputFile.println(" jdversion=\"" + JDiff.version + "\">");
+ outputFile.println();
+ outputFile.println("<!-- Use this file to enter an API change description. For example, when you remove a class, ");
+ outputFile.println(" you can enter a comment for that class that points developers to the replacement class. ");
+ outputFile.println(" You can also provide a change summary for modified API, to give an overview of the changes ");
+ outputFile.println(" why they were made, workarounds, etc. -->");
+ outputFile.println();
+ outputFile.println("<!-- When the API diffs report is generated, the comments in this file get added to the tables of ");
+ outputFile.println(" removed, added, and modified packages, classes, methods, and fields. This file does not ship ");
+ outputFile.println(" with the final report. -->");
+ outputFile.println();
+ outputFile.println("<!-- The id attribute in an identifier element identifies the change as noted in the report. ");
+ outputFile.println(" An id has the form package[.class[.[ctor|method|field].signature]], where [] indicates optional ");
+ outputFile.println(" text. A comment element can have multiple identifier elements, which will will cause the same ");
+ outputFile.println(" text to appear at each place in the report, but will be converted to separate comments when the ");
+ outputFile.println(" comments file is used. -->");
+ outputFile.println();
+ outputFile.println("<!-- HTML tags in the text field will appear in the report. You also need to close p HTML elements, ");
+ outputFile.println(" used for paragraphs - see the top-level documentation. -->");
+ outputFile.println();
+ outputFile.println("<!-- You can include standard javadoc links in your change descriptions. You can use the @first command ");
+ outputFile.println(" to cause jdiff to include the first line of the API documentation. You also need to close p HTML ");
+ outputFile.println(" elements, used for paragraphs - see the top-level documentation. -->");
+ outputFile.println();
+ }
+
+ /**
+ * Emit the XML footer.
+ */
+ public void emitXMLFooter() {
+ outputFile.println();
+ outputFile.println("</comments>");
+ }
+
+ private static List oldAPIList = null;
+ private static List newAPIList = null;
+
+ /**
+ * Return true if the given HTML tag has no separate </tag> end element.
+ *
+ * If you want to be able to use sloppy HTML in your comments, then you can
+ * add the element, e.g. li back into the condition here. However, if you
+ * then become more careful and do provide the closing tag, the output is
+ * generally just the closing tag, which is incorrect.
+ *
+ * tag.equalsIgnoreCase("tr") || // Is sometimes minimized
+ * tag.equalsIgnoreCase("th") || // Is sometimes minimized
+ * tag.equalsIgnoreCase("td") || // Is sometimes minimized
+ * tag.equalsIgnoreCase("dt") || // Is sometimes minimized
+ * tag.equalsIgnoreCase("dd") || // Is sometimes minimized
+ * tag.equalsIgnoreCase("img") || // Is sometimes minimized
+ * tag.equalsIgnoreCase("code") || // Is sometimes minimized (error)
+ * tag.equalsIgnoreCase("font") || // Is sometimes minimized (error)
+ * tag.equalsIgnoreCase("ul") || // Is sometimes minimized
+ * tag.equalsIgnoreCase("ol") || // Is sometimes minimized
+ * tag.equalsIgnoreCase("li") // Is sometimes minimized
+ */
+ public static boolean isMinimizedTag(String tag) {
+ if (tag.equalsIgnoreCase("p") ||
+ tag.equalsIgnoreCase("br") ||
+ tag.equalsIgnoreCase("hr")
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * The file where the XML representing the new Comments object is stored.
+ */
+ private static PrintWriter outputFile = null;
+
+}
+
+