1 /** 2 ******************************************************************************* 3 * Copyright (C) 2004-2014, International Business Machines Corporation and * 4 * others. All Rights Reserved. * 5 ******************************************************************************* 6 */ 7 8 /** 9 * Generate a list of ICU's public APIs, sorted by qualified name and signature 10 * public APIs are all non-internal, non-package apis in com.ibm.icu.[lang|math|text|util]. 11 * For each API, list 12 * - public, package, protected, or private (PB PK PT PR) 13 * - static or non-static (STK NST) 14 * - final or non-final (FN NF) 15 * - synchronized or non-synchronized (SYN NSY) 16 * - stable, draft, deprecated, obsolete (ST DR DP OB) 17 * - abstract or non-abstract (AB NA) 18 * - constructor, member, field (C M F) 19 * 20 * Requires JDK 1.5 or later 21 * 22 * Sample compilation: 23 * c:/doug/java/jdk1.5/build/windows-i586/bin/javac *.java 24 * 25 * Sample execution 26 * c:/j2sdk1.5/bin/javadoc 27 * -classpath c:/jd2sk1.5/lib/tools.jar 28 * -doclet com.ibm.icu.dev.tool.docs.GatherAPIData 29 * -docletpath c:/doug/icu4j/tools/build/out/lib/icu4j-build-tools.jar 30 * -sourcepath c:/doug/icu4j/main/classes/core/src 31 * -name "ICU4J 4.2" 32 * -output icu4j42.api2 33 * -gzip 34 * -source 1.5 35 * com.ibm.icu.lang com.ibm.icu.math com.ibm.icu.text com.ibm.icu.util 36 * 37 * todo: provide command-line control of filters of which subclasses/packages to process 38 * todo: record full inheritance hierarchy, not just immediate inheritance 39 * todo: allow for aliasing comparisons (force (pkg.)*class to be treated as though it 40 * were in a different pkg/class hierarchy (facilitates comparison of icu4j and java) 41 */ 42 43 package com.ibm.icu.dev.tool.docs; 44 45 // standard release sdk won't work, need internal build to get access to javadoc 46 import java.io.BufferedWriter; 47 import java.io.FileOutputStream; 48 import java.io.IOException; 49 import java.io.OutputStream; 50 import java.io.OutputStreamWriter; 51 import java.util.Collection; 52 import java.util.Iterator; 53 import java.util.TreeSet; 54 import java.util.regex.Pattern; 55 import java.util.zip.GZIPOutputStream; 56 import java.util.zip.ZipEntry; 57 import java.util.zip.ZipOutputStream; 58 59 import com.sun.javadoc.ClassDoc; 60 import com.sun.javadoc.ConstructorDoc; 61 import com.sun.javadoc.ExecutableMemberDoc; 62 import com.sun.javadoc.FieldDoc; 63 import com.sun.javadoc.LanguageVersion; 64 import com.sun.javadoc.MemberDoc; 65 import com.sun.javadoc.MethodDoc; 66 import com.sun.javadoc.ProgramElementDoc; 67 import com.sun.javadoc.RootDoc; 68 import com.sun.javadoc.Tag; 69 70 public class GatherAPIData { 71 RootDoc root; 72 TreeSet results; 73 String srcName = "Current"; // default source name 74 String output; // name of output file to write 75 String base; // strip this prefix 76 Pattern pat; 77 boolean zip; 78 boolean gzip; 79 boolean internal; 80 boolean version; 81 optionLength(String option)82 public static int optionLength(String option) { 83 if (option.equals("-name")) { 84 return 2; 85 } else if (option.equals("-output")) { 86 return 2; 87 } else if (option.equals("-base")) { 88 return 2; 89 } else if (option.equals("-filter")) { 90 return 2; 91 } else if (option.equals("-zip")) { 92 return 1; 93 } else if (option.equals("-gzip")) { 94 return 1; 95 } else if (option.equals("-internal")) { 96 return 1; 97 } else if (option.equals("-version")) { 98 return 1; 99 } 100 return 0; 101 } 102 start(RootDoc root)103 public static boolean start(RootDoc root) { 104 return new GatherAPIData(root).run(); 105 } 106 107 /** 108 * If you don't do this, javadoc treats enums like regular classes! 109 * doesn't matter if you pass -source 1.5 or not. 110 */ languageVersion()111 public static LanguageVersion languageVersion() { 112 return LanguageVersion.JAVA_1_5; 113 } 114 GatherAPIData(RootDoc root)115 GatherAPIData(RootDoc root) { 116 this.root = root; 117 118 String[][] options = root.options(); 119 for (int i = 0; i < options.length; ++i) { 120 String opt = options[i][0]; 121 if (opt.equals("-name")) { 122 this.srcName = options[i][1]; 123 } else if (opt.equals("-output")) { 124 this.output = options[i][1]; 125 } else if (opt.equals("-base")) { 126 this.base = options[i][1]; // should not include '.' 127 } else if (opt.equals("-filter")) { 128 this.pat = Pattern.compile(options[i][1], Pattern.CASE_INSENSITIVE); 129 } else if (opt.equals("-zip")) { 130 this.zip = true; 131 } else if (opt.equals("-gzip")) { 132 this.gzip = true; 133 } else if (opt.equals("-internal")) { 134 this.internal = true; 135 } else if (opt.equals("-version")) { 136 this.version = true; 137 } 138 } 139 140 results = new TreeSet(APIInfo.defaultComparator()); 141 } 142 run()143 private boolean run() { 144 doDocs(root.classes()); 145 146 OutputStream os = System.out; 147 if (output != null) { 148 ZipOutputStream zos = null; 149 try { 150 if (zip) { 151 zos = new ZipOutputStream(new FileOutputStream(output + ".zip")); 152 zos.putNextEntry(new ZipEntry(output)); 153 os = zos; 154 } else if (gzip) { 155 os = new GZIPOutputStream(new FileOutputStream(output + ".gz")); 156 } else { 157 os = new FileOutputStream(output); 158 } 159 } 160 catch (IOException e) { 161 RuntimeException re = new RuntimeException(e.getMessage()); 162 re.initCause(e); 163 throw re; 164 } 165 finally { 166 if (zos != null) { 167 try { 168 zos.close(); 169 } catch (Exception e) { 170 // ignore 171 } 172 } 173 } 174 } 175 176 BufferedWriter bw = null; 177 try { 178 OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8"); 179 bw = new BufferedWriter(osw); 180 181 // writing data file 182 bw.write(String.valueOf(APIInfo.VERSION) + APIInfo.SEP); // header version 183 bw.write(srcName + APIInfo.SEP); // source name 184 bw.write((base == null ? "" : base) + APIInfo.SEP); // base 185 bw.newLine(); 186 writeResults(results, bw); 187 bw.close(); // should flush, close all, etc 188 } catch (IOException e) { 189 try { bw.close(); } catch (IOException e2) {} 190 RuntimeException re = new RuntimeException("write error: " + e.getMessage()); 191 re.initCause(e); 192 throw re; 193 } 194 195 return false; 196 } 197 doDocs(ProgramElementDoc[] docs)198 private void doDocs(ProgramElementDoc[] docs) { 199 if (docs != null && docs.length > 0) { 200 for (int i = 0; i < docs.length; ++i) { 201 doDoc(docs[i]); 202 } 203 } 204 } 205 doDoc(ProgramElementDoc doc)206 private void doDoc(ProgramElementDoc doc) { 207 if (ignore(doc)) return; 208 209 if (doc.isClass() || doc.isInterface()) { 210 ClassDoc cdoc = (ClassDoc)doc; 211 doDocs(cdoc.fields()); 212 doDocs(cdoc.constructors()); 213 doDocs(cdoc.methods()); 214 doDocs(cdoc.enumConstants()); 215 // don't call this to iterate over inner classes, 216 // root.classes already includes them 217 // doDocs(cdoc.innerClasses()); 218 } 219 220 APIInfo info = createInfo(doc); 221 if (info != null) { 222 results.add(info); 223 } 224 } 225 226 // Sigh. Javadoc doesn't indicate when the compiler generates 227 // the values and valueOf enum methods. The position of the 228 // method for these is not always the same as the position of 229 // the class, though it often is, so we can't use that. 230 isIgnoredEnumMethod(ProgramElementDoc doc)231 private boolean isIgnoredEnumMethod(ProgramElementDoc doc) { 232 if (doc.isMethod() && doc.containingClass().isEnum()) { 233 // System.out.println("*** " + doc.qualifiedName() + " pos: " + 234 // doc.position().line() + 235 // " containined by: " + 236 // doc.containingClass().name() + 237 // " pos: " + 238 // doc.containingClass().position().line()); 239 // return doc.position().line() == doc.containingClass().position().line(); 240 241 String name = doc.name(); 242 // assume we don't have enums that overload these method names. 243 return "values".equals(name) || "valueOf".equals(name); 244 } 245 return false; 246 } 247 248 // isSynthesized also doesn't seem to work. Let's do this, documenting 249 // synthesized constructors for abstract classes is kind of weird. 250 // We can't actually tell if the constructor was synthesized or is 251 // actually in the docs, but this shouldn't matter. We don't really 252 // care if we didn't properly document the draft status of 253 // default constructors for abstract classes. 254 255 // Update: We mandate a no-arg synthetic constructor with explicit 256 // javadoc comments by the policy. So, we no longer ignore abstract 257 // class's no-arg constructor blindly. -Yoshito 2014-05-21 258 isAbstractClassDefaultConstructor(ProgramElementDoc doc)259 private boolean isAbstractClassDefaultConstructor(ProgramElementDoc doc) { 260 return doc.isConstructor() 261 && doc.containingClass().isAbstract() 262 && "()".equals(((ConstructorDoc) doc).signature()); 263 } 264 265 private static final boolean IGNORE_NO_ARG_ABSTRACT_CTOR = false; 266 ignore(ProgramElementDoc doc)267 private boolean ignore(ProgramElementDoc doc) { 268 if (doc == null) return true; 269 if (doc.isPrivate() || doc.isPackagePrivate()) return true; 270 if (doc instanceof MemberDoc && ((MemberDoc)doc).isSynthetic()) return true; 271 if (doc.qualifiedName().indexOf(".misc") != -1) { 272 System.out.println("misc: " + doc.qualifiedName()); return true; 273 } 274 if (isIgnoredEnumMethod(doc)) { 275 return true; 276 } 277 278 if (IGNORE_NO_ARG_ABSTRACT_CTOR && isAbstractClassDefaultConstructor(doc)) { 279 return true; 280 } 281 282 if (false && doc.qualifiedName().indexOf("LocaleDisplayNames") != -1) { 283 System.err.print("*** " + doc.qualifiedName() + ":"); 284 if (doc.isClass()) System.err.print(" class"); 285 if (doc.isConstructor()) System.err.print(" constructor"); 286 if (doc.isEnum()) System.err.print(" enum"); 287 if (doc.isEnumConstant()) System.err.print(" enum_constant"); 288 if (doc.isError()) System.err.print(" error"); 289 if (doc.isException()) System.err.print(" exception"); 290 if (doc.isField()) System.err.print(" field"); 291 if (doc.isInterface()) System.err.print(" interface"); 292 if (doc.isMethod()) System.err.print(" method"); 293 if (doc.isOrdinaryClass()) System.err.print(" ordinary_class"); 294 System.err.println(); 295 } 296 297 if (!internal) { // debug 298 Tag[] tags = doc.tags(); 299 for (int i = 0; i < tags.length; ++i) { 300 if (tagKindIndex(tags[i].kind()) == INTERNAL) { return true; } 301 } 302 } 303 if (pat != null && (doc.isClass() || doc.isInterface())) { 304 if (!pat.matcher(doc.name()).matches()) { 305 return true; 306 } 307 } 308 return false; 309 } 310 writeResults(Collection c, BufferedWriter w)311 private static void writeResults(Collection c, BufferedWriter w) { 312 Iterator iter = c.iterator(); 313 while (iter.hasNext()) { 314 APIInfo info = (APIInfo)iter.next(); 315 info.writeln(w); 316 } 317 } 318 trimBase(String arg)319 private String trimBase(String arg) { 320 if (base != null) { 321 for (int n = arg.indexOf(base); n != -1; n = arg.indexOf(base, n)) { 322 arg = arg.substring(0, n) + arg.substring(n+base.length()); 323 } 324 } 325 return arg; 326 } 327 createInfo(ProgramElementDoc doc)328 public APIInfo createInfo(ProgramElementDoc doc) { 329 330 // Doc. name 331 // Doc. isField, isMethod, isConstructor, isClass, isInterface 332 // ProgramElementDoc. containingClass, containingPackage 333 // ProgramElementDoc. isPublic, isProtected, isPrivate, isPackagePrivate 334 // ProgramElementDoc. isStatic, isFinal 335 // MemberDoc.isSynthetic 336 // ExecutableMemberDoc isSynchronized, signature 337 // Type.toString() // e.g. "String[][]" 338 // ClassDoc.isAbstract, superClass, interfaces, fields, methods, constructors, innerClasses 339 // FieldDoc type 340 // ConstructorDoc qualifiedName 341 // MethodDoc isAbstract, returnType 342 343 APIInfo info = new APIInfo(); 344 if (version) { 345 info.includeStatusVersion(true); 346 } 347 348 // status 349 String[] version = new String[1]; 350 info.setType(APIInfo.STA, tagStatus(doc, version)); 351 info.setStatusVersion(version[0]); 352 353 // visibility 354 if (doc.isPublic()) { 355 info.setPublic(); 356 } else if (doc.isProtected()) { 357 info.setProtected(); 358 } else if (doc.isPrivate()) { 359 info.setPrivate(); 360 } else { 361 // default is package 362 } 363 364 // static 365 if (doc.isStatic()) { 366 info.setStatic(); 367 } else { 368 // default is non-static 369 } 370 371 // final 372 if (doc.isFinal() && !doc.isEnum()) { 373 info.setFinal(); 374 } else { 375 // default is non-final 376 } 377 378 // type 379 if (doc.isField()) { 380 info.setField(); 381 } else if (doc.isMethod()) { 382 info.setMethod(); 383 } else if (doc.isConstructor()) { 384 info.setConstructor(); 385 } else if (doc.isClass() || doc.isInterface()) { 386 if (doc.isEnum()) { 387 info.setEnum(); 388 } else { 389 info.setClass(); 390 } 391 } else if (doc.isEnumConstant()) { 392 info.setEnumConstant(); 393 } 394 395 info.setPackage(trimBase(doc.containingPackage().name())); 396 info.setClassName((doc.isClass() || doc.isInterface() || (doc.containingClass() == null)) 397 ? "" 398 : trimBase(doc.containingClass().name())); 399 info.setName(trimBase(doc.name())); 400 401 if (doc instanceof FieldDoc) { 402 FieldDoc fdoc = (FieldDoc)doc; 403 info.setSignature(trimBase(fdoc.type().toString())); 404 } else if (doc instanceof ClassDoc) { 405 ClassDoc cdoc = (ClassDoc)doc; 406 407 if (cdoc.isClass() && cdoc.isAbstract()) { 408 // interfaces are abstract by default, don't mark them as abstract 409 info.setAbstract(); 410 } 411 412 StringBuffer buf = new StringBuffer(); 413 if (cdoc.isClass()) { 414 buf.append("extends "); 415 buf.append(cdoc.superclassType().toString()); 416 } 417 ClassDoc[] imp = cdoc.interfaces(); 418 if (imp != null && imp.length > 0) { 419 if (buf.length() > 0) { 420 buf.append(" "); 421 } 422 buf.append("implements"); 423 for (int i = 0; i < imp.length; ++i) { 424 if (i != 0) { 425 buf.append(","); 426 } 427 buf.append(" "); 428 buf.append(imp[i].qualifiedName()); 429 } 430 } 431 info.setSignature(trimBase(buf.toString())); 432 } else { 433 ExecutableMemberDoc emdoc = (ExecutableMemberDoc)doc; 434 if (emdoc.isSynchronized()) { 435 info.setSynchronized(); 436 } 437 438 if (doc instanceof MethodDoc) { 439 MethodDoc mdoc = (MethodDoc)doc; 440 if (mdoc.isAbstract()) { 441 info.setAbstract(); 442 } 443 info.setSignature(trimBase(mdoc.returnType().toString() + emdoc.signature())); 444 } else { 445 // constructor 446 info.setSignature(trimBase(emdoc.signature())); 447 } 448 } 449 450 return info; 451 } 452 tagStatus(final ProgramElementDoc doc, String[] version)453 private int tagStatus(final ProgramElementDoc doc, String[] version) { 454 class Result { 455 boolean deprecatedFlag = false; 456 int res = -1; 457 void set(int val) { 458 if (res != -1) { 459 boolean isValid = true; 460 if (val == APIInfo.STA_DEPRECATED) { 461 // @internal and @obsolete should be always used along with @deprecated. 462 // no change for status 463 isValid = (res == APIInfo.STA_INTERNAL || res == APIInfo.STA_OBSOLETE); 464 deprecatedFlag = true; 465 } else if (val == APIInfo.STA_INTERNAL) { 466 // @deprecated should be always used along with @internal. 467 // update status 468 if (res == APIInfo.STA_DEPRECATED) { 469 res = val; // APIInfo.STA_INTERNAL 470 } else { 471 isValid = false; 472 } 473 } else if (val == APIInfo.STA_OBSOLETE) { 474 // @deprecated should be always used along with @obsolete. 475 // update status 476 if (res == APIInfo.STA_DEPRECATED) { 477 res = val; // APIInfo.STA_OBSOLETE 478 } else { 479 isValid = false; 480 } 481 } else { 482 // two different status tags must not co-exist, except for 483 // following two cases: 484 // 1. @internal and @deprecated 485 // 2. @obsolete and @deprecated 486 isValid = false; 487 } 488 if (!isValid) { 489 System.err.println("bad doc: " + doc + " both: " 490 + APIInfo.getTypeValName(APIInfo.STA, res) + " and: " 491 + APIInfo.getTypeValName(APIInfo.STA, val)); 492 return; 493 } 494 } else { 495 // ok to replace with new tag 496 res = val; 497 if (val == APIInfo.STA_DEPRECATED) { 498 deprecatedFlag = true; 499 } 500 } 501 } 502 int get() { 503 if (res == -1) { 504 System.err.println("warning: no tag for " + doc); 505 return 0; 506 } else if (res == APIInfo.STA_INTERNAL && !deprecatedFlag) { 507 System.err.println("warning: no @deprecated tag for @internal API: " + doc); 508 } 509 return res; 510 } 511 } 512 513 Tag[] tags = doc.tags(); 514 Result result = new Result(); 515 String statusVer = ""; 516 for (int i = 0; i < tags.length; ++i) { 517 Tag tag = tags[i]; 518 519 String kind = tag.kind(); 520 int ix = tagKindIndex(kind); 521 522 switch (ix) { 523 case INTERNAL: 524 result.set(internal ? APIInfo.STA_INTERNAL : -2); // -2 for legacy compatibility 525 statusVer = getStatusVersion(tag); 526 break; 527 528 case DRAFT: 529 result.set(APIInfo.STA_DRAFT); 530 statusVer = getStatusVersion(tag); 531 break; 532 533 case STABLE: 534 result.set(APIInfo.STA_STABLE); 535 statusVer = getStatusVersion(tag); 536 break; 537 538 case DEPRECATED: 539 result.set(APIInfo.STA_DEPRECATED); 540 statusVer = getStatusVersion(tag); 541 break; 542 543 case OBSOLETE: 544 result.set(APIInfo.STA_OBSOLETE); 545 statusVer = getStatusVersion(tag); 546 break; 547 548 case SINCE: 549 case EXCEPTION: 550 case VERSION: 551 case UNKNOWN: 552 case AUTHOR: 553 case SEE: 554 case PARAM: 555 case RETURN: 556 case THROWS: 557 case SERIAL: 558 break; 559 560 default: 561 throw new RuntimeException("unknown index " + ix + " for tag: " + kind); 562 } 563 } 564 565 if (version != null) { 566 version[0] = statusVer; 567 } 568 return result.get(); 569 } 570 getStatusVersion(Tag tag)571 private String getStatusVersion(Tag tag) { 572 String text = tag.text(); 573 if (text != null && text.length() > 0) { 574 // Extract version string 575 int start = -1; 576 int i = 0; 577 for (; i < text.length(); i++) { 578 char ch = text.charAt(i); 579 if (ch == '.' || (ch >= '0' && ch <= '9')) { 580 if (start == -1) { 581 start = i; 582 } 583 } else if (start != -1) { 584 break; 585 } 586 } 587 if (start != -1) { 588 return text.substring(start, i); 589 } 590 } 591 return ""; 592 } 593 594 private static final int UNKNOWN = -1; 595 private static final int INTERNAL = 0; 596 private static final int DRAFT = 1; 597 private static final int STABLE = 2; 598 private static final int SINCE = 3; 599 private static final int DEPRECATED = 4; 600 private static final int AUTHOR = 5; 601 private static final int SEE = 6; 602 private static final int VERSION = 7; 603 private static final int PARAM = 8; 604 private static final int RETURN = 9; 605 private static final int THROWS = 10; 606 private static final int OBSOLETE = 11; 607 private static final int EXCEPTION = 12; 608 private static final int SERIAL = 13; 609 tagKindIndex(String kind)610 private static int tagKindIndex(String kind) { 611 final String[] tagKinds = { 612 "@internal", "@draft", "@stable", "@since", "@deprecated", "@author", "@see", 613 "@version", "@param", "@return", "@throws", "@obsolete", "@exception", "@serial" 614 }; 615 616 for (int i = 0; i < tagKinds.length; ++i) { 617 if (kind.equals(tagKinds[i])) { 618 return i; 619 } 620 } 621 return UNKNOWN; 622 } 623 } 624