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