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