Doclava.java revision 6b980d7341128ae41733aa9a0496301afb6d30f9
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 Map<Character, String> escapeChars = new HashMap<Character, String>(); 85 public static String title = ""; 86 public static SinceTagger sinceTagger = new SinceTagger(); 87 public static HashSet<String> knownTags = new HashSet<String>(); 88 public static FederationTagger federationTagger = new FederationTagger(); 89 public static Set<String> showAnnotations = new HashSet<String>(); 90 public static boolean includeDefaultAssets = true; 91 private static boolean generateDocs = true; 92 private static boolean parseComments = false; 93 private static String yamlNavFile = null; 94 95 public static JSilver jSilver = null; 96 97 private static boolean gmsRef = false; 98 private static boolean gcmRef = false; 99 private static boolean samplesRef = false; 100 private static boolean sac = false; 101 102 public static boolean checkLevel(int level) { 103 return (showLevel & level) == level; 104 } 105 106 /** 107 * Returns true if we should parse javadoc comments, 108 * reporting errors in the process. 109 */ 110 public static boolean parseComments() { 111 return generateDocs || parseComments; 112 } 113 114 public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv, 115 boolean hidden) { 116 int level = 0; 117 if (hidden && !checkLevel(SHOW_HIDDEN)) { 118 return false; 119 } 120 if (pub && checkLevel(SHOW_PUBLIC)) { 121 return true; 122 } 123 if (prot && checkLevel(SHOW_PROTECTED)) { 124 return true; 125 } 126 if (pkgp && checkLevel(SHOW_PACKAGE)) { 127 return true; 128 } 129 if (priv && checkLevel(SHOW_PRIVATE)) { 130 return true; 131 } 132 return false; 133 } 134 135 public static void main(String[] args) { 136 com.sun.tools.javadoc.Main.execute(args); 137 } 138 139 public static boolean start(RootDoc r) { 140 long startTime = System.nanoTime(); 141 String keepListFile = null; 142 String proguardFile = null; 143 String proofreadFile = null; 144 String todoFile = null; 145 String sdkValuePath = null; 146 String stubsDir = null; 147 // Create the dependency graph for the stubs directory 148 boolean offlineMode = false; 149 String apiFile = null; 150 String debugStubsFile = ""; 151 HashSet<String> stubPackages = null; 152 ArrayList<String> knownTagsFiles = new ArrayList<String>(); 153 154 root = r; 155 156 String[][] options = r.options(); 157 for (String[] a : options) { 158 if (a[0].equals("-d")) { 159 outputPathBase = outputPathHtmlDirs = ClearPage.outputDir = a[1]; 160 } else if (a[0].equals("-templatedir")) { 161 ClearPage.addTemplateDir(a[1]); 162 } else if (a[0].equals("-hdf")) { 163 mHDFData.add(new String[] {a[1], a[2]}); 164 } else if (a[0].equals("-knowntags")) { 165 knownTagsFiles.add(a[1]); 166 } else if (a[0].equals("-toroot")) { 167 ClearPage.toroot = a[1]; 168 } else if (a[0].equals("-samplecode")) { 169 sampleCodes.add(new SampleCode(a[1], a[2], a[3])); 170 } else if (a[0].equals("-samplegroup")) { 171 sampleCodeGroups.add(new SampleCode(null, null, a[1])); 172 } else if (a[0].equals("-samplesdir")) { 173 getSampleProjects(new File(a[1])); 174 //the destination output path for main htmldir 175 } else if (a[0].equals("-htmldir")) { 176 inputPathHtmlDirs.add(a[1]); 177 ClearPage.htmlDirs = inputPathHtmlDirs; 178 //the destination output path for additional htmldir 179 } else if (a[0].equals("-htmldir2")) { 180 if (a[2].equals("default")) { 181 inputPathHtmlDirs.add(a[1]); 182 } else { 183 inputPathHtmlDir2.add(a[1]); 184 outputPathHtmlDir2 = a[2]; 185 } 186 } else if (a[0].equals("-title")) { 187 Doclava.title = a[1]; 188 } else if (a[0].equals("-werror")) { 189 Errors.setWarningsAreErrors(true); 190 } else if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-hide")) { 191 try { 192 int level = -1; 193 if (a[0].equals("-error")) { 194 level = Errors.ERROR; 195 } else if (a[0].equals("-warning")) { 196 level = Errors.WARNING; 197 } else if (a[0].equals("-hide")) { 198 level = Errors.HIDDEN; 199 } 200 Errors.setErrorLevel(Integer.parseInt(a[1]), level); 201 } catch (NumberFormatException e) { 202 // already printed below 203 return false; 204 } 205 } else if (a[0].equals("-keeplist")) { 206 keepListFile = a[1]; 207 } else if (a[0].equals("-showAnnotation")) { 208 showAnnotations.add(a[1]); 209 } else if (a[0].equals("-proguard")) { 210 proguardFile = a[1]; 211 } else if (a[0].equals("-proofread")) { 212 proofreadFile = a[1]; 213 } else if (a[0].equals("-todo")) { 214 todoFile = a[1]; 215 } else if (a[0].equals("-public")) { 216 showLevel = SHOW_PUBLIC; 217 } else if (a[0].equals("-protected")) { 218 showLevel = SHOW_PROTECTED; 219 } else if (a[0].equals("-package")) { 220 showLevel = SHOW_PACKAGE; 221 } else if (a[0].equals("-private")) { 222 showLevel = SHOW_PRIVATE; 223 } else if (a[0].equals("-hidden")) { 224 showLevel = SHOW_HIDDEN; 225 } else if (a[0].equals("-stubs")) { 226 stubsDir = a[1]; 227 } else if (a[0].equals("-stubpackages")) { 228 stubPackages = new HashSet<String>(); 229 for (String pkg : a[1].split(":")) { 230 stubPackages.add(pkg); 231 } 232 } else if (a[0].equals("-sdkvalues")) { 233 sdkValuePath = a[1]; 234 } else if (a[0].equals("-api")) { 235 apiFile = a[1]; 236 } else if (a[0].equals("-nodocs")) { 237 generateDocs = false; 238 } else if (a[0].equals("-nodefaultassets")) { 239 includeDefaultAssets = false; 240 } else if (a[0].equals("-parsecomments")) { 241 parseComments = true; 242 } else if (a[0].equals("-since")) { 243 sinceTagger.addVersion(a[1], a[2]); 244 } else if (a[0].equals("-offlinemode")) { 245 offlineMode = true; 246 } else if (a[0].equals("-federate")) { 247 try { 248 String name = a[1]; 249 URL federationURL = new URL(a[2]); 250 federationTagger.addSiteUrl(name, federationURL); 251 } catch (MalformedURLException e) { 252 System.err.println("Could not parse URL for federation: " + a[1]); 253 return false; 254 } 255 } else if (a[0].equals("-federationapi")) { 256 String name = a[1]; 257 String file = a[2]; 258 federationTagger.addSiteApi(name, file); 259 } else if (a[0].equals("-yaml")) { 260 yamlNavFile = a[1]; 261 } else if (a[0].equals("-devsite")) { 262 // Don't copy the doclava assets to devsite output (ie use proj assets only) 263 includeDefaultAssets = false; 264 outputPathHtmlDirs = outputPathHtmlDirs + "/" + devsiteRoot; 265 } 266 } 267 268 if (!readKnownTagsFiles(knownTags, knownTagsFiles)) { 269 return false; 270 } 271 272 // Set up the data structures 273 Converter.makeInfo(r); 274 275 if (generateDocs) { 276 ClearPage.addBundledTemplateDir("assets/customizations"); 277 ClearPage.addBundledTemplateDir("assets/templates"); 278 279 List<ResourceLoader> resourceLoaders = new ArrayList<ResourceLoader>(); 280 List<String> templates = ClearPage.getTemplateDirs(); 281 for (String tmpl : templates) { 282 resourceLoaders.add(new FileSystemResourceLoader(tmpl)); 283 } 284 285 templates = ClearPage.getBundledTemplateDirs(); 286 for (String tmpl : templates) { 287 // TODO - remove commented line - it's here for debugging purposes 288 // resourceLoaders.add(new FileSystemResourceLoader("/Volumes/Android/master/external/doclava/res/" + tmpl)); 289 resourceLoaders.add(new ClassResourceLoader(Doclava.class, '/'+tmpl)); 290 } 291 292 ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders); 293 jSilver = new JSilver(compositeResourceLoader); 294 295 if (!Doclava.readTemplateSettings()) { 296 return false; 297 } 298 299 //startTime = System.nanoTime(); 300 301 // Apply @since tags from the XML file 302 sinceTagger.tagAll(Converter.rootClasses()); 303 304 // Apply details of federated documentation 305 federationTagger.tagAll(Converter.rootClasses()); 306 307 // Files for proofreading 308 if (proofreadFile != null) { 309 Proofread.initProofread(proofreadFile); 310 } 311 if (todoFile != null) { 312 TodoFile.writeTodoFile(todoFile); 313 } 314 315 // HTML2 Pages -- Generate Pages from optional secondary dir 316 if (!inputPathHtmlDir2.isEmpty()) { 317 if (!outputPathHtmlDir2.isEmpty()) { 318 ClearPage.outputDir = outputPathBase + "/" + outputPathHtmlDir2; 319 } 320 ClearPage.htmlDirs = inputPathHtmlDir2; 321 writeHTMLPages(); 322 ClearPage.htmlDirs = inputPathHtmlDirs; 323 } 324 325 // HTML Pages 326 if (!ClearPage.htmlDirs.isEmpty()) { 327 ClearPage.htmlDirs = inputPathHtmlDirs; 328 ClearPage.outputDir = outputPathHtmlDirs; 329 writeHTMLPages(); 330 } 331 332 writeAssets(); 333 334 // Sample code pages 335 if (samplesRef) { 336 // always write samples without offlineMode behaviors 337 writeSamples(false, sampleCodes, SORT_BY_NAV_GROUPS); 338 } 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 < 6) { 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 case 5: 720 classesToCheck = pkg.annotations(); 721 break; 722 default: 723 System.err.println("Error reading package: " + pkg.name()); 724 break; 725 } 726 for (ClassInfo cl : classesToCheck) { 727 if (!cl.isHidden()) { 728 allHidden = false; 729 break; 730 } 731 } 732 if (!allHidden) { 733 break; 734 } 735 pass++; 736 } 737 if (allHidden) { 738 continue; 739 } 740 if(gmsRef){ 741 data.setValue("reference.gms", "true"); 742 } else if(gcmRef){ 743 data.setValue("reference.gcm", "true"); 744 } 745 data.setValue("reference", "1"); 746 data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0"); 747 data.setValue("docs.packages." + i + ".name", s); 748 data.setValue("docs.packages." + i + ".link", pkg.htmlPage()); 749 data.setValue("docs.packages." + i + ".since", pkg.getSince()); 750 TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags()); 751 i++; 752 } 753 754 sinceTagger.writeVersionNames(data); 755 return data; 756 } 757 758 private static void writeDirectory(File dir, String relative, JSilver js) { 759 File[] files = dir.listFiles(); 760 int i, count = files.length; 761 for (i = 0; i < count; i++) { 762 File f = files[i]; 763 if (f.isFile()) { 764 String templ = relative + f.getName(); 765 int len = templ.length(); 766 if (len > 3 && ".cs".equals(templ.substring(len - 3))) { 767 Data data = makeHDF(); 768 String filename = templ.substring(0, len - 3) + htmlExtension; 769 ClearPage.write(data, templ, filename, js); 770 } else if (len > 3 && ".jd".equals(templ.substring(len - 3))) { 771 Data data = makeHDF(); 772 String filename = templ.substring(0, len - 3) + htmlExtension; 773 DocFile.writePage(f.getAbsolutePath(), relative, filename, data); 774 String[] sections = relative.split("\\/"); 775 boolean isIntl = ((sections.length > 0) && (sections[0].equals("intl"))); 776 //if (!isIntl) { 777 PageMetadata.setPageMetadata(f, relative, filename, data, sTaglist); 778 //} 779 } else if(!f.getName().equals(".DS_Store")){ 780 Data data = makeHDF(); 781 String hdfValue = data.getValue("sac") == null ? "" : data.getValue("sac"); 782 boolean allowExcepted = hdfValue.equals("true") ? true : false; 783 ClearPage.copyFile(allowExcepted, f, templ); 784 } 785 } else if (f.isDirectory()) { 786 writeDirectory(f, relative + f.getName() + "/", js); 787 } 788 } 789 } 790 791 public static void writeHTMLPages() { 792 for (String htmlDir : ClearPage.htmlDirs) { 793 File f = new File(htmlDir); 794 if (!f.isDirectory()) { 795 System.err.println("htmlDir not a directory: " + htmlDir); 796 continue; 797 } 798 799 ResourceLoader loader = new FileSystemResourceLoader(f); 800 JSilver js = new JSilver(loader); 801 writeDirectory(f, "", js); 802 } 803 } 804 805 public static void writeAssets() { 806 JarFile thisJar = JarUtils.jarForClass(Doclava.class, null); 807 if ((thisJar != null) && (includeDefaultAssets)) { 808 try { 809 List<String> templateDirs = ClearPage.getBundledTemplateDirs(); 810 for (String templateDir : templateDirs) { 811 String assetsDir = templateDir + "/assets"; 812 JarUtils.copyResourcesToDirectory(thisJar, assetsDir, ClearPage.outputDir + "/assets"); 813 } 814 } catch (IOException e) { 815 System.err.println("Error copying assets directory."); 816 e.printStackTrace(); 817 return; 818 } 819 } 820 821 //write the project-specific assets 822 List<String> templateDirs = ClearPage.getTemplateDirs(); 823 for (String templateDir : templateDirs) { 824 File assets = new File(templateDir + "/assets"); 825 if (assets.isDirectory()) { 826 writeDirectory(assets, "assets/", null); 827 } 828 } 829 830 // Create the timestamp.js file based on .cs file 831 Data timedata = Doclava.makeHDF(); 832 ClearPage.write(timedata, "timestamp.cs", "timestamp.js"); 833 } 834 835 /** Go through the docs and generate meta-data about each 836 page to use in search suggestions */ 837 public static void writeLists() { 838 839 // Write the lists for API references 840 Data data = makeHDF(); 841 842 ClassInfo[] classes = Converter.rootClasses(); 843 844 SortedMap<String, Object> sorted = new TreeMap<String, Object>(); 845 for (ClassInfo cl : classes) { 846 if (cl.isHidden()) { 847 continue; 848 } 849 sorted.put(cl.qualifiedName(), cl); 850 PackageInfo pkg = cl.containingPackage(); 851 String name; 852 if (pkg == null) { 853 name = ""; 854 } else { 855 name = pkg.name(); 856 } 857 sorted.put(name, pkg); 858 } 859 860 int i = 0; 861 for (String s : sorted.keySet()) { 862 data.setValue("docs.pages." + i + ".id", "" + i); 863 data.setValue("docs.pages." + i + ".label", s); 864 865 Object o = sorted.get(s); 866 if (o instanceof PackageInfo) { 867 PackageInfo pkg = (PackageInfo) o; 868 data.setValue("docs.pages." + i + ".link", pkg.htmlPage()); 869 data.setValue("docs.pages." + i + ".type", "package"); 870 data.setValue("docs.pages." + i + ".deprecated", pkg.isDeprecated() ? "true" : "false"); 871 } else if (o instanceof ClassInfo) { 872 ClassInfo cl = (ClassInfo) o; 873 data.setValue("docs.pages." + i + ".link", cl.htmlPage()); 874 data.setValue("docs.pages." + i + ".type", "class"); 875 data.setValue("docs.pages." + i + ".deprecated", cl.isDeprecated() ? "true" : "false"); 876 } 877 i++; 878 } 879 ClearPage.write(data, "lists.cs", javadocDir + "lists.js"); 880 881 882 // Write the lists for JD documents (if there are HTML directories to process) 883 if (inputPathHtmlDirs.size() > 0) { 884 Data jddata = makeHDF(); 885 Iterator counter = new Iterator(); 886 for (String htmlDir : inputPathHtmlDirs) { 887 File dir = new File(htmlDir); 888 if (!dir.isDirectory()) { 889 continue; 890 } 891 writeJdDirList(dir, jddata, counter); 892 } 893 ClearPage.write(jddata, "jd_lists.cs", javadocDir + "jd_lists.js"); 894 } 895 } 896 897 private static class Iterator { 898 int i = 0; 899 } 900 901 /** Write meta-data for a JD file, used for search suggestions */ 902 private static void writeJdDirList(File dir, Data data, Iterator counter) { 903 File[] files = dir.listFiles(); 904 int i, count = files.length; 905 // Loop all files in given directory 906 for (i = 0; i < count; i++) { 907 File f = files[i]; 908 if (f.isFile()) { 909 String filePath = f.getAbsolutePath(); 910 String templ = f.getName(); 911 int len = templ.length(); 912 // If it's a .jd file we want to process 913 if (len > 3 && ".jd".equals(templ.substring(len - 3))) { 914 // remove the directories below the site root 915 String webPath = filePath.substring(filePath.indexOf("docs/html/") + 10, filePath.length()); 916 // replace .jd with .html 917 webPath = webPath.substring(0, webPath.length() - 3) + htmlExtension; 918 // Parse the .jd file for properties data at top of page 919 Data hdf = Doclava.makeHDF(); 920 String filedata = DocFile.readFile(filePath); 921 Matcher lines = DocFile.LINE.matcher(filedata); 922 String line = null; 923 // Get each line to add the key-value to hdf 924 while (lines.find()) { 925 line = lines.group(1); 926 if (line.length() > 0) { 927 // Stop when we hit the body 928 if (line.equals("@jd:body")) { 929 break; 930 } 931 Matcher prop = DocFile.PROP.matcher(line); 932 if (prop.matches()) { 933 String key = prop.group(1); 934 String value = prop.group(2); 935 hdf.setValue(key, value); 936 } else { 937 break; 938 } 939 } 940 } // done gathering page properties 941 942 // Insert the goods into HDF data (title, link, tags, type) 943 String title = hdf.getValue("page.title", ""); 944 title = title.replaceAll("\"", "'"); 945 // if there's a <span> in the title, get rid of it 946 if (title.indexOf("<span") != -1) { 947 String[] splitTitle = title.split("<span(.*?)</span>"); 948 title = splitTitle[0]; 949 for (int j = 1; j < splitTitle.length; j++) { 950 title.concat(splitTitle[j]); 951 } 952 } 953 954 StringBuilder tags = new StringBuilder(); 955 String tagsList = hdf.getValue("page.tags", ""); 956 if (!tagsList.equals("")) { 957 tagsList = tagsList.replaceAll("\"", ""); 958 String[] tagParts = tagsList.split(","); 959 for (int iter = 0; iter < tagParts.length; iter++) { 960 tags.append("\""); 961 tags.append(tagParts[iter].trim()); 962 tags.append("\""); 963 if (iter < tagParts.length - 1) { 964 tags.append(","); 965 } 966 } 967 } 968 969 String dirName = (webPath.indexOf("/") != -1) 970 ? webPath.substring(0, webPath.indexOf("/")) : ""; 971 972 if (!"".equals(title) && 973 !"intl".equals(dirName) && 974 !hdf.getBooleanValue("excludeFromSuggestions")) { 975 data.setValue("docs.pages." + counter.i + ".label", title); 976 data.setValue("docs.pages." + counter.i + ".link", webPath); 977 data.setValue("docs.pages." + counter.i + ".tags", tags.toString()); 978 data.setValue("docs.pages." + counter.i + ".type", dirName); 979 counter.i++; 980 } 981 } 982 } else if (f.isDirectory()) { 983 writeJdDirList(f, data, counter); 984 } 985 } 986 } 987 988 public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) { 989 if (!notStrippable.add(cl)) { 990 // slight optimization: if it already contains cl, it already contains 991 // all of cl's parents 992 return; 993 } 994 ClassInfo supr = cl.superclass(); 995 if (supr != null) { 996 cantStripThis(supr, notStrippable); 997 } 998 for (ClassInfo iface : cl.interfaces()) { 999 cantStripThis(iface, notStrippable); 1000 } 1001 } 1002 1003 private static String getPrintableName(ClassInfo cl) { 1004 ClassInfo containingClass = cl.containingClass(); 1005 if (containingClass != null) { 1006 // This is an inner class. 1007 String baseName = cl.name(); 1008 baseName = baseName.substring(baseName.lastIndexOf('.') + 1); 1009 return getPrintableName(containingClass) + '$' + baseName; 1010 } 1011 return cl.qualifiedName(); 1012 } 1013 1014 /** 1015 * Writes the list of classes that must be present in order to provide the non-hidden APIs known 1016 * to javadoc. 1017 * 1018 * @param filename the path to the file to write the list to 1019 */ 1020 public static void writeKeepList(String filename) { 1021 HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>(); 1022 ClassInfo[] all = Converter.allClasses(); 1023 Arrays.sort(all); // just to make the file a little more readable 1024 1025 // If a class is public and not hidden, then it and everything it derives 1026 // from cannot be stripped. Otherwise we can strip it. 1027 for (ClassInfo cl : all) { 1028 if (cl.isPublic() && !cl.isHidden()) { 1029 cantStripThis(cl, notStrippable); 1030 } 1031 } 1032 PrintStream stream = null; 1033 try { 1034 stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(filename))); 1035 for (ClassInfo cl : notStrippable) { 1036 stream.println(getPrintableName(cl)); 1037 } 1038 } catch (FileNotFoundException e) { 1039 System.err.println("error writing file: " + filename); 1040 } finally { 1041 if (stream != null) { 1042 stream.close(); 1043 } 1044 } 1045 } 1046 1047 private static PackageInfo[] sVisiblePackages = null; 1048 1049 public static PackageInfo[] choosePackages() { 1050 if (sVisiblePackages != null) { 1051 return sVisiblePackages; 1052 } 1053 1054 ClassInfo[] classes = Converter.rootClasses(); 1055 SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>(); 1056 for (ClassInfo cl : classes) { 1057 PackageInfo pkg = cl.containingPackage(); 1058 String name; 1059 if (pkg == null) { 1060 name = ""; 1061 } else { 1062 name = pkg.name(); 1063 } 1064 sorted.put(name, pkg); 1065 } 1066 1067 ArrayList<PackageInfo> result = new ArrayList<PackageInfo>(); 1068 1069 for (String s : sorted.keySet()) { 1070 PackageInfo pkg = sorted.get(s); 1071 1072 if (pkg.isHidden()) { 1073 continue; 1074 } 1075 Boolean allHidden = true; 1076 int pass = 0; 1077 ClassInfo[] classesToCheck = null; 1078 while (pass < 6) { 1079 switch (pass) { 1080 case 0: 1081 classesToCheck = pkg.ordinaryClasses(); 1082 break; 1083 case 1: 1084 classesToCheck = pkg.enums(); 1085 break; 1086 case 2: 1087 classesToCheck = pkg.errors(); 1088 break; 1089 case 3: 1090 classesToCheck = pkg.exceptions(); 1091 break; 1092 case 4: 1093 classesToCheck = pkg.interfaces(); 1094 break; 1095 case 5: 1096 classesToCheck = pkg.annotations(); 1097 break; 1098 default: 1099 System.err.println("Error reading package: " + pkg.name()); 1100 break; 1101 } 1102 for (ClassInfo cl : classesToCheck) { 1103 if (!cl.isHidden()) { 1104 allHidden = false; 1105 break; 1106 } 1107 } 1108 if (!allHidden) { 1109 break; 1110 } 1111 pass++; 1112 } 1113 if (allHidden) { 1114 continue; 1115 } 1116 1117 result.add(pkg); 1118 } 1119 1120 sVisiblePackages = result.toArray(new PackageInfo[result.size()]); 1121 return sVisiblePackages; 1122 } 1123 1124 public static void writePackages(String filename) { 1125 Data data = makePackageHDF(); 1126 1127 int i = 0; 1128 for (PackageInfo pkg : choosePackages()) { 1129 writePackage(pkg); 1130 1131 data.setValue("docs.packages." + i + ".name", pkg.name()); 1132 data.setValue("docs.packages." + i + ".link", pkg.htmlPage()); 1133 TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags()); 1134 1135 i++; 1136 } 1137 1138 setPageTitle(data, "Package Index"); 1139 1140 TagInfo.makeHDF(data, "root.descr", Converter.convertTags(root.inlineTags(), null)); 1141 1142 ClearPage.write(data, "packages.cs", filename); 1143 ClearPage.write(data, "package-list.cs", javadocDir + "package-list"); 1144 1145 Proofread.writePackages(filename, Converter.convertTags(root.inlineTags(), null)); 1146 } 1147 1148 public static void writePackage(PackageInfo pkg) { 1149 // these this and the description are in the same directory, 1150 // so it's okay 1151 Data data = makePackageHDF(); 1152 1153 String name = pkg.name(); 1154 1155 data.setValue("package.name", name); 1156 data.setValue("package.since", pkg.getSince()); 1157 data.setValue("package.descr", "...description..."); 1158 pkg.setFederatedReferences(data, "package"); 1159 1160 makeClassListHDF(data, "package.annotations", ClassInfo.sortByName(pkg.annotations())); 1161 makeClassListHDF(data, "package.interfaces", ClassInfo.sortByName(pkg.interfaces())); 1162 makeClassListHDF(data, "package.classes", ClassInfo.sortByName(pkg.ordinaryClasses())); 1163 makeClassListHDF(data, "package.enums", ClassInfo.sortByName(pkg.enums())); 1164 makeClassListHDF(data, "package.exceptions", ClassInfo.sortByName(pkg.exceptions())); 1165 makeClassListHDF(data, "package.errors", ClassInfo.sortByName(pkg.errors())); 1166 TagInfo.makeHDF(data, "package.shortDescr", pkg.firstSentenceTags()); 1167 TagInfo.makeHDF(data, "package.descr", pkg.inlineTags()); 1168 1169 String filename = pkg.htmlPage(); 1170 setPageTitle(data, name); 1171 ClearPage.write(data, "package.cs", filename); 1172 1173 Proofread.writePackage(filename, pkg.inlineTags()); 1174 } 1175 1176 public static void writeClassLists() { 1177 int i; 1178 Data data = makePackageHDF(); 1179 1180 ClassInfo[] classes = PackageInfo.filterHidden(Converter.convertClasses(root.classes())); 1181 if (classes.length == 0) { 1182 return; 1183 } 1184 1185 Sorter[] sorted = new Sorter[classes.length]; 1186 for (i = 0; i < sorted.length; i++) { 1187 ClassInfo cl = classes[i]; 1188 String name = cl.name(); 1189 sorted[i] = new Sorter(name, cl); 1190 } 1191 1192 Arrays.sort(sorted); 1193 1194 // make a pass and resolve ones that have the same name 1195 int firstMatch = 0; 1196 String lastName = sorted[0].label; 1197 for (i = 1; i < sorted.length; i++) { 1198 String s = sorted[i].label; 1199 if (!lastName.equals(s)) { 1200 if (firstMatch != i - 1) { 1201 // there were duplicates 1202 for (int j = firstMatch; j < i; j++) { 1203 PackageInfo pkg = ((ClassInfo) sorted[j].data).containingPackage(); 1204 if (pkg != null) { 1205 sorted[j].label = sorted[j].label + " (" + pkg.name() + ")"; 1206 } 1207 } 1208 } 1209 firstMatch = i; 1210 lastName = s; 1211 } 1212 } 1213 1214 // and sort again 1215 Arrays.sort(sorted); 1216 1217 for (i = 0; i < sorted.length; i++) { 1218 String s = sorted[i].label; 1219 ClassInfo cl = (ClassInfo) sorted[i].data; 1220 char first = Character.toUpperCase(s.charAt(0)); 1221 cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i); 1222 } 1223 1224 setPageTitle(data, "Class Index"); 1225 ClearPage.write(data, "classes.cs", javadocDir + "classes" + htmlExtension); 1226 } 1227 1228 // we use the word keywords because "index" means something else in html land 1229 // the user only ever sees the word index 1230 /* 1231 * public static void writeKeywords() { ArrayList<KeywordEntry> keywords = new 1232 * ArrayList<KeywordEntry>(); 1233 * 1234 * ClassInfo[] classes = PackageInfo.filterHidden(Converter.convertClasses(root.classes())); 1235 * 1236 * for (ClassInfo cl: classes) { cl.makeKeywordEntries(keywords); } 1237 * 1238 * HDF data = makeHDF(); 1239 * 1240 * Collections.sort(keywords); 1241 * 1242 * int i=0; for (KeywordEntry entry: keywords) { String base = "keywords." + entry.firstChar() + 1243 * "." + i; entry.makeHDF(data, base); i++; } 1244 * 1245 * setPageTitle(data, "Index"); ClearPage.write(data, "keywords.cs", javadocDir + "keywords" + 1246 * htmlExtension); } 1247 */ 1248 1249 public static void writeHierarchy() { 1250 ClassInfo[] classes = Converter.rootClasses(); 1251 ArrayList<ClassInfo> info = new ArrayList<ClassInfo>(); 1252 for (ClassInfo cl : classes) { 1253 if (!cl.isHidden()) { 1254 info.add(cl); 1255 } 1256 } 1257 Data data = makePackageHDF(); 1258 Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()])); 1259 setPageTitle(data, "Class Hierarchy"); 1260 ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension); 1261 } 1262 1263 public static void writeClasses() { 1264 ClassInfo[] classes = Converter.rootClasses(); 1265 1266 for (ClassInfo cl : classes) { 1267 Data data = makePackageHDF(); 1268 if (!cl.isHidden()) { 1269 writeClass(cl, data); 1270 } 1271 } 1272 } 1273 1274 public static void writeClass(ClassInfo cl, Data data) { 1275 cl.makeHDF(data); 1276 setPageTitle(data, cl.name()); 1277 String outfile = cl.htmlPage(); 1278 ClearPage.write(data, "class.cs", outfile); 1279 Proofread.writeClass(cl.htmlPage(), cl); 1280 } 1281 1282 public static void makeClassListHDF(Data data, String base, ClassInfo[] classes) { 1283 for (int i = 0; i < classes.length; i++) { 1284 ClassInfo cl = classes[i]; 1285 if (!cl.isHidden()) { 1286 cl.makeShortDescrHDF(data, base + "." + i); 1287 } 1288 } 1289 } 1290 1291 public static String linkTarget(String source, String target) { 1292 String[] src = source.split("/"); 1293 String[] tgt = target.split("/"); 1294 1295 int srclen = src.length; 1296 int tgtlen = tgt.length; 1297 1298 int same = 0; 1299 while (same < (srclen - 1) && same < (tgtlen - 1) && (src[same].equals(tgt[same]))) { 1300 same++; 1301 } 1302 1303 String s = ""; 1304 1305 int up = srclen - same - 1; 1306 for (int i = 0; i < up; i++) { 1307 s += "../"; 1308 } 1309 1310 1311 int N = tgtlen - 1; 1312 for (int i = same; i < N; i++) { 1313 s += tgt[i] + '/'; 1314 } 1315 s += tgt[tgtlen - 1]; 1316 1317 return s; 1318 } 1319 1320 /** 1321 * Returns true if the given element has an @hide or @pending annotation. 1322 */ 1323 private static boolean hasHideAnnotation(Doc doc) { 1324 String comment = doc.getRawCommentText(); 1325 return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -1; 1326 } 1327 1328 /** 1329 * Returns true if the given element is hidden. 1330 */ 1331 private static boolean isHidden(Doc doc) { 1332 // Methods, fields, constructors. 1333 if (doc instanceof MemberDoc) { 1334 return hasHideAnnotation(doc); 1335 } 1336 1337 // Classes, interfaces, enums, annotation types. 1338 if (doc instanceof ClassDoc) { 1339 ClassDoc classDoc = (ClassDoc) doc; 1340 1341 // Check the containing package. 1342 if (hasHideAnnotation(classDoc.containingPackage())) { 1343 return true; 1344 } 1345 1346 // Check the class doc and containing class docs if this is a 1347 // nested class. 1348 ClassDoc current = classDoc; 1349 do { 1350 if (hasHideAnnotation(current)) { 1351 return true; 1352 } 1353 1354 current = current.containingClass(); 1355 } while (current != null); 1356 } 1357 1358 return false; 1359 } 1360 1361 /** 1362 * Filters out hidden elements. 1363 */ 1364 private static Object filterHidden(Object o, Class<?> expected) { 1365 if (o == null) { 1366 return null; 1367 } 1368 1369 Class type = o.getClass(); 1370 if (type.getName().startsWith("com.sun.")) { 1371 // TODO: Implement interfaces from superclasses, too. 1372 return Proxy 1373 .newProxyInstance(type.getClassLoader(), type.getInterfaces(), new HideHandler(o)); 1374 } else if (o instanceof Object[]) { 1375 Class<?> componentType = expected.getComponentType(); 1376 Object[] array = (Object[]) o; 1377 List<Object> list = new ArrayList<Object>(array.length); 1378 for (Object entry : array) { 1379 if ((entry instanceof Doc) && isHidden((Doc) entry)) { 1380 continue; 1381 } 1382 list.add(filterHidden(entry, componentType)); 1383 } 1384 return list.toArray((Object[]) Array.newInstance(componentType, list.size())); 1385 } else { 1386 return o; 1387 } 1388 } 1389 1390 /** 1391 * Filters hidden elements out of method return values. 1392 */ 1393 private static class HideHandler implements InvocationHandler { 1394 1395 private final Object target; 1396 1397 public HideHandler(Object target) { 1398 this.target = target; 1399 } 1400 1401 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 1402 String methodName = method.getName(); 1403 if (args != null) { 1404 if (methodName.equals("compareTo") || methodName.equals("equals") 1405 || methodName.equals("overrides") || methodName.equals("subclassOf")) { 1406 args[0] = unwrap(args[0]); 1407 } 1408 } 1409 1410 if (methodName.equals("getRawCommentText")) { 1411 return filterComment((String) method.invoke(target, args)); 1412 } 1413 1414 // escape "&" in disjunctive types. 1415 if (proxy instanceof Type && methodName.equals("toString")) { 1416 return ((String) method.invoke(target, args)).replace("&", "&"); 1417 } 1418 1419 try { 1420 return filterHidden(method.invoke(target, args), method.getReturnType()); 1421 } catch (InvocationTargetException e) { 1422 throw e.getTargetException(); 1423 } 1424 } 1425 1426 private String filterComment(String s) { 1427 if (s == null) { 1428 return null; 1429 } 1430 1431 s = s.trim(); 1432 1433 // Work around off by one error 1434 while (s.length() >= 5 && s.charAt(s.length() - 5) == '{') { 1435 s += " "; 1436 } 1437 1438 return s; 1439 } 1440 1441 private static Object unwrap(Object proxy) { 1442 if (proxy instanceof Proxy) return ((HideHandler) Proxy.getInvocationHandler(proxy)).target; 1443 return proxy; 1444 } 1445 } 1446 1447 /** 1448 * Collect the values used by the Dev tools and write them in files packaged with the SDK 1449 * 1450 * @param output the ouput directory for the files. 1451 */ 1452 private static void writeSdkValues(String output) { 1453 ArrayList<String> activityActions = new ArrayList<String>(); 1454 ArrayList<String> broadcastActions = new ArrayList<String>(); 1455 ArrayList<String> serviceActions = new ArrayList<String>(); 1456 ArrayList<String> categories = new ArrayList<String>(); 1457 ArrayList<String> features = new ArrayList<String>(); 1458 1459 ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>(); 1460 ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>(); 1461 ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>(); 1462 1463 ClassInfo[] classes = Converter.allClasses(); 1464 1465 // Go through all the fields of all the classes, looking SDK stuff. 1466 for (ClassInfo clazz : classes) { 1467 1468 // first check constant fields for the SdkConstant annotation. 1469 ArrayList<FieldInfo> fields = clazz.allSelfFields(); 1470 for (FieldInfo field : fields) { 1471 Object cValue = field.constantValue(); 1472 if (cValue != null) { 1473 ArrayList<AnnotationInstanceInfo> annotations = field.annotations(); 1474 if (!annotations.isEmpty()) { 1475 for (AnnotationInstanceInfo annotation : annotations) { 1476 if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) { 1477 if (!annotation.elementValues().isEmpty()) { 1478 String type = annotation.elementValues().get(0).valueString(); 1479 if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) { 1480 activityActions.add(cValue.toString()); 1481 } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) { 1482 broadcastActions.add(cValue.toString()); 1483 } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) { 1484 serviceActions.add(cValue.toString()); 1485 } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) { 1486 categories.add(cValue.toString()); 1487 } else if (SDK_CONSTANT_TYPE_FEATURE.equals(type)) { 1488 features.add(cValue.toString()); 1489 } 1490 } 1491 break; 1492 } 1493 } 1494 } 1495 } 1496 } 1497 1498 // Now check the class for @Widget or if its in the android.widget package 1499 // (unless the class is hidden or abstract, or non public) 1500 if (clazz.isHidden() == false && clazz.isPublic() && clazz.isAbstract() == false) { 1501 boolean annotated = false; 1502 ArrayList<AnnotationInstanceInfo> annotations = clazz.annotations(); 1503 if (!annotations.isEmpty()) { 1504 for (AnnotationInstanceInfo annotation : annotations) { 1505 if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) { 1506 widgets.add(clazz); 1507 annotated = true; 1508 break; 1509 } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) { 1510 layouts.add(clazz); 1511 annotated = true; 1512 break; 1513 } 1514 } 1515 } 1516 1517 if (annotated == false) { 1518 // lets check if this is inside android.widget 1519 PackageInfo pckg = clazz.containingPackage(); 1520 String packageName = pckg.name(); 1521 if ("android.widget".equals(packageName) || "android.view".equals(packageName)) { 1522 // now we check what this class inherits either from android.view.ViewGroup 1523 // or android.view.View, or android.view.ViewGroup.LayoutParams 1524 int type = checkInheritance(clazz); 1525 switch (type) { 1526 case TYPE_WIDGET: 1527 widgets.add(clazz); 1528 break; 1529 case TYPE_LAYOUT: 1530 layouts.add(clazz); 1531 break; 1532 case TYPE_LAYOUT_PARAM: 1533 layoutParams.add(clazz); 1534 break; 1535 } 1536 } 1537 } 1538 } 1539 } 1540 1541 // now write the files, whether or not the list are empty. 1542 // the SDK built requires those files to be present. 1543 1544 Collections.sort(activityActions); 1545 writeValues(output + "/activity_actions.txt", activityActions); 1546 1547 Collections.sort(broadcastActions); 1548 writeValues(output + "/broadcast_actions.txt", broadcastActions); 1549 1550 Collections.sort(serviceActions); 1551 writeValues(output + "/service_actions.txt", serviceActions); 1552 1553 Collections.sort(categories); 1554 writeValues(output + "/categories.txt", categories); 1555 1556 Collections.sort(features); 1557 writeValues(output + "/features.txt", features); 1558 1559 // before writing the list of classes, we do some checks, to make sure the layout params 1560 // are enclosed by a layout class (and not one that has been declared as a widget) 1561 for (int i = 0; i < layoutParams.size();) { 1562 ClassInfo layoutParamClass = layoutParams.get(i); 1563 ClassInfo containingClass = layoutParamClass.containingClass(); 1564 if (containingClass == null || layouts.indexOf(containingClass) == -1) { 1565 layoutParams.remove(i); 1566 } else { 1567 i++; 1568 } 1569 } 1570 1571 writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams); 1572 } 1573 1574 /** 1575 * Writes a list of values into a text files. 1576 * 1577 * @param pathname the absolute os path of the output file. 1578 * @param values the list of values to write. 1579 */ 1580 private static void writeValues(String pathname, ArrayList<String> values) { 1581 FileWriter fw = null; 1582 BufferedWriter bw = null; 1583 try { 1584 fw = new FileWriter(pathname, false); 1585 bw = new BufferedWriter(fw); 1586 1587 for (String value : values) { 1588 bw.append(value).append('\n'); 1589 } 1590 } catch (IOException e) { 1591 // pass for now 1592 } finally { 1593 try { 1594 if (bw != null) bw.close(); 1595 } catch (IOException e) { 1596 // pass for now 1597 } 1598 try { 1599 if (fw != null) fw.close(); 1600 } catch (IOException e) { 1601 // pass for now 1602 } 1603 } 1604 } 1605 1606 /** 1607 * Writes the widget/layout/layout param classes into a text files. 1608 * 1609 * @param pathname the absolute os path of the output file. 1610 * @param widgets the list of widget classes to write. 1611 * @param layouts the list of layout classes to write. 1612 * @param layoutParams the list of layout param classes to write. 1613 */ 1614 private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets, 1615 ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) { 1616 FileWriter fw = null; 1617 BufferedWriter bw = null; 1618 try { 1619 fw = new FileWriter(pathname, false); 1620 bw = new BufferedWriter(fw); 1621 1622 // write the 3 types of classes. 1623 for (ClassInfo clazz : widgets) { 1624 writeClass(bw, clazz, 'W'); 1625 } 1626 for (ClassInfo clazz : layoutParams) { 1627 writeClass(bw, clazz, 'P'); 1628 } 1629 for (ClassInfo clazz : layouts) { 1630 writeClass(bw, clazz, 'L'); 1631 } 1632 } catch (IOException e) { 1633 // pass for now 1634 } finally { 1635 try { 1636 if (bw != null) bw.close(); 1637 } catch (IOException e) { 1638 // pass for now 1639 } 1640 try { 1641 if (fw != null) fw.close(); 1642 } catch (IOException e) { 1643 // pass for now 1644 } 1645 } 1646 } 1647 1648 /** 1649 * Writes a class name and its super class names into a {@link BufferedWriter}. 1650 * 1651 * @param writer the BufferedWriter to write into 1652 * @param clazz the class to write 1653 * @param prefix the prefix to put at the beginning of the line. 1654 * @throws IOException 1655 */ 1656 private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix) 1657 throws IOException { 1658 writer.append(prefix).append(clazz.qualifiedName()); 1659 ClassInfo superClass = clazz; 1660 while ((superClass = superClass.superclass()) != null) { 1661 writer.append(' ').append(superClass.qualifiedName()); 1662 } 1663 writer.append('\n'); 1664 } 1665 1666 /** 1667 * Checks the inheritance of {@link ClassInfo} objects. This method return 1668 * <ul> 1669 * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li> 1670 * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li> 1671 * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends 1672 * <code>android.view.ViewGroup$LayoutParams</code></li> 1673 * <li>{@link #TYPE_NONE}: in all other cases</li> 1674 * </ul> 1675 * 1676 * @param clazz the {@link ClassInfo} to check. 1677 */ 1678 private static int checkInheritance(ClassInfo clazz) { 1679 if ("android.view.ViewGroup".equals(clazz.qualifiedName())) { 1680 return TYPE_LAYOUT; 1681 } else if ("android.view.View".equals(clazz.qualifiedName())) { 1682 return TYPE_WIDGET; 1683 } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) { 1684 return TYPE_LAYOUT_PARAM; 1685 } 1686 1687 ClassInfo parent = clazz.superclass(); 1688 if (parent != null) { 1689 return checkInheritance(parent); 1690 } 1691 1692 return TYPE_NONE; 1693 } 1694 1695 /** 1696 * Ensures a trailing '/' at the end of a string. 1697 */ 1698 static String ensureSlash(String path) { 1699 return path.endsWith("/") ? path : path + "/"; 1700 } 1701 1702 /** 1703 * Process sample projects. Generate html wrapped pages, copy files to 1704 * output dir, and generate a SampleCode NavTree. 1705 */ 1706 public static void writeSamples(boolean offlineMode, ArrayList<SampleCode> sampleCodes, 1707 boolean sortNavByGroups) { 1708 // Go through SCs processing files. Create a root list for SC nodes, 1709 // pass it to SCs for their NavTree children and append them. 1710 List<SampleCode.Node> samplesList = new ArrayList<SampleCode.Node>(); 1711 List<SampleCode.Node> sampleGroupsRootNodes = null; 1712 for (SampleCode sc : sampleCodes) { 1713 samplesList.add(sc.write(offlineMode)); 1714 } 1715 if (sortNavByGroups) { 1716 sampleGroupsRootNodes = new ArrayList<SampleCode.Node>(); 1717 for (SampleCode gsc : sampleCodeGroups) { 1718 String link = "samples/" + gsc.mTitle.replaceAll(" ", "").trim().toLowerCase() + ".html"; 1719 sampleGroupsRootNodes.add(new SampleCode.Node.Builder().setLabel(gsc.mTitle).setLink(link).setType("groupholder").build()); 1720 } 1721 } 1722 // Pass full samplesList to SC to render to js file 1723 if (!offlineMode) { 1724 SampleCode.writeSamplesNavTree(samplesList, sampleGroupsRootNodes); 1725 } 1726 } 1727 1728 /** 1729 * Given an initial samples directory root, walk through the directory collecting 1730 * sample code project roots and adding them to an array of SampleCodes. 1731 * @param rootDir Root directory holding all browseable sample code projects, 1732 * defined in frameworks/base/Android.mk as "-sampleDir path". 1733 */ 1734 public static void getSampleProjects(File rootDir) { 1735 for (File f : rootDir.listFiles()) { 1736 String name = f.getName(); 1737 if (f.isDirectory()) { 1738 if (isValidSampleProjectRoot(f)) { 1739 sampleCodes.add(new SampleCode(f.getAbsolutePath(), "samples/" + name, name)); 1740 } else { 1741 getSampleProjects(f); 1742 } 1743 } 1744 } 1745 } 1746 1747 /** 1748 * Test whether a given directory is the root directory for a sample code project. 1749 * Root directories must include both a src/ directory and a valid _index.jd file. 1750 */ 1751 public static boolean isValidSampleProjectRoot(File dir) { 1752 File srcDir = new File(dir.getAbsolutePath(), "src"); 1753 File indexJd = new File(dir.getAbsolutePath(), "_index.jd"); 1754 if (srcDir.exists() && indexJd.exists()) { 1755 return true; 1756 } else { 1757 return false; 1758 } 1759 } 1760 1761} 1762