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