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