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