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