1 package jdiff; 2 3 import java.io.*; 4 import java.util.*; 5 6 /* For SAX parsing in APIHandler */ 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.helpers.DefaultHandler; 12 13 /** 14 * Handle the parsing of an XML file and the generation of an API object. 15 * 16 * See the file LICENSE.txt for copyright details. 17 * @author Matthew Doar, mdoar@pobox.com 18 */ 19 class APIHandler extends DefaultHandler { 20 21 /** The API object which is populated from the XML file. */ 22 public API api_; 23 24 /** Default constructor. */ APIHandler(API api, boolean createGlobalComments)25 public APIHandler(API api, boolean createGlobalComments) { 26 api_ = api; 27 createGlobalComments_ = createGlobalComments; 28 tagStack = new LinkedList(); 29 } 30 31 /** If set, then check that each comment is a sentence. */ 32 public static boolean checkIsSentence = false; 33 34 /** 35 * Contains the name of the current package element type 36 * where documentation is being added. Also used as the level 37 * at which to add documentation into an element, i.e. class-level 38 * or package-level. 39 */ 40 private String currentElement = null; 41 42 /** If set, then create the global list of comments. */ 43 private boolean createGlobalComments_ = false; 44 45 /** Set if inside a doc element. */ 46 private boolean inDoc = false; 47 48 /** The current comment text being assembled. */ 49 private String currentText = null; 50 51 /** The current text from deprecation, null if empty. */ 52 private String currentDepText = null; 53 54 /** 55 * The stack of SingleComment objects awaiting the comment text 56 * currently being assembled. 57 */ 58 private LinkedList tagStack = null; 59 60 /** Called at the start of the document. */ startDocument()61 public void startDocument() { 62 } 63 64 /** Called when the end of the document is reached. */ endDocument()65 public void endDocument() { 66 if (trace) 67 api_.dump(); 68 System.out.println(" finished"); 69 } 70 71 /** Called when a new element is started. */ startElement(java.lang.String uri, java.lang.String localName, java.lang.String qName, Attributes attributes)72 public void startElement(java.lang.String uri, java.lang.String localName, 73 java.lang.String qName, Attributes attributes) { 74 // The change to JAXP compliance produced this change. 75 if (localName.equals("")) 76 localName = qName; 77 if (localName.compareTo("api") == 0) { 78 String apiName = attributes.getValue("name"); 79 String version = attributes.getValue("jdversion"); // Not used yet 80 XMLToAPI.nameAPI(apiName); 81 } else if (localName.compareTo("package") == 0) { 82 currentElement = localName; 83 String pkgName = attributes.getValue("name"); 84 XMLToAPI.addPackage(pkgName); 85 } else if (localName.compareTo("class") == 0) { 86 currentElement = localName; 87 String className = attributes.getValue("name"); 88 String parentName = attributes.getValue("extends"); 89 boolean isAbstract = false; 90 if (attributes.getValue("abstract").compareTo("true") == 0) 91 isAbstract = true; 92 XMLToAPI.addClass(className, parentName, isAbstract, getModifiers(attributes)); 93 } else if (localName.compareTo("interface") == 0) { 94 currentElement = localName; 95 String className = attributes.getValue("name"); 96 String parentName = attributes.getValue("extends"); 97 boolean isAbstract = false; 98 if (attributes.getValue("abstract").compareTo("true") == 0) 99 isAbstract = true; 100 XMLToAPI.addInterface(className, parentName, isAbstract, getModifiers(attributes)); 101 } else if (localName.compareTo("implements") == 0) { 102 String interfaceName = attributes.getValue("name"); 103 XMLToAPI.addImplements(interfaceName); 104 } else if (localName.compareTo("constructor") == 0) { 105 currentElement = localName; 106 String ctorName = attributes.getValue("name"); 107 String ctorType = attributes.getValue("type"); 108 XMLToAPI.addCtor(ctorName, ctorType, getModifiers(attributes)); 109 } else if (localName.compareTo("method") == 0) { 110 currentElement = localName; 111 String methodName = attributes.getValue("name"); 112 String returnType = attributes.getValue("return"); 113 boolean isAbstract = false; 114 if (attributes.getValue("abstract").compareTo("true") == 0) 115 isAbstract = true; 116 boolean isNative = false; 117 if (attributes.getValue("native").compareTo("true") == 0) 118 isNative = true; 119 boolean isSynchronized = false; 120 if (attributes.getValue("synchronized").compareTo("true") == 0) 121 isSynchronized = true; 122 XMLToAPI.addMethod(methodName, returnType, isAbstract, isNative, 123 isSynchronized, getModifiers(attributes)); 124 } else if (localName.compareTo("field") == 0) { 125 currentElement = localName; 126 String fieldName = attributes.getValue("name"); 127 String fieldType = attributes.getValue("type"); 128 boolean isTransient = false; 129 if (attributes.getValue("transient").compareTo("true") == 0) 130 isTransient = true; 131 boolean isVolatile = false; 132 if (attributes.getValue("volatile").compareTo("true") == 0) 133 isVolatile = true; 134 String value = attributes.getValue("value"); 135 XMLToAPI.addField(fieldName, fieldType, isTransient, isVolatile, 136 value, getModifiers(attributes)); 137 } else if (localName.compareTo("param") == 0 || localName.compareTo("parameter") == 0) { 138 String paramName = attributes.getValue("name"); 139 String paramType = attributes.getValue("type"); 140 XMLToAPI.addParam(paramName, paramType, currentElement.compareTo("constructor") == 0); 141 } else if (localName.compareTo("exception") == 0) { 142 String paramName = attributes.getValue("name"); 143 String paramType = attributes.getValue("type"); 144 XMLToAPI.addException(paramName, paramType, currentElement); 145 } else if (localName.compareTo("doc") == 0) { 146 inDoc = true; 147 currentText = null; 148 } else { 149 if (inDoc) { 150 // Start of an element, probably an HTML element 151 addStartTagToText(localName, attributes); 152 } else { 153 System.out.println("Error: unknown element type: " + localName); 154 System.exit(-1); 155 } 156 } 157 } 158 159 /** Called when the end of an element is reached. */ endElement(java.lang.String uri, java.lang.String localName, java.lang.String qName)160 public void endElement(java.lang.String uri, java.lang.String localName, 161 java.lang.String qName) { 162 if (localName.equals("")) 163 localName = qName; 164 // Deal with the end of doc blocks 165 if (localName.compareTo("doc") == 0) { 166 inDoc = false; 167 // Add the assembled comment text to the appropriate current 168 // program element, as determined by currentElement. 169 addTextToComments(); 170 } else if (inDoc) { 171 // An element was found inside the HTML text 172 addEndTagToText(localName); 173 } else if (currentElement == null) { 174 // No elements were found, nothing to do here 175 } else if (currentElement.compareTo("constructor") == 0 && 176 localName.compareTo("constructor") == 0) { 177 currentElement = "class"; 178 } else if (currentElement.compareTo("method") == 0 && 179 localName.compareTo("method") == 0) { 180 currentElement = "class"; 181 } else if (currentElement.compareTo("field") == 0 && 182 localName.compareTo("field") == 0) { 183 currentElement = "class"; 184 } else if (currentElement.compareTo("class") == 0 || 185 currentElement.compareTo("interface") == 0) { 186 // Feature request 510307 and bug 517383: duplicate comment ids. 187 // The end of a member element leaves the currentElement at the 188 // "class" level, but the next class may in fact be an interface 189 // and so the currentElement here will be "interface". 190 if (localName.compareTo("class") == 0 || 191 localName.compareTo("interface") == 0) { 192 currentElement = "package"; 193 } 194 } 195 } 196 197 /** Called to process text. */ characters(char[] ch, int start, int length)198 public void characters(char[] ch, int start, int length) { 199 if (inDoc) { 200 String chunk = new String(ch, start, length); 201 if (currentText == null) 202 currentText = chunk; 203 else 204 currentText += chunk; 205 } 206 } 207 208 /** 209 * Trim the current text, check it is a sentence and add it to the 210 * current program element. 211 */ addTextToComments()212 public void addTextToComments() { 213 // Eliminate any whitespace at each end of the text. 214 currentText = currentText.trim(); 215 // Convert any @link tags to HTML links. 216 if (convertAtLinks) { 217 currentText = Comments.convertAtLinks(currentText, currentElement, 218 api_.currPkg_, api_.currClass_); 219 } 220 // Check that it is a sentence 221 if (checkIsSentence && !currentText.endsWith(".") && 222 currentText.compareTo(Comments.placeHolderText) != 0) { 223 System.out.println("Warning: text of comment does not end in a period: " + currentText); 224 } 225 // The construction of the commentID assumes that the 226 // documentation is the final element to be parsed. The format matches 227 // the format used in the report generator to look up comments in the 228 // the existingComments object. 229 String commentID = null; 230 // Add this comment to the current API element. 231 if (currentElement.compareTo("package") == 0) { 232 api_.currPkg_.doc_ = currentText; 233 commentID = api_.currPkg_.name_; 234 } else if (currentElement.compareTo("class") == 0 || 235 currentElement.compareTo("interface") == 0) { 236 api_.currClass_.doc_ = currentText; 237 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_; 238 } else if (currentElement.compareTo("constructor") == 0) { 239 api_.currCtor_.doc_ = currentText; 240 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + 241 ".ctor_changed("; 242 if (api_.currCtor_.getSignature().compareTo("void") == 0) 243 commentID = commentID + ")"; 244 else 245 commentID = commentID + api_.currCtor_.getSignature() + ")"; 246 } else if (currentElement.compareTo("method") == 0) { 247 api_.currMethod_.doc_ = currentText; 248 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + 249 "." + api_.currMethod_.name_ + "_changed(" + 250 api_.currMethod_.getSignature() + ")"; 251 } else if (currentElement.compareTo("field") == 0) { 252 api_.currField_.doc_ = currentText; 253 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + 254 "." + api_.currField_.name_; 255 } 256 // Add to the list of possible comments for use when an 257 // element has changed (not removed or added). 258 if (createGlobalComments_ && commentID != null) { 259 String ct = currentText; 260 // Use any deprecation text as the possible comment, ignoring 261 // any other comment text. 262 if (currentDepText != null) { 263 ct = currentDepText; 264 currentDepText = null; // Never reuse it. Bug 469794 265 } 266 String ctOld = (String)(Comments.allPossibleComments.put(commentID, ct)); 267 if (ctOld != null) { 268 System.out.println("Error: duplicate comment id: " + commentID); 269 System.exit(5); 270 } 271 } 272 } 273 274 /** 275 * Add the start tag to the current comment text. 276 */ addStartTagToText(String localName, Attributes attributes)277 public void addStartTagToText(String localName, Attributes attributes) { 278 // Need to insert the HTML tag into the current text 279 String currentHTMLTag = localName; 280 // Save the tag in a stack 281 tagStack.add(currentHTMLTag); 282 String tag = "<" + currentHTMLTag; 283 // Now add all the attributes into the current text 284 int len = attributes.getLength(); 285 for (int i = 0; i < len; i++) { 286 String name = attributes.getLocalName(i); 287 String value = attributes.getValue(i); 288 tag += " " + name + "=\"" + value+ "\""; 289 } 290 291 // End the tag 292 if (Comments.isMinimizedTag(currentHTMLTag)) { 293 tag += "/>"; 294 } else { 295 tag += ">"; 296 } 297 // Now insert the HTML tag into the current text 298 if (currentText == null) 299 currentText = tag; 300 else 301 currentText += tag; 302 } 303 304 /** 305 * Add the end tag to the current comment text. 306 */ addEndTagToText(String localName)307 public void addEndTagToText(String localName) { 308 // Close the current HTML tag 309 String currentHTMLTag = (String)(tagStack.removeLast()); 310 if (!Comments.isMinimizedTag(currentHTMLTag)) 311 currentText += "</" + currentHTMLTag + ">"; 312 } 313 314 /** Extra modifiers which are common to all program elements. */ getModifiers(Attributes attributes)315 public Modifiers getModifiers(Attributes attributes) { 316 Modifiers modifiers = new Modifiers(); 317 modifiers.isStatic = false; 318 if (attributes.getValue("static").compareTo("true") == 0) 319 modifiers.isStatic = true; 320 modifiers.isFinal = false; 321 if (attributes.getValue("final").compareTo("true") == 0) 322 modifiers.isFinal = true; 323 modifiers.isDeprecated = false; 324 String cdt = attributes.getValue("deprecated"); 325 if (cdt.compareTo("not deprecated") == 0) { 326 modifiers.isDeprecated = false; 327 currentDepText = null; 328 } else if (cdt.compareTo("deprecated, no comment") == 0) { 329 modifiers.isDeprecated = true; 330 currentDepText = null; 331 } else { 332 modifiers.isDeprecated = true; 333 currentDepText = API.showHTMLTags(cdt); 334 } 335 modifiers.visibility = attributes.getValue("visibility"); 336 return modifiers; 337 } 338 warning(SAXParseException e)339 public void warning(SAXParseException e) { 340 System.out.println("Warning (" + e.getLineNumber() + "): parsing XML API file:" + e); 341 e.printStackTrace(); 342 } 343 error(SAXParseException e)344 public void error(SAXParseException e) { 345 System.out.println("Error (" + e.getLineNumber() + "): parsing XML API file:" + e); 346 e.printStackTrace(); 347 System.exit(1); 348 } 349 fatalError(SAXParseException e)350 public void fatalError(SAXParseException e) { 351 System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML API file:" + e); 352 e.printStackTrace(); 353 System.exit(1); 354 } 355 356 /** 357 * If set, then attempt to convert @link tags to HTML links. 358 * A few of the HTML links may be broken links. 359 */ 360 private static boolean convertAtLinks = true; 361 362 /** Set to enable increased logging verbosity for debugging. */ 363 private static boolean trace = false; 364 365 } 366