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