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