| 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 == null) { | |
| // No elements were found, nothing to do here | |
| } 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; | |
| } |