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