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