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