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