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