• 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.util.regex.Matcher;
31 import java.io.*;
32 import java.lang.reflect.Proxy;
33 import java.lang.reflect.Array;
34 import java.lang.reflect.InvocationHandler;
35 import java.lang.reflect.InvocationTargetException;
36 import java.lang.reflect.Method;
37 import java.net.MalformedURLException;
38 import java.net.URL;
39 
40 public 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 
checkLevel(int level)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    */
parseComments()104   public static boolean parseComments() {
105     return generateDocs || parseComments;
106   }
107 
checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv, boolean hidden)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 
main(String[] args)129   public static void main(String[] args) {
130     com.sun.tools.javadoc.Main.execute(args);
131   }
132 
start(RootDoc r)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 
writeIndex()383   private static void writeIndex() {
384     Data data = makeHDF();
385     ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension);
386   }
387 
readTemplateSettings()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 
readKnownTagsFiles(HashSet<String> knownTags, ArrayList<String> knownTagsFiles)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 
escape(String s)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 
setPageTitle(Data data, String title)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 
languageVersion()500   public static LanguageVersion languageVersion() {
501     return LanguageVersion.JAVA_1_5;
502   }
503 
504 
optionLength(String option)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   }
validOptions(String[][] options, DocErrorReporter r)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 
makeHDF()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 
makePackageHDF()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 
writeDirectory(File dir, String relative, JSilver js)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 
writeHTMLPages()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 
writeAssets()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 */
writeLists()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 */
writeJdDirList(File dir, Data data, Iterator counter)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 
cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable)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 
getPrintableName(ClassInfo cl)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    */
writeKeepList(String filename)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 
choosePackages()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 
writePackages(String filename)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 
writePackage(PackageInfo pkg)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 
writeClassLists()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 
writeHierarchy()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 
writeClasses()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 
writeClass(ClassInfo cl, Data data)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 
makeClassListHDF(Data data, String base, ClassInfo[] classes)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 
linkTarget(String source, String target)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    */
hasHideAnnotation(Doc doc)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    */
isHidden(Doc doc)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    */
filterHidden(Object o, Class<?> expected)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 
HideHandler(Object target)1344     public HideHandler(Object target) {
1345       this.target = target;
1346     }
1347 
invoke(Object proxy, Method method, Object[] args)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 
filterComment(String s)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 
unwrap(Object proxy)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    */
writeSdkValues(String output)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    */
writeValues(String pathname, ArrayList<String> values)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    */
writeClasses(String pathname, ArrayList<ClassInfo> widgets, ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams)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    */
writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)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    */
checkInheritance(ClassInfo clazz)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    */
ensureSlash(String path)1645   static String ensureSlash(String path) {
1646     return path.endsWith("/") ? path : path + "/";
1647   }
1648 }
1649