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