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