Home | History | Annotate | Download | only in doclava
      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 
     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    */
     90   public static boolean parseComments() {
     91     return generateDocs || parseComments;
     92   }
     93 
     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 
    115   public static void main(String[] args) {
    116     com.sun.tools.javadoc.Main.execute(args);
    117   }
    118 
    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 
    319   private static void writeIndex() {
    320     Data data = makeHDF();
    321     ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension);
    322   }
    323 
    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 
    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 
    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 
    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 
    436   public static LanguageVersion languageVersion() {
    437     return LanguageVersion.JAVA_1_5;
    438   }
    439 
    440 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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    */
    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 
    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 
    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 
    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 
    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 
    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 
   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 
   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 
   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 
   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    */
   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    */
   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    */
   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 
   1145     public HideHandler(Object target) {
   1146       this.target = target;
   1147     }
   1148 
   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("&", "&amp;");
   1165       }
   1166 
   1167       try {
   1168         return filterHidden(method.invoke(target, args), method.getReturnType());
   1169       } catch (InvocationTargetException e) {
   1170         throw e.getTargetException();
   1171       }
   1172     }
   1173 
   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 += "&nbsp;";
   1184       }
   1185 
   1186       return s;
   1187     }
   1188 
   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    */
   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    */
   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    */
   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    */
   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    */
   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