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