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