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