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