1package jdiff; 2 3import java.io.*; 4import java.util.*; 5 6/* For SAX parsing in APIHandler */ 7import org.xml.sax.Attributes; 8import org.xml.sax.SAXException; 9import org.xml.sax.SAXParseException; 10import org.xml.sax.XMLReader; 11import 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 */ 19class 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 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. */ 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.compareTo("constructor") == 0 && 174 localName.compareTo("constructor") == 0) { 175 currentElement = "class"; 176 } else if (currentElement.compareTo("method") == 0 && 177 localName.compareTo("method") == 0) { 178 currentElement = "class"; 179 } else if (currentElement.compareTo("field") == 0 && 180 localName.compareTo("field") == 0) { 181 currentElement = "class"; 182 } else if (currentElement.compareTo("class") == 0 || 183 currentElement.compareTo("interface") == 0) { 184 // Feature request 510307 and bug 517383: duplicate comment ids. 185 // The end of a member element leaves the currentElement at the 186 // "class" level, but the next class may in fact be an interface 187 // and so the currentElement here will be "interface". 188 if (localName.compareTo("class") == 0 || 189 localName.compareTo("interface") == 0) { 190 currentElement = "package"; 191 } 192 } 193 } 194 195 /** Called to process text. */ 196 public void characters(char[] ch, int start, int length) { 197 if (inDoc) { 198 String chunk = new String(ch, start, length); 199 if (currentText == null) 200 currentText = chunk; 201 else 202 currentText += chunk; 203 } 204 } 205 206 /** 207 * Trim the current text, check it is a sentence and add it to the 208 * current program element. 209 */ 210 public void addTextToComments() { 211 // Eliminate any whitespace at each end of the text. 212 currentText = currentText.trim(); 213 // Convert any @link tags to HTML links. 214 if (convertAtLinks) { 215 currentText = Comments.convertAtLinks(currentText, currentElement, 216 api_.currPkg_, api_.currClass_); 217 } 218 // Check that it is a sentence 219 if (checkIsSentence && !currentText.endsWith(".") && 220 currentText.compareTo(Comments.placeHolderText) != 0) { 221 System.out.println("Warning: text of comment does not end in a period: " + currentText); 222 } 223 // The construction of the commentID assumes that the 224 // documentation is the final element to be parsed. The format matches 225 // the format used in the report generator to look up comments in the 226 // the existingComments object. 227 String commentID = null; 228 // Add this comment to the current API element. 229 if (currentElement.compareTo("package") == 0) { 230 api_.currPkg_.doc_ = currentText; 231 commentID = api_.currPkg_.name_; 232 } else if (currentElement.compareTo("class") == 0 || 233 currentElement.compareTo("interface") == 0) { 234 api_.currClass_.doc_ = currentText; 235 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_; 236 } else if (currentElement.compareTo("constructor") == 0) { 237 api_.currCtor_.doc_ = currentText; 238 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + 239 ".ctor_changed("; 240 if (api_.currCtor_.getSignature().compareTo("void") == 0) 241 commentID = commentID + ")"; 242 else 243 commentID = commentID + api_.currCtor_.getSignature() + ")"; 244 } else if (currentElement.compareTo("method") == 0) { 245 api_.currMethod_.doc_ = currentText; 246 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + 247 "." + api_.currMethod_.name_ + "_changed(" + 248 api_.currMethod_.getSignature() + ")"; 249 } else if (currentElement.compareTo("field") == 0) { 250 api_.currField_.doc_ = currentText; 251 commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ + 252 "." + api_.currField_.name_; 253 } 254 // Add to the list of possible comments for use when an 255 // element has changed (not removed or added). 256 if (createGlobalComments_ && commentID != null) { 257 String ct = currentText; 258 // Use any deprecation text as the possible comment, ignoring 259 // any other comment text. 260 if (currentDepText != null) { 261 ct = currentDepText; 262 currentDepText = null; // Never reuse it. Bug 469794 263 } 264 String ctOld = (String)(Comments.allPossibleComments.put(commentID, ct)); 265 if (ctOld != null) { 266 System.out.println("Error: duplicate comment id: " + commentID); 267 System.exit(5); 268 } 269 } 270 } 271 272 /** 273 * Add the start tag to the current comment text. 274 */ 275 public void addStartTagToText(String localName, Attributes attributes) { 276 // Need to insert the HTML tag into the current text 277 String currentHTMLTag = localName; 278 // Save the tag in a stack 279 tagStack.add(currentHTMLTag); 280 String tag = "<" + currentHTMLTag; 281 // Now add all the attributes into the current text 282 int len = attributes.getLength(); 283 for (int i = 0; i < len; i++) { 284 String name = attributes.getLocalName(i); 285 String value = attributes.getValue(i); 286 tag += " " + name + "=\"" + value+ "\""; 287 } 288 289 // End the tag 290 if (Comments.isMinimizedTag(currentHTMLTag)) { 291 tag += "/>"; 292 } else { 293 tag += ">"; 294 } 295 // Now insert the HTML tag into the current text 296 if (currentText == null) 297 currentText = tag; 298 else 299 currentText += tag; 300 } 301 302 /** 303 * Add the end tag to the current comment text. 304 */ 305 public void addEndTagToText(String localName) { 306 // Close the current HTML tag 307 String currentHTMLTag = (String)(tagStack.removeLast()); 308 if (!Comments.isMinimizedTag(currentHTMLTag)) 309 currentText += "</" + currentHTMLTag + ">"; 310 } 311 312 /** Extra modifiers which are common to all program elements. */ 313 public Modifiers getModifiers(Attributes attributes) { 314 Modifiers modifiers = new Modifiers(); 315 modifiers.isStatic = false; 316 if (attributes.getValue("static").compareTo("true") == 0) 317 modifiers.isStatic = true; 318 modifiers.isFinal = false; 319 if (attributes.getValue("final").compareTo("true") == 0) 320 modifiers.isFinal = true; 321 modifiers.isDeprecated = false; 322 String cdt = attributes.getValue("deprecated"); 323 if (cdt.compareTo("not deprecated") == 0) { 324 modifiers.isDeprecated = false; 325 currentDepText = null; 326 } else if (cdt.compareTo("deprecated, no comment") == 0) { 327 modifiers.isDeprecated = true; 328 currentDepText = null; 329 } else { 330 modifiers.isDeprecated = true; 331 currentDepText = API.showHTMLTags(cdt); 332 } 333 modifiers.visibility = attributes.getValue("visibility"); 334 return modifiers; 335 } 336 337 public void warning(SAXParseException e) { 338 System.out.println("Warning (" + e.getLineNumber() + "): parsing XML API file:" + e); 339 e.printStackTrace(); 340 } 341 342 public void error(SAXParseException e) { 343 System.out.println("Error (" + e.getLineNumber() + "): parsing XML API file:" + e); 344 e.printStackTrace(); 345 System.exit(1); 346 } 347 348 public void fatalError(SAXParseException e) { 349 System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML API file:" + e); 350 e.printStackTrace(); 351 System.exit(1); 352 } 353 354 /** 355 * If set, then attempt to convert @link tags to HTML links. 356 * A few of the HTML links may be broken links. 357 */ 358 private static boolean convertAtLinks = true; 359 360 /** Set to enable increased logging verbosity for debugging. */ 361 private static boolean trace = false; 362 363} 364