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