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