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