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