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