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