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