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