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