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