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