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