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;
|
|
}
|