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