• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.doclava;
18 
19 import com.google.clearsilver.jsilver.JSilver;
20 import com.google.clearsilver.jsilver.data.Data;
21 import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader;
22 import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader;
23 import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader;
24 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
25 
26 import com.sun.javadoc.*;
27 
28 import java.util.*;
29 import java.util.jar.JarFile;
30 import java.io.*;
31 import java.lang.reflect.Proxy;
32 import java.lang.reflect.Array;
33 import java.lang.reflect.InvocationHandler;
34 import java.lang.reflect.InvocationTargetException;
35 import java.lang.reflect.Method;
36 import java.net.MalformedURLException;
37 import java.net.URL;
38 
39 public class Doclava {
40   private static final String SDK_CONSTANT_ANNOTATION = "android.annotation.SdkConstant";
41   private static final String SDK_CONSTANT_TYPE_ACTIVITY_ACTION =
42       "android.annotation.SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION";
43   private static final String SDK_CONSTANT_TYPE_BROADCAST_ACTION =
44       "android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION";
45   private static final String SDK_CONSTANT_TYPE_SERVICE_ACTION =
46       "android.annotation.SdkConstant.SdkConstantType.SERVICE_INTENT_ACTION";
47   private static final String SDK_CONSTANT_TYPE_CATEGORY =
48       "android.annotation.SdkConstant.SdkConstantType.INTENT_CATEGORY";
49   private static final String SDK_CONSTANT_TYPE_FEATURE =
50       "android.annotation.SdkConstant.SdkConstantType.FEATURE";
51   private static final String SDK_WIDGET_ANNOTATION = "android.annotation.Widget";
52   private static final String SDK_LAYOUT_ANNOTATION = "android.annotation.Layout";
53 
54   private static final int TYPE_NONE = 0;
55   private static final int TYPE_WIDGET = 1;
56   private static final int TYPE_LAYOUT = 2;
57   private static final int TYPE_LAYOUT_PARAM = 3;
58 
59   public static final int SHOW_PUBLIC = 0x00000001;
60   public static final int SHOW_PROTECTED = 0x00000003;
61   public static final int SHOW_PACKAGE = 0x00000007;
62   public static final int SHOW_PRIVATE = 0x0000000f;
63   public static final int SHOW_HIDDEN = 0x0000001f;
64 
65   public static int showLevel = SHOW_PROTECTED;
66 
67   public static final String javadocDir = "reference/";
68   public static String htmlExtension;
69 
70   public static RootDoc root;
71   public static ArrayList<String[]> mHDFData = new ArrayList<String[]>();
72   public static Map<Character, String> escapeChars = new HashMap<Character, String>();
73   public static String title = "";
74   public static SinceTagger sinceTagger = new SinceTagger();
75   public static HashSet<String> knownTags = new HashSet<String>();
76   public static FederationTagger federationTagger = new FederationTagger();
77   private static boolean generateDocs = true;
78   private static boolean parseComments = false;
79 
80   public static JSilver jSilver = null;
81 
checkLevel(int level)82   public static boolean checkLevel(int level) {
83     return (showLevel & level) == level;
84   }
85 
86   /**
87    * Returns true if we should parse javadoc comments,
88    * reporting errors in the process.
89    */
parseComments()90   public static boolean parseComments() {
91     return generateDocs || parseComments;
92   }
93 
checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv, boolean hidden)94   public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv,
95       boolean hidden) {
96     int level = 0;
97     if (hidden && !checkLevel(SHOW_HIDDEN)) {
98       return false;
99     }
100     if (pub && checkLevel(SHOW_PUBLIC)) {
101       return true;
102     }
103     if (prot && checkLevel(SHOW_PROTECTED)) {
104       return true;
105     }
106     if (pkgp && checkLevel(SHOW_PACKAGE)) {
107       return true;
108     }
109     if (priv && checkLevel(SHOW_PRIVATE)) {
110       return true;
111     }
112     return false;
113   }
114 
main(String[] args)115   public static void main(String[] args) {
116     com.sun.tools.javadoc.Main.execute(args);
117   }
118 
start(RootDoc r)119   public static boolean start(RootDoc r) {
120     long startTime = System.nanoTime();
121     String keepListFile = null;
122     String proofreadFile = null;
123     String todoFile = null;
124     String sdkValuePath = null;
125     ArrayList<SampleCode> sampleCodes = new ArrayList<SampleCode>();
126     String stubsDir = null;
127     // Create the dependency graph for the stubs  directory
128     boolean offlineMode = false;
129     String apiFile = null;
130     String debugStubsFile = "";
131     HashSet<String> stubPackages = null;
132     ArrayList<String> knownTagsFiles = new ArrayList<String>();
133 
134     root = r;
135 
136     String[][] options = r.options();
137     for (String[] a : options) {
138       if (a[0].equals("-d")) {
139         ClearPage.outputDir = a[1];
140       } else if (a[0].equals("-templatedir")) {
141         ClearPage.addTemplateDir(a[1]);
142       } else if (a[0].equals("-hdf")) {
143         mHDFData.add(new String[] {a[1], a[2]});
144       } else if (a[0].equals("-knowntags")) {
145         knownTagsFiles.add(a[1]);
146       } else if (a[0].equals("-toroot")) {
147         ClearPage.toroot = a[1];
148       } else if (a[0].equals("-samplecode")) {
149         sampleCodes.add(new SampleCode(a[1], a[2], a[3]));
150       } else if (a[0].equals("-htmldir")) {
151         ClearPage.htmlDirs.add(a[1]);
152       } else if (a[0].equals("-title")) {
153         Doclava.title = a[1];
154       } else if (a[0].equals("-werror")) {
155         Errors.setWarningsAreErrors(true);
156       } else if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-hide")) {
157         try {
158           int level = -1;
159           if (a[0].equals("-error")) {
160             level = Errors.ERROR;
161           } else if (a[0].equals("-warning")) {
162             level = Errors.WARNING;
163           } else if (a[0].equals("-hide")) {
164             level = Errors.HIDDEN;
165           }
166           Errors.setErrorLevel(Integer.parseInt(a[1]), level);
167         } catch (NumberFormatException e) {
168           // already printed below
169           return false;
170         }
171       } else if (a[0].equals("-keeplist")) {
172         keepListFile = a[1];
173       } else if (a[0].equals("-proofread")) {
174         proofreadFile = a[1];
175       } else if (a[0].equals("-todo")) {
176         todoFile = a[1];
177       } else if (a[0].equals("-public")) {
178         showLevel = SHOW_PUBLIC;
179       } else if (a[0].equals("-protected")) {
180         showLevel = SHOW_PROTECTED;
181       } else if (a[0].equals("-package")) {
182         showLevel = SHOW_PACKAGE;
183       } else if (a[0].equals("-private")) {
184         showLevel = SHOW_PRIVATE;
185       } else if (a[0].equals("-hidden")) {
186         showLevel = SHOW_HIDDEN;
187       } else if (a[0].equals("-stubs")) {
188         stubsDir = a[1];
189       } else if (a[0].equals("-stubpackages")) {
190         stubPackages = new HashSet<String>();
191         for (String pkg : a[1].split(":")) {
192           stubPackages.add(pkg);
193         }
194       } else if (a[0].equals("-sdkvalues")) {
195         sdkValuePath = a[1];
196       } else if (a[0].equals("-api")) {
197         apiFile = a[1];
198       } else if (a[0].equals("-nodocs")) {
199         generateDocs = false;
200       } else if (a[0].equals("-parsecomments")) {
201         parseComments = true;
202       } else if (a[0].equals("-since")) {
203         sinceTagger.addVersion(a[1], a[2]);
204       } else if (a[0].equals("-offlinemode")) {
205         offlineMode = true;
206       } else if (a[0].equals("-federate")) {
207         try {
208           String name = a[1];
209           URL federationURL = new URL(a[2]);
210           federationTagger.addSite(name, federationURL);
211         } catch (MalformedURLException e) {
212           System.err.println("Could not parse URL for federation: " + a[1]);
213           return false;
214         }
215       }
216     }
217 
218     if (!readKnownTagsFiles(knownTags, knownTagsFiles)) {
219       return false;
220     }
221 
222     // Set up the data structures
223     Converter.makeInfo(r);
224 
225     if (generateDocs) {
226       ClearPage.addBundledTemplateDir("assets/customizations");
227       ClearPage.addBundledTemplateDir("assets/templates");
228 
229       List<ResourceLoader> resourceLoaders = new ArrayList<ResourceLoader>();
230       List<String> templates = ClearPage.getTemplateDirs();
231       for (String tmpl : templates) {
232         resourceLoaders.add(new FileSystemResourceLoader(tmpl));
233       }
234 
235       templates = ClearPage.getBundledTemplateDirs();
236       for (String tmpl : templates) {
237           // TODO - remove commented line - it's here for debugging purposes
238         //  resourceLoaders.add(new FileSystemResourceLoader("/Volumes/Android/master/external/doclava/res/" + tmpl));
239         resourceLoaders.add(new ClassResourceLoader(Doclava.class, '/'+tmpl));
240       }
241 
242       ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders);
243       jSilver = new JSilver(compositeResourceLoader);
244 
245       if (!Doclava.readTemplateSettings()) {
246         return false;
247       }
248 
249       //startTime = System.nanoTime();
250 
251       // Apply @since tags from the XML file
252       sinceTagger.tagAll(Converter.rootClasses());
253 
254       // Apply details of federated documentation
255       federationTagger.tagAll(Converter.rootClasses());
256 
257       // Files for proofreading
258       if (proofreadFile != null) {
259         Proofread.initProofread(proofreadFile);
260       }
261       if (todoFile != null) {
262         TodoFile.writeTodoFile(todoFile);
263       }
264 
265       // HTML Pages
266       if (!ClearPage.htmlDirs.isEmpty()) {
267         writeHTMLPages();
268       }
269 
270       writeAssets();
271 
272       // Navigation tree
273       NavTree.writeNavTree(javadocDir);
274 
275       // Packages Pages
276       writePackages(javadocDir + "packages" + htmlExtension);
277 
278       // Classes
279       writeClassLists();
280       writeClasses();
281       writeHierarchy();
282       // writeKeywords();
283 
284       // Lists for JavaScript
285       writeLists();
286       if (keepListFile != null) {
287         writeKeepList(keepListFile);
288       }
289 
290       // Sample Code
291       for (SampleCode sc : sampleCodes) {
292         sc.write(offlineMode);
293       }
294 
295       // Index page
296       writeIndex();
297 
298       Proofread.finishProofread(proofreadFile);
299 
300       if (sdkValuePath != null) {
301         writeSdkValues(sdkValuePath);
302       }
303     }
304 
305     // Stubs
306     if (stubsDir != null || apiFile != null) {
307       Stubs.writeStubsAndApi(stubsDir, apiFile, stubPackages);
308     }
309 
310     Errors.printErrors();
311 
312     long time = System.nanoTime() - startTime;
313     System.out.println("DroidDoc took " + (time / 1000000000) + " sec. to write docs to "
314         + ClearPage.outputDir);
315 
316     return !Errors.hadError;
317   }
318 
writeIndex()319   private static void writeIndex() {
320     Data data = makeHDF();
321     ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension);
322   }
323 
readTemplateSettings()324   private static boolean readTemplateSettings() {
325     Data data = makeHDF();
326 
327     // The .html extension is hard-coded in several .cs files,
328     // and so you cannot currently set it as a property.
329     htmlExtension = ".html";
330     // htmlExtension = data.getValue("template.extension", ".html");
331     int i = 0;
332     while (true) {
333       String k = data.getValue("template.escape." + i + ".key", "");
334       String v = data.getValue("template.escape." + i + ".value", "");
335       if ("".equals(k)) {
336         break;
337       }
338       if (k.length() != 1) {
339         System.err.println("template.escape." + i + ".key must have a length of 1: " + k);
340         return false;
341       }
342       escapeChars.put(k.charAt(0), v);
343       i++;
344     }
345     return true;
346   }
347 
readKnownTagsFiles(HashSet<String> knownTags, ArrayList<String> knownTagsFiles)348     private static boolean readKnownTagsFiles(HashSet<String> knownTags,
349             ArrayList<String> knownTagsFiles) {
350         for (String fn: knownTagsFiles) {
351            BufferedReader in = null;
352            try {
353                in = new BufferedReader(new FileReader(fn));
354                int lineno = 0;
355                boolean fail = false;
356                while (true) {
357                    lineno++;
358                    String line = in.readLine();
359                    if (line == null) {
360                        break;
361                    }
362                    line = line.trim();
363                    if (line.length() == 0) {
364                        continue;
365                    } else if (line.charAt(0) == '#') {
366                        continue;
367                    }
368                    String[] words = line.split("\\s+", 2);
369                    if (words.length == 2) {
370                        if (words[1].charAt(0) != '#') {
371                            System.err.println(fn + ":" + lineno
372                                    + ": Only one tag allowed per line: " + line);
373                            fail = true;
374                            continue;
375                        }
376                    }
377                    knownTags.add(words[0]);
378                }
379                if (fail) {
380                    return false;
381                }
382            } catch (IOException ex) {
383                System.err.println("Error reading file: " + fn + " (" + ex.getMessage() + ")");
384                return false;
385            } finally {
386                if (in != null) {
387                    try {
388                        in.close();
389                    } catch (IOException e) {
390                    }
391                }
392            }
393         }
394         return true;
395     }
396 
escape(String s)397   public static String escape(String s) {
398     if (escapeChars.size() == 0) {
399       return s;
400     }
401     StringBuffer b = null;
402     int begin = 0;
403     final int N = s.length();
404     for (int i = 0; i < N; i++) {
405       char c = s.charAt(i);
406       String mapped = escapeChars.get(c);
407       if (mapped != null) {
408         if (b == null) {
409           b = new StringBuffer(s.length() + mapped.length());
410         }
411         if (begin != i) {
412           b.append(s.substring(begin, i));
413         }
414         b.append(mapped);
415         begin = i + 1;
416       }
417     }
418     if (b != null) {
419       if (begin != N) {
420         b.append(s.substring(begin, N));
421       }
422       return b.toString();
423     }
424     return s;
425   }
426 
setPageTitle(Data data, String title)427   public static void setPageTitle(Data data, String title) {
428     String s = title;
429     if (Doclava.title.length() > 0) {
430       s += " - " + Doclava.title;
431     }
432     data.setValue("page.title", s);
433   }
434 
435 
languageVersion()436   public static LanguageVersion languageVersion() {
437     return LanguageVersion.JAVA_1_5;
438   }
439 
440 
optionLength(String option)441   public static int optionLength(String option) {
442     if (option.equals("-d")) {
443       return 2;
444     }
445     if (option.equals("-templatedir")) {
446       return 2;
447     }
448     if (option.equals("-hdf")) {
449       return 3;
450     }
451     if (option.equals("-knowntags")) {
452       return 2;
453     }
454     if (option.equals("-toroot")) {
455       return 2;
456     }
457     if (option.equals("-samplecode")) {
458       return 4;
459     }
460     if (option.equals("-htmldir")) {
461       return 2;
462     }
463     if (option.equals("-title")) {
464       return 2;
465     }
466     if (option.equals("-werror")) {
467       return 1;
468     }
469     if (option.equals("-hide")) {
470       return 2;
471     }
472     if (option.equals("-warning")) {
473       return 2;
474     }
475     if (option.equals("-error")) {
476       return 2;
477     }
478     if (option.equals("-keeplist")) {
479       return 2;
480     }
481     if (option.equals("-proofread")) {
482       return 2;
483     }
484     if (option.equals("-todo")) {
485       return 2;
486     }
487     if (option.equals("-public")) {
488       return 1;
489     }
490     if (option.equals("-protected")) {
491       return 1;
492     }
493     if (option.equals("-package")) {
494       return 1;
495     }
496     if (option.equals("-private")) {
497       return 1;
498     }
499     if (option.equals("-hidden")) {
500       return 1;
501     }
502     if (option.equals("-stubs")) {
503       return 2;
504     }
505     if (option.equals("-stubpackages")) {
506       return 2;
507     }
508     if (option.equals("-sdkvalues")) {
509       return 2;
510     }
511     if (option.equals("-api")) {
512       return 2;
513     }
514     if (option.equals("-nodocs")) {
515       return 1;
516     }
517     if (option.equals("-parsecomments")) {
518       return 1;
519     }
520     if (option.equals("-since")) {
521       return 3;
522     }
523     if (option.equals("-offlinemode")) {
524       return 1;
525     }
526     if (option.equals("-federate")) {
527       return 3;
528     }
529     return 0;
530   }
531 
validOptions(String[][] options, DocErrorReporter r)532   public static boolean validOptions(String[][] options, DocErrorReporter r) {
533     for (String[] a : options) {
534       if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-hide")) {
535         try {
536           Integer.parseInt(a[1]);
537         } catch (NumberFormatException e) {
538           r.printError("bad -" + a[0] + " value must be a number: " + a[1]);
539           return false;
540         }
541       }
542     }
543 
544     return true;
545   }
546 
makeHDF()547   public static Data makeHDF() {
548     Data data = jSilver.createData();
549 
550     for (String[] p : mHDFData) {
551       data.setValue(p[0], p[1]);
552     }
553 
554     return data;
555   }
556 
557 
558 
makePackageHDF()559   public static Data makePackageHDF() {
560     Data data = makeHDF();
561     ClassInfo[] classes = Converter.rootClasses();
562 
563     SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
564     for (ClassInfo cl : classes) {
565       PackageInfo pkg = cl.containingPackage();
566       String name;
567       if (pkg == null) {
568         name = "";
569       } else {
570         name = pkg.name();
571       }
572       sorted.put(name, pkg);
573     }
574 
575     int i = 0;
576     for (String s : sorted.keySet()) {
577       PackageInfo pkg = sorted.get(s);
578 
579       if (pkg.isHidden()) {
580         continue;
581       }
582       Boolean allHidden = true;
583       int pass = 0;
584       ClassInfo[] classesToCheck = null;
585       while (pass < 5) {
586         switch (pass) {
587           case 0:
588             classesToCheck = pkg.ordinaryClasses();
589             break;
590           case 1:
591             classesToCheck = pkg.enums();
592             break;
593           case 2:
594             classesToCheck = pkg.errors();
595             break;
596           case 3:
597             classesToCheck = pkg.exceptions();
598             break;
599           case 4:
600             classesToCheck = pkg.interfaces();
601             break;
602           default:
603             System.err.println("Error reading package: " + pkg.name());
604             break;
605         }
606         for (ClassInfo cl : classesToCheck) {
607           if (!cl.isHidden()) {
608             allHidden = false;
609             break;
610           }
611         }
612         if (!allHidden) {
613           break;
614         }
615         pass++;
616       }
617       if (allHidden) {
618         continue;
619       }
620 
621       data.setValue("reference", "1");
622       data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0");
623       data.setValue("docs.packages." + i + ".name", s);
624       data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
625       data.setValue("docs.packages." + i + ".since", pkg.getSince());
626       TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags());
627       i++;
628     }
629 
630     sinceTagger.writeVersionNames(data);
631     return data;
632   }
633 
writeDirectory(File dir, String relative, JSilver js)634   private static void writeDirectory(File dir, String relative, JSilver js) {
635     File[] files = dir.listFiles();
636     int i, count = files.length;
637     for (i = 0; i < count; i++) {
638       File f = files[i];
639       if (f.isFile()) {
640         String templ = relative + f.getName();
641         int len = templ.length();
642         if (len > 3 && ".cs".equals(templ.substring(len - 3))) {
643           Data data = makeHDF();
644           String filename = templ.substring(0, len - 3) + htmlExtension;
645           ClearPage.write(data, templ, filename, js);
646         } else if (len > 3 && ".jd".equals(templ.substring(len - 3))) {
647           String filename = templ.substring(0, len - 3) + htmlExtension;
648           DocFile.writePage(f.getAbsolutePath(), relative, filename);
649         } else {
650           ClearPage.copyFile(f, templ);
651         }
652       } else if (f.isDirectory()) {
653         writeDirectory(f, relative + f.getName() + "/", js);
654       }
655     }
656   }
657 
writeHTMLPages()658   public static void writeHTMLPages() {
659     for (String htmlDir : ClearPage.htmlDirs) {
660       File f = new File(htmlDir);
661       if (!f.isDirectory()) {
662         System.err.println("htmlDir not a directory: " + htmlDir);
663         continue;
664       }
665 
666       ResourceLoader loader = new FileSystemResourceLoader(f);
667       JSilver js = new JSilver(loader);
668       writeDirectory(f, "", js);
669     }
670   }
671 
writeAssets()672   public static void writeAssets() {
673     JarFile thisJar = JarUtils.jarForClass(Doclava.class, null);
674     if (thisJar != null) {
675       try {
676         List<String> templateDirs = ClearPage.getBundledTemplateDirs();
677         for (String templateDir : templateDirs) {
678           String assetsDir = templateDir + "/assets";
679           JarUtils.copyResourcesToDirectory(thisJar, assetsDir, ClearPage.outputDir + "/assets");
680         }
681       } catch (IOException e) {
682         System.err.println("Error copying assets directory.");
683         e.printStackTrace();
684         return;
685       }
686     }
687 
688     List<String> templateDirs = ClearPage.getTemplateDirs();
689     for (String templateDir : templateDirs) {
690       File assets = new File(templateDir + "/assets");
691       if (assets.isDirectory()) {
692         writeDirectory(assets, "assets/", null);
693       }
694     }
695   }
696 
writeLists()697   public static void writeLists() {
698     Data data = makeHDF();
699 
700     ClassInfo[] classes = Converter.rootClasses();
701 
702     SortedMap<String, Object> sorted = new TreeMap<String, Object>();
703     for (ClassInfo cl : classes) {
704       if (cl.isHidden()) {
705         continue;
706       }
707       sorted.put(cl.qualifiedName(), cl);
708       PackageInfo pkg = cl.containingPackage();
709       String name;
710       if (pkg == null) {
711         name = "";
712       } else {
713         name = pkg.name();
714       }
715       sorted.put(name, pkg);
716     }
717 
718     int i = 0;
719     for (String s : sorted.keySet()) {
720       data.setValue("docs.pages." + i + ".id", "" + i);
721       data.setValue("docs.pages." + i + ".label", s);
722 
723       Object o = sorted.get(s);
724       if (o instanceof PackageInfo) {
725         PackageInfo pkg = (PackageInfo) o;
726         data.setValue("docs.pages." + i + ".link", pkg.htmlPage());
727         data.setValue("docs.pages." + i + ".type", "package");
728       } else if (o instanceof ClassInfo) {
729         ClassInfo cl = (ClassInfo) o;
730         data.setValue("docs.pages." + i + ".link", cl.htmlPage());
731         data.setValue("docs.pages." + i + ".type", "class");
732       }
733       i++;
734     }
735 
736     ClearPage.write(data, "lists.cs", javadocDir + "lists.js");
737   }
738 
cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable)739   public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) {
740     if (!notStrippable.add(cl)) {
741       // slight optimization: if it already contains cl, it already contains
742       // all of cl's parents
743       return;
744     }
745     ClassInfo supr = cl.superclass();
746     if (supr != null) {
747       cantStripThis(supr, notStrippable);
748     }
749     for (ClassInfo iface : cl.interfaces()) {
750       cantStripThis(iface, notStrippable);
751     }
752   }
753 
getPrintableName(ClassInfo cl)754   private static String getPrintableName(ClassInfo cl) {
755     ClassInfo containingClass = cl.containingClass();
756     if (containingClass != null) {
757       // This is an inner class.
758       String baseName = cl.name();
759       baseName = baseName.substring(baseName.lastIndexOf('.') + 1);
760       return getPrintableName(containingClass) + '$' + baseName;
761     }
762     return cl.qualifiedName();
763   }
764 
765   /**
766    * Writes the list of classes that must be present in order to provide the non-hidden APIs known
767    * to javadoc.
768    *
769    * @param filename the path to the file to write the list to
770    */
writeKeepList(String filename)771   public static void writeKeepList(String filename) {
772     HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
773     ClassInfo[] all = Converter.allClasses();
774     Arrays.sort(all); // just to make the file a little more readable
775 
776     // If a class is public and not hidden, then it and everything it derives
777     // from cannot be stripped. Otherwise we can strip it.
778     for (ClassInfo cl : all) {
779       if (cl.isPublic() && !cl.isHidden()) {
780         cantStripThis(cl, notStrippable);
781       }
782     }
783     PrintStream stream = null;
784     try {
785       stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(filename)));
786       for (ClassInfo cl : notStrippable) {
787         stream.println(getPrintableName(cl));
788       }
789     } catch (FileNotFoundException e) {
790       System.err.println("error writing file: " + filename);
791     } finally {
792       if (stream != null) {
793         stream.close();
794       }
795     }
796   }
797 
798   private static PackageInfo[] sVisiblePackages = null;
799 
choosePackages()800   public static PackageInfo[] choosePackages() {
801     if (sVisiblePackages != null) {
802       return sVisiblePackages;
803     }
804 
805     ClassInfo[] classes = Converter.rootClasses();
806     SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
807     for (ClassInfo cl : classes) {
808       PackageInfo pkg = cl.containingPackage();
809       String name;
810       if (pkg == null) {
811         name = "";
812       } else {
813         name = pkg.name();
814       }
815       sorted.put(name, pkg);
816     }
817 
818     ArrayList<PackageInfo> result = new ArrayList<PackageInfo>();
819 
820     for (String s : sorted.keySet()) {
821       PackageInfo pkg = sorted.get(s);
822 
823       if (pkg.isHidden()) {
824         continue;
825       }
826       Boolean allHidden = true;
827       int pass = 0;
828       ClassInfo[] classesToCheck = null;
829       while (pass < 5) {
830         switch (pass) {
831           case 0:
832             classesToCheck = pkg.ordinaryClasses();
833             break;
834           case 1:
835             classesToCheck = pkg.enums();
836             break;
837           case 2:
838             classesToCheck = pkg.errors();
839             break;
840           case 3:
841             classesToCheck = pkg.exceptions();
842             break;
843           case 4:
844             classesToCheck = pkg.interfaces();
845             break;
846           default:
847             System.err.println("Error reading package: " + pkg.name());
848             break;
849         }
850         for (ClassInfo cl : classesToCheck) {
851           if (!cl.isHidden()) {
852             allHidden = false;
853             break;
854           }
855         }
856         if (!allHidden) {
857           break;
858         }
859         pass++;
860       }
861       if (allHidden) {
862         continue;
863       }
864 
865       result.add(pkg);
866     }
867 
868     sVisiblePackages = result.toArray(new PackageInfo[result.size()]);
869     return sVisiblePackages;
870   }
871 
writePackages(String filename)872   public static void writePackages(String filename) {
873     Data data = makePackageHDF();
874 
875     int i = 0;
876     for (PackageInfo pkg : choosePackages()) {
877       writePackage(pkg);
878 
879       data.setValue("docs.packages." + i + ".name", pkg.name());
880       data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
881       TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags());
882 
883       i++;
884     }
885 
886     setPageTitle(data, "Package Index");
887 
888     TagInfo.makeHDF(data, "root.descr", Converter.convertTags(root.inlineTags(), null));
889 
890     ClearPage.write(data, "packages.cs", filename);
891     ClearPage.write(data, "package-list.cs", javadocDir + "package-list");
892 
893     Proofread.writePackages(filename, Converter.convertTags(root.inlineTags(), null));
894   }
895 
writePackage(PackageInfo pkg)896   public static void writePackage(PackageInfo pkg) {
897     // these this and the description are in the same directory,
898     // so it's okay
899     Data data = makePackageHDF();
900 
901     String name = pkg.name();
902 
903     data.setValue("package.name", name);
904     data.setValue("package.since", pkg.getSince());
905     data.setValue("package.descr", "...description...");
906     pkg.setFederatedReferences(data, "package");
907 
908     makeClassListHDF(data, "package.interfaces", ClassInfo.sortByName(pkg.interfaces()));
909     makeClassListHDF(data, "package.classes", ClassInfo.sortByName(pkg.ordinaryClasses()));
910     makeClassListHDF(data, "package.enums", ClassInfo.sortByName(pkg.enums()));
911     makeClassListHDF(data, "package.exceptions", ClassInfo.sortByName(pkg.exceptions()));
912     makeClassListHDF(data, "package.errors", ClassInfo.sortByName(pkg.errors()));
913     TagInfo.makeHDF(data, "package.shortDescr", pkg.firstSentenceTags());
914     TagInfo.makeHDF(data, "package.descr", pkg.inlineTags());
915 
916     String filename = pkg.htmlPage();
917     setPageTitle(data, name);
918     ClearPage.write(data, "package.cs", filename);
919 
920     Proofread.writePackage(filename, pkg.inlineTags());
921   }
922 
writeClassLists()923   public static void writeClassLists() {
924     int i;
925     Data data = makePackageHDF();
926 
927     ClassInfo[] classes = PackageInfo.filterHidden(Converter.convertClasses(root.classes()));
928     if (classes.length == 0) {
929       return;
930     }
931 
932     Sorter[] sorted = new Sorter[classes.length];
933     for (i = 0; i < sorted.length; i++) {
934       ClassInfo cl = classes[i];
935       String name = cl.name();
936       sorted[i] = new Sorter(name, cl);
937     }
938 
939     Arrays.sort(sorted);
940 
941     // make a pass and resolve ones that have the same name
942     int firstMatch = 0;
943     String lastName = sorted[0].label;
944     for (i = 1; i < sorted.length; i++) {
945       String s = sorted[i].label;
946       if (!lastName.equals(s)) {
947         if (firstMatch != i - 1) {
948           // there were duplicates
949           for (int j = firstMatch; j < i; j++) {
950             PackageInfo pkg = ((ClassInfo) sorted[j].data).containingPackage();
951             if (pkg != null) {
952               sorted[j].label = sorted[j].label + " (" + pkg.name() + ")";
953             }
954           }
955         }
956         firstMatch = i;
957         lastName = s;
958       }
959     }
960 
961     // and sort again
962     Arrays.sort(sorted);
963 
964     for (i = 0; i < sorted.length; i++) {
965       String s = sorted[i].label;
966       ClassInfo cl = (ClassInfo) sorted[i].data;
967       char first = Character.toUpperCase(s.charAt(0));
968       cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i);
969     }
970 
971     setPageTitle(data, "Class Index");
972     ClearPage.write(data, "classes.cs", javadocDir + "classes" + htmlExtension);
973   }
974 
975   // we use the word keywords because "index" means something else in html land
976   // the user only ever sees the word index
977   /*
978    * public static void writeKeywords() { ArrayList<KeywordEntry> keywords = new
979    * ArrayList<KeywordEntry>();
980    *
981    * ClassInfo[] classes = PackageInfo.filterHidden(Converter.convertClasses(root.classes()));
982    *
983    * for (ClassInfo cl: classes) { cl.makeKeywordEntries(keywords); }
984    *
985    * HDF data = makeHDF();
986    *
987    * Collections.sort(keywords);
988    *
989    * int i=0; for (KeywordEntry entry: keywords) { String base = "keywords." + entry.firstChar() +
990    * "." + i; entry.makeHDF(data, base); i++; }
991    *
992    * setPageTitle(data, "Index"); ClearPage.write(data, "keywords.cs", javadocDir + "keywords" +
993    * htmlExtension); }
994    */
995 
writeHierarchy()996   public static void writeHierarchy() {
997     ClassInfo[] classes = Converter.rootClasses();
998     ArrayList<ClassInfo> info = new ArrayList<ClassInfo>();
999     for (ClassInfo cl : classes) {
1000       if (!cl.isHidden()) {
1001         info.add(cl);
1002       }
1003     }
1004     Data data = makePackageHDF();
1005     Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()]));
1006     setPageTitle(data, "Class Hierarchy");
1007     ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension);
1008   }
1009 
writeClasses()1010   public static void writeClasses() {
1011     ClassInfo[] classes = Converter.rootClasses();
1012 
1013     for (ClassInfo cl : classes) {
1014       Data data = makePackageHDF();
1015       if (!cl.isHidden()) {
1016         writeClass(cl, data);
1017       }
1018     }
1019   }
1020 
writeClass(ClassInfo cl, Data data)1021   public static void writeClass(ClassInfo cl, Data data) {
1022     cl.makeHDF(data);
1023 
1024     setPageTitle(data, cl.name());
1025     ClearPage.write(data, "class.cs", cl.htmlPage());
1026 
1027     Proofread.writeClass(cl.htmlPage(), cl);
1028   }
1029 
makeClassListHDF(Data data, String base, ClassInfo[] classes)1030   public static void makeClassListHDF(Data data, String base, ClassInfo[] classes) {
1031     for (int i = 0; i < classes.length; i++) {
1032       ClassInfo cl = classes[i];
1033       if (!cl.isHidden()) {
1034         cl.makeShortDescrHDF(data, base + "." + i);
1035       }
1036     }
1037   }
1038 
linkTarget(String source, String target)1039   public static String linkTarget(String source, String target) {
1040     String[] src = source.split("/");
1041     String[] tgt = target.split("/");
1042 
1043     int srclen = src.length;
1044     int tgtlen = tgt.length;
1045 
1046     int same = 0;
1047     while (same < (srclen - 1) && same < (tgtlen - 1) && (src[same].equals(tgt[same]))) {
1048       same++;
1049     }
1050 
1051     String s = "";
1052 
1053     int up = srclen - same - 1;
1054     for (int i = 0; i < up; i++) {
1055       s += "../";
1056     }
1057 
1058 
1059     int N = tgtlen - 1;
1060     for (int i = same; i < N; i++) {
1061       s += tgt[i] + '/';
1062     }
1063     s += tgt[tgtlen - 1];
1064 
1065     return s;
1066   }
1067 
1068   /**
1069    * Returns true if the given element has an @hide or @pending annotation.
1070    */
hasHideAnnotation(Doc doc)1071   private static boolean hasHideAnnotation(Doc doc) {
1072     String comment = doc.getRawCommentText();
1073     return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -1;
1074   }
1075 
1076   /**
1077    * Returns true if the given element is hidden.
1078    */
isHidden(Doc doc)1079   private static boolean isHidden(Doc doc) {
1080     // Methods, fields, constructors.
1081     if (doc instanceof MemberDoc) {
1082       return hasHideAnnotation(doc);
1083     }
1084 
1085     // Classes, interfaces, enums, annotation types.
1086     if (doc instanceof ClassDoc) {
1087       ClassDoc classDoc = (ClassDoc) doc;
1088 
1089       // Check the containing package.
1090       if (hasHideAnnotation(classDoc.containingPackage())) {
1091         return true;
1092       }
1093 
1094       // Check the class doc and containing class docs if this is a
1095       // nested class.
1096       ClassDoc current = classDoc;
1097       do {
1098         if (hasHideAnnotation(current)) {
1099           return true;
1100         }
1101 
1102         current = current.containingClass();
1103       } while (current != null);
1104     }
1105 
1106     return false;
1107   }
1108 
1109   /**
1110    * Filters out hidden elements.
1111    */
filterHidden(Object o, Class<?> expected)1112   private static Object filterHidden(Object o, Class<?> expected) {
1113     if (o == null) {
1114       return null;
1115     }
1116 
1117     Class type = o.getClass();
1118     if (type.getName().startsWith("com.sun.")) {
1119       // TODO: Implement interfaces from superclasses, too.
1120       return Proxy
1121           .newProxyInstance(type.getClassLoader(), type.getInterfaces(), new HideHandler(o));
1122     } else if (o instanceof Object[]) {
1123       Class<?> componentType = expected.getComponentType();
1124       Object[] array = (Object[]) o;
1125       List<Object> list = new ArrayList<Object>(array.length);
1126       for (Object entry : array) {
1127         if ((entry instanceof Doc) && isHidden((Doc) entry)) {
1128           continue;
1129         }
1130         list.add(filterHidden(entry, componentType));
1131       }
1132       return list.toArray((Object[]) Array.newInstance(componentType, list.size()));
1133     } else {
1134       return o;
1135     }
1136   }
1137 
1138   /**
1139    * Filters hidden elements out of method return values.
1140    */
1141   private static class HideHandler implements InvocationHandler {
1142 
1143     private final Object target;
1144 
HideHandler(Object target)1145     public HideHandler(Object target) {
1146       this.target = target;
1147     }
1148 
invoke(Object proxy, Method method, Object[] args)1149     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1150       String methodName = method.getName();
1151       if (args != null) {
1152         if (methodName.equals("compareTo") || methodName.equals("equals")
1153             || methodName.equals("overrides") || methodName.equals("subclassOf")) {
1154           args[0] = unwrap(args[0]);
1155         }
1156       }
1157 
1158       if (methodName.equals("getRawCommentText")) {
1159         return filterComment((String) method.invoke(target, args));
1160       }
1161 
1162       // escape "&" in disjunctive types.
1163       if (proxy instanceof Type && methodName.equals("toString")) {
1164         return ((String) method.invoke(target, args)).replace("&", "&amp;");
1165       }
1166 
1167       try {
1168         return filterHidden(method.invoke(target, args), method.getReturnType());
1169       } catch (InvocationTargetException e) {
1170         throw e.getTargetException();
1171       }
1172     }
1173 
filterComment(String s)1174     private String filterComment(String s) {
1175       if (s == null) {
1176         return null;
1177       }
1178 
1179       s = s.trim();
1180 
1181       // Work around off by one error
1182       while (s.length() >= 5 && s.charAt(s.length() - 5) == '{') {
1183         s += "&nbsp;";
1184       }
1185 
1186       return s;
1187     }
1188 
unwrap(Object proxy)1189     private static Object unwrap(Object proxy) {
1190       if (proxy instanceof Proxy) return ((HideHandler) Proxy.getInvocationHandler(proxy)).target;
1191       return proxy;
1192     }
1193   }
1194 
1195   /**
1196    * Collect the values used by the Dev tools and write them in files packaged with the SDK
1197    *
1198    * @param output the ouput directory for the files.
1199    */
writeSdkValues(String output)1200   private static void writeSdkValues(String output) {
1201     ArrayList<String> activityActions = new ArrayList<String>();
1202     ArrayList<String> broadcastActions = new ArrayList<String>();
1203     ArrayList<String> serviceActions = new ArrayList<String>();
1204     ArrayList<String> categories = new ArrayList<String>();
1205     ArrayList<String> features = new ArrayList<String>();
1206 
1207     ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>();
1208     ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>();
1209     ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>();
1210 
1211     ClassInfo[] classes = Converter.allClasses();
1212 
1213     // Go through all the fields of all the classes, looking SDK stuff.
1214     for (ClassInfo clazz : classes) {
1215 
1216       // first check constant fields for the SdkConstant annotation.
1217       ArrayList<FieldInfo> fields = clazz.allSelfFields();
1218       for (FieldInfo field : fields) {
1219         Object cValue = field.constantValue();
1220         if (cValue != null) {
1221             ArrayList<AnnotationInstanceInfo> annotations = field.annotations();
1222           if (!annotations.isEmpty()) {
1223             for (AnnotationInstanceInfo annotation : annotations) {
1224               if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) {
1225                 if (!annotation.elementValues().isEmpty()) {
1226                   String type = annotation.elementValues().get(0).valueString();
1227                   if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) {
1228                     activityActions.add(cValue.toString());
1229                   } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) {
1230                     broadcastActions.add(cValue.toString());
1231                   } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) {
1232                     serviceActions.add(cValue.toString());
1233                   } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) {
1234                     categories.add(cValue.toString());
1235                   } else if (SDK_CONSTANT_TYPE_FEATURE.equals(type)) {
1236                     features.add(cValue.toString());
1237                   }
1238                 }
1239                 break;
1240               }
1241             }
1242           }
1243         }
1244       }
1245 
1246       // Now check the class for @Widget or if its in the android.widget package
1247       // (unless the class is hidden or abstract, or non public)
1248       if (clazz.isHidden() == false && clazz.isPublic() && clazz.isAbstract() == false) {
1249         boolean annotated = false;
1250         ArrayList<AnnotationInstanceInfo> annotations = clazz.annotations();
1251         if (!annotations.isEmpty()) {
1252           for (AnnotationInstanceInfo annotation : annotations) {
1253             if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) {
1254               widgets.add(clazz);
1255               annotated = true;
1256               break;
1257             } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) {
1258               layouts.add(clazz);
1259               annotated = true;
1260               break;
1261             }
1262           }
1263         }
1264 
1265         if (annotated == false) {
1266           // lets check if this is inside android.widget
1267           PackageInfo pckg = clazz.containingPackage();
1268           String packageName = pckg.name();
1269           if ("android.widget".equals(packageName) || "android.view".equals(packageName)) {
1270             // now we check what this class inherits either from android.view.ViewGroup
1271             // or android.view.View, or android.view.ViewGroup.LayoutParams
1272             int type = checkInheritance(clazz);
1273             switch (type) {
1274               case TYPE_WIDGET:
1275                 widgets.add(clazz);
1276                 break;
1277               case TYPE_LAYOUT:
1278                 layouts.add(clazz);
1279                 break;
1280               case TYPE_LAYOUT_PARAM:
1281                 layoutParams.add(clazz);
1282                 break;
1283             }
1284           }
1285         }
1286       }
1287     }
1288 
1289     // now write the files, whether or not the list are empty.
1290     // the SDK built requires those files to be present.
1291 
1292     Collections.sort(activityActions);
1293     writeValues(output + "/activity_actions.txt", activityActions);
1294 
1295     Collections.sort(broadcastActions);
1296     writeValues(output + "/broadcast_actions.txt", broadcastActions);
1297 
1298     Collections.sort(serviceActions);
1299     writeValues(output + "/service_actions.txt", serviceActions);
1300 
1301     Collections.sort(categories);
1302     writeValues(output + "/categories.txt", categories);
1303 
1304     Collections.sort(features);
1305     writeValues(output + "/features.txt", features);
1306 
1307     // before writing the list of classes, we do some checks, to make sure the layout params
1308     // are enclosed by a layout class (and not one that has been declared as a widget)
1309     for (int i = 0; i < layoutParams.size();) {
1310       ClassInfo layoutParamClass = layoutParams.get(i);
1311       ClassInfo containingClass = layoutParamClass.containingClass();
1312       if (containingClass == null || layouts.indexOf(containingClass) == -1) {
1313         layoutParams.remove(i);
1314       } else {
1315         i++;
1316       }
1317     }
1318 
1319     writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams);
1320   }
1321 
1322   /**
1323    * Writes a list of values into a text files.
1324    *
1325    * @param pathname the absolute os path of the output file.
1326    * @param values the list of values to write.
1327    */
writeValues(String pathname, ArrayList<String> values)1328   private static void writeValues(String pathname, ArrayList<String> values) {
1329     FileWriter fw = null;
1330     BufferedWriter bw = null;
1331     try {
1332       fw = new FileWriter(pathname, false);
1333       bw = new BufferedWriter(fw);
1334 
1335       for (String value : values) {
1336         bw.append(value).append('\n');
1337       }
1338     } catch (IOException e) {
1339       // pass for now
1340     } finally {
1341       try {
1342         if (bw != null) bw.close();
1343       } catch (IOException e) {
1344         // pass for now
1345       }
1346       try {
1347         if (fw != null) fw.close();
1348       } catch (IOException e) {
1349         // pass for now
1350       }
1351     }
1352   }
1353 
1354   /**
1355    * Writes the widget/layout/layout param classes into a text files.
1356    *
1357    * @param pathname the absolute os path of the output file.
1358    * @param widgets the list of widget classes to write.
1359    * @param layouts the list of layout classes to write.
1360    * @param layoutParams the list of layout param classes to write.
1361    */
writeClasses(String pathname, ArrayList<ClassInfo> widgets, ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams)1362   private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets,
1363       ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) {
1364     FileWriter fw = null;
1365     BufferedWriter bw = null;
1366     try {
1367       fw = new FileWriter(pathname, false);
1368       bw = new BufferedWriter(fw);
1369 
1370       // write the 3 types of classes.
1371       for (ClassInfo clazz : widgets) {
1372         writeClass(bw, clazz, 'W');
1373       }
1374       for (ClassInfo clazz : layoutParams) {
1375         writeClass(bw, clazz, 'P');
1376       }
1377       for (ClassInfo clazz : layouts) {
1378         writeClass(bw, clazz, 'L');
1379       }
1380     } catch (IOException e) {
1381       // pass for now
1382     } finally {
1383       try {
1384         if (bw != null) bw.close();
1385       } catch (IOException e) {
1386         // pass for now
1387       }
1388       try {
1389         if (fw != null) fw.close();
1390       } catch (IOException e) {
1391         // pass for now
1392       }
1393     }
1394   }
1395 
1396   /**
1397    * Writes a class name and its super class names into a {@link BufferedWriter}.
1398    *
1399    * @param writer the BufferedWriter to write into
1400    * @param clazz the class to write
1401    * @param prefix the prefix to put at the beginning of the line.
1402    * @throws IOException
1403    */
writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)1404   private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)
1405       throws IOException {
1406     writer.append(prefix).append(clazz.qualifiedName());
1407     ClassInfo superClass = clazz;
1408     while ((superClass = superClass.superclass()) != null) {
1409       writer.append(' ').append(superClass.qualifiedName());
1410     }
1411     writer.append('\n');
1412   }
1413 
1414   /**
1415    * Checks the inheritance of {@link ClassInfo} objects. This method return
1416    * <ul>
1417    * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li>
1418    * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li>
1419    * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends
1420    * <code>android.view.ViewGroup$LayoutParams</code></li>
1421    * <li>{@link #TYPE_NONE}: in all other cases</li>
1422    * </ul>
1423    *
1424    * @param clazz the {@link ClassInfo} to check.
1425    */
checkInheritance(ClassInfo clazz)1426   private static int checkInheritance(ClassInfo clazz) {
1427     if ("android.view.ViewGroup".equals(clazz.qualifiedName())) {
1428       return TYPE_LAYOUT;
1429     } else if ("android.view.View".equals(clazz.qualifiedName())) {
1430       return TYPE_WIDGET;
1431     } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
1432       return TYPE_LAYOUT_PARAM;
1433     }
1434 
1435     ClassInfo parent = clazz.superclass();
1436     if (parent != null) {
1437       return checkInheritance(parent);
1438     }
1439 
1440     return TYPE_NONE;
1441   }
1442 }
1443