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