OSDN Git Service

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