1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import com.sun.javadoc.*; 18 19 import org.clearsilver.HDF; 20 21 import java.util.*; 22 import java.io.*; 23 import java.lang.reflect.Proxy; 24 import java.lang.reflect.Array; 25 import java.lang.reflect.InvocationHandler; 26 import java.lang.reflect.InvocationTargetException; 27 import java.lang.reflect.Method; 28 29 public class DroidDoc 30 { 31 private static final String SDK_CONSTANT_ANNOTATION = "android.annotation.SdkConstant"; 32 private static final String SDK_CONSTANT_TYPE_ACTIVITY_ACTION = "android.annotation.SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION"; 33 private static final String SDK_CONSTANT_TYPE_BROADCAST_ACTION = "android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION"; 34 private static final String SDK_CONSTANT_TYPE_SERVICE_ACTION = "android.annotation.SdkConstant.SdkConstantType.SERVICE_INTENT_ACTION"; 35 private static final String SDK_CONSTANT_TYPE_CATEGORY = "android.annotation.SdkConstant.SdkConstantType.INTENT_CATEGORY"; 36 private static final String SDK_WIDGET_ANNOTATION = "android.annotation.Widget"; 37 private static final String SDK_LAYOUT_ANNOTATION = "android.annotation.Layout"; 38 39 private static final int TYPE_NONE = 0; 40 private static final int TYPE_WIDGET = 1; 41 private static final int TYPE_LAYOUT = 2; 42 private static final int TYPE_LAYOUT_PARAM = 3; 43 44 public static final int SHOW_PUBLIC = 0x00000001; 45 public static final int SHOW_PROTECTED = 0x00000003; 46 public static final int SHOW_PACKAGE = 0x00000007; 47 public static final int SHOW_PRIVATE = 0x0000000f; 48 public static final int SHOW_HIDDEN = 0x0000001f; 49 50 public static int showLevel = SHOW_PROTECTED; 51 52 public static final String javadocDir = "reference/"; 53 public static String htmlExtension; 54 55 public static RootDoc root; 56 public static ArrayList<String[]> mHDFData = new ArrayList<String[]>(); 57 public static Map<Character,String> escapeChars = new HashMap<Character,String>(); 58 public static String title = ""; 59 public static SinceTagger sinceTagger = new SinceTagger(); 60 checkLevel(int level)61 public static boolean checkLevel(int level) 62 { 63 return (showLevel & level) == level; 64 } 65 checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv, boolean hidden)66 public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp, 67 boolean priv, boolean hidden) 68 { 69 int level = 0; 70 if (hidden && !checkLevel(SHOW_HIDDEN)) { 71 return false; 72 } 73 if (pub && checkLevel(SHOW_PUBLIC)) { 74 return true; 75 } 76 if (prot && checkLevel(SHOW_PROTECTED)) { 77 return true; 78 } 79 if (pkgp && checkLevel(SHOW_PACKAGE)) { 80 return true; 81 } 82 if (priv && checkLevel(SHOW_PRIVATE)) { 83 return true; 84 } 85 return false; 86 } 87 start(RootDoc r)88 public static boolean start(RootDoc r) 89 { 90 String keepListFile = null; 91 String proofreadFile = null; 92 String todoFile = null; 93 String sdkValuePath = null; 94 ArrayList<SampleCode> sampleCodes = new ArrayList<SampleCode>(); 95 String stubsDir = null; 96 //Create the dependency graph for the stubs directory 97 boolean apiXML = false; 98 boolean noDocs = false; 99 boolean offlineMode = false; 100 String apiFile = null; 101 String debugStubsFile = ""; 102 HashSet<String> stubPackages = null; 103 104 root = r; 105 106 String[][] options = r.options(); 107 for (String[] a: options) { 108 if (a[0].equals("-d")) { 109 ClearPage.outputDir = a[1]; 110 } 111 else if (a[0].equals("-templatedir")) { 112 ClearPage.addTemplateDir(a[1]); 113 } 114 else if (a[0].equals("-hdf")) { 115 mHDFData.add(new String[] {a[1], a[2]}); 116 } 117 else if (a[0].equals("-toroot")) { 118 ClearPage.toroot = a[1]; 119 } 120 else if (a[0].equals("-samplecode")) { 121 sampleCodes.add(new SampleCode(a[1], a[2], a[3])); 122 } 123 else if (a[0].equals("-htmldir")) { 124 ClearPage.htmlDir = a[1]; 125 } 126 else if (a[0].equals("-title")) { 127 DroidDoc.title = a[1]; 128 } 129 else if (a[0].equals("-werror")) { 130 Errors.setWarningsAreErrors(true); 131 } 132 else if (a[0].equals("-error") || a[0].equals("-warning") 133 || a[0].equals("-hide")) { 134 try { 135 int level = -1; 136 if (a[0].equals("-error")) { 137 level = Errors.ERROR; 138 } 139 else if (a[0].equals("-warning")) { 140 level = Errors.WARNING; 141 } 142 else if (a[0].equals("-hide")) { 143 level = Errors.HIDDEN; 144 } 145 Errors.setErrorLevel(Integer.parseInt(a[1]), level); 146 } 147 catch (NumberFormatException e) { 148 // already printed below 149 return false; 150 } 151 } 152 else if (a[0].equals("-keeplist")) { 153 keepListFile = a[1]; 154 } 155 else if (a[0].equals("-proofread")) { 156 proofreadFile = a[1]; 157 } 158 else if (a[0].equals("-todo")) { 159 todoFile = a[1]; 160 } 161 else if (a[0].equals("-public")) { 162 showLevel = SHOW_PUBLIC; 163 } 164 else if (a[0].equals("-protected")) { 165 showLevel = SHOW_PROTECTED; 166 } 167 else if (a[0].equals("-package")) { 168 showLevel = SHOW_PACKAGE; 169 } 170 else if (a[0].equals("-private")) { 171 showLevel = SHOW_PRIVATE; 172 } 173 else if (a[0].equals("-hidden")) { 174 showLevel = SHOW_HIDDEN; 175 } 176 else if (a[0].equals("-stubs")) { 177 stubsDir = a[1]; 178 } 179 else if (a[0].equals("-stubpackages")) { 180 stubPackages = new HashSet(); 181 for (String pkg: a[1].split(":")) { 182 stubPackages.add(pkg); 183 } 184 } 185 else if (a[0].equals("-sdkvalues")) { 186 sdkValuePath = a[1]; 187 } 188 else if (a[0].equals("-apixml")) { 189 apiXML = true; 190 apiFile = a[1]; 191 } 192 else if (a[0].equals("-nodocs")) { 193 noDocs = true; 194 } 195 else if (a[0].equals("-since")) { 196 sinceTagger.addVersion(a[1], a[2]); 197 } 198 else if (a[0].equals("-offlinemode")) { 199 offlineMode = true; 200 } 201 } 202 203 // read some prefs from the template 204 if (!readTemplateSettings()) { 205 return false; 206 } 207 208 // Set up the data structures 209 Converter.makeInfo(r); 210 211 if (!noDocs) { 212 long startTime = System.nanoTime(); 213 214 // Apply @since tags from the XML file 215 sinceTagger.tagAll(Converter.rootClasses()); 216 217 // Files for proofreading 218 if (proofreadFile != null) { 219 Proofread.initProofread(proofreadFile); 220 } 221 if (todoFile != null) { 222 TodoFile.writeTodoFile(todoFile); 223 } 224 225 // HTML Pages 226 if (ClearPage.htmlDir != null) { 227 writeHTMLPages(); 228 } 229 230 // Navigation tree 231 NavTree.writeNavTree(javadocDir); 232 233 // Packages Pages 234 writePackages(javadocDir 235 + (ClearPage.htmlDir!=null 236 ? "packages" + htmlExtension 237 : "index" + htmlExtension)); 238 239 // Classes 240 writeClassLists(); 241 writeClasses(); 242 writeHierarchy(); 243 // writeKeywords(); 244 245 // Lists for JavaScript 246 writeLists(); 247 if (keepListFile != null) { 248 writeKeepList(keepListFile); 249 } 250 251 // Sample Code 252 for (SampleCode sc: sampleCodes) { 253 sc.write(offlineMode); 254 } 255 256 // Index page 257 writeIndex(); 258 259 Proofread.finishProofread(proofreadFile); 260 261 if (sdkValuePath != null) { 262 writeSdkValues(sdkValuePath); 263 } 264 265 long time = System.nanoTime() - startTime; 266 System.out.println("DroidDoc took " + (time / 1000000000) + " sec. to write docs to " 267 + ClearPage.outputDir); 268 } 269 270 // Stubs 271 if (stubsDir != null) { 272 Stubs.writeStubs(stubsDir, apiXML, apiFile, stubPackages); 273 } 274 275 Errors.printErrors(); 276 return !Errors.hadError; 277 } 278 writeIndex()279 private static void writeIndex() { 280 HDF data = makeHDF(); 281 ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension); 282 } 283 readTemplateSettings()284 private static boolean readTemplateSettings() 285 { 286 HDF data = makeHDF(); 287 htmlExtension = data.getValue("template.extension", ".html"); 288 int i=0; 289 while (true) { 290 String k = data.getValue("template.escape." + i + ".key", ""); 291 String v = data.getValue("template.escape." + i + ".value", ""); 292 if ("".equals(k)) { 293 break; 294 } 295 if (k.length() != 1) { 296 System.err.println("template.escape." + i + ".key must have a length of 1: " + k); 297 return false; 298 } 299 escapeChars.put(k.charAt(0), v); 300 i++; 301 } 302 return true; 303 } 304 escape(String s)305 public static String escape(String s) { 306 if (escapeChars.size() == 0) { 307 return s; 308 } 309 StringBuffer b = null; 310 int begin = 0; 311 final int N = s.length(); 312 for (int i=0; i<N; i++) { 313 char c = s.charAt(i); 314 String mapped = escapeChars.get(c); 315 if (mapped != null) { 316 if (b == null) { 317 b = new StringBuffer(s.length() + mapped.length()); 318 } 319 if (begin != i) { 320 b.append(s.substring(begin, i)); 321 } 322 b.append(mapped); 323 begin = i+1; 324 } 325 } 326 if (b != null) { 327 if (begin != N) { 328 b.append(s.substring(begin, N)); 329 } 330 return b.toString(); 331 } 332 return s; 333 } 334 setPageTitle(HDF data, String title)335 public static void setPageTitle(HDF data, String title) 336 { 337 String s = title; 338 if (DroidDoc.title.length() > 0) { 339 s += " - " + DroidDoc.title; 340 } 341 data.setValue("page.title", s); 342 } 343 languageVersion()344 public static LanguageVersion languageVersion() 345 { 346 return LanguageVersion.JAVA_1_5; 347 } 348 optionLength(String option)349 public static int optionLength(String option) 350 { 351 if (option.equals("-d")) { 352 return 2; 353 } 354 if (option.equals("-templatedir")) { 355 return 2; 356 } 357 if (option.equals("-hdf")) { 358 return 3; 359 } 360 if (option.equals("-toroot")) { 361 return 2; 362 } 363 if (option.equals("-samplecode")) { 364 return 4; 365 } 366 if (option.equals("-htmldir")) { 367 return 2; 368 } 369 if (option.equals("-title")) { 370 return 2; 371 } 372 if (option.equals("-werror")) { 373 return 1; 374 } 375 if (option.equals("-hide")) { 376 return 2; 377 } 378 if (option.equals("-warning")) { 379 return 2; 380 } 381 if (option.equals("-error")) { 382 return 2; 383 } 384 if (option.equals("-keeplist")) { 385 return 2; 386 } 387 if (option.equals("-proofread")) { 388 return 2; 389 } 390 if (option.equals("-todo")) { 391 return 2; 392 } 393 if (option.equals("-public")) { 394 return 1; 395 } 396 if (option.equals("-protected")) { 397 return 1; 398 } 399 if (option.equals("-package")) { 400 return 1; 401 } 402 if (option.equals("-private")) { 403 return 1; 404 } 405 if (option.equals("-hidden")) { 406 return 1; 407 } 408 if (option.equals("-stubs")) { 409 return 2; 410 } 411 if (option.equals("-stubpackages")) { 412 return 2; 413 } 414 if (option.equals("-sdkvalues")) { 415 return 2; 416 } 417 if (option.equals("-apixml")) { 418 return 2; 419 } 420 if (option.equals("-nodocs")) { 421 return 1; 422 } 423 if (option.equals("-since")) { 424 return 3; 425 } 426 if (option.equals("-offlinemode")) { 427 return 1; 428 } 429 return 0; 430 } 431 validOptions(String[][] options, DocErrorReporter r)432 public static boolean validOptions(String[][] options, DocErrorReporter r) 433 { 434 for (String[] a: options) { 435 if (a[0].equals("-error") || a[0].equals("-warning") 436 || a[0].equals("-hide")) { 437 try { 438 Integer.parseInt(a[1]); 439 } 440 catch (NumberFormatException e) { 441 r.printError("bad -" + a[0] + " value must be a number: " 442 + a[1]); 443 return false; 444 } 445 } 446 } 447 448 return true; 449 } 450 makeHDF()451 public static HDF makeHDF() 452 { 453 HDF data = new HDF(); 454 455 for (String[] p: mHDFData) { 456 data.setValue(p[0], p[1]); 457 } 458 459 try { 460 for (String p: ClearPage.hdfFiles) { 461 data.readFile(p); 462 } 463 } 464 catch (IOException e) { 465 throw new RuntimeException(e); 466 } 467 468 return data; 469 } 470 makePackageHDF()471 public static HDF makePackageHDF() 472 { 473 HDF data = makeHDF(); 474 ClassInfo[] classes = Converter.rootClasses(); 475 476 SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>(); 477 for (ClassInfo cl: classes) { 478 PackageInfo pkg = cl.containingPackage(); 479 String name; 480 if (pkg == null) { 481 name = ""; 482 } else { 483 name = pkg.name(); 484 } 485 sorted.put(name, pkg); 486 } 487 488 int i = 0; 489 for (String s: sorted.keySet()) { 490 PackageInfo pkg = sorted.get(s); 491 492 if (pkg.isHidden()) { 493 continue; 494 } 495 Boolean allHidden = true; 496 int pass = 0; 497 ClassInfo[] classesToCheck = null; 498 while (pass < 5 ) { 499 switch(pass) { 500 case 0: 501 classesToCheck = pkg.ordinaryClasses(); 502 break; 503 case 1: 504 classesToCheck = pkg.enums(); 505 break; 506 case 2: 507 classesToCheck = pkg.errors(); 508 break; 509 case 3: 510 classesToCheck = pkg.exceptions(); 511 break; 512 case 4: 513 classesToCheck = pkg.interfaces(); 514 break; 515 default: 516 System.err.println("Error reading package: " + pkg.name()); 517 break; 518 } 519 for (ClassInfo cl : classesToCheck) { 520 if (!cl.isHidden()) { 521 allHidden = false; 522 break; 523 } 524 } 525 if (!allHidden) { 526 break; 527 } 528 pass++; 529 } 530 if (allHidden) { 531 continue; 532 } 533 534 data.setValue("reference", "true"); 535 data.setValue("docs.packages." + i + ".name", s); 536 data.setValue("docs.packages." + i + ".link", pkg.htmlPage()); 537 data.setValue("docs.packages." + i + ".since", pkg.getSince()); 538 TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", 539 pkg.firstSentenceTags()); 540 i++; 541 } 542 543 sinceTagger.writeVersionNames(data); 544 return data; 545 } 546 writeDirectory(File dir, String relative)547 public static void writeDirectory(File dir, String relative) 548 { 549 File[] files = dir.listFiles(); 550 int i, count = files.length; 551 for (i=0; i<count; i++) { 552 File f = files[i]; 553 if (f.isFile()) { 554 String templ = relative + f.getName(); 555 int len = templ.length(); 556 if (len > 3 && ".cs".equals(templ.substring(len-3))) { 557 HDF data = makeHDF(); 558 String filename = templ.substring(0,len-3) + htmlExtension; 559 ClearPage.write(data, templ, filename); 560 } 561 else if (len > 3 && ".jd".equals(templ.substring(len-3))) { 562 String filename = templ.substring(0,len-3) + htmlExtension; 563 DocFile.writePage(f.getAbsolutePath(), relative, filename); 564 } 565 else { 566 ClearPage.copyFile(f, templ); 567 } 568 } 569 else if (f.isDirectory()) { 570 writeDirectory(f, relative + f.getName() + "/"); 571 } 572 } 573 } 574 writeHTMLPages()575 public static void writeHTMLPages() 576 { 577 File f = new File(ClearPage.htmlDir); 578 if (!f.isDirectory()) { 579 System.err.println("htmlDir not a directory: " + ClearPage.htmlDir); 580 } 581 writeDirectory(f, ""); 582 } 583 writeLists()584 public static void writeLists() 585 { 586 HDF data = makeHDF(); 587 588 ClassInfo[] classes = Converter.rootClasses(); 589 590 SortedMap<String, Object> sorted = new TreeMap<String, Object>(); 591 for (ClassInfo cl: classes) { 592 if (cl.isHidden()) { 593 continue; 594 } 595 sorted.put(cl.qualifiedName(), cl); 596 PackageInfo pkg = cl.containingPackage(); 597 String name; 598 if (pkg == null) { 599 name = ""; 600 } else { 601 name = pkg.name(); 602 } 603 sorted.put(name, pkg); 604 } 605 606 int i = 0; 607 for (String s: sorted.keySet()) { 608 data.setValue("docs.pages." + i + ".id" , ""+i); 609 data.setValue("docs.pages." + i + ".label" , s); 610 611 Object o = sorted.get(s); 612 if (o instanceof PackageInfo) { 613 PackageInfo pkg = (PackageInfo)o; 614 data.setValue("docs.pages." + i + ".link" , pkg.htmlPage()); 615 data.setValue("docs.pages." + i + ".type" , "package"); 616 } 617 else if (o instanceof ClassInfo) { 618 ClassInfo cl = (ClassInfo)o; 619 data.setValue("docs.pages." + i + ".link" , cl.htmlPage()); 620 data.setValue("docs.pages." + i + ".type" , "class"); 621 } 622 i++; 623 } 624 625 ClearPage.write(data, "lists.cs", javadocDir + "lists.js"); 626 } 627 cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable)628 public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) { 629 if (!notStrippable.add(cl)) { 630 // slight optimization: if it already contains cl, it already contains 631 // all of cl's parents 632 return; 633 } 634 ClassInfo supr = cl.superclass(); 635 if (supr != null) { 636 cantStripThis(supr, notStrippable); 637 } 638 for (ClassInfo iface: cl.interfaces()) { 639 cantStripThis(iface, notStrippable); 640 } 641 } 642 getPrintableName(ClassInfo cl)643 private static String getPrintableName(ClassInfo cl) { 644 ClassInfo containingClass = cl.containingClass(); 645 if (containingClass != null) { 646 // This is an inner class. 647 String baseName = cl.name(); 648 baseName = baseName.substring(baseName.lastIndexOf('.') + 1); 649 return getPrintableName(containingClass) + '$' + baseName; 650 } 651 return cl.qualifiedName(); 652 } 653 654 /** 655 * Writes the list of classes that must be present in order to 656 * provide the non-hidden APIs known to javadoc. 657 * 658 * @param filename the path to the file to write the list to 659 */ writeKeepList(String filename)660 public static void writeKeepList(String filename) { 661 HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>(); 662 ClassInfo[] all = Converter.allClasses(); 663 Arrays.sort(all); // just to make the file a little more readable 664 665 // If a class is public and not hidden, then it and everything it derives 666 // from cannot be stripped. Otherwise we can strip it. 667 for (ClassInfo cl: all) { 668 if (cl.isPublic() && !cl.isHidden()) { 669 cantStripThis(cl, notStrippable); 670 } 671 } 672 PrintStream stream = null; 673 try { 674 stream = new PrintStream(filename); 675 for (ClassInfo cl: notStrippable) { 676 stream.println(getPrintableName(cl)); 677 } 678 } 679 catch (FileNotFoundException e) { 680 System.err.println("error writing file: " + filename); 681 } 682 finally { 683 if (stream != null) { 684 stream.close(); 685 } 686 } 687 } 688 689 private static PackageInfo[] sVisiblePackages = null; choosePackages()690 public static PackageInfo[] choosePackages() { 691 if (sVisiblePackages != null) { 692 return sVisiblePackages; 693 } 694 695 ClassInfo[] classes = Converter.rootClasses(); 696 SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>(); 697 for (ClassInfo cl: classes) { 698 PackageInfo pkg = cl.containingPackage(); 699 String name; 700 if (pkg == null) { 701 name = ""; 702 } else { 703 name = pkg.name(); 704 } 705 sorted.put(name, pkg); 706 } 707 708 ArrayList<PackageInfo> result = new ArrayList(); 709 710 for (String s: sorted.keySet()) { 711 PackageInfo pkg = sorted.get(s); 712 713 if (pkg.isHidden()) { 714 continue; 715 } 716 Boolean allHidden = true; 717 int pass = 0; 718 ClassInfo[] classesToCheck = null; 719 while (pass < 5 ) { 720 switch(pass) { 721 case 0: 722 classesToCheck = pkg.ordinaryClasses(); 723 break; 724 case 1: 725 classesToCheck = pkg.enums(); 726 break; 727 case 2: 728 classesToCheck = pkg.errors(); 729 break; 730 case 3: 731 classesToCheck = pkg.exceptions(); 732 break; 733 case 4: 734 classesToCheck = pkg.interfaces(); 735 break; 736 default: 737 System.err.println("Error reading package: " + pkg.name()); 738 break; 739 } 740 for (ClassInfo cl : classesToCheck) { 741 if (!cl.isHidden()) { 742 allHidden = false; 743 break; 744 } 745 } 746 if (!allHidden) { 747 break; 748 } 749 pass++; 750 } 751 if (allHidden) { 752 continue; 753 } 754 755 result.add(pkg); 756 } 757 758 sVisiblePackages = result.toArray(new PackageInfo[result.size()]); 759 return sVisiblePackages; 760 } 761 writePackages(String filename)762 public static void writePackages(String filename) 763 { 764 HDF data = makePackageHDF(); 765 766 int i = 0; 767 for (PackageInfo pkg: choosePackages()) { 768 writePackage(pkg); 769 770 data.setValue("docs.packages." + i + ".name", pkg.name()); 771 data.setValue("docs.packages." + i + ".link", pkg.htmlPage()); 772 TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", 773 pkg.firstSentenceTags()); 774 775 i++; 776 } 777 778 setPageTitle(data, "Package Index"); 779 780 TagInfo.makeHDF(data, "root.descr", 781 Converter.convertTags(root.inlineTags(), null)); 782 783 ClearPage.write(data, "packages.cs", filename); 784 ClearPage.write(data, "package-list.cs", javadocDir + "package-list"); 785 786 Proofread.writePackages(filename, 787 Converter.convertTags(root.inlineTags(), null)); 788 } 789 writePackage(PackageInfo pkg)790 public static void writePackage(PackageInfo pkg) 791 { 792 // these this and the description are in the same directory, 793 // so it's okay 794 HDF data = makePackageHDF(); 795 796 String name = pkg.name(); 797 798 data.setValue("package.name", name); 799 data.setValue("package.since", pkg.getSince()); 800 data.setValue("package.descr", "...description..."); 801 802 makeClassListHDF(data, "package.interfaces", 803 ClassInfo.sortByName(pkg.interfaces())); 804 makeClassListHDF(data, "package.classes", 805 ClassInfo.sortByName(pkg.ordinaryClasses())); 806 makeClassListHDF(data, "package.enums", 807 ClassInfo.sortByName(pkg.enums())); 808 makeClassListHDF(data, "package.exceptions", 809 ClassInfo.sortByName(pkg.exceptions())); 810 makeClassListHDF(data, "package.errors", 811 ClassInfo.sortByName(pkg.errors())); 812 TagInfo.makeHDF(data, "package.shortDescr", 813 pkg.firstSentenceTags()); 814 TagInfo.makeHDF(data, "package.descr", pkg.inlineTags()); 815 816 String filename = pkg.htmlPage(); 817 setPageTitle(data, name); 818 ClearPage.write(data, "package.cs", filename); 819 820 filename = pkg.fullDescriptionHtmlPage(); 821 setPageTitle(data, name + " Details"); 822 ClearPage.write(data, "package-descr.cs", filename); 823 824 Proofread.writePackage(filename, pkg.inlineTags()); 825 } 826 writeClassLists()827 public static void writeClassLists() 828 { 829 int i; 830 HDF data = makePackageHDF(); 831 832 ClassInfo[] classes = PackageInfo.filterHidden( 833 Converter.convertClasses(root.classes())); 834 if (classes.length == 0) { 835 return ; 836 } 837 838 Sorter[] sorted = new Sorter[classes.length]; 839 for (i=0; i<sorted.length; i++) { 840 ClassInfo cl = classes[i]; 841 String name = cl.name(); 842 sorted[i] = new Sorter(name, cl); 843 } 844 845 Arrays.sort(sorted); 846 847 // make a pass and resolve ones that have the same name 848 int firstMatch = 0; 849 String lastName = sorted[0].label; 850 for (i=1; i<sorted.length; i++) { 851 String s = sorted[i].label; 852 if (!lastName.equals(s)) { 853 if (firstMatch != i-1) { 854 // there were duplicates 855 for (int j=firstMatch; j<i; j++) { 856 PackageInfo pkg = ((ClassInfo)sorted[j].data).containingPackage(); 857 if (pkg != null) { 858 sorted[j].label = sorted[j].label + " (" + pkg.name() + ")"; 859 } 860 } 861 } 862 firstMatch = i; 863 lastName = s; 864 } 865 } 866 867 // and sort again 868 Arrays.sort(sorted); 869 870 for (i=0; i<sorted.length; i++) { 871 String s = sorted[i].label; 872 ClassInfo cl = (ClassInfo)sorted[i].data; 873 char first = Character.toUpperCase(s.charAt(0)); 874 cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i); 875 } 876 877 setPageTitle(data, "Class Index"); 878 ClearPage.write(data, "classes.cs", javadocDir + "classes" + htmlExtension); 879 } 880 881 // we use the word keywords because "index" means something else in html land 882 // the user only ever sees the word index 883 /* public static void writeKeywords() 884 { 885 ArrayList<KeywordEntry> keywords = new ArrayList<KeywordEntry>(); 886 887 ClassInfo[] classes = PackageInfo.filterHidden(Converter.convertClasses(root.classes())); 888 889 for (ClassInfo cl: classes) { 890 cl.makeKeywordEntries(keywords); 891 } 892 893 HDF data = makeHDF(); 894 895 Collections.sort(keywords); 896 897 int i=0; 898 for (KeywordEntry entry: keywords) { 899 String base = "keywords." + entry.firstChar() + "." + i; 900 entry.makeHDF(data, base); 901 i++; 902 } 903 904 setPageTitle(data, "Index"); 905 ClearPage.write(data, "keywords.cs", javadocDir + "keywords" + htmlExtension); 906 } */ 907 writeHierarchy()908 public static void writeHierarchy() 909 { 910 ClassInfo[] classes = Converter.rootClasses(); 911 ArrayList<ClassInfo> info = new ArrayList<ClassInfo>(); 912 for (ClassInfo cl: classes) { 913 if (!cl.isHidden()) { 914 info.add(cl); 915 } 916 } 917 HDF data = makePackageHDF(); 918 Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()])); 919 setPageTitle(data, "Class Hierarchy"); 920 ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension); 921 } 922 writeClasses()923 public static void writeClasses() 924 { 925 ClassInfo[] classes = Converter.rootClasses(); 926 927 for (ClassInfo cl: classes) { 928 HDF data = makePackageHDF(); 929 if (!cl.isHidden()) { 930 writeClass(cl, data); 931 } 932 } 933 } 934 writeClass(ClassInfo cl, HDF data)935 public static void writeClass(ClassInfo cl, HDF data) 936 { 937 cl.makeHDF(data); 938 939 setPageTitle(data, cl.name()); 940 ClearPage.write(data, "class.cs", cl.htmlPage()); 941 942 Proofread.writeClass(cl.htmlPage(), cl); 943 } 944 makeClassListHDF(HDF data, String base, ClassInfo[] classes)945 public static void makeClassListHDF(HDF data, String base, 946 ClassInfo[] classes) 947 { 948 for (int i=0; i<classes.length; i++) { 949 ClassInfo cl = classes[i]; 950 if (!cl.isHidden()) { 951 cl.makeShortDescrHDF(data, base + "." + i); 952 } 953 } 954 } 955 linkTarget(String source, String target)956 public static String linkTarget(String source, String target) 957 { 958 String[] src = source.split("/"); 959 String[] tgt = target.split("/"); 960 961 int srclen = src.length; 962 int tgtlen = tgt.length; 963 964 int same = 0; 965 while (same < (srclen-1) 966 && same < (tgtlen-1) 967 && (src[same].equals(tgt[same]))) { 968 same++; 969 } 970 971 String s = ""; 972 973 int up = srclen-same-1; 974 for (int i=0; i<up; i++) { 975 s += "../"; 976 } 977 978 979 int N = tgtlen-1; 980 for (int i=same; i<N; i++) { 981 s += tgt[i] + '/'; 982 } 983 s += tgt[tgtlen-1]; 984 985 return s; 986 } 987 988 /** 989 * Returns true if the given element has an @hide or @pending annotation. 990 */ hasHideAnnotation(Doc doc)991 private static boolean hasHideAnnotation(Doc doc) { 992 String comment = doc.getRawCommentText(); 993 return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -1; 994 } 995 996 /** 997 * Returns true if the given element is hidden. 998 */ isHidden(Doc doc)999 private static boolean isHidden(Doc doc) { 1000 // Methods, fields, constructors. 1001 if (doc instanceof MemberDoc) { 1002 return hasHideAnnotation(doc); 1003 } 1004 1005 // Classes, interfaces, enums, annotation types. 1006 if (doc instanceof ClassDoc) { 1007 ClassDoc classDoc = (ClassDoc) doc; 1008 1009 // Check the containing package. 1010 if (hasHideAnnotation(classDoc.containingPackage())) { 1011 return true; 1012 } 1013 1014 // Check the class doc and containing class docs if this is a 1015 // nested class. 1016 ClassDoc current = classDoc; 1017 do { 1018 if (hasHideAnnotation(current)) { 1019 return true; 1020 } 1021 1022 current = current.containingClass(); 1023 } while (current != null); 1024 } 1025 1026 return false; 1027 } 1028 1029 /** 1030 * Filters out hidden elements. 1031 */ filterHidden(Object o, Class<?> expected)1032 private static Object filterHidden(Object o, Class<?> expected) { 1033 if (o == null) { 1034 return null; 1035 } 1036 1037 Class type = o.getClass(); 1038 if (type.getName().startsWith("com.sun.")) { 1039 // TODO: Implement interfaces from superclasses, too. 1040 return Proxy.newProxyInstance(type.getClassLoader(), 1041 type.getInterfaces(), new HideHandler(o)); 1042 } else if (o instanceof Object[]) { 1043 Class<?> componentType = expected.getComponentType(); 1044 Object[] array = (Object[]) o; 1045 List<Object> list = new ArrayList<Object>(array.length); 1046 for (Object entry : array) { 1047 if ((entry instanceof Doc) && isHidden((Doc) entry)) { 1048 continue; 1049 } 1050 list.add(filterHidden(entry, componentType)); 1051 } 1052 return list.toArray( 1053 (Object[]) Array.newInstance(componentType, list.size())); 1054 } else { 1055 return o; 1056 } 1057 } 1058 1059 /** 1060 * Filters hidden elements out of method return values. 1061 */ 1062 private static class HideHandler implements InvocationHandler { 1063 1064 private final Object target; 1065 HideHandler(Object target)1066 public HideHandler(Object target) { 1067 this.target = target; 1068 } 1069 invoke(Object proxy, Method method, Object[] args)1070 public Object invoke(Object proxy, Method method, Object[] args) 1071 throws Throwable { 1072 String methodName = method.getName(); 1073 if (args != null) { 1074 if (methodName.equals("compareTo") || 1075 methodName.equals("equals") || 1076 methodName.equals("overrides") || 1077 methodName.equals("subclassOf")) { 1078 args[0] = unwrap(args[0]); 1079 } 1080 } 1081 1082 if (methodName.equals("getRawCommentText")) { 1083 return filterComment((String) method.invoke(target, args)); 1084 } 1085 1086 // escape "&" in disjunctive types. 1087 if (proxy instanceof Type && methodName.equals("toString")) { 1088 return ((String) method.invoke(target, args)) 1089 .replace("&", "&"); 1090 } 1091 1092 try { 1093 return filterHidden(method.invoke(target, args), 1094 method.getReturnType()); 1095 } catch (InvocationTargetException e) { 1096 throw e.getTargetException(); 1097 } 1098 } 1099 filterComment(String s)1100 private String filterComment(String s) { 1101 if (s == null) { 1102 return null; 1103 } 1104 1105 s = s.trim(); 1106 1107 // Work around off by one error 1108 while (s.length() >= 5 1109 && s.charAt(s.length() - 5) == '{') { 1110 s += " "; 1111 } 1112 1113 return s; 1114 } 1115 unwrap(Object proxy)1116 private static Object unwrap(Object proxy) { 1117 if (proxy instanceof Proxy) 1118 return ((HideHandler)Proxy.getInvocationHandler(proxy)).target; 1119 return proxy; 1120 } 1121 } 1122 scope(Scoped scoped)1123 public static String scope(Scoped scoped) { 1124 if (scoped.isPublic()) { 1125 return "public"; 1126 } 1127 else if (scoped.isProtected()) { 1128 return "protected"; 1129 } 1130 else if (scoped.isPackagePrivate()) { 1131 return ""; 1132 } 1133 else if (scoped.isPrivate()) { 1134 return "private"; 1135 } 1136 else { 1137 throw new RuntimeException("invalid scope for object " + scoped); 1138 } 1139 } 1140 1141 /** 1142 * Collect the values used by the Dev tools and write them in files packaged with the SDK 1143 * @param output the ouput directory for the files. 1144 */ writeSdkValues(String output)1145 private static void writeSdkValues(String output) { 1146 ArrayList<String> activityActions = new ArrayList<String>(); 1147 ArrayList<String> broadcastActions = new ArrayList<String>(); 1148 ArrayList<String> serviceActions = new ArrayList<String>(); 1149 ArrayList<String> categories = new ArrayList<String>(); 1150 1151 ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>(); 1152 ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>(); 1153 ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>(); 1154 1155 ClassInfo[] classes = Converter.allClasses(); 1156 1157 // Go through all the fields of all the classes, looking SDK stuff. 1158 for (ClassInfo clazz : classes) { 1159 1160 // first check constant fields for the SdkConstant annotation. 1161 FieldInfo[] fields = clazz.allSelfFields(); 1162 for (FieldInfo field : fields) { 1163 Object cValue = field.constantValue(); 1164 if (cValue != null) { 1165 AnnotationInstanceInfo[] annotations = field.annotations(); 1166 if (annotations.length > 0) { 1167 for (AnnotationInstanceInfo annotation : annotations) { 1168 if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) { 1169 AnnotationValueInfo[] values = annotation.elementValues(); 1170 if (values.length > 0) { 1171 String type = values[0].valueString(); 1172 if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) { 1173 activityActions.add(cValue.toString()); 1174 } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) { 1175 broadcastActions.add(cValue.toString()); 1176 } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) { 1177 serviceActions.add(cValue.toString()); 1178 } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) { 1179 categories.add(cValue.toString()); 1180 } 1181 } 1182 break; 1183 } 1184 } 1185 } 1186 } 1187 } 1188 1189 // Now check the class for @Widget or if its in the android.widget package 1190 // (unless the class is hidden or abstract, or non public) 1191 if (clazz.isHidden() == false && clazz.isPublic() && clazz.isAbstract() == false) { 1192 boolean annotated = false; 1193 AnnotationInstanceInfo[] annotations = clazz.annotations(); 1194 if (annotations.length > 0) { 1195 for (AnnotationInstanceInfo annotation : annotations) { 1196 if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) { 1197 widgets.add(clazz); 1198 annotated = true; 1199 break; 1200 } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) { 1201 layouts.add(clazz); 1202 annotated = true; 1203 break; 1204 } 1205 } 1206 } 1207 1208 if (annotated == false) { 1209 // lets check if this is inside android.widget 1210 PackageInfo pckg = clazz.containingPackage(); 1211 String packageName = pckg.name(); 1212 if ("android.widget".equals(packageName) || 1213 "android.view".equals(packageName)) { 1214 // now we check what this class inherits either from android.view.ViewGroup 1215 // or android.view.View, or android.view.ViewGroup.LayoutParams 1216 int type = checkInheritance(clazz); 1217 switch (type) { 1218 case TYPE_WIDGET: 1219 widgets.add(clazz); 1220 break; 1221 case TYPE_LAYOUT: 1222 layouts.add(clazz); 1223 break; 1224 case TYPE_LAYOUT_PARAM: 1225 layoutParams.add(clazz); 1226 break; 1227 } 1228 } 1229 } 1230 } 1231 } 1232 1233 // now write the files, whether or not the list are empty. 1234 // the SDK built requires those files to be present. 1235 1236 Collections.sort(activityActions); 1237 writeValues(output + "/activity_actions.txt", activityActions); 1238 1239 Collections.sort(broadcastActions); 1240 writeValues(output + "/broadcast_actions.txt", broadcastActions); 1241 1242 Collections.sort(serviceActions); 1243 writeValues(output + "/service_actions.txt", serviceActions); 1244 1245 Collections.sort(categories); 1246 writeValues(output + "/categories.txt", categories); 1247 1248 // before writing the list of classes, we do some checks, to make sure the layout params 1249 // are enclosed by a layout class (and not one that has been declared as a widget) 1250 for (int i = 0 ; i < layoutParams.size();) { 1251 ClassInfo layoutParamClass = layoutParams.get(i); 1252 ClassInfo containingClass = layoutParamClass.containingClass(); 1253 if (containingClass == null || layouts.indexOf(containingClass) == -1) { 1254 layoutParams.remove(i); 1255 } else { 1256 i++; 1257 } 1258 } 1259 1260 writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams); 1261 } 1262 1263 /** 1264 * Writes a list of values into a text files. 1265 * @param pathname the absolute os path of the output file. 1266 * @param values the list of values to write. 1267 */ writeValues(String pathname, ArrayList<String> values)1268 private static void writeValues(String pathname, ArrayList<String> values) { 1269 FileWriter fw = null; 1270 BufferedWriter bw = null; 1271 try { 1272 fw = new FileWriter(pathname, false); 1273 bw = new BufferedWriter(fw); 1274 1275 for (String value : values) { 1276 bw.append(value).append('\n'); 1277 } 1278 } catch (IOException e) { 1279 // pass for now 1280 } finally { 1281 try { 1282 if (bw != null) bw.close(); 1283 } catch (IOException e) { 1284 // pass for now 1285 } 1286 try { 1287 if (fw != null) fw.close(); 1288 } catch (IOException e) { 1289 // pass for now 1290 } 1291 } 1292 } 1293 1294 /** 1295 * Writes the widget/layout/layout param classes into a text files. 1296 * @param pathname the absolute os path of the output file. 1297 * @param widgets the list of widget classes to write. 1298 * @param layouts the list of layout classes to write. 1299 * @param layoutParams the list of layout param classes to write. 1300 */ writeClasses(String pathname, ArrayList<ClassInfo> widgets, ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams)1301 private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets, 1302 ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) { 1303 FileWriter fw = null; 1304 BufferedWriter bw = null; 1305 try { 1306 fw = new FileWriter(pathname, false); 1307 bw = new BufferedWriter(fw); 1308 1309 // write the 3 types of classes. 1310 for (ClassInfo clazz : widgets) { 1311 writeClass(bw, clazz, 'W'); 1312 } 1313 for (ClassInfo clazz : layoutParams) { 1314 writeClass(bw, clazz, 'P'); 1315 } 1316 for (ClassInfo clazz : layouts) { 1317 writeClass(bw, clazz, 'L'); 1318 } 1319 } catch (IOException e) { 1320 // pass for now 1321 } finally { 1322 try { 1323 if (bw != null) bw.close(); 1324 } catch (IOException e) { 1325 // pass for now 1326 } 1327 try { 1328 if (fw != null) fw.close(); 1329 } catch (IOException e) { 1330 // pass for now 1331 } 1332 } 1333 } 1334 1335 /** 1336 * Writes a class name and its super class names into a {@link BufferedWriter}. 1337 * @param writer the BufferedWriter to write into 1338 * @param clazz the class to write 1339 * @param prefix the prefix to put at the beginning of the line. 1340 * @throws IOException 1341 */ writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)1342 private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix) 1343 throws IOException { 1344 writer.append(prefix).append(clazz.qualifiedName()); 1345 ClassInfo superClass = clazz; 1346 while ((superClass = superClass.superclass()) != null) { 1347 writer.append(' ').append(superClass.qualifiedName()); 1348 } 1349 writer.append('\n'); 1350 } 1351 1352 /** 1353 * Checks the inheritance of {@link ClassInfo} objects. This method return 1354 * <ul> 1355 * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li> 1356 * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li> 1357 * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends <code>android.view.ViewGroup$LayoutParams</code></li> 1358 * <li>{@link #TYPE_NONE}: in all other cases</li> 1359 * </ul> 1360 * @param clazz the {@link ClassInfo} to check. 1361 */ checkInheritance(ClassInfo clazz)1362 private static int checkInheritance(ClassInfo clazz) { 1363 if ("android.view.ViewGroup".equals(clazz.qualifiedName())) { 1364 return TYPE_LAYOUT; 1365 } else if ("android.view.View".equals(clazz.qualifiedName())) { 1366 return TYPE_WIDGET; 1367 } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) { 1368 return TYPE_LAYOUT_PARAM; 1369 } 1370 1371 ClassInfo parent = clazz.superclass(); 1372 if (parent != null) { 1373 return checkInheritance(parent); 1374 } 1375 1376 return TYPE_NONE; 1377 } 1378 } 1379