1 package jdiff; 2 3 import com.sun.javadoc.*; 4 import com.sun.javadoc.ParameterizedType; 5 import com.sun.javadoc.Type; 6 7 import java.util.*; 8 import java.io.*; 9 import java.lang.reflect.*; 10 11 /** 12 * Converts a Javadoc RootDoc object into a representation in an 13 * XML file. 14 * 15 * See the file LICENSE.txt for copyright details. 16 * @author Matthew Doar, mdoar@pobox.com 17 */ 18 public class RootDocToXML { 19 20 /** Default constructor. */ RootDocToXML()21 public RootDocToXML() { 22 } 23 24 /** 25 * Write the XML representation of the API to a file. 26 * 27 * @param root the RootDoc object passed by Javadoc 28 * @return true if no problems encountered 29 */ writeXML(RootDoc root)30 public static boolean writeXML(RootDoc root) { 31 String tempFileName = outputFileName; 32 if (outputDirectory != null) { 33 tempFileName = outputDirectory; 34 if (!tempFileName.endsWith(JDiff.DIR_SEP)) 35 tempFileName += JDiff.DIR_SEP; 36 tempFileName += outputFileName; 37 } 38 39 try { 40 FileOutputStream fos = new FileOutputStream(tempFileName); 41 outputFile = new PrintWriter(fos); 42 System.out.println("JDiff: writing the API to file '" + tempFileName + "'..."); 43 if (root.specifiedPackages().length != 0 || root.specifiedClasses().length != 0) { 44 RootDocToXML apiWriter = new RootDocToXML(); 45 apiWriter.emitXMLHeader(); 46 apiWriter.logOptions(); 47 apiWriter.processPackages(root); 48 apiWriter.emitXMLFooter(); 49 } 50 outputFile.close(); 51 } catch(IOException e) { 52 System.out.println("IO Error while attempting to create " + tempFileName); 53 System.out.println("Error: " + e.getMessage()); 54 System.exit(1); 55 } 56 // If validation is desired, write out the appropriate api.xsd file 57 // in the same directory as the XML file. 58 if (XMLToAPI.validateXML) { 59 writeXSD(); 60 } 61 return true; 62 } 63 64 /** 65 * Write the XML Schema file used for validation. 66 */ writeXSD()67 public static void writeXSD() { 68 String xsdFileName = outputFileName; 69 if (outputDirectory == null) { 70 int idx = xsdFileName.lastIndexOf('\\'); 71 int idx2 = xsdFileName.lastIndexOf('/'); 72 if (idx == -1 && idx2 == -1) { 73 xsdFileName = ""; 74 } else if (idx == -1 && idx2 != -1) { 75 xsdFileName = xsdFileName.substring(0, idx2); 76 } else if (idx != -1 && idx2 == -1) { 77 xsdFileName = xsdFileName.substring(0, idx); 78 } else if (idx != -1 && idx2 != -1) { 79 int max = idx2 > idx ? idx2 : idx; 80 xsdFileName = xsdFileName.substring(0, max); 81 } 82 } else { 83 xsdFileName = outputDirectory; 84 if (!xsdFileName.endsWith(JDiff.DIR_SEP)) 85 xsdFileName += JDiff.DIR_SEP; 86 } 87 xsdFileName += "api.xsd"; 88 try { 89 FileOutputStream fos = new FileOutputStream(xsdFileName); 90 PrintWriter xsdFile = new PrintWriter(fos); 91 // The contents of the api.xsd file 92 xsdFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>"); 93 xsdFile.println("<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"); 94 xsdFile.println(""); 95 xsdFile.println("<xsd:annotation>"); 96 xsdFile.println(" <xsd:documentation>"); 97 xsdFile.println(" Schema for JDiff API representation."); 98 xsdFile.println(" </xsd:documentation>"); 99 xsdFile.println("</xsd:annotation>"); 100 xsdFile.println(); 101 xsdFile.println("<xsd:element name=\"api\" type=\"apiType\"/>"); 102 xsdFile.println(""); 103 xsdFile.println("<xsd:complexType name=\"apiType\">"); 104 xsdFile.println(" <xsd:sequence>"); 105 xsdFile.println(" <xsd:element name=\"package\" type=\"packageType\" minOccurs='1' maxOccurs='unbounded'/>"); 106 xsdFile.println(" </xsd:sequence>"); 107 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 108 xsdFile.println(" <xsd:attribute name=\"jdversion\" type=\"xsd:string\"/>"); 109 xsdFile.println("</xsd:complexType>"); 110 xsdFile.println(); 111 xsdFile.println("<xsd:complexType name=\"packageType\">"); 112 xsdFile.println(" <xsd:sequence>"); 113 xsdFile.println(" <xsd:choice maxOccurs='unbounded'>"); 114 xsdFile.println(" <xsd:element name=\"class\" type=\"classType\"/>"); 115 xsdFile.println(" <xsd:element name=\"interface\" type=\"classType\"/>"); 116 xsdFile.println(" </xsd:choice>"); 117 xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>"); 118 xsdFile.println(" </xsd:sequence>"); 119 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 120 xsdFile.println("</xsd:complexType>"); 121 xsdFile.println(); 122 xsdFile.println("<xsd:complexType name=\"classType\">"); 123 xsdFile.println(" <xsd:sequence>"); 124 xsdFile.println(" <xsd:element name=\"implements\" type=\"interfaceTypeName\" minOccurs='0' maxOccurs='unbounded'/>"); 125 xsdFile.println(" <xsd:element name=\"constructor\" type=\"constructorType\" minOccurs='0' maxOccurs='unbounded'/>"); 126 xsdFile.println(" <xsd:element name=\"method\" type=\"methodType\" minOccurs='0' maxOccurs='unbounded'/>"); 127 xsdFile.println(" <xsd:element name=\"field\" type=\"fieldType\" minOccurs='0' maxOccurs='unbounded'/>"); 128 xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>"); 129 xsdFile.println(" </xsd:sequence>"); 130 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 131 xsdFile.println(" <xsd:attribute name=\"extends\" type=\"xsd:string\" use='optional'/>"); 132 xsdFile.println(" <xsd:attribute name=\"abstract\" type=\"xsd:boolean\"/>"); 133 xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>"); 134 xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>"); 135 xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>"); 136 xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>"); 137 xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>"); 138 xsdFile.println("</xsd:complexType>"); 139 xsdFile.println(); 140 xsdFile.println("<xsd:complexType name=\"interfaceTypeName\">"); 141 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 142 xsdFile.println("</xsd:complexType>"); 143 xsdFile.println(); 144 xsdFile.println("<xsd:complexType name=\"constructorType\">"); 145 xsdFile.println(" <xsd:sequence>"); 146 xsdFile.println(" <xsd:element name=\"exception\" type=\"exceptionType\" minOccurs='0' maxOccurs='unbounded'/>"); 147 xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>"); 148 xsdFile.println(" </xsd:sequence>"); 149 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 150 xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\" use='optional'/>"); 151 xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>"); 152 xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>"); 153 xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>"); 154 xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>"); 155 xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>"); 156 xsdFile.println("</xsd:complexType>"); 157 xsdFile.println(); 158 xsdFile.println("<xsd:complexType name=\"paramsType\">"); 159 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 160 xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\"/>"); 161 xsdFile.println("</xsd:complexType>"); 162 xsdFile.println(); 163 xsdFile.println("<xsd:complexType name=\"exceptionType\">"); 164 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 165 xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\"/>"); 166 xsdFile.println("</xsd:complexType>"); 167 xsdFile.println(); 168 xsdFile.println("<xsd:complexType name=\"methodType\">"); 169 xsdFile.println(" <xsd:sequence>"); 170 xsdFile.println(" <xsd:element name=\"param\" type=\"paramsType\" minOccurs='0' maxOccurs='unbounded'/>"); 171 xsdFile.println(" <xsd:element name=\"exception\" type=\"exceptionType\" minOccurs='0' maxOccurs='unbounded'/>"); 172 xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>"); 173 xsdFile.println(" </xsd:sequence>"); 174 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 175 xsdFile.println(" <xsd:attribute name=\"return\" type=\"xsd:string\" use='optional'/>"); 176 xsdFile.println(" <xsd:attribute name=\"abstract\" type=\"xsd:boolean\"/>"); 177 xsdFile.println(" <xsd:attribute name=\"native\" type=\"xsd:boolean\"/>"); 178 xsdFile.println(" <xsd:attribute name=\"synchronized\" type=\"xsd:boolean\"/>"); 179 xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>"); 180 xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>"); 181 xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>"); 182 xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>"); 183 xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>"); 184 xsdFile.println("</xsd:complexType>"); 185 xsdFile.println(); 186 xsdFile.println("<xsd:complexType name=\"fieldType\">"); 187 xsdFile.println(" <xsd:sequence>"); 188 xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>"); 189 xsdFile.println(" </xsd:sequence>"); 190 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 191 xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\"/>"); 192 xsdFile.println(" <xsd:attribute name=\"transient\" type=\"xsd:boolean\"/>"); 193 xsdFile.println(" <xsd:attribute name=\"volatile\" type=\"xsd:boolean\"/>"); 194 xsdFile.println(" <xsd:attribute name=\"value\" type=\"xsd:string\" use='optional'/>"); 195 xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>"); 196 xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>"); 197 xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>"); 198 xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>"); 199 xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>"); 200 xsdFile.println("</xsd:complexType>"); 201 xsdFile.println(); 202 xsdFile.println("</xsd:schema>"); 203 xsdFile.close(); 204 } catch(IOException e) { 205 System.out.println("IO Error while attempting to create " + xsdFileName); 206 System.out.println("Error: " + e.getMessage()); 207 System.exit(1); 208 } 209 } 210 211 /** 212 * Write the options which were used to generate this XML file 213 * out as XML comments. 214 */ logOptions()215 public void logOptions() { 216 outputFile.print("<!-- "); 217 outputFile.print(" Command line arguments = " + Options.cmdOptions); 218 outputFile.println(" -->"); 219 } 220 221 /** 222 * Process each package and the classes/interfaces within it. 223 * 224 * @param pd an array of PackageDoc objects 225 */ processPackages(RootDoc root)226 public void processPackages(RootDoc root) { 227 PackageDoc[] specified_pd = root.specifiedPackages(); 228 Map pdl = new TreeMap(); 229 for (int i = 0; specified_pd != null && i < specified_pd.length; i++) { 230 pdl.put(specified_pd[i].name(), specified_pd[i]); 231 } 232 233 // Classes may be specified separately, so merge their packages into the 234 // list of specified packages. 235 ClassDoc[] cd = root.specifiedClasses(); 236 // This is lists of the specific classes to document 237 Map classesToUse = new HashMap(); 238 for (int i = 0; cd != null && i < cd.length; i++) { 239 PackageDoc cpd = cd[i].containingPackage(); 240 if (cpd == null && !packagesOnly) { 241 // If the RootDoc object has been created from a jar file 242 // this duplicates classes, so we have to be able to disable it. 243 // TODO this is still null? 244 cpd = root.packageNamed("anonymous"); 245 } 246 String pkgName = cpd.name(); 247 String className = cd[i].name(); 248 if (trace) System.out.println("Found package " + pkgName + " for class " + className); 249 if (!pdl.containsKey(pkgName)) { 250 if (trace) System.out.println("Adding new package " + pkgName); 251 pdl.put(pkgName, cpd); 252 } 253 254 // Keep track of the specific classes to be used for this package 255 List classes; 256 if (classesToUse.containsKey(pkgName)) { 257 classes = (ArrayList) classesToUse.get(pkgName); 258 } else { 259 classes = new ArrayList(); 260 } 261 classes.add(cd[i]); 262 classesToUse.put(pkgName, classes); 263 } 264 265 PackageDoc[] pd = (PackageDoc[]) pdl.values().toArray(new PackageDoc[0]); 266 for (int i = 0; pd != null && i < pd.length; i++) { 267 String pkgName = pd[i].name(); 268 269 // Check for an exclude tag in the package doc block, but not 270 // in the package.htm[l] file. 271 if (!shownElement(pd[i], null)) 272 continue; 273 274 if (trace) System.out.println("PROCESSING PACKAGE: " + pkgName); 275 outputFile.println("<package name=\"" + pkgName + "\">"); 276 277 int tagCount = pd[i].tags().length; 278 if (trace) System.out.println("#tags: " + tagCount); 279 280 List classList; 281 if (classesToUse.containsKey(pkgName)) { 282 // Use only the specified classes in the package 283 System.out.println("Using the specified classes"); 284 classList = (ArrayList) classesToUse.get(pkgName); 285 } else { 286 // Use all classes in the package 287 classList = new LinkedList(Arrays.asList(pd[i].allClasses())); 288 } 289 Collections.sort(classList); 290 ClassDoc[] classes = new ClassDoc[classList.size()]; 291 classes = (ClassDoc[])classList.toArray(classes); 292 processClasses(classes, pkgName); 293 294 addPkgDocumentation(root, pd[i], 2); 295 296 outputFile.println("</package>"); 297 } 298 } // processPackages 299 300 /** 301 * Process classes and interfaces. 302 * 303 * @param cd An array of ClassDoc objects. 304 */ processClasses(ClassDoc[] cd, String pkgName)305 public void processClasses(ClassDoc[] cd, String pkgName) { 306 if (cd.length == 0) 307 return; 308 if (trace) System.out.println("PROCESSING CLASSES, number=" + cd.length); 309 for (int i = 0; i < cd.length; i++) { 310 String className = cd[i].name(); 311 if (trace) System.out.println("PROCESSING CLASS/IFC: " + className); 312 // Only save the shown elements 313 if (!shownElement(cd[i], classVisibilityLevel)) 314 continue; 315 boolean isInterface = false; 316 if (cd[i].isInterface()) 317 isInterface = true; 318 if (isInterface) { 319 outputFile.println(" <!-- start interface " + pkgName + "." + className + " -->"); 320 outputFile.print(" <interface name=\"" + className + "\""); 321 } else { 322 outputFile.println(" <!-- start class " + pkgName + "." + className + " -->"); 323 outputFile.print(" <class name=\"" + className + "\""); 324 } 325 // Add attributes to the class element 326 Type parent = cd[i].superclassType(); 327 if (parent != null) 328 outputFile.println(" extends=\"" + buildEmittableTypeString(parent) + "\""); 329 outputFile.println(" abstract=\"" + cd[i].isAbstract() + "\""); 330 addCommonModifiers(cd[i], 4); 331 outputFile.println(">"); 332 // Process class members. (Treat inner classes as members.) 333 processInterfaces(cd[i].interfaceTypes()); 334 processConstructors(cd[i].constructors()); 335 processMethods(cd[i], cd[i].methods()); 336 processFields(cd[i].fields()); 337 338 addDocumentation(cd[i], 4); 339 340 if (isInterface) { 341 outputFile.println(" </interface>"); 342 outputFile.println(" <!-- end interface " + pkgName + "." + className + " -->"); 343 } else { 344 outputFile.println(" </class>"); 345 outputFile.println(" <!-- end class " + pkgName + "." + className + " -->"); 346 } 347 // Inner classes have already been added. 348 /* 349 ClassDoc[] ic = cd[i].innerClasses(); 350 for (int k = 0; k < ic.length; k++) { 351 System.out.println("Inner class " + k + ", name = " + ic[k].name()); 352 } 353 */ 354 }//for 355 }//processClasses() 356 357 /** 358 * Add qualifiers for the program element as attributes. 359 * 360 * @param ped The given program element. 361 */ addCommonModifiers(ProgramElementDoc ped, int indent)362 public void addCommonModifiers(ProgramElementDoc ped, int indent) { 363 addSourcePosition(ped, indent); 364 // Static and final and visibility on one line 365 for (int i = 0; i < indent; i++) outputFile.print(" "); 366 outputFile.print("static=\"" + ped.isStatic() + "\""); 367 outputFile.print(" final=\"" + ped.isFinal() + "\""); 368 // Visibility 369 String visibility = null; 370 if (ped.isPublic()) 371 visibility = "public"; 372 else if (ped.isProtected()) 373 visibility = "protected"; 374 else if (ped.isPackagePrivate()) 375 visibility = "package"; 376 else if (ped.isPrivate()) 377 visibility = "private"; 378 outputFile.println(" visibility=\"" + visibility + "\""); 379 380 // Deprecation on its own line 381 for (int i = 0; i < indent; i++) outputFile.print(" "); 382 boolean isDeprecated = false; 383 Tag[] ta = ((Doc)ped).tags("deprecated"); 384 if (ta.length != 0) { 385 isDeprecated = true; 386 } 387 if (ta.length > 1) { 388 System.out.println("JDiff: warning: multiple @deprecated tags found in comments for " + ped.name() + ". Using the first one only."); 389 System.out.println("Text is: " + ((Doc)ped).getRawCommentText()); 390 } 391 if (isDeprecated) { 392 String text = ta[0].text(); // Use only one @deprecated tag 393 if (text != null && text.compareTo("") != 0) { 394 int idx = endOfFirstSentence(text); 395 if (idx == 0) { 396 // No useful comment 397 outputFile.print("deprecated=\"deprecated, no comment\""); 398 } else { 399 String fs = null; 400 if (idx == -1) 401 fs = text; 402 else 403 fs = text.substring(0, idx+1); 404 String st = API.hideHTMLTags(fs); 405 outputFile.print("deprecated=\"" + st + "\""); 406 } 407 } else { 408 outputFile.print("deprecated=\"deprecated, no comment\""); 409 } 410 } else { 411 outputFile.print("deprecated=\"not deprecated\""); 412 } 413 414 } //addQualifiers() 415 416 /** 417 * Insert the source code details, if available. 418 * 419 * @param ped The given program element. 420 */ addSourcePosition(ProgramElementDoc ped, int indent)421 public void addSourcePosition(ProgramElementDoc ped, int indent) { 422 if (!addSrcInfo) 423 return; 424 if (JDiff.javaVersion.startsWith("1.1") || 425 JDiff.javaVersion.startsWith("1.2") || 426 JDiff.javaVersion.startsWith("1.3")) { 427 return; // position() only appeared in J2SE1.4 428 } 429 try { 430 // Could cache the method for improved performance 431 Class c = ProgramElementDoc.class; 432 Method m = c.getMethod("position", (Class[]) null); 433 Object sp = m.invoke(ped, (Object[]) null); 434 if (sp != null) { 435 for (int i = 0; i < indent; i++) outputFile.print(" "); 436 outputFile.println("src=\"" + sp + "\""); 437 } 438 } catch (NoSuchMethodException e2) { 439 System.err.println("Error: method \"position\" not found"); 440 e2.printStackTrace(); 441 } catch (IllegalAccessException e4) { 442 System.err.println("Error: class not permitted to be instantiated"); 443 e4.printStackTrace(); 444 } catch (InvocationTargetException e5) { 445 System.err.println("Error: method \"position\" could not be invoked"); 446 e5.printStackTrace(); 447 } catch (Exception e6) { 448 System.err.println("Error: "); 449 e6.printStackTrace(); 450 } 451 } 452 453 /** 454 * Process the interfaces implemented by the class. 455 * 456 * @param ifaces An array of ClassDoc objects 457 */ processInterfaces(Type[] ifaces)458 public void processInterfaces(Type[] ifaces) { 459 if (trace) System.out.println("PROCESSING INTERFACES, number=" + ifaces.length); 460 for (int i = 0; i < ifaces.length; i++) { 461 String ifaceName = buildEmittableTypeString(ifaces[i]); 462 if (trace) System.out.println("PROCESSING INTERFACE: " + ifaceName); 463 outputFile.println(" <implements name=\"" + ifaceName + "\"/>"); 464 }//for 465 }//processInterfaces() 466 467 /** 468 * Process the constructors in the class. 469 * 470 * @param ct An array of ConstructorDoc objects 471 */ processConstructors(ConstructorDoc[] ct)472 public void processConstructors(ConstructorDoc[] ct) { 473 if (trace) System.out.println("PROCESSING CONSTRUCTORS, number=" + ct.length); 474 for (int i = 0; i < ct.length; i++) { 475 String ctorName = ct[i].name(); 476 if (trace) System.out.println("PROCESSING CONSTRUCTOR: " + ctorName); 477 // Only save the shown elements 478 if (!shownElement(ct[i], memberVisibilityLevel)) 479 continue; 480 outputFile.print(" <constructor name=\"" + ctorName + "\""); 481 482 Parameter[] params = ct[i].parameters(); 483 boolean first = true; 484 if (params.length != 0) { 485 outputFile.print(" type=\""); 486 for (int j = 0; j < params.length; j++) { 487 if (!first) 488 outputFile.print(", "); 489 emitType(params[j].type()); 490 first = false; 491 } 492 outputFile.println("\""); 493 } else 494 outputFile.println(); 495 addCommonModifiers(ct[i], 6); 496 outputFile.println(">"); 497 498 // Generate the exception elements if any exceptions are thrown 499 processExceptions(ct[i].thrownExceptions()); 500 501 addDocumentation(ct[i], 6); 502 503 outputFile.println(" </constructor>"); 504 }//for 505 }//processConstructors() 506 507 /** 508 * Process all exceptions thrown by a constructor or method. 509 * 510 * @param cd An array of ClassDoc objects 511 */ processExceptions(ClassDoc[] cd)512 public void processExceptions(ClassDoc[] cd) { 513 if (trace) System.out.println("PROCESSING EXCEPTIONS, number=" + cd.length); 514 for (int i = 0; i < cd.length; i++) { 515 String exceptionName = cd[i].name(); 516 if (trace) System.out.println("PROCESSING EXCEPTION: " + exceptionName); 517 outputFile.print(" <exception name=\"" + exceptionName + "\" type=\""); 518 emitType(cd[i]); 519 outputFile.println("\"/>"); 520 }//for 521 }//processExceptions() 522 523 /** 524 * Process the methods in the class. 525 * 526 * @param md An array of MethodDoc objects 527 */ processMethods(ClassDoc cd, MethodDoc[] md)528 public void processMethods(ClassDoc cd, MethodDoc[] md) { 529 if (trace) System.out.println("PROCESSING " +cd.name()+" METHODS, number = " + md.length); 530 for (int i = 0; i < md.length; i++) { 531 String methodName = md[i].name(); 532 if (trace) System.out.println("PROCESSING METHOD: " + methodName); 533 // Skip <init> and <clinit> 534 if (methodName.startsWith("<")) 535 continue; 536 // Only save the shown elements 537 if (!shownElement(md[i], memberVisibilityLevel)) 538 continue; 539 outputFile.print(" <method name=\"" + methodName + "\""); 540 com.sun.javadoc.Type retType = md[i].returnType(); 541 if (retType.qualifiedTypeName().compareTo("void") == 0) { 542 // Don't add a return attribute if the return type is void 543 outputFile.println(); 544 } else { 545 outputFile.print(" return=\""); 546 emitType(retType); 547 outputFile.println("\""); 548 } 549 outputFile.print(" abstract=\"" + md[i].isAbstract() + "\""); 550 outputFile.print(" native=\"" + md[i].isNative() + "\""); 551 outputFile.println(" synchronized=\"" + md[i].isSynchronized() + "\""); 552 addCommonModifiers(md[i], 6); 553 outputFile.println(">"); 554 // Generate the parameter elements, if any 555 Parameter[] params = md[i].parameters(); 556 for (int j = 0; j < params.length; j++) { 557 outputFile.print(" <param name=\"" + params[j].name() + "\""); 558 outputFile.print(" type=\""); 559 emitType(params[j].type()); 560 outputFile.println("\"/>"); 561 } 562 563 // Generate the exception elements if any exceptions are thrown 564 processExceptions(md[i].thrownExceptions()); 565 566 addDocumentation(md[i], 6); 567 568 outputFile.println(" </method>"); 569 }//for 570 }//processMethods() 571 572 /** 573 * Process the fields in the class. 574 * 575 * @param fd An array of FieldDoc objects 576 */ processFields(FieldDoc[] fd)577 public void processFields(FieldDoc[] fd) { 578 if (trace) System.out.println("PROCESSING FIELDS, number=" + fd.length); 579 for (int i = 0; i < fd.length; i++) { 580 String fieldName = fd[i].name(); 581 if (trace) System.out.println("PROCESSING FIELD: " + fieldName); 582 // Only save the shown elements 583 if (!shownElement(fd[i], memberVisibilityLevel)) 584 continue; 585 outputFile.print(" <field name=\"" + fieldName + "\""); 586 outputFile.print(" type=\""); 587 emitType(fd[i].type()); 588 outputFile.println("\""); 589 outputFile.print(" transient=\"" + fd[i].isTransient() + "\""); 590 outputFile.println(" volatile=\"" + fd[i].isVolatile() + "\""); 591 /* JDK 1.4 and later */ 592 /* 593 String value = fd[i].constantValueExpression(); 594 if (value != null) 595 outputFile.println(" value=\"" + value + "\""); 596 */ 597 addCommonModifiers(fd[i], 6); 598 outputFile.println(">"); 599 600 addDocumentation(fd[i], 6); 601 602 outputFile.println(" </field>"); 603 604 }//for 605 }//processFields() 606 607 /** 608 * Emit the type name. Removed any prefixed warnings about ambiguity. 609 * The type maybe an array. 610 * 611 * @param type A Type object. 612 */ emitType(com.sun.javadoc.Type type)613 public void emitType(com.sun.javadoc.Type type) { 614 String name = buildEmittableTypeString(type); 615 if (name == null) 616 return; 617 outputFile.print(name); 618 } 619 620 /** 621 * Build the emittable type name. The type may be an array and/or 622 * a generic type. 623 * 624 * @param type A Type object 625 * @return The emittable type name 626 */ buildEmittableTypeString(com.sun.javadoc.Type type)627 private String buildEmittableTypeString(com.sun.javadoc.Type type) { 628 if (type == null) { 629 return null; 630 } 631 // type.toString() returns the fully qualified name of the type 632 // including the dimension and the parameters we just need to 633 // escape the generic parameters brackets so that the XML 634 // generated is correct 635 String name = type.toString().replaceAll("<", "<").replaceAll(">", ">"); 636 if (name.startsWith("<<ambiguous>>")) { 637 name = name.substring(13); 638 } 639 return name; 640 } 641 642 /** 643 * Emit the XML header. 644 */ emitXMLHeader()645 public void emitXMLHeader() { 646 outputFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>"); 647 outputFile.println("<!-- Generated by the JDiff Javadoc doclet -->"); 648 outputFile.println("<!-- (" + JDiff.jDiffLocation + ") -->"); 649 outputFile.println("<!-- on " + new Date() + " -->"); 650 outputFile.println(); 651 /* No need for this any longer, since doc block text is in an CDATA element 652 outputFile.println("<!-- XML Schema is used, but XHTML transitional DTD is needed for nbsp -->"); 653 outputFile.println("<!-- entity definitions etc.-->"); 654 outputFile.println("<!DOCTYPE api"); 655 outputFile.println(" PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\""); 656 outputFile.println(" \"" + baseURI + "/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"); 657 */ 658 outputFile.println("<api"); 659 outputFile.println(" xmlns:xsi='" + baseURI + "/2001/XMLSchema-instance'"); 660 outputFile.println(" xsi:noNamespaceSchemaLocation='api.xsd'"); 661 outputFile.println(" name=\"" + apiIdentifier + "\""); 662 outputFile.println(" jdversion=\"" + JDiff.version + "\">"); 663 outputFile.println(); 664 } 665 666 /** 667 * Emit the XML footer. 668 */ emitXMLFooter()669 public void emitXMLFooter() { 670 outputFile.println(); 671 outputFile.println("</api>"); 672 } 673 674 /** 675 * Determine if the program element is shown, according to the given 676 * level of visibility. 677 * 678 * @param ped The given program element. 679 * @param visLevel The desired visibility level; "public", "protected", 680 * "package" or "private". If null, only check for an exclude tag. 681 * @return boolean Set if this element is shown. 682 */ shownElement(Doc doc, String visLevel)683 public boolean shownElement(Doc doc, String visLevel) { 684 // If a doc block contains @exclude or a similar such tag, 685 // then don't display it. 686 if (doExclude && excludeTag != null && doc != null) { 687 String rct = doc.getRawCommentText(); 688 if (rct != null && rct.indexOf(excludeTag) != -1) { 689 return false; 690 } 691 } 692 if (visLevel == null) { 693 return true; 694 } 695 ProgramElementDoc ped = null; 696 if (doc instanceof ProgramElementDoc) { 697 ped = (ProgramElementDoc)doc; 698 } 699 if (visLevel.compareTo("private") == 0) 700 return true; 701 // Show all that is not private 702 if (visLevel.compareTo("package") == 0) 703 return !ped.isPrivate(); 704 // Show all that is not private or package 705 if (visLevel.compareTo("protected") == 0) 706 return !(ped.isPrivate() || ped.isPackagePrivate()); 707 // Show all that is not private or package or protected, 708 // i.e. all that is public 709 if (visLevel.compareTo("public") == 0) 710 return ped.isPublic(); 711 return false; 712 } //shownElement() 713 714 /** 715 * Strip out non-printing characters, replacing them with a character 716 * which will not change where the end of the first sentence is found. 717 * This character is the hash mark, '#'. 718 */ stripNonPrintingChars(String s, Doc doc)719 public String stripNonPrintingChars(String s, Doc doc) { 720 if (!stripNonPrintables) 721 return s; 722 char[] sa = s.toCharArray(); 723 for (int i = 0; i < sa.length; i++) { 724 char c = sa[i]; 725 // TODO still have an issue with Unicode: 0xfc in java.lang.String.toUpperCase comments 726 // if (Character.isDefined(c)) 727 if (Character.isLetterOrDigit(c)) 728 continue; 729 // There must be a better way that is still platform independent! 730 if (c == ' ' || 731 c == '.' || 732 c == ',' || 733 c == '\r' || 734 c == '\t' || 735 c == '\n' || 736 c == '!' || 737 c == '?' || 738 c == ';' || 739 c == ':' || 740 c == '[' || 741 c == ']' || 742 c == '(' || 743 c == ')' || 744 c == '~' || 745 c == '@' || 746 c == '#' || 747 c == '$' || 748 c == '%' || 749 c == '^' || 750 c == '&' || 751 c == '*' || 752 c == '-' || 753 c == '=' || 754 c == '+' || 755 c == '_' || 756 c == '|' || 757 c == '\\' || 758 c == '/' || 759 c == '\'' || 760 c == '}' || 761 c == '{' || 762 c == '"' || 763 c == '<' || 764 c == '>' || 765 c == '`' 766 ) 767 continue; 768 /* Doesn't seem to return the expected values? 769 int val = Character.getNumericValue(c); 770 // if (s.indexOf("which is also a test for non-printable") != -1) 771 // System.out.println("** Char " + i + "[" + c + "], val =" + val); //DEBUG 772 // Ranges from http://www.unicode.org/unicode/reports/tr20/ 773 // Should really replace 0x2028 and 0x2029 with <br/> 774 if (val == 0x0 || 775 inRange(val, 0x2028, 0x2029) || 776 inRange(val, 0x202A, 0x202E) || 777 inRange(val, 0x206A, 0x206F) || 778 inRange(val, 0xFFF9, 0xFFFC) || 779 inRange(val, 0xE0000, 0xE007F)) { 780 if (trace) { 781 System.out.println("Warning: changed non-printing character " + sa[i] + " in " + doc.name()); 782 } 783 sa[i] = '#'; 784 } 785 */ 786 // Replace the non-printable character with a printable character 787 // which does not change the end of the first sentence 788 sa[i] = '#'; 789 } 790 return new String(sa); 791 } 792 793 /** Return true if val is in the range [min|max], inclusive. */ inRange(int val, int min, int max)794 public boolean inRange(int val, int min, int max) { 795 if (val < min) 796 return false; 797 if (val > max) 798 return false; 799 return true; 800 } 801 802 /** 803 * Add at least the first sentence from a doc block to the API. This is 804 * used by the report generator if no comment is provided. 805 * Need to make sure that HTML tags are not confused with XML tags. 806 * This could be done by stuffing the < character to another string 807 * or by handling HTML in the parser. This second option seems neater. Note that 808 * XML expects all element tags to have either a closing "/>" or a matching 809 * end element tag. Due to the difficulties of converting incorrect HTML 810 * to XHTML, the first option is used. 811 */ addDocumentation(ProgramElementDoc ped, int indent)812 public void addDocumentation(ProgramElementDoc ped, int indent) { 813 String rct = ((Doc)ped).getRawCommentText(); 814 if (rct != null) { 815 rct = stripNonPrintingChars(rct, (Doc)ped); 816 rct = rct.trim(); 817 if (rct.compareTo("") != 0 && 818 rct.indexOf(Comments.placeHolderText) == -1 && 819 rct.indexOf("InsertOtherCommentsHere") == -1) { 820 int idx = endOfFirstSentence(rct); 821 if (idx == 0) 822 return; 823 for (int i = 0; i < indent; i++) outputFile.print(" "); 824 outputFile.println("<doc>"); 825 for (int i = 0; i < indent; i++) outputFile.print(" "); 826 String firstSentence = null; 827 if (idx == -1) 828 firstSentence = rct; 829 else 830 firstSentence = rct.substring(0, idx+1); 831 boolean checkForAts = false; 832 if (checkForAts && firstSentence.indexOf("@") != -1 && 833 firstSentence.indexOf("@link") == -1) { 834 System.out.println("Warning: @ tag seen in comment: " + 835 firstSentence); 836 } 837 String firstSentenceNoTags = API.stuffHTMLTags(firstSentence); 838 outputFile.println(firstSentenceNoTags); 839 for (int i = 0; i < indent; i++) outputFile.print(" "); 840 outputFile.println("</doc>"); 841 } 842 } 843 } 844 845 /** 846 * Add at least the first sentence from a doc block for a package to the API. This is 847 * used by the report generator if no comment is provided. 848 * The default source tree may not include the package.html files, so 849 * this may be unavailable in many cases. 850 * Need to make sure that HTML tags are not confused with XML tags. 851 * This could be done by stuffing the < character to another string 852 * or by handling HTML in the parser. This second option is neater. Note that 853 * XML expects all element tags to have either a closing "/>" or a matching 854 * end element tag. Due to the difficulties of converting incorrect HTML 855 * to XHTML, the first option is used. 856 */ addPkgDocumentation(RootDoc root, PackageDoc pd, int indent)857 public void addPkgDocumentation(RootDoc root, PackageDoc pd, int indent) { 858 String rct = null; 859 String filename = pd.name(); 860 try { 861 // See if the source path was specified as part of the 862 // options and prepend it if it was. 863 String srcLocation = null; 864 String[][] options = root.options(); 865 for (int opt = 0; opt < options.length; opt++) { 866 if ((options[opt][0]).compareTo("-sourcepath") == 0) { 867 srcLocation = options[opt][1]; 868 break; 869 } 870 } 871 filename = filename.replace('.', JDiff.DIR_SEP.charAt(0)); 872 if (srcLocation != null) { 873 // Make a relative location absolute 874 if (srcLocation.startsWith("..")) { 875 String curDir = System.getProperty("user.dir"); 876 while (srcLocation.startsWith("..")) { 877 srcLocation = srcLocation.substring(3); 878 int idx = curDir.lastIndexOf(JDiff.DIR_SEP); 879 curDir = curDir.substring(0, idx+1); 880 } 881 srcLocation = curDir + srcLocation; 882 } 883 filename = srcLocation + JDiff.DIR_SEP + filename; 884 } 885 // Try both ".htm" and ".html" 886 filename += JDiff.DIR_SEP + "package.htm"; 887 File f2 = new File(filename); 888 if (!f2.exists()) { 889 filename += "l"; 890 } 891 FileInputStream f = new FileInputStream(filename); 892 BufferedReader d = new BufferedReader(new InputStreamReader(f)); 893 String str = d.readLine(); 894 // Ignore everything except the lines between <body> elements 895 boolean inBody = false; 896 while(str != null) { 897 if (!inBody) { 898 if (str.toLowerCase().trim().startsWith("<body")) { 899 inBody = true; 900 } 901 str = d.readLine(); // Get the next line 902 continue; // Ignore the line 903 } else { 904 if (str.toLowerCase().trim().startsWith("</body")) { 905 inBody = false; 906 continue; // Ignore the line 907 } 908 } 909 if (rct == null) 910 rct = str + "\n"; 911 else 912 rct += str + "\n"; 913 str = d.readLine(); 914 } 915 } catch(java.io.FileNotFoundException e) { 916 // If it doesn't exist, that's fine 917 if (trace) 918 System.out.println("No package level documentation file at '" + filename + "'"); 919 } catch(java.io.IOException e) { 920 System.out.println("Error reading file \"" + filename + "\": " + e.getMessage()); 921 System.exit(5); 922 } 923 if (rct != null) { 924 rct = stripNonPrintingChars(rct, (Doc)pd); 925 rct = rct.trim(); 926 if (rct.compareTo("") != 0 && 927 rct.indexOf(Comments.placeHolderText) == -1 && 928 rct.indexOf("InsertOtherCommentsHere") == -1) { 929 int idx = endOfFirstSentence(rct); 930 if (idx == 0) 931 return; 932 for (int i = 0; i < indent; i++) outputFile.print(" "); 933 outputFile.println("<doc>"); 934 for (int i = 0; i < indent; i++) outputFile.print(" "); 935 String firstSentence = null; 936 if (idx == -1) 937 firstSentence = rct; 938 else 939 firstSentence = rct.substring(0, idx+1); 940 String firstSentenceNoTags = API.stuffHTMLTags(firstSentence); 941 outputFile.println(firstSentenceNoTags); 942 for (int i = 0; i < indent; i++) outputFile.print(" "); 943 outputFile.println("</doc>"); 944 } 945 } 946 } 947 948 /** 949 * Find the index of the end of the first sentence in the given text, 950 * when writing out to an XML file. 951 * This is an extended version of the algorithm used by the DocCheck 952 * Javadoc doclet. It checks for @tags too. 953 * 954 * @param text The text to be searched. 955 * @return The index of the end of the first sentence. If there is no 956 * end, return -1. If there is no useful text, return 0. 957 * If the whole doc block comment is wanted (default), return -1. 958 */ endOfFirstSentence(String text)959 public static int endOfFirstSentence(String text) { 960 return endOfFirstSentence(text, true); 961 } 962 963 /** 964 * Find the index of the end of the first sentence in the given text. 965 * This is an extended version of the algorithm used by the DocCheck 966 * Javadoc doclet. It checks for @tags too. 967 * 968 * @param text The text to be searched. 969 * @param writingToXML Set to true when writing out XML. 970 * @return The index of the end of the first sentence. If there is no 971 * end, return -1. If there is no useful text, return 0. 972 * If the whole doc block comment is wanted (default), return -1. 973 */ endOfFirstSentence(String text, boolean writingToXML)974 public static int endOfFirstSentence(String text, boolean writingToXML) { 975 if (saveAllDocs && writingToXML) 976 return -1; 977 int textLen = text.length(); 978 if (textLen == 0) 979 return 0; 980 int index = -1; 981 // Handle some special cases 982 int fromindex = 0; 983 int ellipsis = text.indexOf(". . ."); // Handles one instance of this 984 if (ellipsis != -1) 985 fromindex = ellipsis + 5; 986 // If the first non-whitespace character is an @, go beyond it 987 int i = 0; 988 while (i < textLen && text.charAt(i) == ' ') { 989 i++; 990 } 991 if (text.charAt(i) == '@' && fromindex < textLen-1) 992 fromindex = i + 1; 993 // Use the brute force approach. 994 index = minIndex(index, text.indexOf("? ", fromindex)); 995 index = minIndex(index, text.indexOf("?\t", fromindex)); 996 index = minIndex(index, text.indexOf("?\n", fromindex)); 997 index = minIndex(index, text.indexOf("?\r", fromindex)); 998 index = minIndex(index, text.indexOf("?\f", fromindex)); 999 index = minIndex(index, text.indexOf("! ", fromindex)); 1000 index = minIndex(index, text.indexOf("!\t", fromindex)); 1001 index = minIndex(index, text.indexOf("!\n", fromindex)); 1002 index = minIndex(index, text.indexOf("!\r", fromindex)); 1003 index = minIndex(index, text.indexOf("!\f", fromindex)); 1004 index = minIndex(index, text.indexOf(". ", fromindex)); 1005 index = minIndex(index, text.indexOf(".\t", fromindex)); 1006 index = minIndex(index, text.indexOf(".\n", fromindex)); 1007 index = minIndex(index, text.indexOf(".\r", fromindex)); 1008 index = minIndex(index, text.indexOf(".\f", fromindex)); 1009 index = minIndex(index, text.indexOf("@param", fromindex)); 1010 index = minIndex(index, text.indexOf("@return", fromindex)); 1011 index = minIndex(index, text.indexOf("@throw", fromindex)); 1012 index = minIndex(index, text.indexOf("@serial", fromindex)); 1013 index = minIndex(index, text.indexOf("@exception", fromindex)); 1014 index = minIndex(index, text.indexOf("@deprecate", fromindex)); 1015 index = minIndex(index, text.indexOf("@author", fromindex)); 1016 index = minIndex(index, text.indexOf("@since", fromindex)); 1017 index = minIndex(index, text.indexOf("@see", fromindex)); 1018 index = minIndex(index, text.indexOf("@version", fromindex)); 1019 if (doExclude && excludeTag != null) 1020 index = minIndex(index, text.indexOf(excludeTag)); 1021 index = minIndex(index, text.indexOf("@vtexclude", fromindex)); 1022 index = minIndex(index, text.indexOf("@vtinclude", fromindex)); 1023 index = minIndex(index, text.indexOf("<p>", 2)); // Not at start 1024 index = minIndex(index, text.indexOf("<P>", 2)); // Not at start 1025 index = minIndex(index, text.indexOf("<blockquote", 2)); // Not at start 1026 index = minIndex(index, text.indexOf("<pre", fromindex)); // May contain anything! 1027 // Avoid the char at the start of a tag in some cases 1028 if (index != -1 && 1029 (text.charAt(index) == '@' || text.charAt(index) == '<')) { 1030 if (index != 0) 1031 index--; 1032 } 1033 1034 /* Not used for jdiff, since tags are explicitly checked for above. 1035 // Look for a sentence terminated by an HTML tag. 1036 index = minIndex(index, text.indexOf(".<", fromindex)); 1037 if (index == -1) { 1038 // If period-whitespace etc was not found, check to see if 1039 // last character is a period, 1040 int endIndex = text.length()-1; 1041 if (text.charAt(endIndex) == '.' || 1042 text.charAt(endIndex) == '?' || 1043 text.charAt(endIndex) == '!') 1044 index = endIndex; 1045 } 1046 */ 1047 return index; 1048 } 1049 1050 /** 1051 * Return the minimum of two indexes if > -1, and return -1 1052 * only if both indexes = -1. 1053 * @param i an int index 1054 * @param j an int index 1055 * @return an int equal to the minimum index > -1, or -1 1056 */ minIndex(int i, int j)1057 public static int minIndex(int i, int j) { 1058 if (i == -1) return j; 1059 if (j == -1) return i; 1060 return Math.min(i,j); 1061 } 1062 1063 /** 1064 * The name of the file where the XML representing the API will be 1065 * stored. 1066 */ 1067 public static String outputFileName = null; 1068 1069 /** 1070 * The identifier of the API being written out in XML, e.g. 1071 * "SuperProduct 1.3". 1072 */ 1073 public static String apiIdentifier = null; 1074 1075 /** 1076 * The file where the XML representing the API will be stored. 1077 */ 1078 private static PrintWriter outputFile = null; 1079 1080 /** 1081 * The name of the directory where the XML representing the API will be 1082 * stored. 1083 */ 1084 public static String outputDirectory = null; 1085 1086 /** 1087 * Do not display a class with a lower level of visibility than this. 1088 * Default is to display all public and protected classes. 1089 */ 1090 public static String classVisibilityLevel = "protected"; 1091 1092 /** 1093 * Do not display a member with a lower level of visibility than this. 1094 * Default is to display all public and protected members 1095 * (constructors, methods, fields). 1096 */ 1097 public static String memberVisibilityLevel = "protected"; 1098 1099 /** 1100 * If set, then save the entire contents of a doc block comment in the 1101 * API file. If not set, then just save the first sentence. Default is 1102 * that this is set. 1103 */ 1104 public static boolean saveAllDocs = true; 1105 1106 /** 1107 * If set, exclude program elements marked with whatever the exclude tag 1108 * is specified as, e.g. "@exclude". 1109 */ 1110 public static boolean doExclude = false; 1111 1112 /** 1113 * Exclude program elements marked with this String, e.g. "@exclude". 1114 */ 1115 public static String excludeTag = null; 1116 1117 /** 1118 * The base URI for locating necessary DTDs and Schemas. By default, this 1119 * is "http://www.w3.org". A typical value to use local copies of DTD files 1120 * might be "file:///C:/jdiff/lib" 1121 */ 1122 public static String baseURI = "http://www.w3.org"; 1123 1124 /** 1125 * If set, then strip out non-printing characters from documentation. 1126 * Default is that this is set. 1127 */ 1128 static boolean stripNonPrintables = true; 1129 1130 /** 1131 * If set, then add the information about the source file and line number 1132 * which is available in J2SE1.4. Default is that this is not set. 1133 */ 1134 static boolean addSrcInfo = false; 1135 1136 /** 1137 * If set, scan classes with no packages. 1138 * If the source is a jar file this may duplicates classes, so 1139 * disable it using the -packagesonly option. Default is that this is 1140 * not set. 1141 */ 1142 static boolean packagesOnly = false; 1143 1144 /** Set to enable increased logging verbosity for debugging. */ 1145 private static boolean trace = false; 1146 1147 } //RootDocToXML 1148