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