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