| package jdiff; |
| |
| import java.io.*; |
| import java.util.*; |
| |
| /* For SAX parsing in APIHandler */ |
| import org.xml.sax.Attributes; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| import org.xml.sax.XMLReader; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * Handle the parsing of an XML file and the generation of an API object. |
| * |
| * See the file LICENSE.txt for copyright details. |
| * @author Matthew Doar, mdoar@pobox.com |
| */ |
| class APIHandler extends DefaultHandler { |
| |
| /** The API object which is populated from the XML file. */ |
| public API api_; |
| |
| /** Default constructor. */ |
| public APIHandler(API api, boolean createGlobalComments) { |
| api_ = api; |
| createGlobalComments_ = createGlobalComments; |
| tagStack = new LinkedList(); |
| } |
| |
| /** If set, then check that each comment is a sentence. */ |
| public static boolean checkIsSentence = false; |
| |
| /** |
| * Contains the name of the current package element type |
| * where documentation is being added. Also used as the level |
| * at which to add documentation into an element, i.e. class-level |
| * or package-level. |
| */ |
| private String currentElement = null; |
| |
| /** If set, then create the global list of comments. */ |
| private boolean createGlobalComments_ = false; |
| |
| /** Set if inside a doc element. */ |
| private boolean inDoc = false; |
| |
| /** The current comment text being assembled. */ |
| private String currentText = null; |
| |
| /** The current text from deprecation, null if empty. */ |
| private String currentDepText = null; |
| |
| /** |
| * The stack of SingleComment objects awaiting the comment text |
| * currently being assembled. |
| */ |
| private LinkedList tagStack = null; |
| |
| /** Called at the start of the document. */ |
| public void startDocument() { |
| } |
| |
| /** Called when the end of the document is reached. */ |
| public void endDocument() { |
| if (trace) |
| api_.dump(); |
| System.out.println(" finished"); |
| } |
| |
| /** Called when a new element is started. */ |
| public void startElement(java.lang.String uri, java.lang.String localName, |
| java.lang.String qName, Attributes attributes) { |
| // The change to JAXP compliance produced this change. |
| if (localName.equals("")) |
| localName = qName; |
| if (localName.compareTo("api") == 0) { |
| String apiName = attributes.getValue("name"); |
| String version = attributes.getValue("jdversion"); // Not used yet |
| XMLToAPI.nameAPI(apiName); |
| } else if (localName.compareTo("package") == 0) { |
| currentElement = localName; |
| String pkgName = attributes.getValue("name"); |
| XMLToAPI.addPackage(pkgName); |
| } else if (localName.compareTo("class") == 0) { |
| currentElement = localName; |
| String className = attributes.getValue("name"); |
| String parentName = attributes.getValue("extends"); |
| boolean isAbstract = false; |
| if (attributes.getValue("abstract").compareTo("true") == 0) |
| isAbstract = true; |
| XMLToAPI.addClass(className, parentName, isAbstract, getModifiers(attributes)); |
| } else if (localName.compareTo("interface") == 0) { |
| currentElement = localName; |
| String className = attributes.getValue("name"); |
| String parentName = attributes.getValue("extends"); |
| boolean isAbstract = false; |
| if (attributes.getValue("abstract").compareTo("true") == 0) |
| isAbstract = true; |
| XMLToAPI.addInterface(className, parentName, isAbstract, getModifiers(attributes)); |
| } else if (localName.compareTo("implements") == 0) { |
| String interfaceName = attributes.getValue("name"); |
| XMLToAPI.addImplements(interfaceName); |
| } else if (localName.compareTo("constructor") == 0) { |
| currentElement = localName; |
| String ctorName = attributes.getValue("name"); |
| String ctorType = attributes.getValue("type"); |
| XMLToAPI.addCtor(ctorName, ctorType, getModifiers(attributes)); |
| } else if (localName.compareTo("method") == 0) { |
| currentElement = localName; |
| String methodName = attributes.getValue("name"); |
| String returnType = attributes.getValue("return"); |
| boolean isAbstract = false; |
| if (attributes.getValue("abstract").compareTo("true") == 0) |
| isAbstract = true; |
| boolean isNative = false; |
| if (attributes.getValue("native").compareTo("true") == 0) |
| isNative = true; |
| boolean isSynchronized = false; |
| if (attributes.getValue("synchronized").compareTo("true") == 0) |
| isSynchronized = true; |
| XMLToAPI.addMethod(methodName, returnType, isAbstract, isNative, |
| isSynchronized, getModifiers(attributes)); |
| } else if (localName.compareTo("field") == 0) { |
| currentElement = localName; |
| String fieldName = attributes.getValue("name"); |
| String fieldType = attributes.getValue("type"); |
| boolean isTransient = false; |
| if (attributes.getValue("transient").compareTo("true") == 0) |
| isTransient = true; |
| boolean isVolatile = false; |
| if (attributes.getValue("volatile").compareTo("true") == 0) |
| isVolatile = true; |
| String value = attributes.getValue("value"); |
| XMLToAPI.addField(fieldName, fieldType, isTransient, isVolatile, |
| value, getModifiers(attributes)); |
| } else if (localName.compareTo("param") == 0 || localName.compareTo("parameter") == 0) { |
| String paramName = attributes.getValue("name"); |
| String paramType = attributes.getValue("type"); |
| XMLToAPI.addParam(paramName, paramType, currentElement.compareTo("constructor") == 0); |
| } else if (localName.compareTo("exception") == 0) { |
| String paramName = attributes.getValue("name"); |
| String paramType = attributes.getValue("type"); |
| XMLToAPI.addException(paramName, paramType, currentElement); |
| } else if (localName.compareTo("doc") == 0) { |
| inDoc = true; |
| currentText = null; |
| } else { |
| if (inDoc) { |
| // Start of an element, probably an HTML element |
| addStartTagToText(localName, attributes); |
| } else { |
| System.out.println("Error: unknown element type: " + localName); |
| System.exit(-1); |
| } |
| } |
| } |
| |
| /** Called when the end of an element is reached. */ |
| public void endElement(java.lang.String uri, java.lang.String localName, |
| java.lang.String qName) { |
| if (localName.equals("")) |
| localName = qName; |
| // Deal with the end of doc blocks |
| if (localName.compareTo("doc") == 0) { |
| inDoc = false; |
| // Add the assembled comment text to the appropriate current |
| // program element, as determined by currentElement. |
| addTextToComments(); |
| } else if (inDoc) { |
| // An element was found inside the HTML text |
| addEndTagToText(localName); |
| } else if (currentElement.compareTo("constructor") == 0 && |
| localName.compareTo("constructor") == 0) { |
| currentElement = "class"; |
| } else if (currentElement.compareTo("method") == 0 && |
| localName.compareTo("method") == 0) { |
| currentElement = "class"; |
| } else if (currentElement.compareTo("field") == 0 && |
| localName.compareTo("field") == 0) { |
| currentElement = "class"; |
| } else if (currentElement.compareTo("class") == 0 || |
| currentElement.compareTo("interface") == 0) { |
| // Feature request 510307 and bug 517383: duplicate comment ids. |
| // The end of a member element leaves the currentElement at the |
| // "class" level, but the next class may in fact be an interface |
| // and so the currentElement here will be "interface". |
| if (localName.compareTo("class") == 0 || |
| localName.compareTo("interface") == 0) { |
| currentElement = "package"; |
| } |
| } |
| } |
| |
| /** Called to process text. */ |
| public void characters(char[] ch, int start, int length) { |
| if (inDoc) { |
| String chunk = new String(ch, start, length); |
| if (currentText == null) |
| currentText = chunk; |
| else |
| currentText += chunk; |
| } |
| } |
| |
| /** |
| * Trim the current text, check it is a sentence and add it to the |
| * current program element. |
| */ |
| public void addTextToComments() { |
| // Eliminate any whitespace at each end of the text. |
| currentText = currentText.trim(); |
| // Convert any @link tags to HTML links. |
| if (convertAtLinks) { |
| currentText = Comments.convertAtLinks(currentText, currentElement, |
| api_.currPkg_, api_.currClass_); |
| } |
| // Check that it is a sentence |
| if (checkIsSentence && !currentText.endsWith(".") && |
| currentText.compareTo(Comments.placeHolderText) != 0) { |
| System.out.println("Warning: text of comment does not end in a period: " + currentText); |
| } |
| // The construction of the commentID assumes that the |
| // documentation is the final element to be parsed. The format matches |
| // the format used in the report generator to look up comments in the |
| // the existingComments object. |
| String commentID = null; |
| // Add this comment to the current API element. |
| if (currentElement.compareTo("package") == 0) { |
| api_.currPkg_.doc_ = currentText; |
| commentID = api_.currPkg_.name_; |
| } else if (currentElement.compareTo("class") == 0 || |
| currentElement.compareTo("interface") == 0) { |
| api_.currClass_.doc_ = currentText; |
| commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_; |
| } else if (currentElement.compareTo("constructor") == 0) { |
| api_.currCtor_.doc_ = currentText; |
| commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + |
| ".ctor_changed("; |
| if (api_.currCtor_.getSignature().compareTo("void") == 0) |
| commentID = commentID + ")"; |
| else |
| commentID = commentID + api_.currCtor_.getSignature() + ")"; |
| } else if (currentElement.compareTo("method") == 0) { |
| api_.currMethod_.doc_ = currentText; |
| commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + |
| "." + api_.currMethod_.name_ + "_changed(" + |
| api_.currMethod_.getSignature() + ")"; |
| } else if (currentElement.compareTo("field") == 0) { |
| api_.currField_.doc_ = currentText; |
| commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + |
| "." + api_.currField_.name_; |
| } |
| // Add to the list of possible comments for use when an |
| // element has changed (not removed or added). |
| if (createGlobalComments_ && commentID != null) { |
| String ct = currentText; |
| // Use any deprecation text as the possible comment, ignoring |
| // any other comment text. |
| if (currentDepText != null) { |
| ct = currentDepText; |
| currentDepText = null; // Never reuse it. Bug 469794 |
| } |
| String ctOld = (String)(Comments.allPossibleComments.put(commentID, ct)); |
| if (ctOld != null) { |
| System.out.println("Error: duplicate comment id: " + commentID); |
| System.exit(5); |
| } |
| } |
| } |
| |
| /** |
| * Add the start tag to the current comment text. |
| */ |
| public void addStartTagToText(String localName, Attributes attributes) { |
| // Need to insert the HTML tag into the current text |
| String currentHTMLTag = localName; |
| // Save the tag in a stack |
| tagStack.add(currentHTMLTag); |
| String tag = "<" + currentHTMLTag; |
| // Now add all the attributes into the current text |
| int len = attributes.getLength(); |
| for (int i = 0; i < len; i++) { |
| String name = attributes.getLocalName(i); |
| String value = attributes.getValue(i); |
| tag += " " + name + "=\"" + value+ "\""; |
| } |
| |
| // End the tag |
| if (Comments.isMinimizedTag(currentHTMLTag)) { |
| tag += "/>"; |
| } else { |
| tag += ">"; |
| } |
| // Now insert the HTML tag into the current text |
| if (currentText == null) |
| currentText = tag; |
| else |
| currentText += tag; |
| } |
| |
| /** |
| * Add the end tag to the current comment text. |
| */ |
| public void addEndTagToText(String localName) { |
| // Close the current HTML tag |
| String currentHTMLTag = (String)(tagStack.removeLast()); |
| if (!Comments.isMinimizedTag(currentHTMLTag)) |
| currentText += "</" + currentHTMLTag + ">"; |
| } |
| |
| /** Extra modifiers which are common to all program elements. */ |
| public Modifiers getModifiers(Attributes attributes) { |
| Modifiers modifiers = new Modifiers(); |
| modifiers.isStatic = false; |
| if (attributes.getValue("static").compareTo("true") == 0) |
| modifiers.isStatic = true; |
| modifiers.isFinal = false; |
| if (attributes.getValue("final").compareTo("true") == 0) |
| modifiers.isFinal = true; |
| modifiers.isDeprecated = false; |
| String cdt = attributes.getValue("deprecated"); |
| if (cdt.compareTo("not deprecated") == 0) { |
| modifiers.isDeprecated = false; |
| currentDepText = null; |
| } else if (cdt.compareTo("deprecated, no comment") == 0) { |
| modifiers.isDeprecated = true; |
| currentDepText = null; |
| } else { |
| modifiers.isDeprecated = true; |
| currentDepText = API.showHTMLTags(cdt); |
| } |
| modifiers.visibility = attributes.getValue("visibility"); |
| return modifiers; |
| } |
| |
| public void warning(SAXParseException e) { |
| System.out.println("Warning (" + e.getLineNumber() + "): parsing XML API file:" + e); |
| e.printStackTrace(); |
| } |
| |
| public void error(SAXParseException e) { |
| System.out.println("Error (" + e.getLineNumber() + "): parsing XML API file:" + e); |
| e.printStackTrace(); |
| System.exit(1); |
| } |
| |
| public void fatalError(SAXParseException e) { |
| System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML API file:" + e); |
| e.printStackTrace(); |
| System.exit(1); |
| } |
| |
| /** |
| * If set, then attempt to convert @link tags to HTML links. |
| * A few of the HTML links may be broken links. |
| */ |
| private static boolean convertAtLinks = true; |
| |
| /** Set to enable increased logging verbosity for debugging. */ |
| private static boolean trace = false; |
| |
| } |