Doclava.java revision 9af82c2937fa7652dab0a34dc111b43447c41351
1/* 2 * Copyright (C) 2010 Google Inc. 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 17package com.google.doclava; 18 19import com.google.clearsilver.jsilver.JSilver; 20import com.google.clearsilver.jsilver.data.Data; 21import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader; 22import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader; 23import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader; 24import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; 25 26import com.sun.javadoc.*; 27 28import java.util.*; 29import java.util.jar.JarFile; 30import java.util.regex.Matcher; 31import java.io.*; 32import java.lang.reflect.Proxy; 33import java.lang.reflect.Array; 34import java.lang.reflect.InvocationHandler; 35import java.lang.reflect.InvocationTargetException; 36import java.lang.reflect.Method; 37import java.net.MalformedURLException; 38import java.net.URL; 39 40public class Doclava { 41 private static final String SDK_CONSTANT_ANNOTATION = "android.annotation.SdkConstant"; 42 private static final String SDK_CONSTANT_TYPE_ACTIVITY_ACTION = 43 "android.annotation.SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION"; 44 private static final String SDK_CONSTANT_TYPE_BROADCAST_ACTION = 45 "android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION"; 46 private static final String SDK_CONSTANT_TYPE_SERVICE_ACTION = 47 "android.annotation.SdkConstant.SdkConstantType.SERVICE_ACTION"; 48 private static final String SDK_CONSTANT_TYPE_CATEGORY = 49 "android.annotation.SdkConstant.SdkConstantType.INTENT_CATEGORY"; 50 private static final String SDK_CONSTANT_TYPE_FEATURE = 51 "android.annotation.SdkConstant.SdkConstantType.FEATURE"; 52 private static final String SDK_WIDGET_ANNOTATION = "android.annotation.Widget"; 53 private static final String SDK_LAYOUT_ANNOTATION = "android.annotation.Layout"; 54 55 private static final int TYPE_NONE = 0; 56 private static final int TYPE_WIDGET = 1; 57 private static final int TYPE_LAYOUT = 2; 58 private static final int TYPE_LAYOUT_PARAM = 3; 59 60 public static final int SHOW_PUBLIC = 0x00000001; 61 public static final int SHOW_PROTECTED = 0x00000003; 62 public static final int SHOW_PACKAGE = 0x00000007; 63 public static final int SHOW_PRIVATE = 0x0000000f; 64 public static final int SHOW_HIDDEN = 0x0000001f; 65 66 public static int showLevel = SHOW_PROTECTED; 67 68 public static final boolean SORT_BY_NAV_GROUPS = true; 69 /* Debug output for PageMetadata, format urls from site root */ 70 public static boolean META_DBG=false; 71 /* Generate the static html docs with devsite tempating only */ 72 public static boolean DEVSITE_STATIC_ONLY = false; 73 /* Don't resolve @link refs found in devsite pages */ 74 public static boolean DEVSITE_IGNORE_JDLINKS = false; 75 /* Show Preview navigation and process preview docs */ 76 public static boolean INCLUDE_PREVIEW = false; 77 /* output en, es, ja without parent intl/ container */ 78 public static boolean USE_DEVSITE_LOCALE_OUTPUT_PATHS = false; 79 /* generate navtree.js without other docs */ 80 public static boolean NAVTREE_ONLY = false; 81 /* Generate reference navtree.js with all inherited members */ 82 public static boolean AT_LINKS_NAVTREE = false; 83 public static String outputPathBase = "/"; 84 public static ArrayList<String> inputPathHtmlDirs = new ArrayList<String>(); 85 public static ArrayList<String> inputPathHtmlDir2 = new ArrayList<String>(); 86 public static String inputPathResourcesDir; 87 public static String outputPathHtmlDirs; 88 public static String outputPathHtmlDir2; 89 90 public static String javadocDir = "reference/"; 91 public static String htmlExtension; 92 93 public static RootDoc root; 94 public static ArrayList<String[]> mHDFData = new ArrayList<String[]>(); 95 public static List<PageMetadata.Node> sTaglist = new ArrayList<PageMetadata.Node>(); 96 public static ArrayList<SampleCode> sampleCodes = new ArrayList<SampleCode>(); 97 public static ArrayList<SampleCode> sampleCodeGroups = new ArrayList<SampleCode>(); 98 public static Data samplesNavTree; 99 public static Map<Character, String> escapeChars = new HashMap<Character, String>(); 100 public static String title = ""; 101 public static SinceTagger sinceTagger = new SinceTagger(); 102 public static HashSet<String> knownTags = new HashSet<String>(); 103 public static FederationTagger federationTagger = new FederationTagger(); 104 public static Set<String> showAnnotations = new HashSet<String>(); 105 public static boolean showAnnotationOverridesVisibility = false; 106 public static Set<String> hiddenPackages = new HashSet<String>(); 107 public static boolean includeDefaultAssets = true; 108 private static boolean generateDocs = true; 109 private static boolean parseComments = false; 110 private static String yamlNavFile = null; 111 public static boolean documentAnnotations = false; 112 public static String documentAnnotationsPath = null; 113 public static Map<String, String> annotationDocumentationMap = null; 114 public static boolean referenceOnly = false; 115 public static boolean staticOnly = false; 116 117 public static JSilver jSilver = null; 118 119 //API reference extensions 120 private static boolean gmsRef = false; 121 private static boolean gcmRef = false; 122 public static boolean testSupportRef = false; 123 public static String testSupportPath = "android/support/test/"; 124 public static boolean wearableSupportRef = false; 125 public static String wearableSupportPath = "android/support/wearable/"; 126 public static boolean androidSupportRef = false; 127 public static String androidSupportPath = "android/support/"; 128 public static boolean constraintSupportRef = false; 129 public static String constraintSupportPath = "android/support/constraint/"; 130 private static boolean samplesRef = false; 131 private static boolean sac = false; 132 133 public static boolean checkLevel(int level) { 134 return (showLevel & level) == level; 135 } 136 137 /** 138 * Returns true if we should parse javadoc comments, 139 * reporting errors in the process. 140 */ 141 public static boolean parseComments() { 142 return generateDocs || parseComments; 143 } 144 145 public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv, 146 boolean hidden) { 147 if (hidden && !checkLevel(SHOW_HIDDEN)) { 148 return false; 149 } 150 if (pub && checkLevel(SHOW_PUBLIC)) { 151 return true; 152 } 153 if (prot && checkLevel(SHOW_PROTECTED)) { 154 return true; 155 } 156 if (pkgp && checkLevel(SHOW_PACKAGE)) { 157 return true; 158 } 159 if (priv && checkLevel(SHOW_PRIVATE)) { 160 return true; 161 } 162 return false; 163 } 164 165 public static void main(String[] args) { 166 com.sun.tools.javadoc.Main.execute(args); 167 } 168 169 public static boolean start(RootDoc r) { 170 long startTime = System.nanoTime(); 171 String keepListFile = null; 172 String proguardFile = null; 173 String proofreadFile = null; 174 String todoFile = null; 175 String sdkValuePath = null; 176 String stubsDir = null; 177 // Create the dependency graph for the stubs directory 178 boolean offlineMode = false; 179 String apiFile = null; 180 String removedApiFile = null; 181 String debugStubsFile = ""; 182 HashSet<String> stubPackages = null; 183 ArrayList<String> knownTagsFiles = new ArrayList<String>(); 184 185 root = r; 186 187 String[][] options = r.options(); 188 for (String[] a : options) { 189 if (a[0].equals("-d")) { 190 outputPathBase = outputPathHtmlDirs = ClearPage.outputDir = a[1]; 191 } else if (a[0].equals("-templatedir")) { 192 ClearPage.addTemplateDir(a[1]); 193 } else if (a[0].equals("-hdf")) { 194 mHDFData.add(new String[] {a[1], a[2]}); 195 } else if (a[0].equals("-knowntags")) { 196 knownTagsFiles.add(a[1]); 197 } else if (a[0].equals("-toroot")) { 198 ClearPage.toroot = a[1]; 199 } else if (a[0].equals("-samplecode")) { 200 sampleCodes.add(new SampleCode(a[1], a[2], a[3])); 201 } else if (a[0].equals("-samplegroup")) { 202 sampleCodeGroups.add(new SampleCode(null, null, a[1])); 203 } else if (a[0].equals("-samplesdir")) { 204 getSampleProjects(new File(a[1])); 205 //the destination output path for main htmldir 206 } else if (a[0].equals("-htmldir")) { 207 inputPathHtmlDirs.add(a[1]); 208 ClearPage.htmlDirs = inputPathHtmlDirs; 209 //the destination output path for additional htmldir 210 } else if (a[0].equals("-htmldir2")) { 211 if (a[2].equals("default")) { 212 inputPathHtmlDir2.add(a[1]); 213 } else { 214 inputPathHtmlDir2.add(a[1]); 215 outputPathHtmlDir2 = a[2]; 216 } 217 //the destination output path for additional resources (images) 218 } else if (a[0].equals("-resourcesdir")) { 219 inputPathResourcesDir = a[1]; 220 } else if (a[0].equals("-title")) { 221 Doclava.title = a[1]; 222 } else if (a[0].equals("-werror")) { 223 Errors.setWarningsAreErrors(true); 224 } else if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-hide")) { 225 try { 226 int level = -1; 227 if (a[0].equals("-error")) { 228 level = Errors.ERROR; 229 } else if (a[0].equals("-warning")) { 230 level = Errors.WARNING; 231 } else if (a[0].equals("-hide")) { 232 level = Errors.HIDDEN; 233 } 234 Errors.setErrorLevel(Integer.parseInt(a[1]), level); 235 } catch (NumberFormatException e) { 236 // already printed below 237 return false; 238 } 239 } else if (a[0].equals("-keeplist")) { 240 keepListFile = a[1]; 241 } else if (a[0].equals("-showAnnotation")) { 242 showAnnotations.add(a[1]); 243 } else if (a[0].equals("-showAnnotationOverridesVisibility")) { 244 showAnnotationOverridesVisibility = true; 245 } else if (a[0].equals("-hidePackage")) { 246 hiddenPackages.add(a[1]); 247 } else if (a[0].equals("-proguard")) { 248 proguardFile = a[1]; 249 } else if (a[0].equals("-proofread")) { 250 proofreadFile = a[1]; 251 } else if (a[0].equals("-todo")) { 252 todoFile = a[1]; 253 } else if (a[0].equals("-public")) { 254 showLevel = SHOW_PUBLIC; 255 } else if (a[0].equals("-protected")) { 256 showLevel = SHOW_PROTECTED; 257 } else if (a[0].equals("-package")) { 258 showLevel = SHOW_PACKAGE; 259 } else if (a[0].equals("-private")) { 260 showLevel = SHOW_PRIVATE; 261 } else if (a[0].equals("-hidden")) { 262 showLevel = SHOW_HIDDEN; 263 } else if (a[0].equals("-stubs")) { 264 stubsDir = a[1]; 265 } else if (a[0].equals("-stubpackages")) { 266 stubPackages = new HashSet<String>(); 267 for (String pkg : a[1].split(":")) { 268 stubPackages.add(pkg); 269 } 270 } else if (a[0].equals("-sdkvalues")) { 271 sdkValuePath = a[1]; 272 } else if (a[0].equals("-api")) { 273 apiFile = a[1]; 274 } else if (a[0].equals("-removedApi")) { 275 removedApiFile = a[1]; 276 } 277 else if (a[0].equals("-nodocs")) { 278 generateDocs = false; 279 } else if (a[0].equals("-nodefaultassets")) { 280 includeDefaultAssets = false; 281 } else if (a[0].equals("-parsecomments")) { 282 parseComments = true; 283 } else if (a[0].equals("-since")) { 284 sinceTagger.addVersion(a[1], a[2]); 285 } else if (a[0].equals("-offlinemode")) { 286 offlineMode = true; 287 } else if (a[0].equals("-metadataDebug")) { 288 META_DBG = true; 289 } else if (a[0].equals("-includePreview")) { 290 INCLUDE_PREVIEW = true; 291 } else if (a[0].equals("-ignoreJdLinks")) { 292 if (DEVSITE_STATIC_ONLY) { 293 DEVSITE_IGNORE_JDLINKS = true; 294 } 295 } else if (a[0].equals("-federate")) { 296 try { 297 String name = a[1]; 298 URL federationURL = new URL(a[2]); 299 federationTagger.addSiteUrl(name, federationURL); 300 } catch (MalformedURLException e) { 301 System.err.println("Could not parse URL for federation: " + a[1]); 302 return false; 303 } 304 } else if (a[0].equals("-federationapi")) { 305 String name = a[1]; 306 String file = a[2]; 307 federationTagger.addSiteApi(name, file); 308 } else if (a[0].equals("-yaml")) { 309 yamlNavFile = a[1]; 310 } else if (a[0].equals("-documentannotations")) { 311 documentAnnotations = true; 312 documentAnnotationsPath = a[1]; 313 } else if (a[0].equals("-referenceonly")) { 314 referenceOnly = true; 315 mHDFData.add(new String[] {"referenceonly", "1"}); 316 } else if (a[0].equals("-staticonly")) { 317 staticOnly = true; 318 mHDFData.add(new String[] {"staticonly", "1"}); 319 } else if (a[0].equals("-navtreeonly")) { 320 NAVTREE_ONLY = true; 321 } else if (a[0].equals("-atLinksNavtree")) { 322 AT_LINKS_NAVTREE = true; 323 } else if (a[0].equals("-devsite")) { 324 // Don't copy the doclava assets to devsite output (ie use proj assets only) 325 includeDefaultAssets = false; 326 USE_DEVSITE_LOCALE_OUTPUT_PATHS = true; 327 mHDFData.add(new String[] {"devsite", "1"}); 328 if (staticOnly) { 329 DEVSITE_STATIC_ONLY = true; 330 System.out.println(" ... Generating static html only for devsite"); 331 } 332 yamlNavFile = "_book.yaml"; 333 } 334 } 335 336 if (!readKnownTagsFiles(knownTags, knownTagsFiles)) { 337 return false; 338 } 339 340 // Set up the data structures 341 Converter.makeInfo(r); 342 343 if (generateDocs) { 344 ClearPage.addBundledTemplateDir("assets/customizations"); 345 ClearPage.addBundledTemplateDir("assets/templates"); 346 347 List<ResourceLoader> resourceLoaders = new ArrayList<ResourceLoader>(); 348 List<String> templates = ClearPage.getTemplateDirs(); 349 for (String tmpl : templates) { 350 resourceLoaders.add(new FileSystemResourceLoader(tmpl)); 351 } 352 353 templates = ClearPage.getBundledTemplateDirs(); 354 for (String tmpl : templates) { 355 // TODO - remove commented line - it's here for debugging purposes 356 // resourceLoaders.add(new FileSystemResourceLoader("/Volumes/Android/master/external/doclava/res/" + tmpl)); 357 resourceLoaders.add(new ClassResourceLoader(Doclava.class, '/'+tmpl)); 358 } 359 360 ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders); 361 jSilver = new JSilver(compositeResourceLoader); 362 363 if (!Doclava.readTemplateSettings()) { 364 return false; 365 } 366 367 // if requested, only generate the navtree for ds use-case 368 if (NAVTREE_ONLY) { 369 if (AT_LINKS_NAVTREE) { 370 AtLinksNavTree.writeAtLinksNavTree(javadocDir); 371 } else { 372 NavTree.writeNavTree(javadocDir, ""); 373 } 374 return true; 375 } 376 377 // don't do ref doc tasks in devsite static-only builds 378 if (!DEVSITE_STATIC_ONLY) { 379 // Apply @since tags from the XML file 380 sinceTagger.tagAll(Converter.rootClasses()); 381 382 // Apply details of federated documentation 383 federationTagger.tagAll(Converter.rootClasses()); 384 385 // Files for proofreading 386 if (proofreadFile != null) { 387 Proofread.initProofread(proofreadFile); 388 } 389 if (todoFile != null) { 390 TodoFile.writeTodoFile(todoFile); 391 } 392 393 if (samplesRef) { 394 // always write samples without offlineMode behaviors 395 writeSamples(false, sampleCodes, SORT_BY_NAV_GROUPS); 396 } 397 } 398 if (!referenceOnly) { 399 // HTML2 Pages -- Generate Pages from optional secondary dir 400 if (!inputPathHtmlDir2.isEmpty()) { 401 if (!outputPathHtmlDir2.isEmpty()) { 402 ClearPage.outputDir = outputPathBase + "/" + outputPathHtmlDir2; 403 } 404 ClearPage.htmlDirs = inputPathHtmlDir2; 405 writeHTMLPages(); 406 ClearPage.htmlDirs = inputPathHtmlDirs; 407 } 408 409 // HTML Pages 410 if (!ClearPage.htmlDirs.isEmpty()) { 411 ClearPage.htmlDirs = inputPathHtmlDirs; 412 if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) { 413 ClearPage.outputDir = outputPathHtmlDirs + "/en/"; 414 } else { 415 ClearPage.outputDir = outputPathHtmlDirs; 416 } 417 writeHTMLPages(); 418 } 419 } 420 421 writeResources(); 422 423 writeAssets(); 424 425 // don't do ref doc tasks in devsite static-only builds 426 if (!DEVSITE_STATIC_ONLY) { 427 // Navigation tree 428 String refPrefix = new String(); 429 if(gmsRef){ 430 refPrefix = "gms-"; 431 } else if(gcmRef){ 432 refPrefix = "gcm-"; 433 } 434 NavTree.writeNavTree(javadocDir, refPrefix); 435 436 // Write yaml tree. 437 if (yamlNavFile != null){ 438 NavTree.writeYamlTree(javadocDir, yamlNavFile); 439 } 440 441 // Packages Pages 442 writePackages(javadocDir + refPrefix + "packages" + htmlExtension); 443 444 // Classes 445 writeClassLists(); 446 writeClasses(); 447 writeHierarchy(); 448 // writeKeywords(); 449 450 // Lists for JavaScript 451 writeLists(); 452 if (keepListFile != null) { 453 writeKeepList(keepListFile); 454 } 455 456 // Index page 457 writeIndex(); 458 459 Proofread.finishProofread(proofreadFile); 460 461 if (sdkValuePath != null) { 462 writeSdkValues(sdkValuePath); 463 } 464 } 465 // Write metadata for all processed files to jd_lists_unified in out dir 466 if (!sTaglist.isEmpty()) { 467 PageMetadata.WriteListByLang(sTaglist); 468 // For devsite (ds) reference only, write samples_metadata to out dir 469 if ((USE_DEVSITE_LOCALE_OUTPUT_PATHS) && (!DEVSITE_STATIC_ONLY)) { 470 PageMetadata.WriteSamplesListByLang(sTaglist); 471 } 472 } 473 } 474 475 // Stubs 476 if (stubsDir != null || apiFile != null || proguardFile != null || removedApiFile != null) { 477 Stubs.writeStubsAndApi(stubsDir, apiFile, proguardFile, removedApiFile, stubPackages); 478 } 479 480 Errors.printErrors(); 481 482 long time = System.nanoTime() - startTime; 483 System.out.println("DroidDoc took " + (time / 1000000000) + " sec. to write docs to " 484 + outputPathBase ); 485 486 return !Errors.hadError; 487 } 488 489 private static void writeIndex() { 490 Data data = makeHDF(); 491 ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension); 492 } 493 494 private static boolean readTemplateSettings() { 495 Data data = makeHDF(); 496 497 // The .html extension is hard-coded in several .cs files, 498 // and so you cannot currently set it as a property. 499 htmlExtension = ".html"; 500 // htmlExtension = data.getValue("template.extension", ".html"); 501 int i = 0; 502 while (true) { 503 String k = data.getValue("template.escape." + i + ".key", ""); 504 String v = data.getValue("template.escape." + i + ".value", ""); 505 if ("".equals(k)) { 506 break; 507 } 508 if (k.length() != 1) { 509 System.err.println("template.escape." + i + ".key must have a length of 1: " + k); 510 return false; 511 } 512 escapeChars.put(k.charAt(0), v); 513 i++; 514 } 515 return true; 516 } 517 518 private static boolean readKnownTagsFiles(HashSet<String> knownTags, 519 ArrayList<String> knownTagsFiles) { 520 for (String fn: knownTagsFiles) { 521 BufferedReader in = null; 522 try { 523 in = new BufferedReader(new FileReader(fn)); 524 int lineno = 0; 525 boolean fail = false; 526 while (true) { 527 lineno++; 528 String line = in.readLine(); 529 if (line == null) { 530 break; 531 } 532 line = line.trim(); 533 if (line.length() == 0) { 534 continue; 535 } else if (line.charAt(0) == '#') { 536 continue; 537 } 538 String[] words = line.split("\\s+", 2); 539 if (words.length == 2) { 540 if (words[1].charAt(0) != '#') { 541 System.err.println(fn + ":" + lineno 542 + ": Only one tag allowed per line: " + line); 543 fail = true; 544 continue; 545 } 546 } 547 knownTags.add(words[0]); 548 } 549 if (fail) { 550 return false; 551 } 552 } catch (IOException ex) { 553 System.err.println("Error reading file: " + fn + " (" + ex.getMessage() + ")"); 554 return false; 555 } finally { 556 if (in != null) { 557 try { 558 in.close(); 559 } catch (IOException e) { 560 } 561 } 562 } 563 } 564 return true; 565 } 566 567 public static String escape(String s) { 568 if (escapeChars.size() == 0) { 569 return s; 570 } 571 StringBuffer b = null; 572 int begin = 0; 573 final int N = s.length(); 574 for (int i = 0; i < N; i++) { 575 char c = s.charAt(i); 576 String mapped = escapeChars.get(c); 577 if (mapped != null) { 578 if (b == null) { 579 b = new StringBuffer(s.length() + mapped.length()); 580 } 581 if (begin != i) { 582 b.append(s.substring(begin, i)); 583 } 584 b.append(mapped); 585 begin = i + 1; 586 } 587 } 588 if (b != null) { 589 if (begin != N) { 590 b.append(s.substring(begin, N)); 591 } 592 return b.toString(); 593 } 594 return s; 595 } 596 597 public static void setPageTitle(Data data, String title) { 598 String s = title; 599 if (Doclava.title.length() > 0) { 600 s += " - " + Doclava.title; 601 } 602 data.setValue("page.title", s); 603 } 604 605 606 public static LanguageVersion languageVersion() { 607 return LanguageVersion.JAVA_1_5; 608 } 609 610 611 public static int optionLength(String option) { 612 if (option.equals("-d")) { 613 return 2; 614 } 615 if (option.equals("-templatedir")) { 616 return 2; 617 } 618 if (option.equals("-hdf")) { 619 return 3; 620 } 621 if (option.equals("-knowntags")) { 622 return 2; 623 } 624 if (option.equals("-toroot")) { 625 return 2; 626 } 627 if (option.equals("-samplecode")) { 628 samplesRef = true; 629 return 4; 630 } 631 if (option.equals("-samplegroup")) { 632 return 2; 633 } 634 if (option.equals("-samplesdir")) { 635 samplesRef = true; 636 return 2; 637 } 638 if (option.equals("-devsite")) { 639 return 1; 640 } 641 if (option.equals("-ignoreJdLinks")) { 642 return 1; 643 } 644 if (option.equals("-htmldir")) { 645 return 2; 646 } 647 if (option.equals("-htmldir2")) { 648 return 3; 649 } 650 if (option.equals("-resourcesdir")) { 651 return 2; 652 } 653 if (option.equals("-title")) { 654 return 2; 655 } 656 if (option.equals("-werror")) { 657 return 1; 658 } 659 if (option.equals("-hide")) { 660 return 2; 661 } 662 if (option.equals("-warning")) { 663 return 2; 664 } 665 if (option.equals("-error")) { 666 return 2; 667 } 668 if (option.equals("-keeplist")) { 669 return 2; 670 } 671 if (option.equals("-showAnnotation")) { 672 return 2; 673 } 674 if (option.equals("-showAnnotationOverridesVisibility")) { 675 return 1; 676 } 677 if (option.equals("-hidePackage")) { 678 return 2; 679 } 680 if (option.equals("-proguard")) { 681 return 2; 682 } 683 if (option.equals("-proofread")) { 684 return 2; 685 } 686 if (option.equals("-todo")) { 687 return 2; 688 } 689 if (option.equals("-public")) { 690 return 1; 691 } 692 if (option.equals("-protected")) { 693 return 1; 694 } 695 if (option.equals("-package")) { 696 return 1; 697 } 698 if (option.equals("-private")) { 699 return 1; 700 } 701 if (option.equals("-hidden")) { 702 return 1; 703 } 704 if (option.equals("-stubs")) { 705 return 2; 706 } 707 if (option.equals("-stubpackages")) { 708 return 2; 709 } 710 if (option.equals("-sdkvalues")) { 711 return 2; 712 } 713 if (option.equals("-api")) { 714 return 2; 715 } 716 if (option.equals("-removedApi")) { 717 return 2; 718 } 719 if (option.equals("-nodocs")) { 720 return 1; 721 } 722 if (option.equals("-nodefaultassets")) { 723 return 1; 724 } 725 if (option.equals("-parsecomments")) { 726 return 1; 727 } 728 if (option.equals("-since")) { 729 return 3; 730 } 731 if (option.equals("-offlinemode")) { 732 return 1; 733 } 734 if (option.equals("-federate")) { 735 return 3; 736 } 737 if (option.equals("-federationapi")) { 738 return 3; 739 } 740 if (option.equals("-yaml")) { 741 return 2; 742 } 743 if (option.equals("-gmsref")) { 744 gmsRef = true; 745 return 1; 746 } 747 if (option.equals("-gcmref")) { 748 gcmRef = true; 749 return 1; 750 } 751 if (option.equals("-testSupportRef")) { 752 testSupportRef = true; 753 return 1; 754 } 755 if (option.equals("-wearableSupportRef")) { 756 wearableSupportRef = true; 757 return 1; 758 } 759 if (option.equals("-androidSupportRef")) { 760 androidSupportRef = true; 761 } 762 if (option.equals("-constraintSupportRef")) { 763 constraintSupportRef = true; 764 return 1; 765 } 766 if (option.equals("-metadataDebug")) { 767 return 1; 768 } 769 if (option.equals("-includePreview")) { 770 return 1; 771 } 772 if (option.equals("-documentannotations")) { 773 return 2; 774 } 775 if (option.equals("-referenceonly")) { 776 return 1; 777 } 778 if (option.equals("-staticonly")) { 779 return 1; 780 } 781 if (option.equals("-navtreeonly")) { 782 return 1; 783 } 784 if (option.equals("-atLinksNavtree")) { 785 return 1; 786 } 787 return 0; 788 } 789 public static boolean validOptions(String[][] options, DocErrorReporter r) { 790 for (String[] a : options) { 791 if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-hide")) { 792 try { 793 Integer.parseInt(a[1]); 794 } catch (NumberFormatException e) { 795 r.printError("bad -" + a[0] + " value must be a number: " + a[1]); 796 return false; 797 } 798 } 799 } 800 801 return true; 802 } 803 804 public static Data makeHDF() { 805 Data data = jSilver.createData(); 806 807 for (String[] p : mHDFData) { 808 data.setValue(p[0], p[1]); 809 } 810 811 return data; 812 } 813 814 815 816 public static Data makePackageHDF() { 817 Data data = makeHDF(); 818 ClassInfo[] classes = Converter.rootClasses(); 819 820 SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>(); 821 for (ClassInfo cl : classes) { 822 PackageInfo pkg = cl.containingPackage(); 823 String name; 824 if (pkg == null) { 825 name = ""; 826 } else { 827 name = pkg.name(); 828 } 829 sorted.put(name, pkg); 830 } 831 832 int i = 0; 833 for (Map.Entry<String, PackageInfo> entry : sorted.entrySet()) { 834 String s = entry.getKey(); 835 PackageInfo pkg = entry.getValue(); 836 837 if (pkg.isHiddenOrRemoved()) { 838 continue; 839 } 840 boolean allHiddenOrRemoved = true; 841 int pass = 0; 842 ClassInfo[] classesToCheck = null; 843 while (pass < 6) { 844 switch (pass) { 845 case 0: 846 classesToCheck = pkg.ordinaryClasses(); 847 break; 848 case 1: 849 classesToCheck = pkg.enums(); 850 break; 851 case 2: 852 classesToCheck = pkg.errors(); 853 break; 854 case 3: 855 classesToCheck = pkg.exceptions(); 856 break; 857 case 4: 858 classesToCheck = pkg.interfaces(); 859 break; 860 case 5: 861 classesToCheck = pkg.annotations(); 862 break; 863 default: 864 System.err.println("Error reading package: " + pkg.name()); 865 break; 866 } 867 for (ClassInfo cl : classesToCheck) { 868 if (!cl.isHiddenOrRemoved()) { 869 allHiddenOrRemoved = false; 870 break; 871 } 872 } 873 if (!allHiddenOrRemoved) { 874 break; 875 } 876 pass++; 877 } 878 if (allHiddenOrRemoved) { 879 continue; 880 } 881 if(gmsRef){ 882 data.setValue("reference.gms", "true"); 883 } else if(gcmRef){ 884 data.setValue("reference.gcm", "true"); 885 } else if(testSupportRef){ 886 data.setValue("reference.testSupport", "true"); 887 } else if(wearableSupportRef){ 888 data.setValue("reference.wearableSupport", "true"); 889 } else if(androidSupportRef){ 890 data.setValue("reference.androidSupport", "true"); 891 } else if(constraintSupportRef){ 892 data.setValue("reference.constraintSupport", "true"); 893 } 894 data.setValue("reference", "1"); 895 data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0"); 896 data.setValue("docs.packages." + i + ".name", s); 897 data.setValue("docs.packages." + i + ".link", pkg.htmlPage()); 898 data.setValue("docs.packages." + i + ".since", pkg.getSince()); 899 TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags()); 900 i++; 901 } 902 903 sinceTagger.writeVersionNames(data); 904 return data; 905 } 906 907 private static void writeDirectory(File dir, String relative, JSilver js) { 908 File[] files = dir.listFiles(); 909 int i, count = files.length; 910 for (i = 0; i < count; i++) { 911 File f = files[i]; 912 if (f.isFile()) { 913 String templ = relative + f.getName(); 914 int len = templ.length(); 915 if (len > 3 && ".cs".equals(templ.substring(len - 3))) { 916 Data data = makePackageHDF(); 917 String filename = templ.substring(0, len - 3) + htmlExtension; 918 ClearPage.write(data, templ, filename, js); 919 } else if (len > 3 && ".jd".equals(templ.substring(len - 3))) { 920 Data data = makePackageHDF(); 921 String filename = templ.substring(0, len - 3) + htmlExtension; 922 DocFile.writePage(f.getAbsolutePath(), relative, filename, data); 923 } else if(!f.getName().equals(".DS_Store")){ 924 Data data = makeHDF(); 925 String hdfValue = data.getValue("sac") == null ? "" : data.getValue("sac"); 926 boolean allowExcepted = hdfValue.equals("true") ? true : false; 927 boolean append = false; 928 ClearPage.copyFile(allowExcepted, f, templ, append); 929 } 930 } else if (f.isDirectory()) { 931 writeDirectory(f, relative + f.getName() + "/", js); 932 } 933 } 934 } 935 936 public static void writeHTMLPages() { 937 for (String htmlDir : ClearPage.htmlDirs) { 938 File f = new File(htmlDir); 939 if (!f.isDirectory()) { 940 System.err.println("htmlDir not a directory: " + htmlDir); 941 continue; 942 } 943 944 ResourceLoader loader = new FileSystemResourceLoader(f); 945 JSilver js = new JSilver(loader); 946 writeDirectory(f, "", js); 947 } 948 } 949 950 /* copy files supplied by the -resourcesdir flag */ 951 public static void writeResources() { 952 if (inputPathResourcesDir != null && !inputPathResourcesDir.isEmpty()) { 953 try { 954 File f = new File(inputPathResourcesDir); 955 if (!f.isDirectory()) { 956 System.err.println("resourcesdir is not a directory: " + inputPathResourcesDir); 957 return; 958 } 959 960 ResourceLoader loader = new FileSystemResourceLoader(f); 961 JSilver js = new JSilver(loader); 962 writeDirectory(f, "resources/", js); 963 } catch(Exception e) { 964 System.err.println("Could not copy resourcesdir: " + e); 965 } 966 } 967 } 968 969 public static void writeAssets() { 970 JarFile thisJar = JarUtils.jarForClass(Doclava.class, null); 971 if ((thisJar != null) && (includeDefaultAssets)) { 972 try { 973 List<String> templateDirs = ClearPage.getBundledTemplateDirs(); 974 for (String templateDir : templateDirs) { 975 String assetsDir = templateDir + "/assets"; 976 JarUtils.copyResourcesToDirectory(thisJar, assetsDir, ClearPage.outputDir + "/assets"); 977 } 978 } catch (IOException e) { 979 System.err.println("Error copying assets directory."); 980 e.printStackTrace(); 981 return; 982 } 983 } 984 985 //write the project-specific assets 986 List<String> templateDirs = ClearPage.getTemplateDirs(); 987 for (String templateDir : templateDirs) { 988 File assets = new File(templateDir + "/assets"); 989 if (assets.isDirectory()) { 990 writeDirectory(assets, "assets/", null); 991 } 992 } 993 994 // Create the timestamp.js file based on .cs file 995 Data timedata = Doclava.makeHDF(); 996 ClearPage.write(timedata, "timestamp.cs", "timestamp.js"); 997 } 998 999 /** Go through the docs and generate meta-data about each 1000 page to use in search suggestions */ 1001 public static void writeLists() { 1002 1003 // Write the lists for API references 1004 Data data = makeHDF(); 1005 1006 ClassInfo[] classes = Converter.rootClasses(); 1007 1008 SortedMap<String, Object> sorted = new TreeMap<String, Object>(); 1009 for (ClassInfo cl : classes) { 1010 if (cl.isHiddenOrRemoved()) { 1011 continue; 1012 } 1013 sorted.put(cl.qualifiedName(), cl); 1014 PackageInfo pkg = cl.containingPackage(); 1015 String name; 1016 if (pkg == null) { 1017 name = ""; 1018 } else { 1019 name = pkg.name(); 1020 } 1021 sorted.put(name, pkg); 1022 } 1023 1024 int i = 0; 1025 String listDir = javadocDir; 1026 if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) { 1027 if (testSupportRef) { 1028 listDir = listDir + testSupportPath; 1029 data.setValue("reference.testSupport", "true"); 1030 } else if (wearableSupportRef) { 1031 listDir = listDir + wearableSupportPath; 1032 data.setValue("reference.wearableSupport", "true"); 1033 } else if (androidSupportRef) { 1034 listDir = listDir + androidSupportPath; 1035 data.setValue("reference.androidSupport", "true"); 1036 } else if (constraintSupportRef) { 1037 listDir = listDir + androidSupportPath; 1038 data.setValue("reference.constraintSupport", "true"); 1039 } 1040 } 1041 for (String s : sorted.keySet()) { 1042 data.setValue("docs.pages." + i + ".id", "" + i); 1043 data.setValue("docs.pages." + i + ".label", s); 1044 1045 Object o = sorted.get(s); 1046 if (o instanceof PackageInfo) { 1047 PackageInfo pkg = (PackageInfo) o; 1048 data.setValue("docs.pages." + i + ".link", pkg.htmlPage()); 1049 data.setValue("docs.pages." + i + ".type", "package"); 1050 data.setValue("docs.pages." + i + ".deprecated", pkg.isDeprecated() ? "true" : "false"); 1051 } else if (o instanceof ClassInfo) { 1052 ClassInfo cl = (ClassInfo) o; 1053 data.setValue("docs.pages." + i + ".link", cl.htmlPage()); 1054 data.setValue("docs.pages." + i + ".type", "class"); 1055 data.setValue("docs.pages." + i + ".deprecated", cl.isDeprecated() ? "true" : "false"); 1056 } 1057 i++; 1058 } 1059 ClearPage.write(data, "lists.cs", listDir + "lists.js"); 1060 1061 1062 // Write the lists for JD documents (if there are HTML directories to process) 1063 // Skip this for devsite builds 1064 if ((inputPathHtmlDirs.size() > 0) && (!USE_DEVSITE_LOCALE_OUTPUT_PATHS)) { 1065 Data jddata = makeHDF(); 1066 Iterator counter = new Iterator(); 1067 for (String htmlDir : inputPathHtmlDirs) { 1068 File dir = new File(htmlDir); 1069 if (!dir.isDirectory()) { 1070 continue; 1071 } 1072 writeJdDirList(dir, jddata, counter); 1073 } 1074 ClearPage.write(jddata, "jd_lists.cs", javadocDir + "jd_lists.js"); 1075 } 1076 } 1077 1078 private static class Iterator { 1079 int i = 0; 1080 } 1081 1082 /** Write meta-data for a JD file, used for search suggestions */ 1083 private static void writeJdDirList(File dir, Data data, Iterator counter) { 1084 File[] files = dir.listFiles(); 1085 int i, count = files.length; 1086 // Loop all files in given directory 1087 for (i = 0; i < count; i++) { 1088 File f = files[i]; 1089 if (f.isFile()) { 1090 String filePath = f.getAbsolutePath(); 1091 String templ = f.getName(); 1092 int len = templ.length(); 1093 // If it's a .jd file we want to process 1094 if (len > 3 && ".jd".equals(templ.substring(len - 3))) { 1095 // remove the directories below the site root 1096 String webPath = filePath.substring(filePath.indexOf("docs/html/") + 10, 1097 filePath.length()); 1098 // replace .jd with .html 1099 webPath = webPath.substring(0, webPath.length() - 3) + htmlExtension; 1100 // Parse the .jd file for properties data at top of page 1101 Data hdf = Doclava.makeHDF(); 1102 String filedata = DocFile.readFile(filePath); 1103 Matcher lines = DocFile.LINE.matcher(filedata); 1104 String line = null; 1105 // Get each line to add the key-value to hdf 1106 while (lines.find()) { 1107 line = lines.group(1); 1108 if (line.length() > 0) { 1109 // Stop when we hit the body 1110 if (line.equals("@jd:body")) { 1111 break; 1112 } 1113 Matcher prop = DocFile.PROP.matcher(line); 1114 if (prop.matches()) { 1115 String key = prop.group(1); 1116 String value = prop.group(2); 1117 hdf.setValue(key, value); 1118 } else { 1119 break; 1120 } 1121 } 1122 } // done gathering page properties 1123 1124 // Insert the goods into HDF data (title, link, tags, type) 1125 String title = hdf.getValue("page.title", ""); 1126 title = title.replaceAll("\"", "'"); 1127 // if there's a <span> in the title, get rid of it 1128 if (title.indexOf("<span") != -1) { 1129 String[] splitTitle = title.split("<span(.*?)</span>"); 1130 title = splitTitle[0]; 1131 for (int j = 1; j < splitTitle.length; j++) { 1132 title.concat(splitTitle[j]); 1133 } 1134 } 1135 1136 StringBuilder tags = new StringBuilder(); 1137 String tagsList = hdf.getValue("page.tags", ""); 1138 if (!tagsList.equals("")) { 1139 tagsList = tagsList.replaceAll("\"", ""); 1140 String[] tagParts = tagsList.split(","); 1141 for (int iter = 0; iter < tagParts.length; iter++) { 1142 tags.append("\""); 1143 tags.append(tagParts[iter].trim()); 1144 tags.append("\""); 1145 if (iter < tagParts.length - 1) { 1146 tags.append(","); 1147 } 1148 } 1149 } 1150 1151 String dirName = (webPath.indexOf("/") != -1) 1152 ? webPath.substring(0, webPath.indexOf("/")) : ""; 1153 1154 if (!"".equals(title) && 1155 !"intl".equals(dirName) && 1156 !hdf.getBooleanValue("excludeFromSuggestions")) { 1157 data.setValue("docs.pages." + counter.i + ".label", title); 1158 data.setValue("docs.pages." + counter.i + ".link", webPath); 1159 data.setValue("docs.pages." + counter.i + ".tags", tags.toString()); 1160 data.setValue("docs.pages." + counter.i + ".type", dirName); 1161 counter.i++; 1162 } 1163 } 1164 } else if (f.isDirectory()) { 1165 writeJdDirList(f, data, counter); 1166 } 1167 } 1168 } 1169 1170 public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) { 1171 if (!notStrippable.add(cl)) { 1172 // slight optimization: if it already contains cl, it already contains 1173 // all of cl's parents 1174 return; 1175 } 1176 ClassInfo supr = cl.superclass(); 1177 if (supr != null) { 1178 cantStripThis(supr, notStrippable); 1179 } 1180 for (ClassInfo iface : cl.interfaces()) { 1181 cantStripThis(iface, notStrippable); 1182 } 1183 } 1184 1185 private static String getPrintableName(ClassInfo cl) { 1186 ClassInfo containingClass = cl.containingClass(); 1187 if (containingClass != null) { 1188 // This is an inner class. 1189 String baseName = cl.name(); 1190 baseName = baseName.substring(baseName.lastIndexOf('.') + 1); 1191 return getPrintableName(containingClass) + '$' + baseName; 1192 } 1193 return cl.qualifiedName(); 1194 } 1195 1196 /** 1197 * Writes the list of classes that must be present in order to provide the non-hidden APIs known 1198 * to javadoc. 1199 * 1200 * @param filename the path to the file to write the list to 1201 */ 1202 public static void writeKeepList(String filename) { 1203 HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>(); 1204 ClassInfo[] all = Converter.allClasses(); 1205 Arrays.sort(all); // just to make the file a little more readable 1206 1207 // If a class is public and not hidden, then it and everything it derives 1208 // from cannot be stripped. Otherwise we can strip it. 1209 for (ClassInfo cl : all) { 1210 if (cl.isPublic() && !cl.isHiddenOrRemoved()) { 1211 cantStripThis(cl, notStrippable); 1212 } 1213 } 1214 PrintStream stream = null; 1215 try { 1216 stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(filename))); 1217 for (ClassInfo cl : notStrippable) { 1218 stream.println(getPrintableName(cl)); 1219 } 1220 } catch (FileNotFoundException e) { 1221 System.err.println("error writing file: " + filename); 1222 } finally { 1223 if (stream != null) { 1224 stream.close(); 1225 } 1226 } 1227 } 1228 1229 private static PackageInfo[] sVisiblePackages = null; 1230 1231 public static PackageInfo[] choosePackages() { 1232 if (sVisiblePackages != null) { 1233 return sVisiblePackages; 1234 } 1235 1236 ClassInfo[] classes = Converter.rootClasses(); 1237 SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>(); 1238 for (ClassInfo cl : classes) { 1239 PackageInfo pkg = cl.containingPackage(); 1240 String name; 1241 if (pkg == null) { 1242 name = ""; 1243 } else { 1244 name = pkg.name(); 1245 } 1246 sorted.put(name, pkg); 1247 } 1248 1249 ArrayList<PackageInfo> result = new ArrayList<PackageInfo>(); 1250 1251 for (String s : sorted.keySet()) { 1252 PackageInfo pkg = sorted.get(s); 1253 1254 if (pkg.isHiddenOrRemoved()) { 1255 continue; 1256 } 1257 1258 boolean allHiddenOrRemoved = true; 1259 int pass = 0; 1260 ClassInfo[] classesToCheck = null; 1261 while (pass < 6) { 1262 switch (pass) { 1263 case 0: 1264 classesToCheck = pkg.ordinaryClasses(); 1265 break; 1266 case 1: 1267 classesToCheck = pkg.enums(); 1268 break; 1269 case 2: 1270 classesToCheck = pkg.errors(); 1271 break; 1272 case 3: 1273 classesToCheck = pkg.exceptions(); 1274 break; 1275 case 4: 1276 classesToCheck = pkg.interfaces(); 1277 break; 1278 case 5: 1279 classesToCheck = pkg.annotations(); 1280 break; 1281 default: 1282 System.err.println("Error reading package: " + pkg.name()); 1283 break; 1284 } 1285 for (ClassInfo cl : classesToCheck) { 1286 if (!cl.isHiddenOrRemoved()) { 1287 allHiddenOrRemoved = false; 1288 break; 1289 } 1290 } 1291 if (!allHiddenOrRemoved) { 1292 break; 1293 } 1294 pass++; 1295 } 1296 if (allHiddenOrRemoved) { 1297 continue; 1298 } 1299 1300 result.add(pkg); 1301 } 1302 1303 sVisiblePackages = result.toArray(new PackageInfo[result.size()]); 1304 return sVisiblePackages; 1305 } 1306 1307 public static void writePackages(String filename) { 1308 Data data = makePackageHDF(); 1309 1310 int i = 0; 1311 for (PackageInfo pkg : choosePackages()) { 1312 writePackage(pkg); 1313 1314 data.setValue("docs.packages." + i + ".name", pkg.name()); 1315 data.setValue("docs.packages." + i + ".link", pkg.htmlPage()); 1316 TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags()); 1317 1318 i++; 1319 } 1320 1321 setPageTitle(data, "Package Index"); 1322 1323 TagInfo.makeHDF(data, "root.descr", Converter.convertTags(root.inlineTags(), null)); 1324 1325 ClearPage.write(data, "packages.cs", filename); 1326 ClearPage.write(data, "package-list.cs", javadocDir + "package-list"); 1327 1328 Proofread.writePackages(filename, Converter.convertTags(root.inlineTags(), null)); 1329 } 1330 1331 public static void writePackage(PackageInfo pkg) { 1332 // these this and the description are in the same directory, 1333 // so it's okay 1334 Data data = makePackageHDF(); 1335 1336 String name = pkg.name(); 1337 1338 data.setValue("package.name", name); 1339 data.setValue("package.since", pkg.getSince()); 1340 data.setValue("package.descr", "...description..."); 1341 pkg.setFederatedReferences(data, "package"); 1342 1343 makeClassListHDF(data, "package.annotations", ClassInfo.sortByName(pkg.annotations())); 1344 makeClassListHDF(data, "package.interfaces", ClassInfo.sortByName(pkg.interfaces())); 1345 makeClassListHDF(data, "package.classes", ClassInfo.sortByName(pkg.ordinaryClasses())); 1346 makeClassListHDF(data, "package.enums", ClassInfo.sortByName(pkg.enums())); 1347 makeClassListHDF(data, "package.exceptions", ClassInfo.sortByName(pkg.exceptions())); 1348 makeClassListHDF(data, "package.errors", ClassInfo.sortByName(pkg.errors())); 1349 TagInfo.makeHDF(data, "package.shortDescr", pkg.firstSentenceTags()); 1350 TagInfo.makeHDF(data, "package.descr", pkg.inlineTags()); 1351 1352 String filename = pkg.htmlPage(); 1353 setPageTitle(data, name); 1354 ClearPage.write(data, "package.cs", filename); 1355 1356 Proofread.writePackage(filename, pkg.inlineTags()); 1357 } 1358 1359 public static void writeClassLists() { 1360 int i; 1361 Data data = makePackageHDF(); 1362 1363 ClassInfo[] classes = PackageInfo.filterHiddenAndRemoved( 1364 Converter.convertClasses(root.classes())); 1365 if (classes.length == 0) { 1366 return; 1367 } 1368 1369 Sorter[] sorted = new Sorter[classes.length]; 1370 for (i = 0; i < sorted.length; i++) { 1371 ClassInfo cl = classes[i]; 1372 String name = cl.name(); 1373 sorted[i] = new Sorter(name, cl); 1374 } 1375 1376 Arrays.sort(sorted); 1377 1378 // make a pass and resolve ones that have the same name 1379 int firstMatch = 0; 1380 String lastName = sorted[0].label; 1381 for (i = 1; i < sorted.length; i++) { 1382 String s = sorted[i].label; 1383 if (!lastName.equals(s)) { 1384 if (firstMatch != i - 1) { 1385 // there were duplicates 1386 for (int j = firstMatch; j < i; j++) { 1387 PackageInfo pkg = ((ClassInfo) sorted[j].data).containingPackage(); 1388 if (pkg != null) { 1389 sorted[j].label = sorted[j].label + " (" + pkg.name() + ")"; 1390 } 1391 } 1392 } 1393 firstMatch = i; 1394 lastName = s; 1395 } 1396 } 1397 1398 // and sort again 1399 Arrays.sort(sorted); 1400 1401 for (i = 0; i < sorted.length; i++) { 1402 String s = sorted[i].label; 1403 ClassInfo cl = (ClassInfo) sorted[i].data; 1404 char first = Character.toUpperCase(s.charAt(0)); 1405 cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i); 1406 } 1407 1408 setPageTitle(data, "Class Index"); 1409 ClearPage.write(data, "classes.cs", javadocDir + "classes" + htmlExtension); 1410 } 1411 1412 // we use the word keywords because "index" means something else in html land 1413 // the user only ever sees the word index 1414 /* 1415 * public static void writeKeywords() { ArrayList<KeywordEntry> keywords = new 1416 * ArrayList<KeywordEntry>(); 1417 * 1418 * ClassInfo[] classes = PackageInfo.filterHiddenAndRemoved(Converter.convertClasses(root.classes())); 1419 * 1420 * for (ClassInfo cl: classes) { cl.makeKeywordEntries(keywords); } 1421 * 1422 * HDF data = makeHDF(); 1423 * 1424 * Collections.sort(keywords); 1425 * 1426 * int i=0; for (KeywordEntry entry: keywords) { String base = "keywords." + entry.firstChar() + 1427 * "." + i; entry.makeHDF(data, base); i++; } 1428 * 1429 * setPageTitle(data, "Index"); ClearPage.write(data, "keywords.cs", javadocDir + "keywords" + 1430 * htmlExtension); } 1431 */ 1432 1433 public static void writeHierarchy() { 1434 ClassInfo[] classes = Converter.rootClasses(); 1435 ArrayList<ClassInfo> info = new ArrayList<ClassInfo>(); 1436 for (ClassInfo cl : classes) { 1437 if (!cl.isHiddenOrRemoved()) { 1438 info.add(cl); 1439 } 1440 } 1441 Data data = makePackageHDF(); 1442 Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()])); 1443 setPageTitle(data, "Class Hierarchy"); 1444 ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension); 1445 } 1446 1447 public static void writeClasses() { 1448 ClassInfo[] classes = Converter.rootClasses(); 1449 1450 for (ClassInfo cl : classes) { 1451 Data data = makePackageHDF(); 1452 if (!cl.isHiddenOrRemoved()) { 1453 writeClass(cl, data); 1454 } 1455 } 1456 } 1457 1458 public static void writeClass(ClassInfo cl, Data data) { 1459 cl.makeHDF(data); 1460 setPageTitle(data, cl.name()); 1461 String outfile = cl.htmlPage(); 1462 ClearPage.write(data, "class.cs", outfile); 1463 Proofread.writeClass(cl.htmlPage(), cl); 1464 } 1465 1466 public static void makeClassListHDF(Data data, String base, ClassInfo[] classes) { 1467 for (int i = 0; i < classes.length; i++) { 1468 ClassInfo cl = classes[i]; 1469 if (!cl.isHiddenOrRemoved()) { 1470 cl.makeShortDescrHDF(data, base + "." + i); 1471 } 1472 } 1473 } 1474 1475 public static String linkTarget(String source, String target) { 1476 String[] src = source.split("/"); 1477 String[] tgt = target.split("/"); 1478 1479 int srclen = src.length; 1480 int tgtlen = tgt.length; 1481 1482 int same = 0; 1483 while (same < (srclen - 1) && same < (tgtlen - 1) && (src[same].equals(tgt[same]))) { 1484 same++; 1485 } 1486 1487 String s = ""; 1488 1489 int up = srclen - same - 1; 1490 for (int i = 0; i < up; i++) { 1491 s += "../"; 1492 } 1493 1494 1495 int N = tgtlen - 1; 1496 for (int i = same; i < N; i++) { 1497 s += tgt[i] + '/'; 1498 } 1499 s += tgt[tgtlen - 1]; 1500 1501 return s; 1502 } 1503 1504 /** 1505 * Returns true if the given element has an @hide, @removed or @pending annotation. 1506 */ 1507 private static boolean hasHideOrRemovedAnnotation(Doc doc) { 1508 String comment = doc.getRawCommentText(); 1509 return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -1 || 1510 comment.indexOf("@removed") != -1; 1511 } 1512 1513 /** 1514 * Returns true if the given element is hidden. 1515 */ 1516 private static boolean isHiddenOrRemoved(Doc doc) { 1517 // Methods, fields, constructors. 1518 if (doc instanceof MemberDoc) { 1519 return hasHideOrRemovedAnnotation(doc); 1520 } 1521 1522 // Classes, interfaces, enums, annotation types. 1523 if (doc instanceof ClassDoc) { 1524 ClassDoc classDoc = (ClassDoc) doc; 1525 1526 // Check the containing package. 1527 if (hasHideOrRemovedAnnotation(classDoc.containingPackage())) { 1528 return true; 1529 } 1530 1531 // Check the class doc and containing class docs if this is a 1532 // nested class. 1533 ClassDoc current = classDoc; 1534 do { 1535 if (hasHideOrRemovedAnnotation(current)) { 1536 return true; 1537 } 1538 1539 current = current.containingClass(); 1540 } while (current != null); 1541 } 1542 1543 return false; 1544 } 1545 1546 /** 1547 * Filters out hidden and removed elements. 1548 */ 1549 private static Object filterHiddenAndRemoved(Object o, Class<?> expected) { 1550 if (o == null) { 1551 return null; 1552 } 1553 1554 Class type = o.getClass(); 1555 if (type.getName().startsWith("com.sun.")) { 1556 // TODO: Implement interfaces from superclasses, too. 1557 return Proxy 1558 .newProxyInstance(type.getClassLoader(), type.getInterfaces(), new HideHandler(o)); 1559 } else if (o instanceof Object[]) { 1560 Class<?> componentType = expected.getComponentType(); 1561 Object[] array = (Object[]) o; 1562 List<Object> list = new ArrayList<Object>(array.length); 1563 for (Object entry : array) { 1564 if ((entry instanceof Doc) && isHiddenOrRemoved((Doc) entry)) { 1565 continue; 1566 } 1567 list.add(filterHiddenAndRemoved(entry, componentType)); 1568 } 1569 return list.toArray((Object[]) Array.newInstance(componentType, list.size())); 1570 } else { 1571 return o; 1572 } 1573 } 1574 1575 /** 1576 * Filters hidden elements out of method return values. 1577 */ 1578 private static class HideHandler implements InvocationHandler { 1579 1580 private final Object target; 1581 1582 public HideHandler(Object target) { 1583 this.target = target; 1584 } 1585 1586 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 1587 String methodName = method.getName(); 1588 if (args != null) { 1589 if (methodName.equals("compareTo") || methodName.equals("equals") 1590 || methodName.equals("overrides") || methodName.equals("subclassOf")) { 1591 args[0] = unwrap(args[0]); 1592 } 1593 } 1594 1595 if (methodName.equals("getRawCommentText")) { 1596 return filterComment((String) method.invoke(target, args)); 1597 } 1598 1599 // escape "&" in disjunctive types. 1600 if (proxy instanceof Type && methodName.equals("toString")) { 1601 return ((String) method.invoke(target, args)).replace("&", "&"); 1602 } 1603 1604 try { 1605 return filterHiddenAndRemoved(method.invoke(target, args), method.getReturnType()); 1606 } catch (InvocationTargetException e) { 1607 throw e.getTargetException(); 1608 } 1609 } 1610 1611 private String filterComment(String s) { 1612 if (s == null) { 1613 return null; 1614 } 1615 1616 s = s.trim(); 1617 1618 // Work around off by one error 1619 while (s.length() >= 5 && s.charAt(s.length() - 5) == '{') { 1620 s += " "; 1621 } 1622 1623 return s; 1624 } 1625 1626 private static Object unwrap(Object proxy) { 1627 if (proxy instanceof Proxy) return ((HideHandler) Proxy.getInvocationHandler(proxy)).target; 1628 return proxy; 1629 } 1630 } 1631 1632 /** 1633 * Collect the values used by the Dev tools and write them in files packaged with the SDK 1634 * 1635 * @param output the ouput directory for the files. 1636 */ 1637 private static void writeSdkValues(String output) { 1638 ArrayList<String> activityActions = new ArrayList<String>(); 1639 ArrayList<String> broadcastActions = new ArrayList<String>(); 1640 ArrayList<String> serviceActions = new ArrayList<String>(); 1641 ArrayList<String> categories = new ArrayList<String>(); 1642 ArrayList<String> features = new ArrayList<String>(); 1643 1644 ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>(); 1645 ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>(); 1646 ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>(); 1647 1648 ClassInfo[] classes = Converter.allClasses(); 1649 1650 // The topmost LayoutParams class - android.view.ViewGroup.LayoutParams 1651 ClassInfo topLayoutParams = null; 1652 1653 // Go through all the fields of all the classes, looking SDK stuff. 1654 for (ClassInfo clazz : classes) { 1655 1656 // first check constant fields for the SdkConstant annotation. 1657 ArrayList<FieldInfo> fields = clazz.allSelfFields(); 1658 for (FieldInfo field : fields) { 1659 Object cValue = field.constantValue(); 1660 if (cValue != null) { 1661 ArrayList<AnnotationInstanceInfo> annotations = field.annotations(); 1662 if (!annotations.isEmpty()) { 1663 for (AnnotationInstanceInfo annotation : annotations) { 1664 if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) { 1665 if (!annotation.elementValues().isEmpty()) { 1666 String type = annotation.elementValues().get(0).valueString(); 1667 if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) { 1668 activityActions.add(cValue.toString()); 1669 } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) { 1670 broadcastActions.add(cValue.toString()); 1671 } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) { 1672 serviceActions.add(cValue.toString()); 1673 } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) { 1674 categories.add(cValue.toString()); 1675 } else if (SDK_CONSTANT_TYPE_FEATURE.equals(type)) { 1676 features.add(cValue.toString()); 1677 } 1678 } 1679 break; 1680 } 1681 } 1682 } 1683 } 1684 } 1685 1686 // Now check the class for @Widget or if its in the android.widget package 1687 // (unless the class is hidden or abstract, or non public) 1688 if (clazz.isHiddenOrRemoved() == false && clazz.isPublic() && clazz.isAbstract() == false) { 1689 boolean annotated = false; 1690 ArrayList<AnnotationInstanceInfo> annotations = clazz.annotations(); 1691 if (!annotations.isEmpty()) { 1692 for (AnnotationInstanceInfo annotation : annotations) { 1693 if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) { 1694 widgets.add(clazz); 1695 annotated = true; 1696 break; 1697 } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) { 1698 layouts.add(clazz); 1699 annotated = true; 1700 break; 1701 } 1702 } 1703 } 1704 1705 if (annotated == false) { 1706 if (topLayoutParams == null 1707 && "android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) { 1708 topLayoutParams = clazz; 1709 } 1710 // let's check if this is inside android.widget or android.view 1711 if (isIncludedPackage(clazz)) { 1712 // now we check what this class inherits either from android.view.ViewGroup 1713 // or android.view.View, or android.view.ViewGroup.LayoutParams 1714 int type = checkInheritance(clazz); 1715 switch (type) { 1716 case TYPE_WIDGET: 1717 widgets.add(clazz); 1718 break; 1719 case TYPE_LAYOUT: 1720 layouts.add(clazz); 1721 break; 1722 case TYPE_LAYOUT_PARAM: 1723 layoutParams.add(clazz); 1724 break; 1725 } 1726 } 1727 } 1728 } 1729 } 1730 1731 // now write the files, whether or not the list are empty. 1732 // the SDK built requires those files to be present. 1733 1734 Collections.sort(activityActions); 1735 writeValues(output + "/activity_actions.txt", activityActions); 1736 1737 Collections.sort(broadcastActions); 1738 writeValues(output + "/broadcast_actions.txt", broadcastActions); 1739 1740 Collections.sort(serviceActions); 1741 writeValues(output + "/service_actions.txt", serviceActions); 1742 1743 Collections.sort(categories); 1744 writeValues(output + "/categories.txt", categories); 1745 1746 Collections.sort(features); 1747 writeValues(output + "/features.txt", features); 1748 1749 // before writing the list of classes, we do some checks, to make sure the layout params 1750 // are enclosed by a layout class (and not one that has been declared as a widget) 1751 for (int i = 0; i < layoutParams.size();) { 1752 ClassInfo clazz = layoutParams.get(i); 1753 ClassInfo containingClass = clazz.containingClass(); 1754 boolean remove = containingClass == null || layouts.indexOf(containingClass) == -1; 1755 // Also ensure that super classes of the layout params are in android.widget or android.view. 1756 while (!remove && (clazz = clazz.superclass()) != null && !clazz.equals(topLayoutParams)) { 1757 remove = !isIncludedPackage(clazz); 1758 } 1759 if (remove) { 1760 layoutParams.remove(i); 1761 } else { 1762 i++; 1763 } 1764 } 1765 1766 writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams); 1767 } 1768 1769 /** 1770 * Check if the clazz is in package android.view or android.widget 1771 */ 1772 private static boolean isIncludedPackage(ClassInfo clazz) { 1773 String pckg = clazz.containingPackage().name(); 1774 return "android.widget".equals(pckg) || "android.view".equals(pckg); 1775 } 1776 1777 /** 1778 * Writes a list of values into a text files. 1779 * 1780 * @param pathname the absolute os path of the output file. 1781 * @param values the list of values to write. 1782 */ 1783 private static void writeValues(String pathname, ArrayList<String> values) { 1784 FileWriter fw = null; 1785 BufferedWriter bw = null; 1786 try { 1787 fw = new FileWriter(pathname, false); 1788 bw = new BufferedWriter(fw); 1789 1790 for (String value : values) { 1791 bw.append(value).append('\n'); 1792 } 1793 } catch (IOException e) { 1794 // pass for now 1795 } finally { 1796 try { 1797 if (bw != null) bw.close(); 1798 } catch (IOException e) { 1799 // pass for now 1800 } 1801 try { 1802 if (fw != null) fw.close(); 1803 } catch (IOException e) { 1804 // pass for now 1805 } 1806 } 1807 } 1808 1809 /** 1810 * Writes the widget/layout/layout param classes into a text files. 1811 * 1812 * @param pathname the absolute os path of the output file. 1813 * @param widgets the list of widget classes to write. 1814 * @param layouts the list of layout classes to write. 1815 * @param layoutParams the list of layout param classes to write. 1816 */ 1817 private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets, 1818 ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) { 1819 FileWriter fw = null; 1820 BufferedWriter bw = null; 1821 try { 1822 fw = new FileWriter(pathname, false); 1823 bw = new BufferedWriter(fw); 1824 1825 // write the 3 types of classes. 1826 for (ClassInfo clazz : widgets) { 1827 writeClass(bw, clazz, 'W'); 1828 } 1829 for (ClassInfo clazz : layoutParams) { 1830 writeClass(bw, clazz, 'P'); 1831 } 1832 for (ClassInfo clazz : layouts) { 1833 writeClass(bw, clazz, 'L'); 1834 } 1835 } catch (IOException e) { 1836 // pass for now 1837 } finally { 1838 try { 1839 if (bw != null) bw.close(); 1840 } catch (IOException e) { 1841 // pass for now 1842 } 1843 try { 1844 if (fw != null) fw.close(); 1845 } catch (IOException e) { 1846 // pass for now 1847 } 1848 } 1849 } 1850 1851 /** 1852 * Writes a class name and its super class names into a {@link BufferedWriter}. 1853 * 1854 * @param writer the BufferedWriter to write into 1855 * @param clazz the class to write 1856 * @param prefix the prefix to put at the beginning of the line. 1857 * @throws IOException 1858 */ 1859 private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix) 1860 throws IOException { 1861 writer.append(prefix).append(clazz.qualifiedName()); 1862 ClassInfo superClass = clazz; 1863 while ((superClass = superClass.superclass()) != null) { 1864 writer.append(' ').append(superClass.qualifiedName()); 1865 } 1866 writer.append('\n'); 1867 } 1868 1869 /** 1870 * Checks the inheritance of {@link ClassInfo} objects. This method return 1871 * <ul> 1872 * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li> 1873 * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li> 1874 * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends 1875 * <code>android.view.ViewGroup$LayoutParams</code></li> 1876 * <li>{@link #TYPE_NONE}: in all other cases</li> 1877 * </ul> 1878 * 1879 * @param clazz the {@link ClassInfo} to check. 1880 */ 1881 private static int checkInheritance(ClassInfo clazz) { 1882 if ("android.view.ViewGroup".equals(clazz.qualifiedName())) { 1883 return TYPE_LAYOUT; 1884 } else if ("android.view.View".equals(clazz.qualifiedName())) { 1885 return TYPE_WIDGET; 1886 } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) { 1887 return TYPE_LAYOUT_PARAM; 1888 } 1889 1890 ClassInfo parent = clazz.superclass(); 1891 if (parent != null) { 1892 return checkInheritance(parent); 1893 } 1894 1895 return TYPE_NONE; 1896 } 1897 1898 /** 1899 * Ensures a trailing '/' at the end of a string. 1900 */ 1901 static String ensureSlash(String path) { 1902 return path.endsWith("/") ? path : path + "/"; 1903 } 1904 1905 /** 1906 * Process sample projects. Generate the TOC for the samples groups and project 1907 * and write it to a cs var, which is then written to files during templating to 1908 * html output. Collect metadata from sample project _index.jd files. Copy html 1909 * and specific source file types to the output directory. 1910 */ 1911 public static void writeSamples(boolean offlineMode, ArrayList<SampleCode> sampleCodes, 1912 boolean sortNavByGroups) { 1913 samplesNavTree = makeHDF(); 1914 1915 // Go through samples processing files. Create a root list for SC nodes, 1916 // pass it to SCs for their NavTree children and append them. 1917 List<SampleCode.Node> samplesList = new ArrayList<SampleCode.Node>(); 1918 List<SampleCode.Node> sampleGroupsRootNodes = null; 1919 for (SampleCode sc : sampleCodes) { 1920 samplesList.add(sc.setSamplesTOC(offlineMode)); 1921 } 1922 if (sortNavByGroups) { 1923 sampleGroupsRootNodes = new ArrayList<SampleCode.Node>(); 1924 for (SampleCode gsc : sampleCodeGroups) { 1925 String link = ClearPage.toroot + "samples/" + gsc.mTitle.replaceAll(" ", "").trim().toLowerCase() + ".html"; 1926 sampleGroupsRootNodes.add(new SampleCode.Node.Builder().setLabel(gsc.mTitle).setLink(link).setType("groupholder").build()); 1927 } 1928 } 1929 // Pass full samplesList to SC to render the samples TOC to sampleNavTree hdf 1930 if (!offlineMode) { 1931 SampleCode.writeSamplesNavTree(samplesList, sampleGroupsRootNodes); 1932 } 1933 // Iterate the samplecode projects writing the files to out 1934 for (SampleCode sc : sampleCodes) { 1935 sc.writeSamplesFiles(offlineMode); 1936 } 1937 } 1938 1939 /** 1940 * Given an initial samples directory root, walk through the directory collecting 1941 * sample code project roots and adding them to an array of SampleCodes. 1942 * @param rootDir Root directory holding all browseable sample code projects, 1943 * defined in frameworks/base/Android.mk as "-sampleDir path". 1944 */ 1945 public static void getSampleProjects(File rootDir) { 1946 for (File f : rootDir.listFiles()) { 1947 String name = f.getName(); 1948 if (f.isDirectory()) { 1949 if (isValidSampleProjectRoot(f)) { 1950 sampleCodes.add(new SampleCode(f.getAbsolutePath(), "samples/" + name, name)); 1951 } else { 1952 getSampleProjects(f); 1953 } 1954 } 1955 } 1956 } 1957 1958 /** 1959 * Test whether a given directory is the root directory for a sample code project. 1960 * Root directories must contain a valid _index.jd file and a src/ directory 1961 * or a module directory that contains a src/ directory. 1962 */ 1963 public static boolean isValidSampleProjectRoot(File dir) { 1964 File indexJd = new File(dir, "_index.jd"); 1965 if (!indexJd.exists()) { 1966 return false; 1967 } 1968 File srcDir = new File(dir, "src"); 1969 if (srcDir.exists()) { 1970 return true; 1971 } else { 1972 // Look for a src/ directory one level below the root directory, so 1973 // modules are supported. 1974 for (File childDir : dir.listFiles()) { 1975 if (childDir.isDirectory()) { 1976 srcDir = new File(childDir, "src"); 1977 if (srcDir.exists()) { 1978 return true; 1979 } 1980 } 1981 } 1982 return false; 1983 } 1984 } 1985 1986 public static String getDocumentationStringForAnnotation(String annotationName) { 1987 if (!documentAnnotations) return null; 1988 if (annotationDocumentationMap == null) { 1989 // parse the file for map 1990 annotationDocumentationMap = new HashMap<String, String>(); 1991 try { 1992 BufferedReader in = new BufferedReader( 1993 new FileReader(documentAnnotationsPath)); 1994 try { 1995 String line = in.readLine(); 1996 String[] split; 1997 while (line != null) { 1998 split = line.split(":"); 1999 annotationDocumentationMap.put(split[0], split[1]); 2000 line = in.readLine(); 2001 } 2002 } finally { 2003 in.close(); 2004 } 2005 } catch (IOException e) { 2006 System.err.println("Unable to open annotations documentation file for reading: " 2007 + documentAnnotationsPath); 2008 } 2009 } 2010 return annotationDocumentationMap.get(annotationName); 2011 } 2012 2013} 2014