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