1 /*
2  * Copyright (C) 2010 Google Inc.
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 
17 package com.google.doclava;
18 
19 import com.google.clearsilver.jsilver.JSilver;
20 import com.google.clearsilver.jsilver.data.Data;
21 import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader;
22 import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader;
23 import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader;
24 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
25 
26 import com.sun.javadoc.*;
27 
28 import java.util.*;
29 import java.util.jar.JarFile;
30 import java.util.regex.Matcher;
31 import java.io.*;
32 import java.lang.reflect.Proxy;
33 import java.lang.reflect.Array;
34 import java.lang.reflect.InvocationHandler;
35 import java.lang.reflect.InvocationTargetException;
36 import java.lang.reflect.Method;
37 import java.net.MalformedURLException;
38 import java.net.URL;
39 
40 public class Doclava {
41   private static final String SDK_CONSTANT_ANNOTATION = "android.annotation.SdkConstant";
42   private static final String SDK_CONSTANT_TYPE_ACTIVITY_ACTION =
43       "android.annotation.SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION";
44   private static final String SDK_CONSTANT_TYPE_BROADCAST_ACTION =
45       "android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION";
46   private static final String SDK_CONSTANT_TYPE_SERVICE_ACTION =
47       "android.annotation.SdkConstant.SdkConstantType.SERVICE_ACTION";
48   private static final String SDK_CONSTANT_TYPE_CATEGORY =
49       "android.annotation.SdkConstant.SdkConstantType.INTENT_CATEGORY";
50   private static final String SDK_CONSTANT_TYPE_FEATURE =
51       "android.annotation.SdkConstant.SdkConstantType.FEATURE";
52   private static final String SDK_WIDGET_ANNOTATION = "android.annotation.Widget";
53   private static final String SDK_LAYOUT_ANNOTATION = "android.annotation.Layout";
54 
55   private static final int TYPE_NONE = 0;
56   private static final int TYPE_WIDGET = 1;
57   private static final int TYPE_LAYOUT = 2;
58   private static final int TYPE_LAYOUT_PARAM = 3;
59 
60   public static final int SHOW_PUBLIC = 0x00000001;
61   public static final int SHOW_PROTECTED = 0x00000003;
62   public static final int SHOW_PACKAGE = 0x00000007;
63   public static final int SHOW_PRIVATE = 0x0000000f;
64   public static final int SHOW_HIDDEN = 0x0000001f;
65 
66   public static int showLevel = SHOW_PROTECTED;
67 
68   public static final boolean SORT_BY_NAV_GROUPS = true;
69   /* Debug output for PageMetadata, format urls from site root */
70   public static boolean META_DBG=false;
71   /* Remove after updated templates are launched */
72   public static boolean USE_UPDATED_TEMPLATES = false;
73   /* Show Preview navigation and process preview docs */
74   public static boolean INCLUDE_PREVIEW = false;
75   /* output en, es, ja without parent intl/ container */
76   public static boolean USE_DEVSITE_LOCALE_OUTPUT_PATHS = false;
77   public static String outputPathBase = "/";
78   public static ArrayList<String> inputPathHtmlDirs = new ArrayList<String>();
79   public static ArrayList<String> inputPathHtmlDir2 = new ArrayList<String>();
80   public static String outputPathHtmlDirs;
81   public static String outputPathHtmlDir2;
82 
83   public static String javadocDir = "reference/";
84   public static String htmlExtension;
85 
86   public static RootDoc root;
87   public static ArrayList<String[]> mHDFData = new ArrayList<String[]>();
88   public static List<PageMetadata.Node> sTaglist = new ArrayList<PageMetadata.Node>();
89   public static ArrayList<SampleCode> sampleCodes = new ArrayList<SampleCode>();
90   public static ArrayList<SampleCode> sampleCodeGroups = new ArrayList<SampleCode>();
91   public static Data samplesNavTree;
92   public static Map<Character, String> escapeChars = new HashMap<Character, String>();
93   public static String title = "";
94   public static SinceTagger sinceTagger = new SinceTagger();
95   public static HashSet<String> knownTags = new HashSet<String>();
96   public static FederationTagger federationTagger = new FederationTagger();
97   public static Set<String> showAnnotations = new HashSet<String>();
98   public static boolean showAnnotationOverridesVisibility = false;
99   public static Set<String> hiddenPackages = new HashSet<String>();
100   public static boolean includeDefaultAssets = true;
101   private static boolean generateDocs = true;
102   private static boolean parseComments = false;
103   private static String yamlNavFile = null;
104   public static boolean documentAnnotations = false;
105   public static String documentAnnotationsPath = null;
106   public static Map<String, String> annotationDocumentationMap = null;
107   public static boolean referenceOnly = false;
108 
109   public static JSilver jSilver = null;
110 
111   //API reference extensions
112   private static boolean gmsRef = false;
113   private static boolean gcmRef = false;
114   public static boolean testSupportRef = false;
115   public static String testSupportPath = "android/support/test/";
116   public static boolean wearableSupportRef = false;
117   public static String wearableSupportPath = "android/support/wearable/";
118   private static boolean samplesRef = false;
119   private static boolean sac = false;
120 
checkLevel(int level)121   public static boolean checkLevel(int level) {
122     return (showLevel & level) == level;
123   }
124 
125   /**
126    * Returns true if we should parse javadoc comments,
127    * reporting errors in the process.
128    */
parseComments()129   public static boolean parseComments() {
130     return generateDocs || parseComments;
131   }
132 
checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv, boolean hidden)133   public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv,
134       boolean hidden) {
135     if (hidden && !checkLevel(SHOW_HIDDEN)) {
136       return false;
137     }
138     if (pub && checkLevel(SHOW_PUBLIC)) {
139       return true;
140     }
141     if (prot && checkLevel(SHOW_PROTECTED)) {
142       return true;
143     }
144     if (pkgp && checkLevel(SHOW_PACKAGE)) {
145       return true;
146     }
147     if (priv && checkLevel(SHOW_PRIVATE)) {
148       return true;
149     }
150     return false;
151   }
152 
main(String[] args)153   public static void main(String[] args) {
154     com.sun.tools.javadoc.Main.execute(args);
155   }
156 
start(RootDoc r)157   public static boolean start(RootDoc r) {
158     long startTime = System.nanoTime();
159     String keepListFile = null;
160     String proguardFile = null;
161     String proofreadFile = null;
162     String todoFile = null;
163     String sdkValuePath = null;
164     String stubsDir = null;
165     // Create the dependency graph for the stubs  directory
166     boolean offlineMode = false;
167     String apiFile = null;
168     String removedApiFile = null;
169     String debugStubsFile = "";
170     HashSet<String> stubPackages = null;
171     ArrayList<String> knownTagsFiles = new ArrayList<String>();
172 
173     root = r;
174 
175     String[][] options = r.options();
176     for (String[] a : options) {
177       if (a[0].equals("-d")) {
178         outputPathBase = outputPathHtmlDirs = ClearPage.outputDir = a[1];
179       } else if (a[0].equals("-templatedir")) {
180         if (USE_UPDATED_TEMPLATES) {
181           /* remove with updated templates are launched */
182           ClearPage.addTemplateDir("build/tools/droiddoc/templates-sdk-dev");
183         } else {
184           ClearPage.addTemplateDir(a[1]);
185         }
186       } else if (a[0].equals("-hdf")) {
187         mHDFData.add(new String[] {a[1], a[2]});
188       } else if (a[0].equals("-knowntags")) {
189         knownTagsFiles.add(a[1]);
190       } else if (a[0].equals("-toroot")) {
191         ClearPage.toroot = a[1];
192       } else if (a[0].equals("-samplecode")) {
193         sampleCodes.add(new SampleCode(a[1], a[2], a[3]));
194       } else if (a[0].equals("-samplegroup")) {
195         sampleCodeGroups.add(new SampleCode(null, null, a[1]));
196       } else if (a[0].equals("-samplesdir")) {
197         getSampleProjects(new File(a[1]));
198       //the destination output path for main htmldir
199       } else if (a[0].equals("-htmldir")) {
200         inputPathHtmlDirs.add(a[1]);
201         ClearPage.htmlDirs = inputPathHtmlDirs;
202       //the destination output path for additional htmldir
203       } else if (a[0].equals("-htmldir2")) {
204           if (a[2].equals("default")) {
205           inputPathHtmlDirs.add(a[1]);
206         } else {
207           inputPathHtmlDir2.add(a[1]);
208           outputPathHtmlDir2 = a[2];
209         }
210       } else if (a[0].equals("-title")) {
211         Doclava.title = a[1];
212       } else if (a[0].equals("-werror")) {
213         Errors.setWarningsAreErrors(true);
214       } else if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-hide")) {
215         try {
216           int level = -1;
217           if (a[0].equals("-error")) {
218             level = Errors.ERROR;
219           } else if (a[0].equals("-warning")) {
220             level = Errors.WARNING;
221           } else if (a[0].equals("-hide")) {
222             level = Errors.HIDDEN;
223           }
224           Errors.setErrorLevel(Integer.parseInt(a[1]), level);
225         } catch (NumberFormatException e) {
226           // already printed below
227           return false;
228         }
229       } else if (a[0].equals("-keeplist")) {
230         keepListFile = a[1];
231       } else if (a[0].equals("-showAnnotation")) {
232         showAnnotations.add(a[1]);
233       } else if (a[0].equals("-showAnnotationOverridesVisibility")) {
234         showAnnotationOverridesVisibility = true;
235       } else if (a[0].equals("-hidePackage")) {
236         hiddenPackages.add(a[1]);
237       } else if (a[0].equals("-proguard")) {
238         proguardFile = a[1];
239       } else if (a[0].equals("-proofread")) {
240         proofreadFile = a[1];
241       } else if (a[0].equals("-todo")) {
242         todoFile = a[1];
243       } else if (a[0].equals("-public")) {
244         showLevel = SHOW_PUBLIC;
245       } else if (a[0].equals("-protected")) {
246         showLevel = SHOW_PROTECTED;
247       } else if (a[0].equals("-package")) {
248         showLevel = SHOW_PACKAGE;
249       } else if (a[0].equals("-private")) {
250         showLevel = SHOW_PRIVATE;
251       } else if (a[0].equals("-hidden")) {
252         showLevel = SHOW_HIDDEN;
253       } else if (a[0].equals("-stubs")) {
254         stubsDir = a[1];
255       } else if (a[0].equals("-stubpackages")) {
256         stubPackages = new HashSet<String>();
257         for (String pkg : a[1].split(":")) {
258           stubPackages.add(pkg);
259         }
260       } else if (a[0].equals("-sdkvalues")) {
261         sdkValuePath = a[1];
262       } else if (a[0].equals("-api")) {
263         apiFile = a[1];
264       } else if (a[0].equals("-removedApi")) {
265         removedApiFile = a[1];
266       }
267       else if (a[0].equals("-nodocs")) {
268         generateDocs = false;
269       } else if (a[0].equals("-nodefaultassets")) {
270         includeDefaultAssets = false;
271       } else if (a[0].equals("-parsecomments")) {
272         parseComments = true;
273       } else if (a[0].equals("-since")) {
274         sinceTagger.addVersion(a[1], a[2]);
275       } else if (a[0].equals("-offlinemode")) {
276         offlineMode = true;
277       } else if (a[0].equals("-metadataDebug")) {
278         META_DBG = true;
279       } else if (a[0].equals("-useUpdatedTemplates")) {
280         USE_UPDATED_TEMPLATES = true;
281       } else if (a[0].equals("-includePreview")) {
282         INCLUDE_PREVIEW = true;
283       } else if (a[0].equals("-federate")) {
284         try {
285           String name = a[1];
286           URL federationURL = new URL(a[2]);
287           federationTagger.addSiteUrl(name, federationURL);
288         } catch (MalformedURLException e) {
289           System.err.println("Could not parse URL for federation: " + a[1]);
290           return false;
291         }
292       } else if (a[0].equals("-federationapi")) {
293         String name = a[1];
294         String file = a[2];
295         federationTagger.addSiteApi(name, file);
296       } else if (a[0].equals("-yaml")) {
297         yamlNavFile = a[1];
298       } else if (a[0].equals("-devsite")) {
299         // Don't copy the doclava assets to devsite output (ie use proj assets only)
300         includeDefaultAssets = false;
301         USE_DEVSITE_LOCALE_OUTPUT_PATHS = true;
302         mHDFData.add(new String[] {"devsite", "true"});
303       } else if (a[0].equals("-documentannotations")) {
304         documentAnnotations = true;
305         documentAnnotationsPath = a[1];
306       } else if (a[0].equals("-referenceonly")) {
307         referenceOnly = true;
308         mHDFData.add(new String[] {"referenceonly", "1"});
309       }
310     }
311 
312     if (!readKnownTagsFiles(knownTags, knownTagsFiles)) {
313       return false;
314     }
315 
316     // Set up the data structures
317     Converter.makeInfo(r);
318 
319     if (generateDocs) {
320       ClearPage.addBundledTemplateDir("assets/customizations");
321       ClearPage.addBundledTemplateDir("assets/templates");
322 
323       List<ResourceLoader> resourceLoaders = new ArrayList<ResourceLoader>();
324       List<String> templates = ClearPage.getTemplateDirs();
325       for (String tmpl : templates) {
326         resourceLoaders.add(new FileSystemResourceLoader(tmpl));
327       }
328 
329       templates = ClearPage.getBundledTemplateDirs();
330       for (String tmpl : templates) {
331           // TODO - remove commented line - it's here for debugging purposes
332         //  resourceLoaders.add(new FileSystemResourceLoader("/Volumes/Android/master/external/doclava/res/" + tmpl));
333         resourceLoaders.add(new ClassResourceLoader(Doclava.class, '/'+tmpl));
334       }
335 
336       ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders);
337       jSilver = new JSilver(compositeResourceLoader);
338 
339       if (!Doclava.readTemplateSettings()) {
340         return false;
341       }
342 
343       //startTime = System.nanoTime();
344 
345       // Apply @since tags from the XML file
346       sinceTagger.tagAll(Converter.rootClasses());
347 
348       // Apply details of federated documentation
349       federationTagger.tagAll(Converter.rootClasses());
350 
351       // Files for proofreading
352       if (proofreadFile != null) {
353         Proofread.initProofread(proofreadFile);
354       }
355       if (todoFile != null) {
356         TodoFile.writeTodoFile(todoFile);
357       }
358 
359       if (samplesRef) {
360         // always write samples without offlineMode behaviors
361         writeSamples(false, sampleCodes, SORT_BY_NAV_GROUPS);
362       }
363 
364       if (!referenceOnly) {
365         // HTML2 Pages -- Generate Pages from optional secondary dir
366         if (!inputPathHtmlDir2.isEmpty()) {
367           if (!outputPathHtmlDir2.isEmpty()) {
368             ClearPage.outputDir = outputPathBase + "/" + outputPathHtmlDir2;
369           }
370           ClearPage.htmlDirs = inputPathHtmlDir2;
371           writeHTMLPages();
372           ClearPage.htmlDirs = inputPathHtmlDirs;
373         }
374 
375         // HTML Pages
376         if (!ClearPage.htmlDirs.isEmpty()) {
377           ClearPage.htmlDirs = inputPathHtmlDirs;
378           if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
379             ClearPage.outputDir = outputPathHtmlDirs + "/en/";
380           } else {
381             ClearPage.outputDir = outputPathHtmlDirs;
382           }
383           writeHTMLPages();
384         }
385       }
386 
387       writeAssets();
388 
389       // Navigation tree
390       String refPrefix = new String();
391       if(gmsRef){
392         refPrefix = "gms-";
393       } else if(gcmRef){
394         refPrefix = "gcm-";
395       }
396       NavTree.writeNavTree(javadocDir, refPrefix);
397 
398       // Write yaml tree.
399       if (yamlNavFile != null){
400         NavTree.writeYamlTree(javadocDir, yamlNavFile);
401       }
402 
403       // Packages Pages
404       writePackages(javadocDir + refPrefix + "packages" + htmlExtension);
405 
406       // Classes
407   writeClassLists();
408   writeClasses();
409   writeHierarchy();
410       // writeKeywords();
411 
412       // Lists for JavaScript
413   writeLists();
414       if (keepListFile != null) {
415         writeKeepList(keepListFile);
416       }
417 
418       // Index page
419   writeIndex();
420 
421   Proofread.finishProofread(proofreadFile);
422 
423   if (sdkValuePath != null) {
424     writeSdkValues(sdkValuePath);
425   }
426       // Write metadata for all processed files to jd_lists_unified.js in out dir
427       if (!sTaglist.isEmpty()) {
428         if (USE_UPDATED_TEMPLATES) {
429           PageMetadata.WriteListByLang(sTaglist);
430         } else {
431           PageMetadata.WriteList(sTaglist);
432         }
433       }
434     }
435 
436     // Stubs
437     if (stubsDir != null || apiFile != null || proguardFile != null || removedApiFile != null) {
438       Stubs.writeStubsAndApi(stubsDir, apiFile, proguardFile, removedApiFile, stubPackages);
439     }
440 
441     Errors.printErrors();
442 
443     long time = System.nanoTime() - startTime;
444     System.out.println("DroidDoc took " + (time / 1000000000) + " sec. to write docs to "
445         + outputPathBase );
446 
447     return !Errors.hadError;
448   }
449 
writeIndex()450   private static void writeIndex() {
451     Data data = makeHDF();
452     ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension);
453   }
454 
readTemplateSettings()455   private static boolean readTemplateSettings() {
456     Data data = makeHDF();
457 
458     // The .html extension is hard-coded in several .cs files,
459     // and so you cannot currently set it as a property.
460     htmlExtension = ".html";
461     // htmlExtension = data.getValue("template.extension", ".html");
462     int i = 0;
463     while (true) {
464       String k = data.getValue("template.escape." + i + ".key", "");
465       String v = data.getValue("template.escape." + i + ".value", "");
466       if ("".equals(k)) {
467         break;
468       }
469       if (k.length() != 1) {
470         System.err.println("template.escape." + i + ".key must have a length of 1: " + k);
471         return false;
472       }
473       escapeChars.put(k.charAt(0), v);
474       i++;
475     }
476     return true;
477   }
478 
readKnownTagsFiles(HashSet<String> knownTags, ArrayList<String> knownTagsFiles)479     private static boolean readKnownTagsFiles(HashSet<String> knownTags,
480             ArrayList<String> knownTagsFiles) {
481         for (String fn: knownTagsFiles) {
482            BufferedReader in = null;
483            try {
484                in = new BufferedReader(new FileReader(fn));
485                int lineno = 0;
486                boolean fail = false;
487                while (true) {
488                    lineno++;
489                    String line = in.readLine();
490                    if (line == null) {
491                        break;
492                    }
493                    line = line.trim();
494                    if (line.length() == 0) {
495                        continue;
496                    } else if (line.charAt(0) == '#') {
497                        continue;
498                    }
499                    String[] words = line.split("\\s+", 2);
500                    if (words.length == 2) {
501                        if (words[1].charAt(0) != '#') {
502                            System.err.println(fn + ":" + lineno
503                                    + ": Only one tag allowed per line: " + line);
504                            fail = true;
505                            continue;
506                        }
507                    }
508                    knownTags.add(words[0]);
509                }
510                if (fail) {
511                    return false;
512                }
513            } catch (IOException ex) {
514                System.err.println("Error reading file: " + fn + " (" + ex.getMessage() + ")");
515                return false;
516            } finally {
517                if (in != null) {
518                    try {
519                        in.close();
520                    } catch (IOException e) {
521                    }
522                }
523            }
524         }
525         return true;
526     }
527 
escape(String s)528   public static String escape(String s) {
529     if (escapeChars.size() == 0) {
530       return s;
531     }
532     StringBuffer b = null;
533     int begin = 0;
534     final int N = s.length();
535     for (int i = 0; i < N; i++) {
536       char c = s.charAt(i);
537       String mapped = escapeChars.get(c);
538       if (mapped != null) {
539         if (b == null) {
540           b = new StringBuffer(s.length() + mapped.length());
541         }
542         if (begin != i) {
543           b.append(s.substring(begin, i));
544         }
545         b.append(mapped);
546         begin = i + 1;
547       }
548     }
549     if (b != null) {
550       if (begin != N) {
551         b.append(s.substring(begin, N));
552       }
553       return b.toString();
554     }
555     return s;
556   }
557 
setPageTitle(Data data, String title)558   public static void setPageTitle(Data data, String title) {
559     String s = title;
560     if (Doclava.title.length() > 0) {
561       s += " - " + Doclava.title;
562     }
563     data.setValue("page.title", s);
564   }
565 
566 
languageVersion()567   public static LanguageVersion languageVersion() {
568     return LanguageVersion.JAVA_1_5;
569   }
570 
571 
optionLength(String option)572   public static int optionLength(String option) {
573     if (option.equals("-d")) {
574       return 2;
575     }
576     if (option.equals("-templatedir")) {
577       return 2;
578     }
579     if (option.equals("-hdf")) {
580       return 3;
581     }
582     if (option.equals("-knowntags")) {
583       return 2;
584     }
585     if (option.equals("-toroot")) {
586       return 2;
587     }
588     if (option.equals("-samplecode")) {
589       samplesRef = true;
590       return 4;
591     }
592     if (option.equals("-samplegroup")) {
593       return 2;
594     }
595     if (option.equals("-samplesdir")) {
596       samplesRef = true;
597       return 2;
598     }
599     if (option.equals("-devsite")) {
600       return 1;
601     }
602     if (option.equals("-htmldir")) {
603       return 2;
604     }
605     if (option.equals("-htmldir2")) {
606       return 3;
607     }
608     if (option.equals("-title")) {
609       return 2;
610     }
611     if (option.equals("-werror")) {
612       return 1;
613     }
614     if (option.equals("-hide")) {
615       return 2;
616     }
617     if (option.equals("-warning")) {
618       return 2;
619     }
620     if (option.equals("-error")) {
621       return 2;
622     }
623     if (option.equals("-keeplist")) {
624       return 2;
625     }
626     if (option.equals("-showAnnotation")) {
627       return 2;
628     }
629     if (option.equals("-showAnnotationOverridesVisibility")) {
630       return 1;
631     }
632     if (option.equals("-hidePackage")) {
633       return 2;
634     }
635     if (option.equals("-proguard")) {
636       return 2;
637     }
638     if (option.equals("-proofread")) {
639       return 2;
640     }
641     if (option.equals("-todo")) {
642       return 2;
643     }
644     if (option.equals("-public")) {
645       return 1;
646     }
647     if (option.equals("-protected")) {
648       return 1;
649     }
650     if (option.equals("-package")) {
651       return 1;
652     }
653     if (option.equals("-private")) {
654       return 1;
655     }
656     if (option.equals("-hidden")) {
657       return 1;
658     }
659     if (option.equals("-stubs")) {
660       return 2;
661     }
662     if (option.equals("-stubpackages")) {
663       return 2;
664     }
665     if (option.equals("-sdkvalues")) {
666       return 2;
667     }
668     if (option.equals("-api")) {
669       return 2;
670     }
671     if (option.equals("-removedApi")) {
672       return 2;
673     }
674     if (option.equals("-nodocs")) {
675       return 1;
676     }
677     if (option.equals("-nodefaultassets")) {
678       return 1;
679     }
680     if (option.equals("-parsecomments")) {
681       return 1;
682     }
683     if (option.equals("-since")) {
684       return 3;
685     }
686     if (option.equals("-offlinemode")) {
687       return 1;
688     }
689     if (option.equals("-federate")) {
690       return 3;
691     }
692     if (option.equals("-federationapi")) {
693       return 3;
694     }
695     if (option.equals("-yaml")) {
696       return 2;
697     }
698     if (option.equals("-gmsref")) {
699       gmsRef = true;
700       return 1;
701     }
702     if (option.equals("-gcmref")) {
703       gcmRef = true;
704       return 1;
705     }
706     if (option.equals("-testSupportRef")) {
707       testSupportRef = true;
708       return 1;
709     }
710     if (option.equals("-wearableSupportRef")) {
711       wearableSupportRef = true;
712       return 1;
713     }
714     if (option.equals("-metadataDebug")) {
715       return 1;
716     }
717     if (option.equals("-useUpdatedTemplates")) {
718       return 1;
719     }
720     if (option.equals("-includePreview")) {
721       return 1;
722     }
723     if (option.equals("-documentannotations")) {
724       return 2;
725     }
726     if (option.equals("-referenceonly")) {
727       return 1;
728     }
729     return 0;
730   }
validOptions(String[][] options, DocErrorReporter r)731   public static boolean validOptions(String[][] options, DocErrorReporter r) {
732     for (String[] a : options) {
733       if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-hide")) {
734         try {
735           Integer.parseInt(a[1]);
736         } catch (NumberFormatException e) {
737           r.printError("bad -" + a[0] + " value must be a number: " + a[1]);
738           return false;
739         }
740       }
741     }
742 
743     return true;
744   }
745 
makeHDF()746   public static Data makeHDF() {
747     Data data = jSilver.createData();
748 
749     for (String[] p : mHDFData) {
750       data.setValue(p[0], p[1]);
751     }
752 
753     return data;
754   }
755 
756 
757 
makePackageHDF()758   public static Data makePackageHDF() {
759     Data data = makeHDF();
760     ClassInfo[] classes = Converter.rootClasses();
761 
762     SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
763     for (ClassInfo cl : classes) {
764       PackageInfo pkg = cl.containingPackage();
765       String name;
766       if (pkg == null) {
767         name = "";
768       } else {
769         name = pkg.name();
770       }
771       sorted.put(name, pkg);
772     }
773 
774     int i = 0;
775     for (Map.Entry<String, PackageInfo> entry : sorted.entrySet()) {
776       String s = entry.getKey();
777       PackageInfo pkg = entry.getValue();
778 
779       if (pkg.isHiddenOrRemoved()) {
780         continue;
781       }
782       boolean allHiddenOrRemoved = true;
783       int pass = 0;
784       ClassInfo[] classesToCheck = null;
785       while (pass < 6) {
786         switch (pass) {
787           case 0:
788             classesToCheck = pkg.ordinaryClasses();
789             break;
790           case 1:
791             classesToCheck = pkg.enums();
792             break;
793           case 2:
794             classesToCheck = pkg.errors();
795             break;
796           case 3:
797             classesToCheck = pkg.exceptions();
798             break;
799           case 4:
800             classesToCheck = pkg.interfaces();
801             break;
802           case 5:
803             classesToCheck = pkg.annotations();
804             break;
805           default:
806             System.err.println("Error reading package: " + pkg.name());
807             break;
808         }
809         for (ClassInfo cl : classesToCheck) {
810           if (!cl.isHiddenOrRemoved()) {
811             allHiddenOrRemoved = false;
812             break;
813           }
814         }
815         if (!allHiddenOrRemoved) {
816           break;
817         }
818         pass++;
819       }
820       if (allHiddenOrRemoved) {
821         continue;
822       }
823       if(gmsRef){
824           data.setValue("reference.gms", "true");
825       } else if(gcmRef){
826           data.setValue("reference.gcm", "true");
827       } else if(testSupportRef){
828           data.setValue("reference.testSupport", "true");
829       } else if(wearableSupportRef){
830           data.setValue("reference.wearableSupport", "true");
831       }
832       data.setValue("reference", "1");
833       data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0");
834       data.setValue("docs.packages." + i + ".name", s);
835       data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
836       data.setValue("docs.packages." + i + ".since", pkg.getSince());
837       TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags());
838       i++;
839     }
840 
841     sinceTagger.writeVersionNames(data);
842     return data;
843   }
844 
writeDirectory(File dir, String relative, JSilver js)845   private static void writeDirectory(File dir, String relative, JSilver js) {
846     File[] files = dir.listFiles();
847     int i, count = files.length;
848     for (i = 0; i < count; i++) {
849       File f = files[i];
850       if (f.isFile()) {
851         String templ = relative + f.getName();
852         int len = templ.length();
853         if (len > 3 && ".cs".equals(templ.substring(len - 3))) {
854           Data data = makeHDF();
855           String filename = templ.substring(0, len - 3) + htmlExtension;
856           ClearPage.write(data, templ, filename, js);
857         } else if (len > 3 && ".jd".equals(templ.substring(len - 3))) {
858           Data data = makeHDF();
859           String filename = templ.substring(0, len - 3) + htmlExtension;
860           DocFile.writePage(f.getAbsolutePath(), relative, filename, data);
861         } else if(!f.getName().equals(".DS_Store")){
862               Data data = makeHDF();
863               String hdfValue = data.getValue("sac") == null ? "" : data.getValue("sac");
864               boolean allowExcepted = hdfValue.equals("true") ? true : false;
865               boolean append = false;
866               ClearPage.copyFile(allowExcepted, f, templ, append);
867         }
868       } else if (f.isDirectory()) {
869         writeDirectory(f, relative + f.getName() + "/", js);
870       }
871     }
872   }
873 
writeHTMLPages()874   public static void writeHTMLPages() {
875     for (String htmlDir : ClearPage.htmlDirs) {
876       File f = new File(htmlDir);
877       if (!f.isDirectory()) {
878         System.err.println("htmlDir not a directory: " + htmlDir);
879         continue;
880       }
881 
882       ResourceLoader loader = new FileSystemResourceLoader(f);
883       JSilver js = new JSilver(loader);
884       writeDirectory(f, "", js);
885     }
886   }
887 
writeAssets()888   public static void writeAssets() {
889     JarFile thisJar = JarUtils.jarForClass(Doclava.class, null);
890     if ((thisJar != null) && (includeDefaultAssets)) {
891       try {
892         List<String> templateDirs = ClearPage.getBundledTemplateDirs();
893         for (String templateDir : templateDirs) {
894           String assetsDir = templateDir + "/assets";
895           JarUtils.copyResourcesToDirectory(thisJar, assetsDir, ClearPage.outputDir + "/assets");
896         }
897       } catch (IOException e) {
898         System.err.println("Error copying assets directory.");
899         e.printStackTrace();
900         return;
901       }
902     }
903 
904     //write the project-specific assets
905     List<String> templateDirs = ClearPage.getTemplateDirs();
906     for (String templateDir : templateDirs) {
907       File assets = new File(templateDir + "/assets");
908       if (assets.isDirectory()) {
909         writeDirectory(assets, "assets/", null);
910       }
911     }
912 
913     // Create the timestamp.js file based on .cs file
914     Data timedata = Doclava.makeHDF();
915     ClearPage.write(timedata, "timestamp.cs", "timestamp.js");
916   }
917 
918   /** Go through the docs and generate meta-data about each
919       page to use in search suggestions */
writeLists()920   public static void writeLists() {
921 
922     // Write the lists for API references
923     Data data = makeHDF();
924 
925     ClassInfo[] classes = Converter.rootClasses();
926 
927     SortedMap<String, Object> sorted = new TreeMap<String, Object>();
928     for (ClassInfo cl : classes) {
929       if (cl.isHiddenOrRemoved()) {
930         continue;
931       }
932       sorted.put(cl.qualifiedName(), cl);
933       PackageInfo pkg = cl.containingPackage();
934       String name;
935       if (pkg == null) {
936         name = "";
937       } else {
938         name = pkg.name();
939       }
940       sorted.put(name, pkg);
941     }
942 
943     int i = 0;
944     String listDir = javadocDir;
945     if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
946       if (testSupportRef) {
947         listDir = listDir + testSupportPath;
948         data.setValue("reference.testSupport", "true");
949       } else if (wearableSupportRef) {
950         listDir = listDir + wearableSupportPath;
951         data.setValue("reference.wearableSupport", "true");
952       }
953     }
954     for (String s : sorted.keySet()) {
955       data.setValue("docs.pages." + i + ".id", "" + i);
956       data.setValue("docs.pages." + i + ".label", s);
957 
958       Object o = sorted.get(s);
959       if (o instanceof PackageInfo) {
960         PackageInfo pkg = (PackageInfo) o;
961         data.setValue("docs.pages." + i + ".link", pkg.htmlPage());
962         data.setValue("docs.pages." + i + ".type", "package");
963         data.setValue("docs.pages." + i + ".deprecated", pkg.isDeprecated() ? "true" : "false");
964       } else if (o instanceof ClassInfo) {
965         ClassInfo cl = (ClassInfo) o;
966         data.setValue("docs.pages." + i + ".link", cl.htmlPage());
967         data.setValue("docs.pages." + i + ".type", "class");
968         data.setValue("docs.pages." + i + ".deprecated", cl.isDeprecated() ? "true" : "false");
969       }
970       i++;
971     }
972     ClearPage.write(data, "lists.cs", listDir + "lists.js");
973 
974 
975     // Write the lists for JD documents (if there are HTML directories to process)
976     // Skip this for devsite builds
977     if ((inputPathHtmlDirs.size() > 0) && (!USE_DEVSITE_LOCALE_OUTPUT_PATHS)) {
978       Data jddata = makeHDF();
979       Iterator counter = new Iterator();
980       for (String htmlDir : inputPathHtmlDirs) {
981         File dir = new File(htmlDir);
982         if (!dir.isDirectory()) {
983           continue;
984         }
985         writeJdDirList(dir, jddata, counter);
986       }
987       ClearPage.write(jddata, "jd_lists.cs", javadocDir + "jd_lists.js");
988     }
989   }
990 
991   private static class Iterator {
992     int i = 0;
993   }
994 
995   /** Write meta-data for a JD file, used for search suggestions */
writeJdDirList(File dir, Data data, Iterator counter)996   private static void writeJdDirList(File dir, Data data, Iterator counter) {
997     File[] files = dir.listFiles();
998     int i, count = files.length;
999     // Loop all files in given directory
1000     for (i = 0; i < count; i++) {
1001       File f = files[i];
1002       if (f.isFile()) {
1003         String filePath = f.getAbsolutePath();
1004         String templ = f.getName();
1005         int len = templ.length();
1006         // If it's a .jd file we want to process
1007         if (len > 3 && ".jd".equals(templ.substring(len - 3))) {
1008           // remove the directories below the site root
1009           String webPath = filePath.substring(filePath.indexOf("docs/html/") + 10,
1010               filePath.length());
1011           // replace .jd with .html
1012           webPath = webPath.substring(0, webPath.length() - 3) + htmlExtension;
1013           // Parse the .jd file for properties data at top of page
1014           Data hdf = Doclava.makeHDF();
1015           String filedata = DocFile.readFile(filePath);
1016           Matcher lines = DocFile.LINE.matcher(filedata);
1017           String line = null;
1018           // Get each line to add the key-value to hdf
1019           while (lines.find()) {
1020             line = lines.group(1);
1021             if (line.length() > 0) {
1022               // Stop when we hit the body
1023               if (line.equals("@jd:body")) {
1024                 break;
1025               }
1026               Matcher prop = DocFile.PROP.matcher(line);
1027               if (prop.matches()) {
1028                 String key = prop.group(1);
1029                 String value = prop.group(2);
1030                 hdf.setValue(key, value);
1031               } else {
1032                 break;
1033               }
1034             }
1035           } // done gathering page properties
1036 
1037           // Insert the goods into HDF data (title, link, tags, type)
1038           String title = hdf.getValue("page.title", "");
1039           title = title.replaceAll("\"", "'");
1040           // if there's a <span> in the title, get rid of it
1041           if (title.indexOf("<span") != -1) {
1042             String[] splitTitle = title.split("<span(.*?)</span>");
1043             title = splitTitle[0];
1044             for (int j = 1; j < splitTitle.length; j++) {
1045               title.concat(splitTitle[j]);
1046             }
1047           }
1048 
1049           StringBuilder tags =  new StringBuilder();
1050           String tagsList = hdf.getValue("page.tags", "");
1051           if (!tagsList.equals("")) {
1052             tagsList = tagsList.replaceAll("\"", "");
1053             String[] tagParts = tagsList.split(",");
1054             for (int iter = 0; iter < tagParts.length; iter++) {
1055               tags.append("\"");
1056               tags.append(tagParts[iter].trim());
1057               tags.append("\"");
1058               if (iter < tagParts.length - 1) {
1059                 tags.append(",");
1060               }
1061             }
1062           }
1063 
1064           String dirName = (webPath.indexOf("/") != -1)
1065                   ? webPath.substring(0, webPath.indexOf("/")) : "";
1066 
1067           if (!"".equals(title) &&
1068               !"intl".equals(dirName) &&
1069               !hdf.getBooleanValue("excludeFromSuggestions")) {
1070             data.setValue("docs.pages." + counter.i + ".label", title);
1071             data.setValue("docs.pages." + counter.i + ".link", webPath);
1072             data.setValue("docs.pages." + counter.i + ".tags", tags.toString());
1073             data.setValue("docs.pages." + counter.i + ".type", dirName);
1074             counter.i++;
1075           }
1076         }
1077       } else if (f.isDirectory()) {
1078         writeJdDirList(f, data, counter);
1079       }
1080     }
1081   }
1082 
cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable)1083   public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) {
1084     if (!notStrippable.add(cl)) {
1085       // slight optimization: if it already contains cl, it already contains
1086       // all of cl's parents
1087       return;
1088     }
1089     ClassInfo supr = cl.superclass();
1090     if (supr != null) {
1091       cantStripThis(supr, notStrippable);
1092     }
1093     for (ClassInfo iface : cl.interfaces()) {
1094       cantStripThis(iface, notStrippable);
1095     }
1096   }
1097 
getPrintableName(ClassInfo cl)1098   private static String getPrintableName(ClassInfo cl) {
1099     ClassInfo containingClass = cl.containingClass();
1100     if (containingClass != null) {
1101       // This is an inner class.
1102       String baseName = cl.name();
1103       baseName = baseName.substring(baseName.lastIndexOf('.') + 1);
1104       return getPrintableName(containingClass) + '$' + baseName;
1105     }
1106     return cl.qualifiedName();
1107   }
1108 
1109   /**
1110    * Writes the list of classes that must be present in order to provide the non-hidden APIs known
1111    * to javadoc.
1112    *
1113    * @param filename the path to the file to write the list to
1114    */
writeKeepList(String filename)1115   public static void writeKeepList(String filename) {
1116     HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
1117     ClassInfo[] all = Converter.allClasses();
1118     Arrays.sort(all); // just to make the file a little more readable
1119 
1120     // If a class is public and not hidden, then it and everything it derives
1121     // from cannot be stripped. Otherwise we can strip it.
1122     for (ClassInfo cl : all) {
1123       if (cl.isPublic() && !cl.isHiddenOrRemoved()) {
1124         cantStripThis(cl, notStrippable);
1125       }
1126     }
1127     PrintStream stream = null;
1128     try {
1129       stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(filename)));
1130       for (ClassInfo cl : notStrippable) {
1131         stream.println(getPrintableName(cl));
1132       }
1133     } catch (FileNotFoundException e) {
1134       System.err.println("error writing file: " + filename);
1135     } finally {
1136       if (stream != null) {
1137         stream.close();
1138       }
1139     }
1140   }
1141 
1142   private static PackageInfo[] sVisiblePackages = null;
1143 
choosePackages()1144   public static PackageInfo[] choosePackages() {
1145     if (sVisiblePackages != null) {
1146       return sVisiblePackages;
1147     }
1148 
1149     ClassInfo[] classes = Converter.rootClasses();
1150     SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
1151     for (ClassInfo cl : classes) {
1152       PackageInfo pkg = cl.containingPackage();
1153       String name;
1154       if (pkg == null) {
1155         name = "";
1156       } else {
1157         name = pkg.name();
1158       }
1159       sorted.put(name, pkg);
1160     }
1161 
1162     ArrayList<PackageInfo> result = new ArrayList<PackageInfo>();
1163 
1164     for (String s : sorted.keySet()) {
1165       PackageInfo pkg = sorted.get(s);
1166 
1167       if (pkg.isHiddenOrRemoved()) {
1168         continue;
1169       }
1170 
1171       boolean allHiddenOrRemoved = true;
1172       int pass = 0;
1173       ClassInfo[] classesToCheck = null;
1174       while (pass < 6) {
1175         switch (pass) {
1176           case 0:
1177             classesToCheck = pkg.ordinaryClasses();
1178             break;
1179           case 1:
1180             classesToCheck = pkg.enums();
1181             break;
1182           case 2:
1183             classesToCheck = pkg.errors();
1184             break;
1185           case 3:
1186             classesToCheck = pkg.exceptions();
1187             break;
1188           case 4:
1189             classesToCheck = pkg.interfaces();
1190             break;
1191           case 5:
1192             classesToCheck = pkg.annotations();
1193             break;
1194           default:
1195             System.err.println("Error reading package: " + pkg.name());
1196             break;
1197         }
1198         for (ClassInfo cl : classesToCheck) {
1199           if (!cl.isHiddenOrRemoved()) {
1200             allHiddenOrRemoved = false;
1201             break;
1202           }
1203         }
1204         if (!allHiddenOrRemoved) {
1205           break;
1206         }
1207         pass++;
1208       }
1209       if (allHiddenOrRemoved) {
1210         continue;
1211       }
1212 
1213       result.add(pkg);
1214     }
1215 
1216     sVisiblePackages = result.toArray(new PackageInfo[result.size()]);
1217     return sVisiblePackages;
1218   }
1219 
writePackages(String filename)1220   public static void writePackages(String filename) {
1221     Data data = makePackageHDF();
1222 
1223     int i = 0;
1224     for (PackageInfo pkg : choosePackages()) {
1225       writePackage(pkg);
1226 
1227       data.setValue("docs.packages." + i + ".name", pkg.name());
1228       data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
1229       TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags());
1230 
1231       i++;
1232     }
1233 
1234     setPageTitle(data, "Package Index");
1235 
1236     TagInfo.makeHDF(data, "root.descr", Converter.convertTags(root.inlineTags(), null));
1237 
1238     ClearPage.write(data, "packages.cs", filename);
1239     ClearPage.write(data, "package-list.cs", javadocDir + "package-list");
1240 
1241     Proofread.writePackages(filename, Converter.convertTags(root.inlineTags(), null));
1242   }
1243 
writePackage(PackageInfo pkg)1244   public static void writePackage(PackageInfo pkg) {
1245     // these this and the description are in the same directory,
1246     // so it's okay
1247     Data data = makePackageHDF();
1248 
1249     String name = pkg.name();
1250 
1251     data.setValue("package.name", name);
1252     data.setValue("package.since", pkg.getSince());
1253     data.setValue("package.descr", "...description...");
1254     pkg.setFederatedReferences(data, "package");
1255 
1256     makeClassListHDF(data, "package.annotations", ClassInfo.sortByName(pkg.annotations()));
1257     makeClassListHDF(data, "package.interfaces", ClassInfo.sortByName(pkg.interfaces()));
1258     makeClassListHDF(data, "package.classes", ClassInfo.sortByName(pkg.ordinaryClasses()));
1259     makeClassListHDF(data, "package.enums", ClassInfo.sortByName(pkg.enums()));
1260     makeClassListHDF(data, "package.exceptions", ClassInfo.sortByName(pkg.exceptions()));
1261     makeClassListHDF(data, "package.errors", ClassInfo.sortByName(pkg.errors()));
1262     TagInfo.makeHDF(data, "package.shortDescr", pkg.firstSentenceTags());
1263     TagInfo.makeHDF(data, "package.descr", pkg.inlineTags());
1264 
1265     String filename = pkg.htmlPage();
1266     setPageTitle(data, name);
1267     ClearPage.write(data, "package.cs", filename);
1268 
1269     Proofread.writePackage(filename, pkg.inlineTags());
1270   }
1271 
writeClassLists()1272   public static void writeClassLists() {
1273     int i;
1274     Data data = makePackageHDF();
1275 
1276     ClassInfo[] classes = PackageInfo.filterHiddenAndRemoved(
1277         Converter.convertClasses(root.classes()));
1278     if (classes.length == 0) {
1279       return;
1280     }
1281 
1282     Sorter[] sorted = new Sorter[classes.length];
1283     for (i = 0; i < sorted.length; i++) {
1284       ClassInfo cl = classes[i];
1285       String name = cl.name();
1286       sorted[i] = new Sorter(name, cl);
1287     }
1288 
1289     Arrays.sort(sorted);
1290 
1291     // make a pass and resolve ones that have the same name
1292     int firstMatch = 0;
1293     String lastName = sorted[0].label;
1294     for (i = 1; i < sorted.length; i++) {
1295       String s = sorted[i].label;
1296       if (!lastName.equals(s)) {
1297         if (firstMatch != i - 1) {
1298           // there were duplicates
1299           for (int j = firstMatch; j < i; j++) {
1300             PackageInfo pkg = ((ClassInfo) sorted[j].data).containingPackage();
1301             if (pkg != null) {
1302               sorted[j].label = sorted[j].label + " (" + pkg.name() + ")";
1303             }
1304           }
1305         }
1306         firstMatch = i;
1307         lastName = s;
1308       }
1309     }
1310 
1311     // and sort again
1312     Arrays.sort(sorted);
1313 
1314     for (i = 0; i < sorted.length; i++) {
1315       String s = sorted[i].label;
1316       ClassInfo cl = (ClassInfo) sorted[i].data;
1317       char first = Character.toUpperCase(s.charAt(0));
1318       cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i);
1319     }
1320 
1321     setPageTitle(data, "Class Index");
1322     ClearPage.write(data, "classes.cs", javadocDir + "classes" + htmlExtension);
1323   }
1324 
1325   // we use the word keywords because "index" means something else in html land
1326   // the user only ever sees the word index
1327   /*
1328    * public static void writeKeywords() { ArrayList<KeywordEntry> keywords = new
1329    * ArrayList<KeywordEntry>();
1330    *
1331    * ClassInfo[] classes = PackageInfo.filterHiddenAndRemoved(Converter.convertClasses(root.classes()));
1332    *
1333    * for (ClassInfo cl: classes) { cl.makeKeywordEntries(keywords); }
1334    *
1335    * HDF data = makeHDF();
1336    *
1337    * Collections.sort(keywords);
1338    *
1339    * int i=0; for (KeywordEntry entry: keywords) { String base = "keywords." + entry.firstChar() +
1340    * "." + i; entry.makeHDF(data, base); i++; }
1341    *
1342    * setPageTitle(data, "Index"); ClearPage.write(data, "keywords.cs", javadocDir + "keywords" +
1343    * htmlExtension); }
1344    */
1345 
writeHierarchy()1346   public static void writeHierarchy() {
1347     ClassInfo[] classes = Converter.rootClasses();
1348     ArrayList<ClassInfo> info = new ArrayList<ClassInfo>();
1349     for (ClassInfo cl : classes) {
1350       if (!cl.isHiddenOrRemoved()) {
1351         info.add(cl);
1352       }
1353     }
1354     Data data = makePackageHDF();
1355     Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()]));
1356     setPageTitle(data, "Class Hierarchy");
1357     ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension);
1358   }
1359 
writeClasses()1360   public static void writeClasses() {
1361     ClassInfo[] classes = Converter.rootClasses();
1362 
1363     for (ClassInfo cl : classes) {
1364       Data data = makePackageHDF();
1365       if (!cl.isHiddenOrRemoved()) {
1366         writeClass(cl, data);
1367       }
1368     }
1369   }
1370 
writeClass(ClassInfo cl, Data data)1371   public static void writeClass(ClassInfo cl, Data data) {
1372     cl.makeHDF(data);
1373     setPageTitle(data, cl.name());
1374     String outfile = cl.htmlPage();
1375     ClearPage.write(data, "class.cs", outfile);
1376     Proofread.writeClass(cl.htmlPage(), cl);
1377   }
1378 
makeClassListHDF(Data data, String base, ClassInfo[] classes)1379   public static void makeClassListHDF(Data data, String base, ClassInfo[] classes) {
1380     for (int i = 0; i < classes.length; i++) {
1381       ClassInfo cl = classes[i];
1382       if (!cl.isHiddenOrRemoved()) {
1383         cl.makeShortDescrHDF(data, base + "." + i);
1384       }
1385     }
1386   }
1387 
linkTarget(String source, String target)1388   public static String linkTarget(String source, String target) {
1389     String[] src = source.split("/");
1390     String[] tgt = target.split("/");
1391 
1392     int srclen = src.length;
1393     int tgtlen = tgt.length;
1394 
1395     int same = 0;
1396     while (same < (srclen - 1) && same < (tgtlen - 1) && (src[same].equals(tgt[same]))) {
1397       same++;
1398     }
1399 
1400     String s = "";
1401 
1402     int up = srclen - same - 1;
1403     for (int i = 0; i < up; i++) {
1404       s += "../";
1405     }
1406 
1407 
1408     int N = tgtlen - 1;
1409     for (int i = same; i < N; i++) {
1410       s += tgt[i] + '/';
1411     }
1412     s += tgt[tgtlen - 1];
1413 
1414     return s;
1415   }
1416 
1417   /**
1418    * Returns true if the given element has an @hide, @removed or @pending annotation.
1419    */
hasHideOrRemovedAnnotation(Doc doc)1420   private static boolean hasHideOrRemovedAnnotation(Doc doc) {
1421     String comment = doc.getRawCommentText();
1422     return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -1 ||
1423         comment.indexOf("@removed") != -1;
1424   }
1425 
1426   /**
1427    * Returns true if the given element is hidden.
1428    */
isHiddenOrRemoved(Doc doc)1429   private static boolean isHiddenOrRemoved(Doc doc) {
1430     // Methods, fields, constructors.
1431     if (doc instanceof MemberDoc) {
1432       return hasHideOrRemovedAnnotation(doc);
1433     }
1434 
1435     // Classes, interfaces, enums, annotation types.
1436     if (doc instanceof ClassDoc) {
1437       ClassDoc classDoc = (ClassDoc) doc;
1438 
1439       // Check the containing package.
1440       if (hasHideOrRemovedAnnotation(classDoc.containingPackage())) {
1441         return true;
1442       }
1443 
1444       // Check the class doc and containing class docs if this is a
1445       // nested class.
1446       ClassDoc current = classDoc;
1447       do {
1448         if (hasHideOrRemovedAnnotation(current)) {
1449           return true;
1450         }
1451 
1452         current = current.containingClass();
1453       } while (current != null);
1454     }
1455 
1456     return false;
1457   }
1458 
1459   /**
1460    * Filters out hidden and removed elements.
1461    */
filterHiddenAndRemoved(Object o, Class<?> expected)1462   private static Object filterHiddenAndRemoved(Object o, Class<?> expected) {
1463     if (o == null) {
1464       return null;
1465     }
1466 
1467     Class type = o.getClass();
1468     if (type.getName().startsWith("com.sun.")) {
1469       // TODO: Implement interfaces from superclasses, too.
1470       return Proxy
1471           .newProxyInstance(type.getClassLoader(), type.getInterfaces(), new HideHandler(o));
1472     } else if (o instanceof Object[]) {
1473       Class<?> componentType = expected.getComponentType();
1474       Object[] array = (Object[]) o;
1475       List<Object> list = new ArrayList<Object>(array.length);
1476       for (Object entry : array) {
1477         if ((entry instanceof Doc) && isHiddenOrRemoved((Doc) entry)) {
1478           continue;
1479         }
1480         list.add(filterHiddenAndRemoved(entry, componentType));
1481       }
1482       return list.toArray((Object[]) Array.newInstance(componentType, list.size()));
1483     } else {
1484       return o;
1485     }
1486   }
1487 
1488   /**
1489    * Filters hidden elements out of method return values.
1490    */
1491   private static class HideHandler implements InvocationHandler {
1492 
1493     private final Object target;
1494 
HideHandler(Object target)1495     public HideHandler(Object target) {
1496       this.target = target;
1497     }
1498 
invoke(Object proxy, Method method, Object[] args)1499     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1500       String methodName = method.getName();
1501       if (args != null) {
1502         if (methodName.equals("compareTo") || methodName.equals("equals")
1503             || methodName.equals("overrides") || methodName.equals("subclassOf")) {
1504           args[0] = unwrap(args[0]);
1505         }
1506       }
1507 
1508       if (methodName.equals("getRawCommentText")) {
1509         return filterComment((String) method.invoke(target, args));
1510       }
1511 
1512       // escape "&" in disjunctive types.
1513       if (proxy instanceof Type && methodName.equals("toString")) {
1514         return ((String) method.invoke(target, args)).replace("&", "&amp;");
1515       }
1516 
1517       try {
1518         return filterHiddenAndRemoved(method.invoke(target, args), method.getReturnType());
1519       } catch (InvocationTargetException e) {
1520         throw e.getTargetException();
1521       }
1522     }
1523 
filterComment(String s)1524     private String filterComment(String s) {
1525       if (s == null) {
1526         return null;
1527       }
1528 
1529       s = s.trim();
1530 
1531       // Work around off by one error
1532       while (s.length() >= 5 && s.charAt(s.length() - 5) == '{') {
1533         s += "&nbsp;";
1534       }
1535 
1536       return s;
1537     }
1538 
unwrap(Object proxy)1539     private static Object unwrap(Object proxy) {
1540       if (proxy instanceof Proxy) return ((HideHandler) Proxy.getInvocationHandler(proxy)).target;
1541       return proxy;
1542     }
1543   }
1544 
1545   /**
1546    * Collect the values used by the Dev tools and write them in files packaged with the SDK
1547    *
1548    * @param output the ouput directory for the files.
1549    */
writeSdkValues(String output)1550   private static void writeSdkValues(String output) {
1551     ArrayList<String> activityActions = new ArrayList<String>();
1552     ArrayList<String> broadcastActions = new ArrayList<String>();
1553     ArrayList<String> serviceActions = new ArrayList<String>();
1554     ArrayList<String> categories = new ArrayList<String>();
1555     ArrayList<String> features = new ArrayList<String>();
1556 
1557     ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>();
1558     ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>();
1559     ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>();
1560 
1561     ClassInfo[] classes = Converter.allClasses();
1562 
1563     // The topmost LayoutParams class - android.view.ViewGroup.LayoutParams
1564     ClassInfo topLayoutParams = null;
1565 
1566     // Go through all the fields of all the classes, looking SDK stuff.
1567     for (ClassInfo clazz : classes) {
1568 
1569       // first check constant fields for the SdkConstant annotation.
1570       ArrayList<FieldInfo> fields = clazz.allSelfFields();
1571       for (FieldInfo field : fields) {
1572         Object cValue = field.constantValue();
1573         if (cValue != null) {
1574             ArrayList<AnnotationInstanceInfo> annotations = field.annotations();
1575           if (!annotations.isEmpty()) {
1576             for (AnnotationInstanceInfo annotation : annotations) {
1577               if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) {
1578                 if (!annotation.elementValues().isEmpty()) {
1579                   String type = annotation.elementValues().get(0).valueString();
1580                   if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) {
1581                     activityActions.add(cValue.toString());
1582                   } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) {
1583                     broadcastActions.add(cValue.toString());
1584                   } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) {
1585                     serviceActions.add(cValue.toString());
1586                   } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) {
1587                     categories.add(cValue.toString());
1588                   } else if (SDK_CONSTANT_TYPE_FEATURE.equals(type)) {
1589                     features.add(cValue.toString());
1590                   }
1591                 }
1592                 break;
1593               }
1594             }
1595           }
1596         }
1597       }
1598 
1599       // Now check the class for @Widget or if its in the android.widget package
1600       // (unless the class is hidden or abstract, or non public)
1601       if (clazz.isHiddenOrRemoved() == false && clazz.isPublic() && clazz.isAbstract() == false) {
1602         boolean annotated = false;
1603         ArrayList<AnnotationInstanceInfo> annotations = clazz.annotations();
1604         if (!annotations.isEmpty()) {
1605           for (AnnotationInstanceInfo annotation : annotations) {
1606             if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) {
1607               widgets.add(clazz);
1608               annotated = true;
1609               break;
1610             } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) {
1611               layouts.add(clazz);
1612               annotated = true;
1613               break;
1614             }
1615           }
1616         }
1617 
1618         if (annotated == false) {
1619           if (topLayoutParams == null
1620               && "android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
1621             topLayoutParams = clazz;
1622           }
1623           // let's check if this is inside android.widget or android.view
1624           if (isIncludedPackage(clazz)) {
1625             // now we check what this class inherits either from android.view.ViewGroup
1626             // or android.view.View, or android.view.ViewGroup.LayoutParams
1627             int type = checkInheritance(clazz);
1628             switch (type) {
1629               case TYPE_WIDGET:
1630                 widgets.add(clazz);
1631                 break;
1632               case TYPE_LAYOUT:
1633                 layouts.add(clazz);
1634                 break;
1635               case TYPE_LAYOUT_PARAM:
1636                 layoutParams.add(clazz);
1637                 break;
1638             }
1639           }
1640         }
1641       }
1642     }
1643 
1644     // now write the files, whether or not the list are empty.
1645     // the SDK built requires those files to be present.
1646 
1647     Collections.sort(activityActions);
1648     writeValues(output + "/activity_actions.txt", activityActions);
1649 
1650     Collections.sort(broadcastActions);
1651     writeValues(output + "/broadcast_actions.txt", broadcastActions);
1652 
1653     Collections.sort(serviceActions);
1654     writeValues(output + "/service_actions.txt", serviceActions);
1655 
1656     Collections.sort(categories);
1657     writeValues(output + "/categories.txt", categories);
1658 
1659     Collections.sort(features);
1660     writeValues(output + "/features.txt", features);
1661 
1662     // before writing the list of classes, we do some checks, to make sure the layout params
1663     // are enclosed by a layout class (and not one that has been declared as a widget)
1664     for (int i = 0; i < layoutParams.size();) {
1665       ClassInfo clazz = layoutParams.get(i);
1666       ClassInfo containingClass = clazz.containingClass();
1667       boolean remove = containingClass == null || layouts.indexOf(containingClass) == -1;
1668       // Also ensure that super classes of the layout params are in android.widget or android.view.
1669       while (!remove && (clazz = clazz.superclass()) != null && !clazz.equals(topLayoutParams)) {
1670         remove = !isIncludedPackage(clazz);
1671       }
1672       if (remove) {
1673         layoutParams.remove(i);
1674       } else {
1675         i++;
1676       }
1677     }
1678 
1679     writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams);
1680   }
1681 
1682   /**
1683    * Check if the clazz is in package android.view or android.widget
1684    */
isIncludedPackage(ClassInfo clazz)1685   private static boolean isIncludedPackage(ClassInfo clazz) {
1686     String pckg = clazz.containingPackage().name();
1687     return "android.widget".equals(pckg) || "android.view".equals(pckg);
1688   }
1689 
1690   /**
1691    * Writes a list of values into a text files.
1692    *
1693    * @param pathname the absolute os path of the output file.
1694    * @param values the list of values to write.
1695    */
writeValues(String pathname, ArrayList<String> values)1696   private static void writeValues(String pathname, ArrayList<String> values) {
1697     FileWriter fw = null;
1698     BufferedWriter bw = null;
1699     try {
1700       fw = new FileWriter(pathname, false);
1701       bw = new BufferedWriter(fw);
1702 
1703       for (String value : values) {
1704         bw.append(value).append('\n');
1705       }
1706     } catch (IOException e) {
1707       // pass for now
1708     } finally {
1709       try {
1710         if (bw != null) bw.close();
1711       } catch (IOException e) {
1712         // pass for now
1713       }
1714       try {
1715         if (fw != null) fw.close();
1716       } catch (IOException e) {
1717         // pass for now
1718       }
1719     }
1720   }
1721 
1722   /**
1723    * Writes the widget/layout/layout param classes into a text files.
1724    *
1725    * @param pathname the absolute os path of the output file.
1726    * @param widgets the list of widget classes to write.
1727    * @param layouts the list of layout classes to write.
1728    * @param layoutParams the list of layout param classes to write.
1729    */
writeClasses(String pathname, ArrayList<ClassInfo> widgets, ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams)1730   private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets,
1731       ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) {
1732     FileWriter fw = null;
1733     BufferedWriter bw = null;
1734     try {
1735       fw = new FileWriter(pathname, false);
1736       bw = new BufferedWriter(fw);
1737 
1738       // write the 3 types of classes.
1739       for (ClassInfo clazz : widgets) {
1740         writeClass(bw, clazz, 'W');
1741       }
1742       for (ClassInfo clazz : layoutParams) {
1743         writeClass(bw, clazz, 'P');
1744       }
1745       for (ClassInfo clazz : layouts) {
1746         writeClass(bw, clazz, 'L');
1747       }
1748     } catch (IOException e) {
1749       // pass for now
1750     } finally {
1751       try {
1752         if (bw != null) bw.close();
1753       } catch (IOException e) {
1754         // pass for now
1755       }
1756       try {
1757         if (fw != null) fw.close();
1758       } catch (IOException e) {
1759         // pass for now
1760       }
1761     }
1762   }
1763 
1764   /**
1765    * Writes a class name and its super class names into a {@link BufferedWriter}.
1766    *
1767    * @param writer the BufferedWriter to write into
1768    * @param clazz the class to write
1769    * @param prefix the prefix to put at the beginning of the line.
1770    * @throws IOException
1771    */
writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)1772   private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)
1773       throws IOException {
1774     writer.append(prefix).append(clazz.qualifiedName());
1775     ClassInfo superClass = clazz;
1776     while ((superClass = superClass.superclass()) != null) {
1777       writer.append(' ').append(superClass.qualifiedName());
1778     }
1779     writer.append('\n');
1780   }
1781 
1782   /**
1783    * Checks the inheritance of {@link ClassInfo} objects. This method return
1784    * <ul>
1785    * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li>
1786    * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li>
1787    * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends
1788    * <code>android.view.ViewGroup$LayoutParams</code></li>
1789    * <li>{@link #TYPE_NONE}: in all other cases</li>
1790    * </ul>
1791    *
1792    * @param clazz the {@link ClassInfo} to check.
1793    */
checkInheritance(ClassInfo clazz)1794   private static int checkInheritance(ClassInfo clazz) {
1795     if ("android.view.ViewGroup".equals(clazz.qualifiedName())) {
1796       return TYPE_LAYOUT;
1797     } else if ("android.view.View".equals(clazz.qualifiedName())) {
1798       return TYPE_WIDGET;
1799     } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
1800       return TYPE_LAYOUT_PARAM;
1801     }
1802 
1803     ClassInfo parent = clazz.superclass();
1804     if (parent != null) {
1805       return checkInheritance(parent);
1806     }
1807 
1808     return TYPE_NONE;
1809   }
1810 
1811   /**
1812    * Ensures a trailing '/' at the end of a string.
1813    */
ensureSlash(String path)1814   static String ensureSlash(String path) {
1815     return path.endsWith("/") ? path : path + "/";
1816   }
1817 
1818   /**
1819   * Process sample projects. Generate the TOC for the samples groups and project
1820   * and write it to a cs var, which is then written to files during templating to
1821   * html output. Collect metadata from sample project _index.jd files. Copy html
1822   * and specific source file types to the output directory.
1823   */
writeSamples(boolean offlineMode, ArrayList<SampleCode> sampleCodes, boolean sortNavByGroups)1824   public static void writeSamples(boolean offlineMode, ArrayList<SampleCode> sampleCodes,
1825       boolean sortNavByGroups) {
1826     samplesNavTree = makeHDF();
1827 
1828     // Go through samples processing files. Create a root list for SC nodes,
1829     // pass it to SCs for their NavTree children and append them.
1830     List<SampleCode.Node> samplesList = new ArrayList<SampleCode.Node>();
1831     List<SampleCode.Node> sampleGroupsRootNodes = null;
1832     for (SampleCode sc : sampleCodes) {
1833       samplesList.add(sc.setSamplesTOC(offlineMode));
1834      }
1835     if (sortNavByGroups) {
1836       sampleGroupsRootNodes = new ArrayList<SampleCode.Node>();
1837       for (SampleCode gsc : sampleCodeGroups) {
1838         String link =  ClearPage.toroot + "samples/" + gsc.mTitle.replaceAll(" ", "").trim().toLowerCase() + ".html";
1839         sampleGroupsRootNodes.add(new SampleCode.Node.Builder().setLabel(gsc.mTitle).setLink(link).setType("groupholder").build());
1840       }
1841     }
1842     // Pass full samplesList to SC to render the samples TOC to sampleNavTree hdf
1843     if (!offlineMode) {
1844       SampleCode.writeSamplesNavTree(samplesList, sampleGroupsRootNodes);
1845     }
1846     // Iterate the samplecode projects writing the files to out
1847     for (SampleCode sc : sampleCodes) {
1848       sc.writeSamplesFiles(offlineMode);
1849     }
1850   }
1851 
1852   /**
1853   * Given an initial samples directory root, walk through the directory collecting
1854   * sample code project roots and adding them to an array of SampleCodes.
1855   * @param rootDir Root directory holding all browseable sample code projects,
1856   *        defined in frameworks/base/Android.mk as "-sampleDir path".
1857   */
getSampleProjects(File rootDir)1858   public static void getSampleProjects(File rootDir) {
1859     for (File f : rootDir.listFiles()) {
1860       String name = f.getName();
1861       if (f.isDirectory()) {
1862         if (isValidSampleProjectRoot(f)) {
1863           sampleCodes.add(new SampleCode(f.getAbsolutePath(), "samples/" + name, name));
1864         } else {
1865           getSampleProjects(f);
1866         }
1867       }
1868     }
1869   }
1870 
1871   /**
1872   * Test whether a given directory is the root directory for a sample code project.
1873   * Root directories must contain a valid _index.jd file and a src/ directory
1874   * or a module directory that contains a src/ directory.
1875   */
isValidSampleProjectRoot(File dir)1876   public static boolean isValidSampleProjectRoot(File dir) {
1877     File indexJd = new File(dir, "_index.jd");
1878     if (!indexJd.exists()) {
1879       return false;
1880     }
1881     File srcDir = new File(dir, "src");
1882     if (srcDir.exists()) {
1883       return true;
1884     } else {
1885       // Look for a src/ directory one level below the root directory, so
1886       // modules are supported.
1887       for (File childDir : dir.listFiles()) {
1888         if (childDir.isDirectory()) {
1889           srcDir = new File(childDir, "src");
1890           if (srcDir.exists()) {
1891             return true;
1892           }
1893         }
1894       }
1895       return false;
1896     }
1897   }
1898 
getDocumentationStringForAnnotation(String annotationName)1899   public static String getDocumentationStringForAnnotation(String annotationName) {
1900     if (!documentAnnotations) return null;
1901     if (annotationDocumentationMap == null) {
1902       // parse the file for map
1903       annotationDocumentationMap = new HashMap<String, String>();
1904       try {
1905         BufferedReader in = new BufferedReader(
1906             new FileReader(documentAnnotationsPath));
1907         try {
1908           String line = in.readLine();
1909           String[] split;
1910           while (line != null) {
1911             split = line.split(":");
1912             annotationDocumentationMap.put(split[0], split[1]);
1913             line = in.readLine();
1914           }
1915         } finally {
1916           in.close();
1917         }
1918       } catch (IOException e) {
1919         System.err.println("Unable to open annotations documentation file for reading: "
1920             + documentAnnotationsPath);
1921       }
1922     }
1923     return annotationDocumentationMap.get(annotationName);
1924   }
1925 
1926 }
1927