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