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