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.util.regex.Pattern;
     32 import java.util.stream.Collectors;
     33 import java.io.*;
     34 import java.lang.reflect.Proxy;
     35 import java.lang.reflect.Array;
     36 import java.lang.reflect.InvocationHandler;
     37 import java.lang.reflect.InvocationTargetException;
     38 import java.lang.reflect.Method;
     39 import java.net.MalformedURLException;
     40 import java.net.URL;
     41 
     42 public class Doclava {
     43   private static final String SDK_CONSTANT_ANNOTATION = "android.annotation.SdkConstant";
     44   private static final String SDK_CONSTANT_TYPE_ACTIVITY_ACTION =
     45       "android.annotation.SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION";
     46   private static final String SDK_CONSTANT_TYPE_BROADCAST_ACTION =
     47       "android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION";
     48   private static final String SDK_CONSTANT_TYPE_SERVICE_ACTION =
     49       "android.annotation.SdkConstant.SdkConstantType.SERVICE_ACTION";
     50   private static final String SDK_CONSTANT_TYPE_CATEGORY =
     51       "android.annotation.SdkConstant.SdkConstantType.INTENT_CATEGORY";
     52   private static final String SDK_CONSTANT_TYPE_FEATURE =
     53       "android.annotation.SdkConstant.SdkConstantType.FEATURE";
     54   private static final String SDK_WIDGET_ANNOTATION = "android.annotation.Widget";
     55   private static final String SDK_LAYOUT_ANNOTATION = "android.annotation.Layout";
     56 
     57   private static final int TYPE_NONE = 0;
     58   private static final int TYPE_WIDGET = 1;
     59   private static final int TYPE_LAYOUT = 2;
     60   private static final int TYPE_LAYOUT_PARAM = 3;
     61 
     62   public static final int SHOW_PUBLIC = 0x00000001;
     63   public static final int SHOW_PROTECTED = 0x00000003;
     64   public static final int SHOW_PACKAGE = 0x00000007;
     65   public static final int SHOW_PRIVATE = 0x0000000f;
     66   public static final int SHOW_HIDDEN = 0x0000001f;
     67 
     68   public static int showLevel = SHOW_PROTECTED;
     69 
     70   public static final boolean SORT_BY_NAV_GROUPS = true;
     71   /* Debug output for PageMetadata, format urls from site root */
     72   public static boolean META_DBG=false;
     73   /* Generate the static html docs with devsite tempating only */
     74   public static boolean DEVSITE_STATIC_ONLY = false;
     75   /* Don't resolve @link refs found in devsite pages */
     76   public static boolean DEVSITE_IGNORE_JDLINKS = false;
     77   /* Show Preview navigation and process preview docs */
     78   public static boolean INCLUDE_PREVIEW = false;
     79   /* output en, es, ja without parent intl/ container */
     80   public static boolean USE_DEVSITE_LOCALE_OUTPUT_PATHS = false;
     81   /* generate navtree.js without other docs */
     82   public static boolean NAVTREE_ONLY = false;
     83   /* Generate reference navtree.js with all inherited members */
     84   public static boolean AT_LINKS_NAVTREE = false;
     85   public static String outputPathBase = "/";
     86   public static ArrayList<String> inputPathHtmlDirs = new ArrayList<String>();
     87   public static ArrayList<String> inputPathHtmlDir2 = new ArrayList<String>();
     88   public static String inputPathResourcesDir;
     89   public static String outputPathResourcesDir;
     90   public static String outputPathHtmlDirs;
     91   public static String outputPathHtmlDir2;
     92   /* Javadoc output directory and included in url path */
     93   public static String javadocDir = "reference/";
     94   public static String htmlExtension;
     95 
     96   public static RootDoc root;
     97   public static ArrayList<String[]> mHDFData = new ArrayList<String[]>();
     98   public static List<PageMetadata.Node> sTaglist = new ArrayList<PageMetadata.Node>();
     99   public static ArrayList<SampleCode> sampleCodes = new ArrayList<SampleCode>();
    100   public static ArrayList<SampleCode> sampleCodeGroups = new ArrayList<SampleCode>();
    101   public static Data samplesNavTree;
    102   public static Map<Character, String> escapeChars = new HashMap<Character, String>();
    103   public static String title = "";
    104   public static SinceTagger sinceTagger = new SinceTagger();
    105   public static ArtifactTagger artifactTagger = new ArtifactTagger();
    106   public static HashSet<String> knownTags = new HashSet<String>();
    107   public static FederationTagger federationTagger = new FederationTagger();
    108   public static boolean showUnannotated = false;
    109   public static Set<String> showAnnotations = new HashSet<String>();
    110   public static Set<String> hideAnnotations = new HashSet<String>();
    111   public static boolean showAnnotationOverridesVisibility = false;
    112   public static Set<String> hiddenPackages = new HashSet<String>();
    113   public static boolean includeAssets = true;
    114   public static boolean includeDefaultAssets = true;
    115   private static boolean generateDocs = true;
    116   private static boolean parseComments = false;
    117   private static String yamlNavFile = null;
    118   public static boolean documentAnnotations = false;
    119   public static String documentAnnotationsPath = null;
    120   public static Map<String, String> annotationDocumentationMap = null;
    121   public static boolean referenceOnly = false;
    122   public static boolean staticOnly = false;
    123   public static boolean yamlV2 = false; /* whether to build the new version of the yaml file */
    124   public static AuxSource auxSource = new EmptyAuxSource();
    125   public static Linter linter = new EmptyLinter();
    126   public static boolean android = false;
    127   public static String manifestFile = null;
    128   public static Map<String, String> manifestPermissions = new HashMap<>();
    129 
    130   public static JSilver jSilver = null;
    131 
    132   //API reference extensions
    133   private static boolean gmsRef = false;
    134   private static boolean gcmRef = false;
    135   public static String libraryRoot = null;
    136   private static boolean samplesRef = false;
    137   private static boolean sac = false;
    138 
    139   public static boolean checkLevel(int level) {
    140     return (showLevel & level) == level;
    141   }
    142 
    143   /**
    144    * Returns true if we should parse javadoc comments,
    145    * reporting errors in the process.
    146    */
    147   public static boolean parseComments() {
    148     return generateDocs || parseComments;
    149   }
    150 
    151   public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv,
    152       boolean hidden) {
    153     if (hidden && !checkLevel(SHOW_HIDDEN)) {
    154       return false;
    155     }
    156     if (pub && checkLevel(SHOW_PUBLIC)) {
    157       return true;
    158     }
    159     if (prot && checkLevel(SHOW_PROTECTED)) {
    160       return true;
    161     }
    162     if (pkgp && checkLevel(SHOW_PACKAGE)) {
    163       return true;
    164     }
    165     if (priv && checkLevel(SHOW_PRIVATE)) {
    166       return true;
    167     }
    168     return false;
    169   }
    170 
    171   public static void main(String[] args) {
    172     com.sun.tools.javadoc.Main.execute(args);
    173   }
    174 
    175   public static boolean start(RootDoc r) {
    176     long startTime = System.nanoTime();
    177     String keepListFile = null;
    178     String proguardFile = null;
    179     String proofreadFile = null;
    180     String todoFile = null;
    181     String sdkValuePath = null;
    182     String stubsDir = null;
    183     // Create the dependency graph for the stubs  directory
    184     boolean offlineMode = false;
    185     String apiFile = null;
    186     String dexApiFile = null;
    187     String removedApiFile = null;
    188     String removedDexApiFile = null;
    189     String exactApiFile = null;
    190     String privateApiFile = null;
    191     String privateDexApiFile = null;
    192     String debugStubsFile = "";
    193     HashSet<String> stubPackages = null;
    194     HashSet<String> stubImportPackages = null;
    195     boolean stubSourceOnly = false;
    196     ArrayList<String> knownTagsFiles = new ArrayList<String>();
    197 
    198     root = r;
    199 
    200     String[][] options = r.options();
    201     for (String[] a : options) {
    202       if (a[0].equals("-d")) {
    203         outputPathBase = outputPathHtmlDirs = ClearPage.outputDir = a[1];
    204       } else if (a[0].equals("-templatedir")) {
    205         ClearPage.addTemplateDir(a[1]);
    206       } else if (a[0].equals("-hdf")) {
    207         mHDFData.add(new String[] {a[1], a[2]});
    208       } else if (a[0].equals("-knowntags")) {
    209         knownTagsFiles.add(a[1]);
    210       } else if (a[0].equals("-apidocsdir")) {
    211         javadocDir = a[1];
    212       } else if (a[0].equals("-toroot")) {
    213         ClearPage.toroot = a[1];
    214       } else if (a[0].equals("-samplecode")) {
    215         sampleCodes.add(new SampleCode(a[1], a[2], a[3]));
    216       } else if (a[0].equals("-samplegroup")) {
    217         sampleCodeGroups.add(new SampleCode(null, null, a[1]));
    218       } else if (a[0].equals("-samplesdir")) {
    219         getSampleProjects(new File(a[1]));
    220       //the destination output path for main htmldir
    221       } else if (a[0].equals("-htmldir")) {
    222         inputPathHtmlDirs.add(a[1]);
    223         ClearPage.htmlDirs = inputPathHtmlDirs;
    224       //the destination output path for additional htmldir
    225       } else if (a[0].equals("-htmldir2")) {
    226         if (a[2].equals("default")) {
    227           inputPathHtmlDir2.add(a[1]);
    228         } else {
    229           inputPathHtmlDir2.add(a[1]);
    230           outputPathHtmlDir2 = a[2];
    231         }
    232       //the destination output path for additional resources (images)
    233       } else if (a[0].equals("-resourcesdir")) {
    234         inputPathResourcesDir = a[1];
    235       } else if (a[0].equals("-resourcesoutdir")) {
    236         outputPathResourcesDir = a[1];
    237       } else if (a[0].equals("-title")) {
    238         Doclava.title = a[1];
    239       } else if (a[0].equals("-werror")) {
    240         Errors.setWarningsAreErrors(true);
    241       } else if (a[0].equals("-lerror")) {
    242         Errors.setLintsAreErrors(true);
    243       } else if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-lint")
    244           || a[0].equals("-hide")) {
    245         try {
    246           int level = -1;
    247           if (a[0].equals("-error")) {
    248             level = Errors.ERROR;
    249           } else if (a[0].equals("-warning")) {
    250             level = Errors.WARNING;
    251           } else if (a[0].equals("-lint")) {
    252             level = Errors.LINT;
    253           } else if (a[0].equals("-hide")) {
    254             level = Errors.HIDDEN;
    255           }
    256           Errors.setErrorLevel(Integer.parseInt(a[1]), level);
    257         } catch (NumberFormatException e) {
    258           // already printed below
    259           return false;
    260         }
    261       } else if (a[0].equals("-keeplist")) {
    262         keepListFile = a[1];
    263       } else if (a[0].equals("-showUnannotated")) {
    264         showUnannotated = true;
    265       } else if (a[0].equals("-showAnnotation")) {
    266         showAnnotations.add(a[1]);
    267       } else if (a[0].equals("-hideAnnotation")) {
    268         hideAnnotations.add(a[1]);
    269       } else if (a[0].equals("-showAnnotationOverridesVisibility")) {
    270         showAnnotationOverridesVisibility = true;
    271       } else if (a[0].equals("-hidePackage")) {
    272         hiddenPackages.add(a[1]);
    273       } else if (a[0].equals("-proguard")) {
    274         proguardFile = a[1];
    275       } else if (a[0].equals("-proofread")) {
    276         proofreadFile = a[1];
    277       } else if (a[0].equals("-todo")) {
    278         todoFile = a[1];
    279       } else if (a[0].equals("-public")) {
    280         showLevel = SHOW_PUBLIC;
    281       } else if (a[0].equals("-protected")) {
    282         showLevel = SHOW_PROTECTED;
    283       } else if (a[0].equals("-package")) {
    284         showLevel = SHOW_PACKAGE;
    285       } else if (a[0].equals("-private")) {
    286         showLevel = SHOW_PRIVATE;
    287       } else if (a[0].equals("-hidden")) {
    288         showLevel = SHOW_HIDDEN;
    289       } else if (a[0].equals("-stubs")) {
    290         stubsDir = a[1];
    291       } else if (a[0].equals("-stubpackages")) {
    292         stubPackages = new HashSet<String>();
    293         for (String pkg : a[1].split(":")) {
    294           stubPackages.add(pkg);
    295         }
    296       } else if (a[0].equals("-stubimportpackages")) {
    297         stubImportPackages = new HashSet<String>();
    298         for (String pkg : a[1].split(":")) {
    299           stubImportPackages.add(pkg);
    300           hiddenPackages.add(pkg);
    301         }
    302       } else if (a[0].equals("-stubsourceonly")) {
    303         stubSourceOnly = true;
    304       } else if (a[0].equals("-sdkvalues")) {
    305         sdkValuePath = a[1];
    306       } else if (a[0].equals("-api")) {
    307         apiFile = a[1];
    308       } else if (a[0].equals("-dexApi")) {
    309         dexApiFile = a[1];
    310       } else if (a[0].equals("-removedApi")) {
    311         removedApiFile = a[1];
    312       } else if (a[0].equals("-removedDexApi")) {
    313         removedDexApiFile = a[1];
    314       } else if (a[0].equals("-exactApi")) {
    315         exactApiFile = a[1];
    316       } else if (a[0].equals("-privateApi")) {
    317         privateApiFile = a[1];
    318       } else if (a[0].equals("-privateDexApi")) {
    319         privateDexApiFile = a[1];
    320       } else if (a[0].equals("-nodocs")) {
    321         generateDocs = false;
    322       } else if (a[0].equals("-noassets")) {
    323         includeAssets = false;
    324       } else if (a[0].equals("-nodefaultassets")) {
    325         includeDefaultAssets = false;
    326       } else if (a[0].equals("-parsecomments")) {
    327         parseComments = true;
    328       } else if (a[0].equals("-since")) {
    329         sinceTagger.addVersion(a[1], a[2]);
    330       } else if (a[0].equals("-artifact")) {
    331         artifactTagger.addArtifact(a[1], a[2]);
    332       } else if (a[0].equals("-offlinemode")) {
    333         offlineMode = true;
    334       } else if (a[0].equals("-metadataDebug")) {
    335         META_DBG = true;
    336       } else if (a[0].equals("-includePreview")) {
    337         INCLUDE_PREVIEW = true;
    338       } else if (a[0].equals("-ignoreJdLinks")) {
    339         if (DEVSITE_STATIC_ONLY) {
    340           DEVSITE_IGNORE_JDLINKS = true;
    341         }
    342       } else if (a[0].equals("-federate")) {
    343         try {
    344           String name = a[1];
    345           URL federationURL = new URL(a[2]);
    346           federationTagger.addSiteUrl(name, federationURL);
    347         } catch (MalformedURLException e) {
    348           System.err.println("Could not parse URL for federation: " + a[1]);
    349           return false;
    350         }
    351       } else if (a[0].equals("-federationapi")) {
    352         String name = a[1];
    353         String file = a[2];
    354         federationTagger.addSiteApi(name, file);
    355       } else if (a[0].equals("-yaml")) {
    356         yamlNavFile = a[1];
    357       } else if (a[0].equals("-dac_libraryroot")) {
    358         libraryRoot = ensureSlash(a[1]);
    359         mHDFData.add(new String[] {"library.root", a[1]});
    360       } else if (a[0].equals("-dac_dataname")) {
    361         mHDFData.add(new String[] {"dac_dataname", a[1]});
    362       } else if (a[0].equals("-documentannotations")) {
    363         documentAnnotations = true;
    364         documentAnnotationsPath = a[1];
    365       } else if (a[0].equals("-referenceonly")) {
    366         referenceOnly = true;
    367         mHDFData.add(new String[] {"referenceonly", "1"});
    368       } else if (a[0].equals("-staticonly")) {
    369         staticOnly = true;
    370         mHDFData.add(new String[] {"staticonly", "1"});
    371       } else if (a[0].equals("-navtreeonly")) {
    372         NAVTREE_ONLY = true;
    373       } else if (a[0].equals("-atLinksNavtree")) {
    374         AT_LINKS_NAVTREE = true;
    375       } else if (a[0].equals("-yamlV2")) {
    376         yamlV2 = true;
    377       } else if (a[0].equals("-devsite")) {
    378         // Don't copy any assets to devsite output
    379         includeAssets = false;
    380         USE_DEVSITE_LOCALE_OUTPUT_PATHS = true;
    381         mHDFData.add(new String[] {"devsite", "1"});
    382         if (staticOnly) {
    383           DEVSITE_STATIC_ONLY = true;
    384           System.out.println("  ... Generating static html only for devsite");
    385         }
    386         if (yamlNavFile == null) {
    387           yamlNavFile = "_book.yaml";
    388         }
    389       } else if (a[0].equals("-android")) {
    390         auxSource = new AndroidAuxSource();
    391         linter = new AndroidLinter();
    392         android = true;
    393       } else if (a[0].equals("-manifest")) {
    394         manifestFile = a[1];
    395       }
    396     }
    397 
    398     // If the caller has not explicitly requested that unannotated classes and members should be
    399     // shown in the output then only show them if no annotations were provided.
    400     if (!showUnannotated && showAnnotations.isEmpty()) {
    401       showUnannotated = true;
    402     }
    403 
    404     if (!readKnownTagsFiles(knownTags, knownTagsFiles)) {
    405       return false;
    406     }
    407     if (!readManifest()) {
    408       return false;
    409     }
    410 
    411     // Set up the data structures
    412     Converter.makeInfo(r);
    413 
    414     if (generateDocs) {
    415       ClearPage.addBundledTemplateDir("assets/customizations");
    416       ClearPage.addBundledTemplateDir("assets/templates");
    417 
    418       List<ResourceLoader> resourceLoaders = new ArrayList<ResourceLoader>();
    419       List<String> templates = ClearPage.getTemplateDirs();
    420       for (String tmpl : templates) {
    421         resourceLoaders.add(new FileSystemResourceLoader(tmpl));
    422       }
    423       // If no custom template path is provided, and this is a devsite build,
    424       // then use the bundled templates-sdk/ files by default
    425       if (templates.isEmpty() && USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
    426         resourceLoaders.add(new ClassResourceLoader(Doclava.class, "/assets/templates-sdk"));
    427         System.out.println("\n#########  OK, Using templates-sdk ############\n");
    428       }
    429 
    430       templates = ClearPage.getBundledTemplateDirs();
    431       for (String tmpl : templates) {
    432           // TODO - remove commented line - it's here for debugging purposes
    433         //  resourceLoaders.add(new FileSystemResourceLoader("/Volumes/Android/master/external/doclava/res/" + tmpl));
    434         resourceLoaders.add(new ClassResourceLoader(Doclava.class, '/'+tmpl));
    435       }
    436 
    437       ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders);
    438       jSilver = new JSilver(compositeResourceLoader);
    439 
    440       if (!Doclava.readTemplateSettings()) {
    441         return false;
    442       }
    443 
    444       // if requested, only generate the navtree for ds use-case
    445       if (NAVTREE_ONLY) {
    446         if (AT_LINKS_NAVTREE) {
    447           AtLinksNavTree.writeAtLinksNavTree(javadocDir);
    448         } else {
    449           NavTree.writeNavTree(javadocDir, "");
    450         }
    451         return true;
    452       }
    453 
    454       // don't do ref doc tasks in devsite static-only builds
    455       if (!DEVSITE_STATIC_ONLY) {
    456         // Load additional data structures from federated sites.
    457         for(FederatedSite site : federationTagger.getSites()) {
    458           Converter.addApiInfo(site.apiInfo());
    459         }
    460 
    461         // Apply @since tags from the XML file
    462         sinceTagger.tagAll(Converter.rootClasses());
    463 
    464         // Apply @artifact tags from the XML file
    465         artifactTagger.tagAll(Converter.rootClasses());
    466 
    467         // Apply details of federated documentation
    468         federationTagger.tagAll(Converter.rootClasses());
    469 
    470         // Files for proofreading
    471         if (proofreadFile != null) {
    472           Proofread.initProofread(proofreadFile);
    473         }
    474         if (todoFile != null) {
    475           TodoFile.writeTodoFile(todoFile);
    476         }
    477 
    478         if (samplesRef) {
    479           // always write samples without offlineMode behaviors
    480           writeSamples(false, sampleCodes, SORT_BY_NAV_GROUPS);
    481         }
    482       }
    483       if (!referenceOnly) {
    484         // HTML2 Pages -- Generate Pages from optional secondary dir
    485         if (!inputPathHtmlDir2.isEmpty()) {
    486           if (!outputPathHtmlDir2.isEmpty()) {
    487             ClearPage.outputDir = outputPathBase + "/" + outputPathHtmlDir2;
    488           }
    489           ClearPage.htmlDirs = inputPathHtmlDir2;
    490           writeHTMLPages();
    491           ClearPage.htmlDirs = inputPathHtmlDirs;
    492         }
    493 
    494         // HTML Pages
    495         if (!ClearPage.htmlDirs.isEmpty()) {
    496           ClearPage.htmlDirs = inputPathHtmlDirs;
    497           if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
    498             ClearPage.outputDir = outputPathHtmlDirs + "/en/";
    499           } else {
    500             ClearPage.outputDir = outputPathHtmlDirs;
    501           }
    502           writeHTMLPages();
    503         }
    504       }
    505 
    506       writeResources();
    507 
    508       writeAssets();
    509 
    510       // don't do ref doc tasks in devsite static-only builds
    511       if (!DEVSITE_STATIC_ONLY) {
    512         // Navigation tree
    513         String refPrefix = new String();
    514         if(gmsRef){
    515           refPrefix = "gms-";
    516         } else if(gcmRef){
    517           refPrefix = "gcm-";
    518         }
    519         NavTree.writeNavTree(javadocDir, refPrefix);
    520 
    521         // Write yaml tree.
    522         if (yamlNavFile != null){
    523           NavTree.writeYamlTree(javadocDir, yamlNavFile);
    524           if (yamlV2) {
    525             // Generate both for good measure, to make transitions easier, but change the filename
    526             // for the new one so there's yet another explicit opt-in required by fixing the name.
    527             yamlNavFile = "_NEW" + yamlNavFile;
    528             NavTree.writeYamlTree2(javadocDir, yamlNavFile);
    529           }
    530         }
    531 
    532         // Packages Pages
    533         writePackages(refPrefix + "packages" + htmlExtension);
    534 
    535         // Classes
    536         writeClassLists();
    537         writeClasses();
    538         writeHierarchy();
    539         // writeKeywords();
    540 
    541         // Lists for JavaScript
    542         writeLists();
    543         if (keepListFile != null) {
    544           writeKeepList(keepListFile);
    545         }
    546 
    547         Proofread.finishProofread(proofreadFile);
    548 
    549         if (sdkValuePath != null) {
    550           writeSdkValues(sdkValuePath);
    551         }
    552       }
    553       // Write metadata for all processed files to jd_lists_unified in out dir
    554       if (!sTaglist.isEmpty()) {
    555         PageMetadata.WriteListByLang(sTaglist);
    556         // For devsite (ds) reference only, write samples_metadata to out dir
    557         if ((USE_DEVSITE_LOCALE_OUTPUT_PATHS) && (!DEVSITE_STATIC_ONLY)) {
    558           PageMetadata.WriteSamplesListByLang(sTaglist);
    559         }
    560       }
    561     }
    562 
    563     // Stubs
    564     if (stubsDir != null || apiFile != null || dexApiFile != null || proguardFile != null
    565         || removedApiFile != null || removedDexApiFile != null || exactApiFile != null
    566         || privateApiFile != null || privateDexApiFile != null) {
    567       Stubs.writeStubsAndApi(stubsDir, apiFile, dexApiFile, proguardFile, removedApiFile,
    568           removedDexApiFile, exactApiFile, privateApiFile, privateDexApiFile, stubPackages,
    569           stubImportPackages, stubSourceOnly);
    570     }
    571 
    572     Errors.printErrors();
    573 
    574     long time = System.nanoTime() - startTime;
    575     System.out.println("DroidDoc took " + (time / 1000000000) + " sec. to write docs to "
    576         + outputPathBase );
    577 
    578     return !Errors.hadError;
    579   }
    580 
    581   private static void writeIndex(String dir) {
    582     Data data = makeHDF();
    583     ClearPage.write(data, "index.cs", dir + "index" + htmlExtension);
    584   }
    585 
    586   private static boolean readTemplateSettings() {
    587     Data data = makeHDF();
    588 
    589     // The .html extension is hard-coded in several .cs files,
    590     // and so you cannot currently set it as a property.
    591     htmlExtension = ".html";
    592     // htmlExtension = data.getValue("template.extension", ".html");
    593     int i = 0;
    594     while (true) {
    595       String k = data.getValue("template.escape." + i + ".key", "");
    596       String v = data.getValue("template.escape." + i + ".value", "");
    597       if ("".equals(k)) {
    598         break;
    599       }
    600       if (k.length() != 1) {
    601         System.err.println("template.escape." + i + ".key must have a length of 1: " + k);
    602         return false;
    603       }
    604       escapeChars.put(k.charAt(0), v);
    605       i++;
    606     }
    607     return true;
    608   }
    609 
    610     private static boolean readKnownTagsFiles(HashSet<String> knownTags,
    611             ArrayList<String> knownTagsFiles) {
    612         for (String fn: knownTagsFiles) {
    613            BufferedReader in = null;
    614            try {
    615                in = new BufferedReader(new FileReader(fn));
    616                int lineno = 0;
    617                boolean fail = false;
    618                while (true) {
    619                    lineno++;
    620                    String line = in.readLine();
    621                    if (line == null) {
    622                        break;
    623                    }
    624                    line = line.trim();
    625                    if (line.length() == 0) {
    626                        continue;
    627                    } else if (line.charAt(0) == '#') {
    628                        continue;
    629                    }
    630                    String[] words = line.split("\\s+", 2);
    631                    if (words.length == 2) {
    632                        if (words[1].charAt(0) != '#') {
    633                            System.err.println(fn + ":" + lineno
    634                                    + ": Only one tag allowed per line: " + line);
    635                            fail = true;
    636                            continue;
    637                        }
    638                    }
    639                    knownTags.add(words[0]);
    640                }
    641                if (fail) {
    642                    return false;
    643                }
    644            } catch (IOException ex) {
    645                System.err.println("Error reading file: " + fn + " (" + ex.getMessage() + ")");
    646                return false;
    647            } finally {
    648                if (in != null) {
    649                    try {
    650                        in.close();
    651                    } catch (IOException e) {
    652                    }
    653                }
    654            }
    655         }
    656         return true;
    657     }
    658 
    659   private static boolean readManifest() {
    660     manifestPermissions.clear();
    661     if (manifestFile == null) {
    662       return true;
    663     }
    664     try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(manifestFile));
    665         ByteArrayOutputStream out = new ByteArrayOutputStream()) {
    666       byte[] buffer = new byte[1024];
    667       int count;
    668       while ((count = in.read(buffer)) != -1) {
    669         out.write(buffer, 0, count);
    670       }
    671       final Matcher m = Pattern.compile("(?s)<permission "
    672           + "[^>]*android:name=\"([^\">]+)\""
    673           + "[^>]*android:protectionLevel=\"([^\">]+)\"").matcher(out.toString());
    674       while (m.find()) {
    675         manifestPermissions.put(m.group(1), m.group(2));
    676       }
    677     } catch (IOException e) {
    678       Errors.error(Errors.PARSE_ERROR, (SourcePositionInfo) null,
    679           "Failed to parse " + manifestFile + ": " + e);
    680       return false;
    681     }
    682     return true;
    683   }
    684 
    685   public static String escape(String s) {
    686     if (escapeChars.size() == 0) {
    687       return s;
    688     }
    689     StringBuffer b = null;
    690     int begin = 0;
    691     final int N = s.length();
    692     for (int i = 0; i < N; i++) {
    693       char c = s.charAt(i);
    694       String mapped = escapeChars.get(c);
    695       if (mapped != null) {
    696         if (b == null) {
    697           b = new StringBuffer(s.length() + mapped.length());
    698         }
    699         if (begin != i) {
    700           b.append(s.substring(begin, i));
    701         }
    702         b.append(mapped);
    703         begin = i + 1;
    704       }
    705     }
    706     if (b != null) {
    707       if (begin != N) {
    708         b.append(s.substring(begin, N));
    709       }
    710       return b.toString();
    711     }
    712     return s;
    713   }
    714 
    715   public static void setPageTitle(Data data, String title) {
    716     String s = title;
    717     if (Doclava.title.length() > 0) {
    718       s += " - " + Doclava.title;
    719     }
    720     data.setValue("page.title", s);
    721   }
    722 
    723 
    724   public static LanguageVersion languageVersion() {
    725     return LanguageVersion.JAVA_1_5;
    726   }
    727 
    728 
    729   public static int optionLength(String option) {
    730     if (option.equals("-d")) {
    731       return 2;
    732     }
    733     if (option.equals("-templatedir")) {
    734       return 2;
    735     }
    736     if (option.equals("-hdf")) {
    737       return 3;
    738     }
    739     if (option.equals("-knowntags")) {
    740       return 2;
    741     }
    742     if (option.equals("-apidocsdir")) {
    743       return 2;
    744     }
    745     if (option.equals("-toroot")) {
    746       return 2;
    747     }
    748     if (option.equals("-samplecode")) {
    749       samplesRef = true;
    750       return 4;
    751     }
    752     if (option.equals("-samplegroup")) {
    753       return 2;
    754     }
    755     if (option.equals("-samplesdir")) {
    756       samplesRef = true;
    757       return 2;
    758     }
    759     if (option.equals("-devsite")) {
    760       return 1;
    761     }
    762     if (option.equals("-yamlV2")) {
    763       return 1;
    764     }
    765     if (option.equals("-dac_libraryroot")) {
    766       return 2;
    767     }
    768     if (option.equals("-dac_dataname")) {
    769       return 2;
    770     }
    771     if (option.equals("-ignoreJdLinks")) {
    772       return 1;
    773     }
    774     if (option.equals("-htmldir")) {
    775       return 2;
    776     }
    777     if (option.equals("-htmldir2")) {
    778       return 3;
    779     }
    780     if (option.equals("-resourcesdir")) {
    781       return 2;
    782     }
    783     if (option.equals("-resourcesoutdir")) {
    784       return 2;
    785     }
    786     if (option.equals("-title")) {
    787       return 2;
    788     }
    789     if (option.equals("-werror")) {
    790       return 1;
    791     }
    792     if (option.equals("-lerror")) {
    793       return 1;
    794     }
    795     if (option.equals("-hide")) {
    796       return 2;
    797     }
    798     if (option.equals("-warning")) {
    799       return 2;
    800     }
    801     if (option.equals("-error")) {
    802       return 2;
    803     }
    804     if (option.equals("-keeplist")) {
    805       return 2;
    806     }
    807     if (option.equals("-showUnannotated")) {
    808       return 1;
    809     }
    810     if (option.equals("-showAnnotation")) {
    811       return 2;
    812     }
    813     if (option.equals("-hideAnnotation")) {
    814       return 2;
    815     }
    816     if (option.equals("-showAnnotationOverridesVisibility")) {
    817       return 1;
    818     }
    819     if (option.equals("-hidePackage")) {
    820       return 2;
    821     }
    822     if (option.equals("-proguard")) {
    823       return 2;
    824     }
    825     if (option.equals("-proofread")) {
    826       return 2;
    827     }
    828     if (option.equals("-todo")) {
    829       return 2;
    830     }
    831     if (option.equals("-public")) {
    832       return 1;
    833     }
    834     if (option.equals("-protected")) {
    835       return 1;
    836     }
    837     if (option.equals("-package")) {
    838       return 1;
    839     }
    840     if (option.equals("-private")) {
    841       return 1;
    842     }
    843     if (option.equals("-hidden")) {
    844       return 1;
    845     }
    846     if (option.equals("-stubs")) {
    847       return 2;
    848     }
    849     if (option.equals("-stubpackages")) {
    850       return 2;
    851     }
    852     if (option.equals("-stubimportpackages")) {
    853       return 2;
    854     }
    855     if (option.equals("-stubsourceonly")) {
    856       return 1;
    857     }
    858     if (option.equals("-sdkvalues")) {
    859       return 2;
    860     }
    861     if (option.equals("-api")) {
    862       return 2;
    863     }
    864     if (option.equals("-dexApi")) {
    865       return 2;
    866     }
    867     if (option.equals("-removedApi")) {
    868       return 2;
    869     }
    870     if (option.equals("-removedDexApi")) {
    871       return 2;
    872     }
    873     if (option.equals("-exactApi")) {
    874       return 2;
    875     }
    876     if (option.equals("-privateApi")) {
    877       return 2;
    878     }
    879     if (option.equals("-privateDexApi")) {
    880       return 2;
    881     }
    882     if (option.equals("-nodocs")) {
    883       return 1;
    884     }
    885     if (option.equals("-nodefaultassets")) {
    886       return 1;
    887     }
    888     if (option.equals("-parsecomments")) {
    889       return 1;
    890     }
    891     if (option.equals("-since")) {
    892       return 3;
    893     }
    894     if (option.equals("-artifact")) {
    895       return 3;
    896     }
    897     if (option.equals("-offlinemode")) {
    898       return 1;
    899     }
    900     if (option.equals("-federate")) {
    901       return 3;
    902     }
    903     if (option.equals("-federationapi")) {
    904       return 3;
    905     }
    906     if (option.equals("-yaml")) {
    907       return 2;
    908     }
    909     if (option.equals("-gmsref")) {
    910       gmsRef = true;
    911       return 1;
    912     }
    913     if (option.equals("-gcmref")) {
    914       gcmRef = true;
    915       return 1;
    916     }
    917     if (option.equals("-metadataDebug")) {
    918       return 1;
    919     }
    920     if (option.equals("-includePreview")) {
    921       return 1;
    922     }
    923     if (option.equals("-documentannotations")) {
    924       return 2;
    925     }
    926     if (option.equals("-referenceonly")) {
    927       return 1;
    928     }
    929     if (option.equals("-staticonly")) {
    930       return 1;
    931     }
    932     if (option.equals("-navtreeonly")) {
    933       return 1;
    934     }
    935     if (option.equals("-atLinksNavtree")) {
    936       return 1;
    937     }
    938     if (option.equals("-android")) {
    939       return 1;
    940     }
    941     if (option.equals("-manifest")) {
    942       return 2;
    943     }
    944     return 0;
    945   }
    946   public static boolean validOptions(String[][] options, DocErrorReporter r) {
    947     for (String[] a : options) {
    948       if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-hide")) {
    949         try {
    950           Integer.parseInt(a[1]);
    951         } catch (NumberFormatException e) {
    952           r.printError("bad -" + a[0] + " value must be a number: " + a[1]);
    953           return false;
    954         }
    955       }
    956     }
    957 
    958     return true;
    959   }
    960 
    961   public static Data makeHDF() {
    962     Data data = jSilver.createData();
    963 
    964     for (String[] p : mHDFData) {
    965       data.setValue(p[0], p[1]);
    966     }
    967 
    968     return data;
    969   }
    970 
    971   public static Data makePackageHDF() {
    972     Data data = makeHDF();
    973     Collection<ClassInfo> classes = Converter.rootClasses();
    974 
    975     SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
    976     for (ClassInfo cl : classes) {
    977       PackageInfo pkg = cl.containingPackage();
    978       String name;
    979       if (pkg == null) {
    980         name = "";
    981       } else {
    982         name = pkg.name();
    983       }
    984       sorted.put(name, pkg);
    985     }
    986 
    987     int i = 0;
    988     for (Map.Entry<String, PackageInfo> entry : sorted.entrySet()) {
    989       String s = entry.getKey();
    990       PackageInfo pkg = entry.getValue();
    991 
    992       if (pkg.isHiddenOrRemoved()) {
    993         continue;
    994       }
    995       boolean allHiddenOrRemoved = true;
    996       int pass = 0;
    997       ClassInfo[] classesToCheck = null;
    998       while (pass < 6) {
    999         switch (pass) {
   1000           case 0:
   1001             classesToCheck = pkg.ordinaryClasses();
   1002             break;
   1003           case 1:
   1004             classesToCheck = pkg.enums();
   1005             break;
   1006           case 2:
   1007             classesToCheck = pkg.errors();
   1008             break;
   1009           case 3:
   1010             classesToCheck = pkg.exceptions();
   1011             break;
   1012           case 4:
   1013             classesToCheck = pkg.interfaces();
   1014             break;
   1015           case 5:
   1016             classesToCheck = pkg.annotations();
   1017             break;
   1018           default:
   1019             System.err.println("Error reading package: " + pkg.name());
   1020             break;
   1021         }
   1022         for (ClassInfo cl : classesToCheck) {
   1023           if (!cl.isHiddenOrRemoved()) {
   1024             allHiddenOrRemoved = false;
   1025             break;
   1026           }
   1027         }
   1028         if (!allHiddenOrRemoved) {
   1029           break;
   1030         }
   1031         pass++;
   1032       }
   1033       if (allHiddenOrRemoved) {
   1034         continue;
   1035       }
   1036       if(gmsRef){
   1037           data.setValue("reference.gms", "true");
   1038       } else if(gcmRef){
   1039           data.setValue("reference.gcm", "true");
   1040       }
   1041       data.setValue("reference", "1");
   1042       data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0");
   1043       data.setValue("reference.artifacts", artifactTagger.hasArtifacts() ? "1" : "0");
   1044       data.setValue("docs.packages." + i + ".name", s);
   1045       data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
   1046       data.setValue("docs.packages." + i + ".since", pkg.getSince());
   1047       TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags());
   1048       i++;
   1049     }
   1050 
   1051     sinceTagger.writeVersionNames(data);
   1052     return data;
   1053   }
   1054 
   1055   private static void writeDirectory(File dir, String relative, JSilver js) {
   1056     File[] files = dir.listFiles();
   1057     int i, count = files.length;
   1058     for (i = 0; i < count; i++) {
   1059       File f = files[i];
   1060       if (f.isFile()) {
   1061         String templ = relative + f.getName();
   1062         int len = templ.length();
   1063         if (len > 3 && ".cs".equals(templ.substring(len - 3))) {
   1064           Data data = makePackageHDF();
   1065           String filename = templ.substring(0, len - 3) + htmlExtension;
   1066           ClearPage.write(data, templ, filename, js);
   1067         } else if (len > 3 && ".jd".equals(templ.substring(len - 3))) {
   1068           Data data = makePackageHDF();
   1069           String filename = templ.substring(0, len - 3) + htmlExtension;
   1070           DocFile.writePage(f.getAbsolutePath(), relative, filename, data);
   1071         } else if(!f.getName().equals(".DS_Store")){
   1072               Data data = makeHDF();
   1073               String hdfValue = data.getValue("sac") == null ? "" : data.getValue("sac");
   1074               boolean allowExcepted = hdfValue.equals("true") ? true : false;
   1075               boolean append = false;
   1076               ClearPage.copyFile(allowExcepted, f, templ, append);
   1077         }
   1078       } else if (f.isDirectory()) {
   1079         writeDirectory(f, relative + f.getName() + "/", js);
   1080       }
   1081     }
   1082   }
   1083 
   1084   public static void writeHTMLPages() {
   1085     for (String htmlDir : ClearPage.htmlDirs) {
   1086       File f = new File(htmlDir);
   1087       if (!f.isDirectory()) {
   1088         System.err.println("htmlDir not a directory: " + htmlDir);
   1089         continue;
   1090       }
   1091 
   1092       ResourceLoader loader = new FileSystemResourceLoader(f);
   1093       JSilver js = new JSilver(loader);
   1094       writeDirectory(f, "", js);
   1095     }
   1096   }
   1097 
   1098   /* copy files supplied by the -resourcesdir flag */
   1099   public static void writeResources() {
   1100     if (inputPathResourcesDir != null && !inputPathResourcesDir.isEmpty()) {
   1101       try {
   1102         File f = new File(inputPathResourcesDir);
   1103         if (!f.isDirectory()) {
   1104           System.err.println("resourcesdir is not a directory: " + inputPathResourcesDir);
   1105           return;
   1106         }
   1107 
   1108         ResourceLoader loader = new FileSystemResourceLoader(f);
   1109         JSilver js = new JSilver(loader);
   1110         writeDirectory(f, outputPathResourcesDir, js);
   1111       } catch(Exception e) {
   1112         System.err.println("Could not copy resourcesdir: " + e);
   1113       }
   1114     }
   1115   }
   1116 
   1117   public static void writeAssets() {
   1118     if (!includeAssets) return;
   1119     JarFile thisJar = JarUtils.jarForClass(Doclava.class, null);
   1120     if ((thisJar != null) && (includeDefaultAssets)) {
   1121       try {
   1122         List<String> templateDirs = ClearPage.getBundledTemplateDirs();
   1123         for (String templateDir : templateDirs) {
   1124           String assetsDir = templateDir + "/assets";
   1125           JarUtils.copyResourcesToDirectory(thisJar, assetsDir, ClearPage.outputDir + "/assets");
   1126         }
   1127       } catch (IOException e) {
   1128         System.err.println("Error copying assets directory.");
   1129         e.printStackTrace();
   1130         return;
   1131       }
   1132     }
   1133 
   1134     //write the project-specific assets
   1135     List<String> templateDirs = ClearPage.getTemplateDirs();
   1136     for (String templateDir : templateDirs) {
   1137       File assets = new File(templateDir + "/assets");
   1138       if (assets.isDirectory()) {
   1139         writeDirectory(assets, "assets/", null);
   1140       }
   1141     }
   1142 
   1143     // Create the timestamp.js file based on .cs file
   1144     Data timedata = Doclava.makeHDF();
   1145     ClearPage.write(timedata, "timestamp.cs", "timestamp.js");
   1146   }
   1147 
   1148   /** Go through the docs and generate meta-data about each
   1149       page to use in search suggestions */
   1150   public static void writeLists() {
   1151 
   1152     // Write the lists for API references
   1153     Data data = makeHDF();
   1154 
   1155     Collection<ClassInfo> classes = Converter.rootClasses();
   1156 
   1157     SortedMap<String, Object> sorted = new TreeMap<String, Object>();
   1158     for (ClassInfo cl : classes) {
   1159       if (cl.isHiddenOrRemoved()) {
   1160         continue;
   1161       }
   1162       sorted.put(cl.qualifiedName(), cl);
   1163       PackageInfo pkg = cl.containingPackage();
   1164       String name;
   1165       if (pkg == null) {
   1166         name = "";
   1167       } else {
   1168         name = pkg.name();
   1169       }
   1170       sorted.put(name, pkg);
   1171     }
   1172 
   1173     int i = 0;
   1174     String listDir = javadocDir;
   1175     if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
   1176       if (libraryRoot != null) {
   1177         listDir = listDir + libraryRoot;
   1178       }
   1179     }
   1180     for (String s : sorted.keySet()) {
   1181       data.setValue("docs.pages." + i + ".id", "" + i);
   1182       data.setValue("docs.pages." + i + ".label", s);
   1183 
   1184       Object o = sorted.get(s);
   1185       if (o instanceof PackageInfo) {
   1186         PackageInfo pkg = (PackageInfo) o;
   1187         data.setValue("docs.pages." + i + ".link", pkg.htmlPage());
   1188         data.setValue("docs.pages." + i + ".type", "package");
   1189         data.setValue("docs.pages." + i + ".deprecated", pkg.isDeprecated() ? "true" : "false");
   1190       } else if (o instanceof ClassInfo) {
   1191         ClassInfo cl = (ClassInfo) o;
   1192         data.setValue("docs.pages." + i + ".link", cl.htmlPage());
   1193         data.setValue("docs.pages." + i + ".type", "class");
   1194         data.setValue("docs.pages." + i + ".deprecated", cl.isDeprecated() ? "true" : "false");
   1195       }
   1196       i++;
   1197     }
   1198     ClearPage.write(data, "lists.cs", listDir + "lists.js");
   1199 
   1200 
   1201     // Write the lists for JD documents (if there are HTML directories to process)
   1202     // Skip this for devsite builds
   1203     if ((inputPathHtmlDirs.size() > 0) && (!USE_DEVSITE_LOCALE_OUTPUT_PATHS)) {
   1204       Data jddata = makeHDF();
   1205       Iterator counter = new Iterator();
   1206       for (String htmlDir : inputPathHtmlDirs) {
   1207         File dir = new File(htmlDir);
   1208         if (!dir.isDirectory()) {
   1209           continue;
   1210         }
   1211         writeJdDirList(dir, jddata, counter);
   1212       }
   1213       ClearPage.write(jddata, "jd_lists.cs", javadocDir + "jd_lists.js");
   1214     }
   1215   }
   1216 
   1217   private static class Iterator {
   1218     int i = 0;
   1219   }
   1220 
   1221   /** Write meta-data for a JD file, used for search suggestions */
   1222   private static void writeJdDirList(File dir, Data data, Iterator counter) {
   1223     File[] files = dir.listFiles();
   1224     int i, count = files.length;
   1225     // Loop all files in given directory
   1226     for (i = 0; i < count; i++) {
   1227       File f = files[i];
   1228       if (f.isFile()) {
   1229         String filePath = f.getAbsolutePath();
   1230         String templ = f.getName();
   1231         int len = templ.length();
   1232         // If it's a .jd file we want to process
   1233         if (len > 3 && ".jd".equals(templ.substring(len - 3))) {
   1234           // remove the directories below the site root
   1235           String webPath = filePath.substring(filePath.indexOf("docs/html/") + 10,
   1236               filePath.length());
   1237           // replace .jd with .html
   1238           webPath = webPath.substring(0, webPath.length() - 3) + htmlExtension;
   1239           // Parse the .jd file for properties data at top of page
   1240           Data hdf = Doclava.makeHDF();
   1241           String filedata = DocFile.readFile(filePath);
   1242           Matcher lines = DocFile.LINE.matcher(filedata);
   1243           String line = null;
   1244           // Get each line to add the key-value to hdf
   1245           while (lines.find()) {
   1246             line = lines.group(1);
   1247             if (line.length() > 0) {
   1248               // Stop when we hit the body
   1249               if (line.equals("@jd:body")) {
   1250                 break;
   1251               }
   1252               Matcher prop = DocFile.PROP.matcher(line);
   1253               if (prop.matches()) {
   1254                 String key = prop.group(1);
   1255                 String value = prop.group(2);
   1256                 hdf.setValue(key, value);
   1257               } else {
   1258                 break;
   1259               }
   1260             }
   1261           } // done gathering page properties
   1262 
   1263           // Insert the goods into HDF data (title, link, tags, type)
   1264           String title = hdf.getValue("page.title", "");
   1265           title = title.replaceAll("\"", "'");
   1266           // if there's a <span> in the title, get rid of it
   1267           if (title.indexOf("<span") != -1) {
   1268             String[] splitTitle = title.split("<span(.*?)</span>");
   1269             title = splitTitle[0];
   1270             for (int j = 1; j < splitTitle.length; j++) {
   1271               title.concat(splitTitle[j]);
   1272             }
   1273           }
   1274 
   1275           StringBuilder tags =  new StringBuilder();
   1276           String tagsList = hdf.getValue("page.tags", "");
   1277           if (!tagsList.equals("")) {
   1278             tagsList = tagsList.replaceAll("\"", "");
   1279             String[] tagParts = tagsList.split(",");
   1280             for (int iter = 0; iter < tagParts.length; iter++) {
   1281               tags.append("\"");
   1282               tags.append(tagParts[iter].trim());
   1283               tags.append("\"");
   1284               if (iter < tagParts.length - 1) {
   1285                 tags.append(",");
   1286               }
   1287             }
   1288           }
   1289 
   1290           String dirName = (webPath.indexOf("/") != -1)
   1291                   ? webPath.substring(0, webPath.indexOf("/")) : "";
   1292 
   1293           if (!"".equals(title) &&
   1294               !"intl".equals(dirName) &&
   1295               !hdf.getBooleanValue("excludeFromSuggestions")) {
   1296             data.setValue("docs.pages." + counter.i + ".label", title);
   1297             data.setValue("docs.pages." + counter.i + ".link", webPath);
   1298             data.setValue("docs.pages." + counter.i + ".tags", tags.toString());
   1299             data.setValue("docs.pages." + counter.i + ".type", dirName);
   1300             counter.i++;
   1301           }
   1302         }
   1303       } else if (f.isDirectory()) {
   1304         writeJdDirList(f, data, counter);
   1305       }
   1306     }
   1307   }
   1308 
   1309   public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) {
   1310     if (!notStrippable.add(cl)) {
   1311       // slight optimization: if it already contains cl, it already contains
   1312       // all of cl's parents
   1313       return;
   1314     }
   1315     ClassInfo supr = cl.superclass();
   1316     if (supr != null) {
   1317       cantStripThis(supr, notStrippable);
   1318     }
   1319     for (ClassInfo iface : cl.interfaces()) {
   1320       cantStripThis(iface, notStrippable);
   1321     }
   1322   }
   1323 
   1324   private static String getPrintableName(ClassInfo cl) {
   1325     ClassInfo containingClass = cl.containingClass();
   1326     if (containingClass != null) {
   1327       // This is an inner class.
   1328       String baseName = cl.name();
   1329       baseName = baseName.substring(baseName.lastIndexOf('.') + 1);
   1330       return getPrintableName(containingClass) + '$' + baseName;
   1331     }
   1332     return cl.qualifiedName();
   1333   }
   1334 
   1335   /**
   1336    * Writes the list of classes that must be present in order to provide the non-hidden APIs known
   1337    * to javadoc.
   1338    *
   1339    * @param filename the path to the file to write the list to
   1340    */
   1341   public static void writeKeepList(String filename) {
   1342     HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
   1343     Collection<ClassInfo> all = Converter.allClasses().stream().sorted(ClassInfo.comparator)
   1344         .collect(Collectors.toList());
   1345 
   1346     // If a class is public and not hidden, then it and everything it derives
   1347     // from cannot be stripped. Otherwise we can strip it.
   1348     for (ClassInfo cl : all) {
   1349       if (cl.isPublic() && !cl.isHiddenOrRemoved()) {
   1350         cantStripThis(cl, notStrippable);
   1351       }
   1352     }
   1353     PrintStream stream = null;
   1354     try {
   1355       stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(filename)));
   1356       for (ClassInfo cl : notStrippable) {
   1357         stream.println(getPrintableName(cl));
   1358       }
   1359     } catch (FileNotFoundException e) {
   1360       System.err.println("error writing file: " + filename);
   1361     } finally {
   1362       if (stream != null) {
   1363         stream.close();
   1364       }
   1365     }
   1366   }
   1367 
   1368   private static PackageInfo[] sVisiblePackages = null;
   1369 
   1370   public static PackageInfo[] choosePackages() {
   1371     if (sVisiblePackages != null) {
   1372       return sVisiblePackages;
   1373     }
   1374 
   1375     Collection<ClassInfo> classes = Converter.rootClasses();
   1376     SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
   1377     for (ClassInfo cl : classes) {
   1378       PackageInfo pkg = cl.containingPackage();
   1379       String name;
   1380       if (pkg == null) {
   1381         name = "";
   1382       } else {
   1383         name = pkg.name();
   1384       }
   1385       sorted.put(name, pkg);
   1386     }
   1387 
   1388     ArrayList<PackageInfo> result = new ArrayList<PackageInfo>();
   1389 
   1390     for (String s : sorted.keySet()) {
   1391       PackageInfo pkg = sorted.get(s);
   1392 
   1393       if (pkg.isHiddenOrRemoved()) {
   1394         continue;
   1395       }
   1396 
   1397       boolean allHiddenOrRemoved = true;
   1398       int pass = 0;
   1399       ClassInfo[] classesToCheck = null;
   1400       while (pass < 6) {
   1401         switch (pass) {
   1402           case 0:
   1403             classesToCheck = pkg.ordinaryClasses();
   1404             break;
   1405           case 1:
   1406             classesToCheck = pkg.enums();
   1407             break;
   1408           case 2:
   1409             classesToCheck = pkg.errors();
   1410             break;
   1411           case 3:
   1412             classesToCheck = pkg.exceptions();
   1413             break;
   1414           case 4:
   1415             classesToCheck = pkg.interfaces();
   1416             break;
   1417           case 5:
   1418             classesToCheck = pkg.annotations();
   1419             break;
   1420           default:
   1421             System.err.println("Error reading package: " + pkg.name());
   1422             break;
   1423         }
   1424         for (ClassInfo cl : classesToCheck) {
   1425           if (!cl.isHiddenOrRemoved()) {
   1426             allHiddenOrRemoved = false;
   1427             break;
   1428           }
   1429         }
   1430         if (!allHiddenOrRemoved) {
   1431           break;
   1432         }
   1433         pass++;
   1434       }
   1435       if (allHiddenOrRemoved) {
   1436         continue;
   1437       }
   1438 
   1439       result.add(pkg);
   1440     }
   1441 
   1442     sVisiblePackages = result.toArray(new PackageInfo[result.size()]);
   1443     return sVisiblePackages;
   1444   }
   1445 
   1446   public static void writePackages(String filename) {
   1447     Data data = makePackageHDF();
   1448 
   1449     int i = 0;
   1450     for (PackageInfo pkg : choosePackages()) {
   1451       writePackage(pkg);
   1452 
   1453       data.setValue("docs.packages." + i + ".name", pkg.name());
   1454       data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
   1455       TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags());
   1456 
   1457       i++;
   1458     }
   1459 
   1460     setPageTitle(data, "Package Index");
   1461 
   1462     TagInfo.makeHDF(data, "root.descr", Converter.convertTags(root.inlineTags(), null));
   1463 
   1464     String packageDir = javadocDir;
   1465     if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
   1466       if (libraryRoot != null) {
   1467         packageDir = packageDir + libraryRoot;
   1468       }
   1469     }
   1470     data.setValue("page.not-api", "true");
   1471     ClearPage.write(data, "packages.cs", packageDir + filename);
   1472     ClearPage.write(data, "package-list.cs", packageDir + "package-list");
   1473 
   1474     Proofread.writePackages(filename, Converter.convertTags(root.inlineTags(), null));
   1475   }
   1476 
   1477   public static void writePackage(PackageInfo pkg) {
   1478     // these this and the description are in the same directory,
   1479     // so it's okay
   1480     Data data = makePackageHDF();
   1481 
   1482     String name = pkg.name();
   1483 
   1484     data.setValue("package.name", name);
   1485     data.setValue("package.since", pkg.getSince());
   1486     data.setValue("package.descr", "...description...");
   1487     pkg.setFederatedReferences(data, "package");
   1488 
   1489     makeClassListHDF(data, "package.annotations", ClassInfo.sortByName(pkg.annotations()));
   1490     makeClassListHDF(data, "package.interfaces", ClassInfo.sortByName(pkg.interfaces()));
   1491     makeClassListHDF(data, "package.classes", ClassInfo.sortByName(pkg.ordinaryClasses()));
   1492     makeClassListHDF(data, "package.enums", ClassInfo.sortByName(pkg.enums()));
   1493     makeClassListHDF(data, "package.exceptions", ClassInfo.sortByName(pkg.exceptions()));
   1494     makeClassListHDF(data, "package.errors", ClassInfo.sortByName(pkg.errors()));
   1495     TagInfo.makeHDF(data, "package.shortDescr", pkg.firstSentenceTags());
   1496     TagInfo.makeHDF(data, "package.descr", pkg.inlineTags());
   1497 
   1498     String filename = pkg.htmlPage();
   1499     setPageTitle(data, name);
   1500     ClearPage.write(data, "package.cs", filename);
   1501 
   1502     Proofread.writePackage(filename, pkg.inlineTags());
   1503   }
   1504 
   1505   public static void writeClassLists() {
   1506     int i;
   1507     Data data = makePackageHDF();
   1508 
   1509     ClassInfo[] classes = PackageInfo.filterHiddenAndRemoved(
   1510         Converter.convertClasses(root.classes()));
   1511     if (classes.length == 0) {
   1512       return;
   1513     }
   1514 
   1515     Sorter[] sorted = new Sorter[classes.length];
   1516     for (i = 0; i < sorted.length; i++) {
   1517       ClassInfo cl = classes[i];
   1518       String name = cl.name();
   1519       sorted[i] = new Sorter(name, cl);
   1520     }
   1521 
   1522     Arrays.sort(sorted);
   1523 
   1524     // make a pass and resolve ones that have the same name
   1525     int firstMatch = 0;
   1526     String lastName = sorted[0].label;
   1527     for (i = 1; i < sorted.length; i++) {
   1528       String s = sorted[i].label;
   1529       if (!lastName.equals(s)) {
   1530         if (firstMatch != i - 1) {
   1531           // there were duplicates
   1532           for (int j = firstMatch; j < i; j++) {
   1533             PackageInfo pkg = ((ClassInfo) sorted[j].data).containingPackage();
   1534             if (pkg != null) {
   1535               sorted[j].label = sorted[j].label + " (" + pkg.name() + ")";
   1536             }
   1537           }
   1538         }
   1539         firstMatch = i;
   1540         lastName = s;
   1541       }
   1542     }
   1543 
   1544     // and sort again
   1545     Arrays.sort(sorted);
   1546 
   1547     for (i = 0; i < sorted.length; i++) {
   1548       String s = sorted[i].label;
   1549       ClassInfo cl = (ClassInfo) sorted[i].data;
   1550       char first = Character.toUpperCase(s.charAt(0));
   1551       cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i);
   1552     }
   1553 
   1554     String packageDir = javadocDir;
   1555     if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
   1556       if (libraryRoot != null) {
   1557         packageDir = packageDir + libraryRoot;
   1558       }
   1559     }
   1560 
   1561     data.setValue("page.not-api", "true");
   1562     setPageTitle(data, "Class Index");
   1563     ClearPage.write(data, "classes.cs", packageDir + "classes" + htmlExtension);
   1564 
   1565     // Index page redirects to the classes.html page, so use the same directory
   1566     writeIndex(packageDir);
   1567   }
   1568 
   1569   // we use the word keywords because "index" means something else in html land
   1570   // the user only ever sees the word index
   1571   /*
   1572    * public static void writeKeywords() { ArrayList<KeywordEntry> keywords = new
   1573    * ArrayList<KeywordEntry>();
   1574    *
   1575    * ClassInfo[] classes = PackageInfo.filterHiddenAndRemoved(Converter.convertClasses(root.classes()));
   1576    *
   1577    * for (ClassInfo cl: classes) { cl.makeKeywordEntries(keywords); }
   1578    *
   1579    * HDF data = makeHDF();
   1580    *
   1581    * Collections.sort(keywords);
   1582    *
   1583    * int i=0; for (KeywordEntry entry: keywords) { String base = "keywords." + entry.firstChar() +
   1584    * "." + i; entry.makeHDF(data, base); i++; }
   1585    *
   1586    * setPageTitle(data, "Index"); ClearPage.write(data, "keywords.cs", javadocDir + "keywords" +
   1587    * htmlExtension); }
   1588    */
   1589 
   1590   public static void writeHierarchy() {
   1591     Collection<ClassInfo> classes = Converter.rootClasses();
   1592     ArrayList<ClassInfo> info = new ArrayList<ClassInfo>();
   1593     for (ClassInfo cl : classes) {
   1594       if (!cl.isHiddenOrRemoved()) {
   1595         info.add(cl);
   1596       }
   1597     }
   1598     Data data = makePackageHDF();
   1599     Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()]));
   1600     setPageTitle(data, "Class Hierarchy");
   1601     ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension);
   1602   }
   1603 
   1604   public static void writeClasses() {
   1605     Collection<ClassInfo> classes = Converter.rootClasses();
   1606 
   1607     for (ClassInfo cl : classes) {
   1608       Data data = makePackageHDF();
   1609       if (!cl.isHiddenOrRemoved()) {
   1610         writeClass(cl, data);
   1611       }
   1612     }
   1613   }
   1614 
   1615   public static void writeClass(ClassInfo cl, Data data) {
   1616     cl.makeHDF(data);
   1617     setPageTitle(data, cl.name());
   1618     String outfile = cl.htmlPage();
   1619     ClearPage.write(data, "class.cs", outfile);
   1620     Proofread.writeClass(cl.htmlPage(), cl);
   1621   }
   1622 
   1623   public static void makeClassListHDF(Data data, String base, ClassInfo[] classes) {
   1624     for (int i = 0; i < classes.length; i++) {
   1625       ClassInfo cl = classes[i];
   1626       if (!cl.isHiddenOrRemoved()) {
   1627         cl.makeShortDescrHDF(data, base + "." + i);
   1628       }
   1629     }
   1630   }
   1631 
   1632   public static String linkTarget(String source, String target) {
   1633     String[] src = source.split("/");
   1634     String[] tgt = target.split("/");
   1635 
   1636     int srclen = src.length;
   1637     int tgtlen = tgt.length;
   1638 
   1639     int same = 0;
   1640     while (same < (srclen - 1) && same < (tgtlen - 1) && (src[same].equals(tgt[same]))) {
   1641       same++;
   1642     }
   1643 
   1644     String s = "";
   1645 
   1646     int up = srclen - same - 1;
   1647     for (int i = 0; i < up; i++) {
   1648       s += "../";
   1649     }
   1650 
   1651 
   1652     int N = tgtlen - 1;
   1653     for (int i = same; i < N; i++) {
   1654       s += tgt[i] + '/';
   1655     }
   1656     s += tgt[tgtlen - 1];
   1657 
   1658     return s;
   1659   }
   1660 
   1661   /**
   1662    * Returns true if the given element has an @hide, @removed or @pending annotation.
   1663    */
   1664   private static boolean hasHideOrRemovedAnnotation(Doc doc) {
   1665     String comment = doc.getRawCommentText();
   1666     return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -1 ||
   1667         comment.indexOf("@removed") != -1;
   1668   }
   1669 
   1670   /**
   1671    * Returns true if the given element is hidden.
   1672    */
   1673   private static boolean isHiddenOrRemoved(Doc doc) {
   1674     // Methods, fields, constructors.
   1675     if (doc instanceof MemberDoc) {
   1676       return hasHideOrRemovedAnnotation(doc);
   1677     }
   1678 
   1679     // Classes, interfaces, enums, annotation types.
   1680     if (doc instanceof ClassDoc) {
   1681       ClassDoc classDoc = (ClassDoc) doc;
   1682 
   1683       // Check the containing package.
   1684       if (hasHideOrRemovedAnnotation(classDoc.containingPackage())) {
   1685         return true;
   1686       }
   1687 
   1688       // Check the class doc and containing class docs if this is a
   1689       // nested class.
   1690       ClassDoc current = classDoc;
   1691       do {
   1692         if (hasHideOrRemovedAnnotation(current)) {
   1693           return true;
   1694         }
   1695 
   1696         current = current.containingClass();
   1697       } while (current != null);
   1698     }
   1699 
   1700     return false;
   1701   }
   1702 
   1703   /**
   1704    * Filters out hidden and removed elements.
   1705    */
   1706   private static Object filterHiddenAndRemoved(Object o, Class<?> expected) {
   1707     if (o == null) {
   1708       return null;
   1709     }
   1710 
   1711     Class type = o.getClass();
   1712     if (type.getName().startsWith("com.sun.")) {
   1713       // TODO: Implement interfaces from superclasses, too.
   1714       return Proxy
   1715           .newProxyInstance(type.getClassLoader(), type.getInterfaces(), new HideHandler(o));
   1716     } else if (o instanceof Object[]) {
   1717       Class<?> componentType = expected.getComponentType();
   1718       Object[] array = (Object[]) o;
   1719       List<Object> list = new ArrayList<Object>(array.length);
   1720       for (Object entry : array) {
   1721         if ((entry instanceof Doc) && isHiddenOrRemoved((Doc) entry)) {
   1722           continue;
   1723         }
   1724         list.add(filterHiddenAndRemoved(entry, componentType));
   1725       }
   1726       return list.toArray((Object[]) Array.newInstance(componentType, list.size()));
   1727     } else {
   1728       return o;
   1729     }
   1730   }
   1731 
   1732   /**
   1733    * Filters hidden elements out of method return values.
   1734    */
   1735   private static class HideHandler implements InvocationHandler {
   1736 
   1737     private final Object target;
   1738 
   1739     public HideHandler(Object target) {
   1740       this.target = target;
   1741     }
   1742 
   1743     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   1744       String methodName = method.getName();
   1745       if (args != null) {
   1746         if (methodName.equals("compareTo") || methodName.equals("equals")
   1747             || methodName.equals("overrides") || methodName.equals("subclassOf")) {
   1748           args[0] = unwrap(args[0]);
   1749         }
   1750       }
   1751 
   1752       if (methodName.equals("getRawCommentText")) {
   1753         return filterComment((String) method.invoke(target, args));
   1754       }
   1755 
   1756       // escape "&" in disjunctive types.
   1757       if (proxy instanceof Type && methodName.equals("toString")) {
   1758         return ((String) method.invoke(target, args)).replace("&", "&amp;");
   1759       }
   1760 
   1761       try {
   1762         return filterHiddenAndRemoved(method.invoke(target, args), method.getReturnType());
   1763       } catch (InvocationTargetException e) {
   1764         throw e.getTargetException();
   1765       }
   1766     }
   1767 
   1768     private String filterComment(String s) {
   1769       if (s == null) {
   1770         return null;
   1771       }
   1772 
   1773       s = s.trim();
   1774 
   1775       // Work around off by one error
   1776       while (s.length() >= 5 && s.charAt(s.length() - 5) == '{') {
   1777         s += "&nbsp;";
   1778       }
   1779 
   1780       return s;
   1781     }
   1782 
   1783     private static Object unwrap(Object proxy) {
   1784       if (proxy instanceof Proxy) return ((HideHandler) Proxy.getInvocationHandler(proxy)).target;
   1785       return proxy;
   1786     }
   1787   }
   1788 
   1789   /**
   1790    * Collect the values used by the Dev tools and write them in files packaged with the SDK
   1791    *
   1792    * @param output the ouput directory for the files.
   1793    */
   1794   private static void writeSdkValues(String output) {
   1795     ArrayList<String> activityActions = new ArrayList<String>();
   1796     ArrayList<String> broadcastActions = new ArrayList<String>();
   1797     ArrayList<String> serviceActions = new ArrayList<String>();
   1798     ArrayList<String> categories = new ArrayList<String>();
   1799     ArrayList<String> features = new ArrayList<String>();
   1800 
   1801     ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>();
   1802     ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>();
   1803     ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>();
   1804 
   1805     Collection<ClassInfo> classes = Converter.allClasses();
   1806 
   1807     // The topmost LayoutParams class - android.view.ViewGroup.LayoutParams
   1808     ClassInfo topLayoutParams = null;
   1809 
   1810     // Go through all the fields of all the classes, looking SDK stuff.
   1811     for (ClassInfo clazz : classes) {
   1812 
   1813       // first check constant fields for the SdkConstant annotation.
   1814       ArrayList<FieldInfo> fields = clazz.allSelfFields();
   1815       for (FieldInfo field : fields) {
   1816         Object cValue = field.constantValue();
   1817         if (cValue != null) {
   1818             ArrayList<AnnotationInstanceInfo> annotations = field.annotations();
   1819           if (!annotations.isEmpty()) {
   1820             for (AnnotationInstanceInfo annotation : annotations) {
   1821               if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) {
   1822                 if (!annotation.elementValues().isEmpty()) {
   1823                   String type = annotation.elementValues().get(0).valueString();
   1824                   if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) {
   1825                     activityActions.add(cValue.toString());
   1826                   } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) {
   1827                     broadcastActions.add(cValue.toString());
   1828                   } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) {
   1829                     serviceActions.add(cValue.toString());
   1830                   } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) {
   1831                     categories.add(cValue.toString());
   1832                   } else if (SDK_CONSTANT_TYPE_FEATURE.equals(type)) {
   1833                     features.add(cValue.toString());
   1834                   }
   1835                 }
   1836                 break;
   1837               }
   1838             }
   1839           }
   1840         }
   1841       }
   1842 
   1843       // Now check the class for @Widget or if its in the android.widget package
   1844       // (unless the class is hidden or abstract, or non public)
   1845       if (clazz.isHiddenOrRemoved() == false && clazz.isPublic() && clazz.isAbstract() == false) {
   1846         boolean annotated = false;
   1847         ArrayList<AnnotationInstanceInfo> annotations = clazz.annotations();
   1848         if (!annotations.isEmpty()) {
   1849           for (AnnotationInstanceInfo annotation : annotations) {
   1850             if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) {
   1851               widgets.add(clazz);
   1852               annotated = true;
   1853               break;
   1854             } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) {
   1855               layouts.add(clazz);
   1856               annotated = true;
   1857               break;
   1858             }
   1859           }
   1860         }
   1861 
   1862         if (annotated == false) {
   1863           if (topLayoutParams == null
   1864               && "android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
   1865             topLayoutParams = clazz;
   1866           }
   1867           // let's check if this is inside android.widget or android.view
   1868           if (isIncludedPackage(clazz)) {
   1869             // now we check what this class inherits either from android.view.ViewGroup
   1870             // or android.view.View, or android.view.ViewGroup.LayoutParams
   1871             int type = checkInheritance(clazz);
   1872             switch (type) {
   1873               case TYPE_WIDGET:
   1874                 widgets.add(clazz);
   1875                 break;
   1876               case TYPE_LAYOUT:
   1877                 layouts.add(clazz);
   1878                 break;
   1879               case TYPE_LAYOUT_PARAM:
   1880                 layoutParams.add(clazz);
   1881                 break;
   1882             }
   1883           }
   1884         }
   1885       }
   1886     }
   1887 
   1888     // now write the files, whether or not the list are empty.
   1889     // the SDK built requires those files to be present.
   1890 
   1891     Collections.sort(activityActions);
   1892     writeValues(output + "/activity_actions.txt", activityActions);
   1893 
   1894     Collections.sort(broadcastActions);
   1895     writeValues(output + "/broadcast_actions.txt", broadcastActions);
   1896 
   1897     Collections.sort(serviceActions);
   1898     writeValues(output + "/service_actions.txt", serviceActions);
   1899 
   1900     Collections.sort(categories);
   1901     writeValues(output + "/categories.txt", categories);
   1902 
   1903     Collections.sort(features);
   1904     writeValues(output + "/features.txt", features);
   1905 
   1906     // before writing the list of classes, we do some checks, to make sure the layout params
   1907     // are enclosed by a layout class (and not one that has been declared as a widget)
   1908     for (int i = 0; i < layoutParams.size();) {
   1909       ClassInfo clazz = layoutParams.get(i);
   1910       ClassInfo containingClass = clazz.containingClass();
   1911       boolean remove = containingClass == null || layouts.indexOf(containingClass) == -1;
   1912       // Also ensure that super classes of the layout params are in android.widget or android.view.
   1913       while (!remove && (clazz = clazz.superclass()) != null && !clazz.equals(topLayoutParams)) {
   1914         remove = !isIncludedPackage(clazz);
   1915       }
   1916       if (remove) {
   1917         layoutParams.remove(i);
   1918       } else {
   1919         i++;
   1920       }
   1921     }
   1922 
   1923     writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams);
   1924   }
   1925 
   1926   /**
   1927    * Check if the clazz is in package android.view or android.widget
   1928    */
   1929   private static boolean isIncludedPackage(ClassInfo clazz) {
   1930     String pckg = clazz.containingPackage().name();
   1931     return "android.widget".equals(pckg) || "android.view".equals(pckg);
   1932   }
   1933 
   1934   /**
   1935    * Writes a list of values into a text files.
   1936    *
   1937    * @param pathname the absolute os path of the output file.
   1938    * @param values the list of values to write.
   1939    */
   1940   private static void writeValues(String pathname, ArrayList<String> values) {
   1941     FileWriter fw = null;
   1942     BufferedWriter bw = null;
   1943     try {
   1944       fw = new FileWriter(pathname, false);
   1945       bw = new BufferedWriter(fw);
   1946 
   1947       for (String value : values) {
   1948         bw.append(value).append('\n');
   1949       }
   1950     } catch (IOException e) {
   1951       // pass for now
   1952     } finally {
   1953       try {
   1954         if (bw != null) bw.close();
   1955       } catch (IOException e) {
   1956         // pass for now
   1957       }
   1958       try {
   1959         if (fw != null) fw.close();
   1960       } catch (IOException e) {
   1961         // pass for now
   1962       }
   1963     }
   1964   }
   1965 
   1966   /**
   1967    * Writes the widget/layout/layout param classes into a text files.
   1968    *
   1969    * @param pathname the absolute os path of the output file.
   1970    * @param widgets the list of widget classes to write.
   1971    * @param layouts the list of layout classes to write.
   1972    * @param layoutParams the list of layout param classes to write.
   1973    */
   1974   private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets,
   1975       ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) {
   1976     FileWriter fw = null;
   1977     BufferedWriter bw = null;
   1978     try {
   1979       fw = new FileWriter(pathname, false);
   1980       bw = new BufferedWriter(fw);
   1981 
   1982       // write the 3 types of classes.
   1983       for (ClassInfo clazz : widgets) {
   1984         writeClass(bw, clazz, 'W');
   1985       }
   1986       for (ClassInfo clazz : layoutParams) {
   1987         writeClass(bw, clazz, 'P');
   1988       }
   1989       for (ClassInfo clazz : layouts) {
   1990         writeClass(bw, clazz, 'L');
   1991       }
   1992     } catch (IOException e) {
   1993       // pass for now
   1994     } finally {
   1995       try {
   1996         if (bw != null) bw.close();
   1997       } catch (IOException e) {
   1998         // pass for now
   1999       }
   2000       try {
   2001         if (fw != null) fw.close();
   2002       } catch (IOException e) {
   2003         // pass for now
   2004       }
   2005     }
   2006   }
   2007 
   2008   /**
   2009    * Writes a class name and its super class names into a {@link BufferedWriter}.
   2010    *
   2011    * @param writer the BufferedWriter to write into
   2012    * @param clazz the class to write
   2013    * @param prefix the prefix to put at the beginning of the line.
   2014    * @throws IOException
   2015    */
   2016   private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)
   2017       throws IOException {
   2018     writer.append(prefix).append(clazz.qualifiedName());
   2019     ClassInfo superClass = clazz;
   2020     while ((superClass = superClass.superclass()) != null) {
   2021       writer.append(' ').append(superClass.qualifiedName());
   2022     }
   2023     writer.append('\n');
   2024   }
   2025 
   2026   /**
   2027    * Checks the inheritance of {@link ClassInfo} objects. This method return
   2028    * <ul>
   2029    * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li>
   2030    * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li>
   2031    * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends
   2032    * <code>android.view.ViewGroup$LayoutParams</code></li>
   2033    * <li>{@link #TYPE_NONE}: in all other cases</li>
   2034    * </ul>
   2035    *
   2036    * @param clazz the {@link ClassInfo} to check.
   2037    */
   2038   private static int checkInheritance(ClassInfo clazz) {
   2039     if ("android.view.ViewGroup".equals(clazz.qualifiedName())) {
   2040       return TYPE_LAYOUT;
   2041     } else if ("android.view.View".equals(clazz.qualifiedName())) {
   2042       return TYPE_WIDGET;
   2043     } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
   2044       return TYPE_LAYOUT_PARAM;
   2045     }
   2046 
   2047     ClassInfo parent = clazz.superclass();
   2048     if (parent != null) {
   2049       return checkInheritance(parent);
   2050     }
   2051 
   2052     return TYPE_NONE;
   2053   }
   2054 
   2055   /**
   2056    * Ensures a trailing '/' at the end of a string.
   2057    */
   2058   static String ensureSlash(String path) {
   2059     return path.endsWith("/") ? path : path + "/";
   2060   }
   2061 
   2062   /**
   2063   * Process sample projects. Generate the TOC for the samples groups and project
   2064   * and write it to a cs var, which is then written to files during templating to
   2065   * html output. Collect metadata from sample project _index.jd files. Copy html
   2066   * and specific source file types to the output directory.
   2067   */
   2068   public static void writeSamples(boolean offlineMode, ArrayList<SampleCode> sampleCodes,
   2069       boolean sortNavByGroups) {
   2070     samplesNavTree = makeHDF();
   2071 
   2072     // Go through samples processing files. Create a root list for SC nodes,
   2073     // pass it to SCs for their NavTree children and append them.
   2074     List<SampleCode.Node> samplesList = new ArrayList<SampleCode.Node>();
   2075     List<SampleCode.Node> sampleGroupsRootNodes = null;
   2076     for (SampleCode sc : sampleCodes) {
   2077       samplesList.add(sc.setSamplesTOC(offlineMode));
   2078      }
   2079     if (sortNavByGroups) {
   2080       sampleGroupsRootNodes = new ArrayList<SampleCode.Node>();
   2081       for (SampleCode gsc : sampleCodeGroups) {
   2082         String link =  ClearPage.toroot + "samples/" + gsc.mTitle.replaceAll(" ", "").trim().toLowerCase() + ".html";
   2083         sampleGroupsRootNodes.add(new SampleCode.Node.Builder().setLabel(gsc.mTitle).setLink(link).setType("groupholder").build());
   2084       }
   2085     }
   2086     // Pass full samplesList to SC to render the samples TOC to sampleNavTree hdf
   2087     if (!offlineMode) {
   2088       SampleCode.writeSamplesNavTree(samplesList, sampleGroupsRootNodes);
   2089     }
   2090     // Iterate the samplecode projects writing the files to out
   2091     for (SampleCode sc : sampleCodes) {
   2092       sc.writeSamplesFiles(offlineMode);
   2093     }
   2094   }
   2095 
   2096   /**
   2097   * Given an initial samples directory root, walk through the directory collecting
   2098   * sample code project roots and adding them to an array of SampleCodes.
   2099   * @param rootDir Root directory holding all browseable sample code projects,
   2100   *        defined in frameworks/base/Android.mk as "-sampleDir path".
   2101   */
   2102   public static void getSampleProjects(File rootDir) {
   2103     for (File f : rootDir.listFiles()) {
   2104       String name = f.getName();
   2105       if (f.isDirectory()) {
   2106         if (isValidSampleProjectRoot(f)) {
   2107           sampleCodes.add(new SampleCode(f.getAbsolutePath(), "samples/" + name, name));
   2108         } else {
   2109           getSampleProjects(f);
   2110         }
   2111       }
   2112     }
   2113   }
   2114 
   2115   /**
   2116   * Test whether a given directory is the root directory for a sample code project.
   2117   * Root directories must contain a valid _index.jd file and a src/ directory
   2118   * or a module directory that contains a src/ directory.
   2119   */
   2120   public static boolean isValidSampleProjectRoot(File dir) {
   2121     File indexJd = new File(dir, "_index.jd");
   2122     if (!indexJd.exists()) {
   2123       return false;
   2124     }
   2125     File srcDir = new File(dir, "src");
   2126     if (srcDir.exists()) {
   2127       return true;
   2128     } else {
   2129       // Look for a src/ directory one level below the root directory, so
   2130       // modules are supported.
   2131       for (File childDir : dir.listFiles()) {
   2132         if (childDir.isDirectory()) {
   2133           srcDir = new File(childDir, "src");
   2134           if (srcDir.exists()) {
   2135             return true;
   2136           }
   2137         }
   2138       }
   2139       return false;
   2140     }
   2141   }
   2142 
   2143   public static String getDocumentationStringForAnnotation(String annotationName) {
   2144     if (!documentAnnotations) return null;
   2145     if (annotationDocumentationMap == null) {
   2146       // parse the file for map
   2147       annotationDocumentationMap = new HashMap<String, String>();
   2148       try {
   2149         BufferedReader in = new BufferedReader(
   2150             new FileReader(documentAnnotationsPath));
   2151         try {
   2152           String line = in.readLine();
   2153           String[] split;
   2154           while (line != null) {
   2155             split = line.split(":");
   2156             annotationDocumentationMap.put(split[0], split[1]);
   2157             line = in.readLine();
   2158           }
   2159         } finally {
   2160           in.close();
   2161         }
   2162       } catch (IOException e) {
   2163         System.err.println("Unable to open annotations documentation file for reading: "
   2164             + documentAnnotationsPath);
   2165       }
   2166     }
   2167     return annotationDocumentationMap.get(annotationName);
   2168   }
   2169 
   2170 }
   2171