blob: a8b9c73092e55bc75d94194c6ad6eaa36472ae57 [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());
Scott Maindf094242009-07-27 09:47:11 -0700515 data.setValue("docs.packages." + i + ".since", pkg.getSince());
The Android Open Source Project88b60792009-03-03 19:28:42 -0800516 TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr",
517 pkg.firstSentenceTags());
518 i++;
519 }
520
521 return data;
522 }
523
524 public static void writeDirectory(File dir, String relative)
525 {
526 File[] files = dir.listFiles();
527 int i, count = files.length;
528 for (i=0; i<count; i++) {
529 File f = files[i];
530 if (f.isFile()) {
531 String templ = relative + f.getName();
532 int len = templ.length();
533 if (len > 3 && ".cs".equals(templ.substring(len-3))) {
534 HDF data = makeHDF();
535 String filename = templ.substring(0,len-3) + htmlExtension;
536 ClearPage.write(data, templ, filename);
537 }
538 else if (len > 3 && ".jd".equals(templ.substring(len-3))) {
539 String filename = templ.substring(0,len-3) + htmlExtension;
540 DocFile.writePage(f.getAbsolutePath(), relative, filename);
541 }
542 else {
543 ClearPage.copyFile(f, templ);
544 }
545 }
546 else if (f.isDirectory()) {
547 writeDirectory(f, relative + f.getName() + "/");
548 }
549 }
550 }
551
552 public static void writeHTMLPages()
553 {
554 File f = new File(ClearPage.htmlDir);
555 if (!f.isDirectory()) {
556 System.err.println("htmlDir not a directory: " + ClearPage.htmlDir);
557 }
558 writeDirectory(f, "");
559 }
560
561 public static void writeLists()
562 {
563 HDF data = makeHDF();
564
565 ClassInfo[] classes = Converter.rootClasses();
566
567 SortedMap<String, Object> sorted = new TreeMap<String, Object>();
568 for (ClassInfo cl: classes) {
569 if (cl.isHidden()) {
570 continue;
571 }
572 sorted.put(cl.qualifiedName(), cl);
573 PackageInfo pkg = cl.containingPackage();
574 String name;
575 if (pkg == null) {
576 name = "";
577 } else {
578 name = pkg.name();
579 }
580 sorted.put(name, pkg);
581 }
582
583 int i = 0;
584 for (String s: sorted.keySet()) {
585 data.setValue("docs.pages." + i + ".id" , ""+i);
586 data.setValue("docs.pages." + i + ".label" , s);
587
588 Object o = sorted.get(s);
589 if (o instanceof PackageInfo) {
590 PackageInfo pkg = (PackageInfo)o;
591 data.setValue("docs.pages." + i + ".link" , pkg.htmlPage());
592 data.setValue("docs.pages." + i + ".type" , "package");
593 }
594 else if (o instanceof ClassInfo) {
595 ClassInfo cl = (ClassInfo)o;
596 data.setValue("docs.pages." + i + ".link" , cl.htmlPage());
597 data.setValue("docs.pages." + i + ".type" , "class");
598 }
599 i++;
600 }
601
602 ClearPage.write(data, "lists.cs", javadocDir + "lists.js");
603 }
604
605 public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) {
606 if (!notStrippable.add(cl)) {
607 // slight optimization: if it already contains cl, it already contains
608 // all of cl's parents
609 return;
610 }
611 ClassInfo supr = cl.superclass();
612 if (supr != null) {
613 cantStripThis(supr, notStrippable);
614 }
615 for (ClassInfo iface: cl.interfaces()) {
616 cantStripThis(iface, notStrippable);
617 }
618 }
619
620 private static String getPrintableName(ClassInfo cl) {
621 ClassInfo containingClass = cl.containingClass();
622 if (containingClass != null) {
623 // This is an inner class.
624 String baseName = cl.name();
625 baseName = baseName.substring(baseName.lastIndexOf('.') + 1);
626 return getPrintableName(containingClass) + '$' + baseName;
627 }
628 return cl.qualifiedName();
629 }
630
631 /**
632 * Writes the list of classes that must be present in order to
633 * provide the non-hidden APIs known to javadoc.
634 *
635 * @param filename the path to the file to write the list to
636 */
637 public static void writeKeepList(String filename) {
638 HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
639 ClassInfo[] all = Converter.allClasses();
640 Arrays.sort(all); // just to make the file a little more readable
641
642 // If a class is public and not hidden, then it and everything it derives
643 // from cannot be stripped. Otherwise we can strip it.
644 for (ClassInfo cl: all) {
645 if (cl.isPublic() && !cl.isHidden()) {
646 cantStripThis(cl, notStrippable);
647 }
648 }
649 PrintStream stream = null;
650 try {
651 stream = new PrintStream(filename);
652 for (ClassInfo cl: notStrippable) {
653 stream.println(getPrintableName(cl));
654 }
655 }
656 catch (FileNotFoundException e) {
657 System.err.println("error writing file: " + filename);
658 }
659 finally {
660 if (stream != null) {
661 stream.close();
662 }
663 }
664 }
665
666 private static PackageInfo[] sVisiblePackages = null;
667 public static PackageInfo[] choosePackages() {
668 if (sVisiblePackages != null) {
669 return sVisiblePackages;
670 }
671
672 ClassInfo[] classes = Converter.rootClasses();
673 SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
674 for (ClassInfo cl: classes) {
675 PackageInfo pkg = cl.containingPackage();
676 String name;
677 if (pkg == null) {
678 name = "";
679 } else {
680 name = pkg.name();
681 }
682 sorted.put(name, pkg);
683 }
684
685 ArrayList<PackageInfo> result = new ArrayList();
686
687 for (String s: sorted.keySet()) {
688 PackageInfo pkg = sorted.get(s);
689
690 if (pkg.isHidden()) {
691 continue;
692 }
693 Boolean allHidden = true;
694 int pass = 0;
695 ClassInfo[] classesToCheck = null;
696 while (pass < 5 ) {
697 switch(pass) {
698 case 0:
699 classesToCheck = pkg.ordinaryClasses();
700 break;
701 case 1:
702 classesToCheck = pkg.enums();
703 break;
704 case 2:
705 classesToCheck = pkg.errors();
706 break;
707 case 3:
708 classesToCheck = pkg.exceptions();
709 break;
710 case 4:
711 classesToCheck = pkg.interfaces();
712 break;
713 default:
714 System.err.println("Error reading package: " + pkg.name());
715 break;
716 }
717 for (ClassInfo cl : classesToCheck) {
718 if (!cl.isHidden()) {
719 allHidden = false;
720 break;
721 }
722 }
723 if (!allHidden) {
724 break;
725 }
726 pass++;
727 }
728 if (allHidden) {
729 continue;
730 }
731
732 result.add(pkg);
733 }
734
735 sVisiblePackages = result.toArray(new PackageInfo[result.size()]);
736 return sVisiblePackages;
737 }
738
739 public static void writePackages(String filename)
740 {
741 HDF data = makePackageHDF();
742
743 int i = 0;
744 for (PackageInfo pkg: choosePackages()) {
745 writePackage(pkg);
746
747 data.setValue("docs.packages." + i + ".name", pkg.name());
748 data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
749 TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr",
750 pkg.firstSentenceTags());
751
752 i++;
753 }
754
755 setPageTitle(data, "Package Index");
756
757 TagInfo.makeHDF(data, "root.descr",
758 Converter.convertTags(root.inlineTags(), null));
759
760 ClearPage.write(data, "packages.cs", filename);
761 ClearPage.write(data, "package-list.cs", javadocDir + "package-list");
762
763 Proofread.writePackages(filename,
764 Converter.convertTags(root.inlineTags(), null));
765 }
766
767 public static void writePackage(PackageInfo pkg)
768 {
769 // these this and the description are in the same directory,
770 // so it's okay
771 HDF data = makePackageHDF();
772
773 String name = pkg.name();
774
775 data.setValue("package.name", name);
Jesse Wilson289d80e2009-07-21 14:02:14 -0700776 data.setValue("package.since", pkg.getSince());
The Android Open Source Project88b60792009-03-03 19:28:42 -0800777 data.setValue("package.descr", "...description...");
778
779 makeClassListHDF(data, "package.interfaces",
780 ClassInfo.sortByName(pkg.interfaces()));
781 makeClassListHDF(data, "package.classes",
782 ClassInfo.sortByName(pkg.ordinaryClasses()));
783 makeClassListHDF(data, "package.enums",
784 ClassInfo.sortByName(pkg.enums()));
785 makeClassListHDF(data, "package.exceptions",
786 ClassInfo.sortByName(pkg.exceptions()));
787 makeClassListHDF(data, "package.errors",
788 ClassInfo.sortByName(pkg.errors()));
789 TagInfo.makeHDF(data, "package.shortDescr",
790 pkg.firstSentenceTags());
791 TagInfo.makeHDF(data, "package.descr", pkg.inlineTags());
792
793 String filename = pkg.htmlPage();
794 setPageTitle(data, name);
795 ClearPage.write(data, "package.cs", filename);
796
797 filename = pkg.fullDescriptionHtmlPage();
798 setPageTitle(data, name + " Details");
799 ClearPage.write(data, "package-descr.cs", filename);
800
801 Proofread.writePackage(filename, pkg.inlineTags());
802 }
803
804 public static void writeClassLists()
805 {
806 int i;
807 HDF data = makePackageHDF();
808
809 ClassInfo[] classes = PackageInfo.filterHidden(
810 Converter.convertClasses(root.classes()));
811 if (classes.length == 0) {
812 return ;
813 }
814
815 Sorter[] sorted = new Sorter[classes.length];
816 for (i=0; i<sorted.length; i++) {
817 ClassInfo cl = classes[i];
818 String name = cl.name();
819 sorted[i] = new Sorter(name, cl);
820 }
821
822 Arrays.sort(sorted);
823
824 // make a pass and resolve ones that have the same name
825 int firstMatch = 0;
826 String lastName = sorted[0].label;
827 for (i=1; i<sorted.length; i++) {
828 String s = sorted[i].label;
829 if (!lastName.equals(s)) {
830 if (firstMatch != i-1) {
831 // there were duplicates
832 for (int j=firstMatch; j<i; j++) {
833 PackageInfo pkg = ((ClassInfo)sorted[j].data).containingPackage();
834 if (pkg != null) {
835 sorted[j].label = sorted[j].label + " (" + pkg.name() + ")";
836 }
837 }
838 }
839 firstMatch = i;
840 lastName = s;
841 }
842 }
843
844 // and sort again
845 Arrays.sort(sorted);
846
847 for (i=0; i<sorted.length; i++) {
848 String s = sorted[i].label;
849 ClassInfo cl = (ClassInfo)sorted[i].data;
850 char first = Character.toUpperCase(s.charAt(0));
851 cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i);
852 }
853
854 setPageTitle(data, "Class Index");
855 ClearPage.write(data, "classes.cs", javadocDir + "classes" + htmlExtension);
856 }
857
858 // we use the word keywords because "index" means something else in html land
859 // the user only ever sees the word index
860/* public static void writeKeywords()
861 {
862 ArrayList<KeywordEntry> keywords = new ArrayList<KeywordEntry>();
863
864 ClassInfo[] classes = PackageInfo.filterHidden(Converter.convertClasses(root.classes()));
865
866 for (ClassInfo cl: classes) {
867 cl.makeKeywordEntries(keywords);
868 }
869
870 HDF data = makeHDF();
871
872 Collections.sort(keywords);
873
874 int i=0;
875 for (KeywordEntry entry: keywords) {
876 String base = "keywords." + entry.firstChar() + "." + i;
877 entry.makeHDF(data, base);
878 i++;
879 }
880
881 setPageTitle(data, "Index");
882 ClearPage.write(data, "keywords.cs", javadocDir + "keywords" + htmlExtension);
883 } */
884
885 public static void writeHierarchy()
886 {
887 ClassInfo[] classes = Converter.rootClasses();
888 ArrayList<ClassInfo> info = new ArrayList<ClassInfo>();
889 for (ClassInfo cl: classes) {
890 if (!cl.isHidden()) {
891 info.add(cl);
892 }
893 }
894 HDF data = makePackageHDF();
895 Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()]));
896 setPageTitle(data, "Class Hierarchy");
897 ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension);
898 }
899
900 public static void writeClasses()
901 {
902 ClassInfo[] classes = Converter.rootClasses();
903
904 for (ClassInfo cl: classes) {
905 HDF data = makePackageHDF();
906 if (!cl.isHidden()) {
907 writeClass(cl, data);
908 }
909 }
910 }
911
912 public static void writeClass(ClassInfo cl, HDF data)
913 {
914 cl.makeHDF(data);
915
916 setPageTitle(data, cl.name());
917 ClearPage.write(data, "class.cs", cl.htmlPage());
918
919 Proofread.writeClass(cl.htmlPage(), cl);
920 }
921
922 public static void makeClassListHDF(HDF data, String base,
923 ClassInfo[] classes)
924 {
925 for (int i=0; i<classes.length; i++) {
926 ClassInfo cl = classes[i];
927 if (!cl.isHidden()) {
928 cl.makeShortDescrHDF(data, base + "." + i);
929 }
930 }
931 }
932
933 public static String linkTarget(String source, String target)
934 {
935 String[] src = source.split("/");
936 String[] tgt = target.split("/");
937
938 int srclen = src.length;
939 int tgtlen = tgt.length;
940
941 int same = 0;
942 while (same < (srclen-1)
943 && same < (tgtlen-1)
944 && (src[same].equals(tgt[same]))) {
945 same++;
946 }
947
948 String s = "";
949
950 int up = srclen-same-1;
951 for (int i=0; i<up; i++) {
952 s += "../";
953 }
954
955
956 int N = tgtlen-1;
957 for (int i=same; i<N; i++) {
958 s += tgt[i] + '/';
959 }
960 s += tgt[tgtlen-1];
961
962 return s;
963 }
964
965 /**
966 * Returns true if the given element has an @hide annotation.
967 */
968 private static boolean hasHideAnnotation(Doc doc) {
969 return doc.getRawCommentText().indexOf("@hide") != -1;
970 }
971
972 /**
973 * Returns true if the given element is hidden.
974 */
975 private static boolean isHidden(Doc doc) {
976 // Methods, fields, constructors.
977 if (doc instanceof MemberDoc) {
978 return hasHideAnnotation(doc);
979 }
980
981 // Classes, interfaces, enums, annotation types.
982 if (doc instanceof ClassDoc) {
983 ClassDoc classDoc = (ClassDoc) doc;
984
985 // Check the containing package.
986 if (hasHideAnnotation(classDoc.containingPackage())) {
987 return true;
988 }
989
990 // Check the class doc and containing class docs if this is a
991 // nested class.
992 ClassDoc current = classDoc;
993 do {
994 if (hasHideAnnotation(current)) {
995 return true;
996 }
997
998 current = current.containingClass();
999 } while (current != null);
1000 }
1001
1002 return false;
1003 }
1004
1005 /**
1006 * Filters out hidden elements.
1007 */
1008 private static Object filterHidden(Object o, Class<?> expected) {
1009 if (o == null) {
1010 return null;
1011 }
1012
1013 Class type = o.getClass();
1014 if (type.getName().startsWith("com.sun.")) {
1015 // TODO: Implement interfaces from superclasses, too.
1016 return Proxy.newProxyInstance(type.getClassLoader(),
1017 type.getInterfaces(), new HideHandler(o));
1018 } else if (o instanceof Object[]) {
1019 Class<?> componentType = expected.getComponentType();
1020 Object[] array = (Object[]) o;
1021 List<Object> list = new ArrayList<Object>(array.length);
1022 for (Object entry : array) {
1023 if ((entry instanceof Doc) && isHidden((Doc) entry)) {
1024 continue;
1025 }
1026 list.add(filterHidden(entry, componentType));
1027 }
1028 return list.toArray(
1029 (Object[]) Array.newInstance(componentType, list.size()));
1030 } else {
1031 return o;
1032 }
1033 }
1034
1035 /**
1036 * Filters hidden elements out of method return values.
1037 */
1038 private static class HideHandler implements InvocationHandler {
1039
1040 private final Object target;
1041
1042 public HideHandler(Object target) {
1043 this.target = target;
1044 }
1045
1046 public Object invoke(Object proxy, Method method, Object[] args)
1047 throws Throwable {
1048 String methodName = method.getName();
1049 if (args != null) {
1050 if (methodName.equals("compareTo") ||
1051 methodName.equals("equals") ||
1052 methodName.equals("overrides") ||
1053 methodName.equals("subclassOf")) {
1054 args[0] = unwrap(args[0]);
1055 }
1056 }
1057
1058 if (methodName.equals("getRawCommentText")) {
1059 return filterComment((String) method.invoke(target, args));
1060 }
1061
1062 // escape "&" in disjunctive types.
1063 if (proxy instanceof Type && methodName.equals("toString")) {
1064 return ((String) method.invoke(target, args))
1065 .replace("&", "&amp;");
1066 }
1067
1068 try {
1069 return filterHidden(method.invoke(target, args),
1070 method.getReturnType());
1071 } catch (InvocationTargetException e) {
1072 throw e.getTargetException();
1073 }
1074 }
1075
1076 private String filterComment(String s) {
1077 if (s == null) {
1078 return null;
1079 }
1080
1081 s = s.trim();
1082
1083 // Work around off by one error
1084 while (s.length() >= 5
1085 && s.charAt(s.length() - 5) == '{') {
1086 s += "&nbsp;";
1087 }
1088
1089 return s;
1090 }
1091
1092 private static Object unwrap(Object proxy) {
1093 if (proxy instanceof Proxy)
1094 return ((HideHandler)Proxy.getInvocationHandler(proxy)).target;
1095 return proxy;
1096 }
1097 }
1098
1099 public static String scope(Scoped scoped) {
1100 if (scoped.isPublic()) {
1101 return "public";
1102 }
1103 else if (scoped.isProtected()) {
1104 return "protected";
1105 }
1106 else if (scoped.isPackagePrivate()) {
1107 return "";
1108 }
1109 else if (scoped.isPrivate()) {
1110 return "private";
1111 }
1112 else {
1113 throw new RuntimeException("invalid scope for object " + scoped);
1114 }
1115 }
1116
1117 /**
1118 * Collect the values used by the Dev tools and write them in files packaged with the SDK
1119 * @param output the ouput directory for the files.
1120 */
1121 private static void writeSdkValues(String output) {
1122 ArrayList<String> activityActions = new ArrayList<String>();
1123 ArrayList<String> broadcastActions = new ArrayList<String>();
1124 ArrayList<String> serviceActions = new ArrayList<String>();
1125 ArrayList<String> categories = new ArrayList<String>();
1126
1127 ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>();
1128 ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>();
1129 ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>();
1130
1131 ClassInfo[] classes = Converter.allClasses();
1132
1133 // Go through all the fields of all the classes, looking SDK stuff.
1134 for (ClassInfo clazz : classes) {
1135
1136 // first check constant fields for the SdkConstant annotation.
1137 FieldInfo[] fields = clazz.allSelfFields();
1138 for (FieldInfo field : fields) {
1139 Object cValue = field.constantValue();
1140 if (cValue != null) {
1141 AnnotationInstanceInfo[] annotations = field.annotations();
1142 if (annotations.length > 0) {
1143 for (AnnotationInstanceInfo annotation : annotations) {
1144 if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) {
1145 AnnotationValueInfo[] values = annotation.elementValues();
1146 if (values.length > 0) {
1147 String type = values[0].valueString();
1148 if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) {
1149 activityActions.add(cValue.toString());
1150 } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) {
1151 broadcastActions.add(cValue.toString());
1152 } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) {
1153 serviceActions.add(cValue.toString());
1154 } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) {
1155 categories.add(cValue.toString());
1156 }
1157 }
1158 break;
1159 }
1160 }
1161 }
1162 }
1163 }
1164
1165 // Now check the class for @Widget or if its in the android.widget package
1166 // (unless the class is hidden or abstract, or non public)
1167 if (clazz.isHidden() == false && clazz.isPublic() && clazz.isAbstract() == false) {
1168 boolean annotated = false;
1169 AnnotationInstanceInfo[] annotations = clazz.annotations();
1170 if (annotations.length > 0) {
1171 for (AnnotationInstanceInfo annotation : annotations) {
1172 if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) {
1173 widgets.add(clazz);
1174 annotated = true;
1175 break;
1176 } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) {
1177 layouts.add(clazz);
1178 annotated = true;
1179 break;
1180 }
1181 }
1182 }
1183
1184 if (annotated == false) {
1185 // lets check if this is inside android.widget
1186 PackageInfo pckg = clazz.containingPackage();
1187 String packageName = pckg.name();
1188 if ("android.widget".equals(packageName) ||
1189 "android.view".equals(packageName)) {
1190 // now we check what this class inherits either from android.view.ViewGroup
1191 // or android.view.View, or android.view.ViewGroup.LayoutParams
1192 int type = checkInheritance(clazz);
1193 switch (type) {
1194 case TYPE_WIDGET:
1195 widgets.add(clazz);
1196 break;
1197 case TYPE_LAYOUT:
1198 layouts.add(clazz);
1199 break;
1200 case TYPE_LAYOUT_PARAM:
1201 layoutParams.add(clazz);
1202 break;
1203 }
1204 }
1205 }
1206 }
1207 }
1208
1209 // now write the files, whether or not the list are empty.
1210 // the SDK built requires those files to be present.
1211
1212 Collections.sort(activityActions);
1213 writeValues(output + "/activity_actions.txt", activityActions);
1214
1215 Collections.sort(broadcastActions);
1216 writeValues(output + "/broadcast_actions.txt", broadcastActions);
1217
1218 Collections.sort(serviceActions);
1219 writeValues(output + "/service_actions.txt", serviceActions);
1220
1221 Collections.sort(categories);
1222 writeValues(output + "/categories.txt", categories);
1223
1224 // before writing the list of classes, we do some checks, to make sure the layout params
1225 // are enclosed by a layout class (and not one that has been declared as a widget)
1226 for (int i = 0 ; i < layoutParams.size();) {
1227 ClassInfo layoutParamClass = layoutParams.get(i);
1228 ClassInfo containingClass = layoutParamClass.containingClass();
1229 if (containingClass == null || layouts.indexOf(containingClass) == -1) {
1230 layoutParams.remove(i);
1231 } else {
1232 i++;
1233 }
1234 }
1235
1236 writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams);
1237 }
1238
1239 /**
1240 * Writes a list of values into a text files.
1241 * @param pathname the absolute os path of the output file.
1242 * @param values the list of values to write.
1243 */
1244 private static void writeValues(String pathname, ArrayList<String> values) {
1245 FileWriter fw = null;
1246 BufferedWriter bw = null;
1247 try {
1248 fw = new FileWriter(pathname, false);
1249 bw = new BufferedWriter(fw);
1250
1251 for (String value : values) {
1252 bw.append(value).append('\n');
1253 }
1254 } catch (IOException e) {
1255 // pass for now
1256 } finally {
1257 try {
1258 if (bw != null) bw.close();
1259 } catch (IOException e) {
1260 // pass for now
1261 }
1262 try {
1263 if (fw != null) fw.close();
1264 } catch (IOException e) {
1265 // pass for now
1266 }
1267 }
1268 }
1269
1270 /**
1271 * Writes the widget/layout/layout param classes into a text files.
1272 * @param pathname the absolute os path of the output file.
1273 * @param widgets the list of widget classes to write.
1274 * @param layouts the list of layout classes to write.
1275 * @param layoutParams the list of layout param classes to write.
1276 */
1277 private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets,
1278 ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) {
1279 FileWriter fw = null;
1280 BufferedWriter bw = null;
1281 try {
1282 fw = new FileWriter(pathname, false);
1283 bw = new BufferedWriter(fw);
1284
1285 // write the 3 types of classes.
1286 for (ClassInfo clazz : widgets) {
1287 writeClass(bw, clazz, 'W');
1288 }
1289 for (ClassInfo clazz : layoutParams) {
1290 writeClass(bw, clazz, 'P');
1291 }
1292 for (ClassInfo clazz : layouts) {
1293 writeClass(bw, clazz, 'L');
1294 }
1295 } catch (IOException e) {
1296 // pass for now
1297 } finally {
1298 try {
1299 if (bw != null) bw.close();
1300 } catch (IOException e) {
1301 // pass for now
1302 }
1303 try {
1304 if (fw != null) fw.close();
1305 } catch (IOException e) {
1306 // pass for now
1307 }
1308 }
1309 }
1310
1311 /**
1312 * Writes a class name and its super class names into a {@link BufferedWriter}.
1313 * @param writer the BufferedWriter to write into
1314 * @param clazz the class to write
1315 * @param prefix the prefix to put at the beginning of the line.
1316 * @throws IOException
1317 */
1318 private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)
1319 throws IOException {
1320 writer.append(prefix).append(clazz.qualifiedName());
1321 ClassInfo superClass = clazz;
1322 while ((superClass = superClass.superclass()) != null) {
1323 writer.append(' ').append(superClass.qualifiedName());
1324 }
1325 writer.append('\n');
1326 }
1327
1328 /**
1329 * Checks the inheritance of {@link ClassInfo} objects. This method return
1330 * <ul>
1331 * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li>
1332 * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li>
1333 * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends <code>android.view.ViewGroup$LayoutParams</code></li>
1334 * <li>{@link #TYPE_NONE}: in all other cases</li>
1335 * </ul>
1336 * @param clazz the {@link ClassInfo} to check.
1337 */
1338 private static int checkInheritance(ClassInfo clazz) {
1339 if ("android.view.ViewGroup".equals(clazz.qualifiedName())) {
1340 return TYPE_LAYOUT;
1341 } else if ("android.view.View".equals(clazz.qualifiedName())) {
1342 return TYPE_WIDGET;
1343 } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
1344 return TYPE_LAYOUT_PARAM;
1345 }
1346
1347 ClassInfo parent = clazz.superclass();
1348 if (parent != null) {
1349 return checkInheritance(parent);
1350 }
1351
1352 return TYPE_NONE;
1353 }
1354}