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