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 (at) 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. */ 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. */ 61 public void startDocument() { 62 } 63 64 /** Called when the end of the document is reached. */ 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. */ 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 ctorType = attributes.getValue("type"); 107 XMLToAPI.addCtor(ctorType, getModifiers(attributes)); 108 } else if (localName.compareTo("method") == 0) { 109 currentElement = localName; 110 String methodName = attributes.getValue("name"); 111 String returnType = attributes.getValue("return"); 112 boolean isAbstract = false; 113 if (attributes.getValue("abstract").compareTo("true") == 0) 114 isAbstract = true; 115 boolean isNative = false; 116 if (attributes.getValue("native").compareTo("true") == 0) 117 isNative = true; 118 boolean isSynchronized = false; 119 if (attributes.getValue("synchronized").compareTo("true") == 0) 120 isSynchronized = true; 121 XMLToAPI.addMethod(methodName, returnType, isAbstract, isNative, 122 isSynchronized, getModifiers(attributes)); 123 } else if (localName.compareTo("field") == 0) { 124 currentElement = localName; 125 String fieldName = attributes.getValue("name"); 126 String fieldType = attributes.getValue("type"); 127 boolean isTransient = false; 128 if (attributes.getValue("transient").compareTo("true") == 0) 129 isTransient = true; 130 boolean isVolatile = false; 131 if (attributes.getValue("volatile").compareTo("true") == 0) 132 isVolatile = true; 133 String value = attributes.getValue("value"); 134 XMLToAPI.addField(fieldName, fieldType, isTransient, isVolatile, 135 value, getModifiers(attributes)); 136 } else if (localName.compareTo("param") == 0) { 137 String paramName = attributes.getValue("name"); 138 String paramType = attributes.getValue("type"); 139 XMLToAPI.addParam(paramName, paramType); 140 } else if (localName.compareTo("exception") == 0) { 141 String paramName = attributes.getValue("name"); 142 String paramType = attributes.getValue("type"); 143 XMLToAPI.addException(paramName, paramType, currentElement); 144 } else if (localName.compareTo("doc") == 0) { 145 inDoc = true; 146 currentText = null; 147 } else { 148 if (inDoc) { 149 // Start of an element, probably an HTML element 150 addStartTagToText(localName, attributes); 151 } else { 152 System.out.println("Error: unknown element type: " + localName); 153 System.exit(-1); 154 } 155 } 156 } 157 158 /** Called when the end of an element is reached. */ 159 public void endElement(java.lang.String uri, java.lang.String localName, 160 java.lang.String qName) { 161 if (localName.equals("")) 162 localName = qName; 163 // Deal with the end of doc blocks 164 if (localName.compareTo("doc") == 0) { 165 inDoc = false; 166 // Add the assembled comment text to the appropriate current 167 // program element, as determined by currentElement. 168 addTextToComments(); 169 } else if (inDoc) { 170 // An element was found inside the HTML text 171 addEndTagToText(localName); 172 } else if (currentElement.compareTo("constructor") == 0 && 173 localName.compareTo("constructor") == 0) { 174 currentElement = "class"; 175 } else if (currentElement.compareTo("method") == 0 && 176 localName.compareTo("method") == 0) { 177 currentElement = "class"; 178 } else if (currentElement.compareTo("field") == 0 && 179 localName.compareTo("field") == 0) { 180 currentElement = "class"; 181 } else if (currentElement.compareTo("class") == 0 || 182 currentElement.compareTo("interface") == 0) { 183 // Feature request 510307 and bug 517383: duplicate comment ids. 184 // The end of a member element leaves the currentElement at the 185 // "class" level, but the next class may in fact be an interface 186 // and so the currentElement here will be "interface". 187 if (localName.compareTo("class") == 0 || 188 localName.compareTo("interface") == 0) { 189 currentElement = "package"; 190 } 191 } 192 } 193 194 /** Called to process text. */ 195 public void characters(char[] ch, int start, int length) { 196 if (inDoc) { 197 String chunk = new String(ch, start, length); 198 if (currentText == null) 199 currentText = chunk; 200 else 201 currentText += chunk; 202 } 203 } 204 205 /** 206 * Trim the current text, check it is a sentence and add it to the 207 * current program element. 208 */ 209 public void addTextToComments() { 210 // Eliminate any whitespace at each end of the text. 211 currentText = currentText.trim(); 212 // Convert any @link tags to HTML links. 213 if (convertAtLinks) { 214 currentText = Comments.convertAtLinks(currentText, currentElement, 215 api_.currPkg_, api_.currClass_); 216 } 217 // Check that it is a sentence 218 if (checkIsSentence && !currentText.endsWith(".") && 219 currentText.compareTo(Comments.placeHolderText) != 0) { 220 System.out.println("Warning: text of comment does not end in a period: " + currentText); 221 } 222 // The construction of the commentID assumes that the 223 // documentation is the final element to be parsed. The format matches 224 // the format used in the report generator to look up comments in the 225 // the existingComments object. 226 String commentID = null; 227 // Add this comment to the current API element. 228 if (currentElement.compareTo("package") == 0) { 229 api_.currPkg_.doc_ = currentText; 230 commentID = api_.currPkg_.name_; 231 } else if (currentElement.compareTo("class") == 0 || 232 currentElement.compareTo("interface") == 0) { 233 api_.currClass_.doc_ = currentText; 234 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_; 235 } else if (currentElement.compareTo("constructor") == 0) { 236 api_.currCtor_.doc_ = currentText; 237 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + 238 ".ctor_changed("; 239 if (api_.currCtor_.type_.compareTo("void") == 0) 240 commentID = commentID + ")"; 241 else 242 commentID = commentID + api_.currCtor_.type_ + ")"; 243 } else if (currentElement.compareTo("method") == 0) { 244 api_.currMethod_.doc_ = currentText; 245 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + 246 "." + api_.currMethod_.name_ + "_changed(" + 247 api_.currMethod_.getSignature() + ")"; 248 } else if (currentElement.compareTo("field") == 0) { 249 api_.currField_.doc_ = currentText; 250 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + 251 "." + api_.currField_.name_; 252 } 253 // Add to the list of possible comments for use when an 254 // element has changed (not removed or added). 255 if (createGlobalComments_ && commentID != null) { 256 String ct = currentText; 257 // Use any deprecation text as the possible comment, ignoring 258 // any other comment text. 259 if (currentDepText != null) { 260 ct = currentDepText; 261 currentDepText = null; // Never reuse it. Bug 469794 262 } 263 String ctOld = (String)(Comments.allPossibleComments.put(commentID, ct)); 264 if (ctOld != null) { 265 System.out.println("Error: duplicate comment id: " + commentID); 266 System.exit(5); 267 } 268 } 269 } 270 271 /** 272 * Add the start tag to the current comment text. 273 */ 274 public void addStartTagToText(String localName, Attributes attributes) { 275 // Need to insert the HTML tag into the current text 276 String currentHTMLTag = localName; 277 // Save the tag in a stack 278 tagStack.add(currentHTMLTag); 279 String tag = "<" + currentHTMLTag; 280 // Now add all the attributes into the current text 281 int len = attributes.getLength(); 282 for (int i = 0; i < len; i++) { 283 String name = attributes.getLocalName(i); 284 String value = attributes.getValue(i); 285 tag += " " + name + "=\"" + value+ "\""; 286 } 287 288 // End the tag 289 if (Comments.isMinimizedTag(currentHTMLTag)) { 290 tag += "/>"; 291 } else { 292 tag += ">"; 293 } 294 // Now insert the HTML tag into the current text 295 if (currentText == null) 296 currentText = tag; 297 else 298 currentText += tag; 299 } 300 301 /** 302 * Add the end tag to the current comment text. 303 */ 304 public void addEndTagToText(String localName) { 305 // Close the current HTML tag 306 String currentHTMLTag = (String)(tagStack.removeLast()); 307 if (!Comments.isMinimizedTag(currentHTMLTag)) 308 currentText += "</" + currentHTMLTag + ">"; 309 } 310 311 /** Extra modifiers which are common to all program elements. */ 312 public Modifiers getModifiers(Attributes attributes) { 313 Modifiers modifiers = new Modifiers(); 314 modifiers.isStatic = false; 315 if (attributes.getValue("static").compareTo("true") == 0) 316 modifiers.isStatic = true; 317 modifiers.isFinal = false; 318 if (attributes.getValue("final").compareTo("true") == 0) 319 modifiers.isFinal = true; 320 modifiers.isDeprecated = false; 321 String cdt = attributes.getValue("deprecated"); 322 if (cdt.compareTo("not deprecated") == 0) { 323 modifiers.isDeprecated = false; 324 currentDepText = null; 325 } else if (cdt.compareTo("deprecated, no comment") == 0) { 326 modifiers.isDeprecated = true; 327 currentDepText = null; 328 } else { 329 modifiers.isDeprecated = true; 330 currentDepText = API.showHTMLTags(cdt); 331 } 332 modifiers.visibility = attributes.getValue("visibility"); 333 return modifiers; 334 } 335 336 public void warning(SAXParseException e) { 337 System.out.println("Warning (" + e.getLineNumber() + "): parsing XML API file:" + e); 338 e.printStackTrace(); 339 } 340 341 public void error(SAXParseException e) { 342 System.out.println("Error (" + e.getLineNumber() + "): parsing XML API file:" + e); 343 e.printStackTrace(); 344 System.exit(1); 345 } 346 347 public void fatalError(SAXParseException e) { 348 System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML API file:" + e); 349 e.printStackTrace(); 350 System.exit(1); 351 } 352 353 /** 354 * If set, then attempt to convert @link tags to HTML links. 355 * A few of the HTML links may be broken links. 356 */ 357 private static boolean convertAtLinks = true; 358 359 /** Set to enable increased logging verbosity for debugging. */ 360 private static boolean trace = false; 361 362 } 363