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