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