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