1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.doclava;
18 
19 import com.google.clearsilver.jsilver.JSilver;
20 import com.google.clearsilver.jsilver.data.Data;
21 import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader;
22 import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader;
23 import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader;
24 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
25 
26 import com.sun.javadoc.*;
27 
28 import java.util.*;
29 import java.util.jar.JarFile;
30 import java.util.regex.Matcher;
31 import java.io.*;
32 import java.lang.reflect.Proxy;
33 import java.lang.reflect.Array;
34 import java.lang.reflect.InvocationHandler;
35 import java.lang.reflect.InvocationTargetException;
36 import java.lang.reflect.Method;
37 import java.net.MalformedURLException;
38 import java.net.URL;
39 
40 public 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 
checkLevel(int level)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    */
parseComments()117   public static boolean parseComments() {
118     return generateDocs || parseComments;
119   }
120 
checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv, boolean hidden)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 
main(String[] args)141   public static void main(String[] args) {
142     com.sun.tools.javadoc.Main.execute(args);
143   }
144 
start(RootDoc r)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 
writeIndex()413   private static void writeIndex() {
414     Data data = makeHDF();
415     ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension);
416   }
417 
readTemplateSettings()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 
readKnownTagsFiles(HashSet<String> knownTags, ArrayList<String> knownTagsFiles)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 
escape(String s)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 
setPageTitle(Data data, String title)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 
languageVersion()530   public static LanguageVersion languageVersion() {
531     return LanguageVersion.JAVA_1_5;
532   }
533 
534 
optionLength(String option)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   }
validOptions(String[][] options, DocErrorReporter r)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 
makeHDF()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 
makePackageHDF()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 
writeDirectory(File dir, String relative, JSilver js)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 
writeHTMLPages()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 
writeAssets()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 */
writeLists()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 */
writeJdDirList(File dir, Data data, Iterator counter)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 
cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable)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 
getPrintableName(ClassInfo cl)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    */
writeKeepList(String filename)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 
choosePackages()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 
writePackages(String filename)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 
writePackage(PackageInfo pkg)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 
writeClassLists()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 
writeHierarchy()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 
writeClasses()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 
writeClass(ClassInfo cl, Data data)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 
makeClassListHDF(Data data, String base, ClassInfo[] classes)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 
linkTarget(String source, String target)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    */
hasHideOrRemovedAnnotation(Doc doc)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    */
isHiddenOrRemoved(Doc doc)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    */
filterHiddenAndRemoved(Object o, Class<?> expected)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 
HideHandler(Object target)1425     public HideHandler(Object target) {
1426       this.target = target;
1427     }
1428 
invoke(Object proxy, Method method, Object[] args)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 
filterComment(String s)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 
unwrap(Object proxy)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    */
writeSdkValues(String output)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    */
isIncludedPackage(ClassInfo clazz)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    */
writeValues(String pathname, ArrayList<String> values)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    */
writeClasses(String pathname, ArrayList<ClassInfo> widgets, ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams)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    */
writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)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    */
checkInheritance(ClassInfo clazz)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    */
ensureSlash(String path)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   */
writeSamples(boolean offlineMode, ArrayList<SampleCode> sampleCodes, boolean sortNavByGroups)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   */
getSampleProjects(File rootDir)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   */
isValidSampleProjectRoot(File dir)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 
getDocumentationStringForAnnotation(String annotationName)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