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