• 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 java.io.BufferedOutputStream;
20 import java.io.BufferedReader;
21 import java.io.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileNotFoundException;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.PrintStream;
30 import java.nio.charset.StandardCharsets;
31 import java.nio.file.Files;
32 import java.nio.file.Paths;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Scanner;
43 import java.util.Set;
44 import java.util.function.Predicate;
45 import java.util.regex.Pattern;
46 import java.util.stream.Collectors;
47 
48 public class Stubs {
writeStubsAndApi(String stubsDir, String apiFile, String dexApiFile, String keepListFile, String removedApiFile, String removedDexApiFile, String exactApiFile, String privateApiFile, String privateDexApiFile, String apiMappingFile, HashSet<String> stubPackages, HashSet<String> stubImportPackages, boolean stubSourceOnly, boolean keepStubComments)49   public static void writeStubsAndApi(String stubsDir, String apiFile, String dexApiFile,
50       String keepListFile, String removedApiFile, String removedDexApiFile, String exactApiFile,
51       String privateApiFile, String privateDexApiFile, String apiMappingFile,
52       HashSet<String> stubPackages, HashSet<String> stubImportPackages, boolean stubSourceOnly,
53       boolean keepStubComments) {
54     // figure out which classes we need
55     final HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
56     Collection<ClassInfo> all = Converter.allClasses();
57     Map<PackageInfo, List<ClassInfo>> allClassesByPackage = null;
58     PrintStream apiWriter = null;
59     PrintStream dexApiWriter = null;
60     PrintStream keepListWriter = null;
61     PrintStream removedApiWriter = null;
62     PrintStream removedDexApiWriter = null;
63     PrintStream exactApiWriter = null;
64     PrintStream privateApiWriter = null;
65     PrintStream privateDexApiWriter = null;
66     PrintStream apiMappingWriter = null;
67 
68     if (apiFile != null) {
69       try {
70         File xml = new File(apiFile);
71         xml.getParentFile().mkdirs();
72         apiWriter = new PrintStream(new BufferedOutputStream(new FileOutputStream(xml)));
73       } catch (FileNotFoundException e) {
74         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(apiFile, 0, 0),
75             "Cannot open file for write.");
76       }
77     }
78     if (dexApiFile != null) {
79       try {
80         File dexApi = new File(dexApiFile);
81         dexApi.getParentFile().mkdirs();
82         dexApiWriter = new PrintStream(new BufferedOutputStream(new FileOutputStream(dexApi)));
83       } catch (FileNotFoundException e) {
84         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(dexApiFile, 0, 0),
85             "Cannot open file for write.");
86       }
87     }
88     if (keepListFile != null) {
89       try {
90         File keepList = new File(keepListFile);
91         keepList.getParentFile().mkdirs();
92         keepListWriter = new PrintStream(new BufferedOutputStream(new FileOutputStream(keepList)));
93       } catch (FileNotFoundException e) {
94         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(keepListFile, 0, 0),
95             "Cannot open file for write.");
96       }
97     }
98     if (removedApiFile != null) {
99       try {
100         File removedApi = new File(removedApiFile);
101         removedApi.getParentFile().mkdirs();
102         removedApiWriter = new PrintStream(
103             new BufferedOutputStream(new FileOutputStream(removedApi)));
104       } catch (FileNotFoundException e) {
105         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(removedApiFile, 0, 0),
106             "Cannot open file for write");
107       }
108     }
109     if (removedDexApiFile != null) {
110       try {
111         File removedDexApi = new File(removedDexApiFile);
112         removedDexApi.getParentFile().mkdirs();
113         removedDexApiWriter = new PrintStream(
114             new BufferedOutputStream(new FileOutputStream(removedDexApi)));
115       } catch (FileNotFoundException e) {
116         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(removedDexApiFile, 0, 0),
117             "Cannot open file for write");
118       }
119     }
120     if (exactApiFile != null) {
121       try {
122         File exactApi = new File(exactApiFile);
123         exactApi.getParentFile().mkdirs();
124         exactApiWriter = new PrintStream(
125             new BufferedOutputStream(new FileOutputStream(exactApi)));
126       } catch (FileNotFoundException e) {
127         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(exactApiFile, 0, 0),
128             "Cannot open file for write");
129       }
130     }
131     if (privateApiFile != null) {
132       try {
133         File privateApi = new File(privateApiFile);
134         privateApi.getParentFile().mkdirs();
135         privateApiWriter = new PrintStream(
136             new BufferedOutputStream(new FileOutputStream(privateApi)));
137       } catch (FileNotFoundException e) {
138         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(privateApiFile, 0, 0),
139             "Cannot open file for write");
140       }
141     }
142     if (privateDexApiFile != null) {
143       try {
144         File privateDexApi = new File(privateDexApiFile);
145         privateDexApi.getParentFile().mkdirs();
146         privateDexApiWriter = new PrintStream(
147             new BufferedOutputStream(new FileOutputStream(privateDexApi)));
148       } catch (FileNotFoundException e) {
149         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(privateDexApiFile, 0, 0),
150             "Cannot open file for write");
151       }
152     }
153     if (apiMappingFile != null) {
154       try {
155         File apiMapping = new File(apiMappingFile);
156         apiMapping.getParentFile().mkdirs();
157         apiMappingWriter = new PrintStream(
158             new BufferedOutputStream(new FileOutputStream(apiMapping)));
159       } catch (FileNotFoundException e) {
160         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(apiMappingFile, 0, 0),
161             "Cannot open file for write");
162       }
163     }
164     // If a class is public or protected, not hidden, not imported and marked as included,
165     // then we can't strip it
166     for (ClassInfo cl : all) {
167       if (cl.checkLevel() && cl.isIncluded()) {
168         cantStripThis(cl, notStrippable, "0:0", stubImportPackages);
169       }
170     }
171 
172     // complain about anything that looks includeable but is not supposed to
173     // be written, e.g. hidden things
174     for (ClassInfo cl : notStrippable) {
175       if (!cl.isHiddenOrRemoved()) {
176         for (MethodInfo m : cl.selfMethods()) {
177           if (m.isHiddenOrRemoved()) {
178             Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Reference to unavailable method "
179                 + m.name());
180           } else if (m.isDeprecated()) {
181             // don't bother reporting deprecated methods
182             // unless they are public
183             Errors.error(Errors.DEPRECATED, m.position(), "Method " + cl.qualifiedName() + "."
184                 + m.name() + " is deprecated");
185           }
186 
187           ClassInfo hiddenClass = findHiddenClasses(m.returnType(), stubImportPackages);
188           if (null != hiddenClass) {
189             if (hiddenClass.qualifiedName() == m.returnType().asClassInfo().qualifiedName()) {
190               // Return type is hidden
191               Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Method " + cl.qualifiedName()
192                   + "." + m.name() + " returns unavailable type " + hiddenClass.name());
193             } else {
194               // Return type contains a generic parameter
195               Errors.error(Errors.HIDDEN_TYPE_PARAMETER, m.position(), "Method " + cl.qualifiedName()
196                   + "." + m.name() + " returns unavailable type " + hiddenClass.name()
197                   + " as a type parameter");
198             }
199           }
200 
201           for (ParameterInfo p :  m.parameters()) {
202             TypeInfo t = p.type();
203             if (!t.isPrimitive()) {
204               hiddenClass = findHiddenClasses(t, stubImportPackages);
205               if (null != hiddenClass) {
206                 if (hiddenClass.qualifiedName() == t.asClassInfo().qualifiedName()) {
207                   // Parameter type is hidden
208                   Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(),
209                       "Parameter of unavailable type " + t.fullName() + " in " + cl.qualifiedName()
210                       + "." + m.name() + "()");
211                 } else {
212                   // Parameter type contains a generic parameter
213                   Errors.error(Errors.HIDDEN_TYPE_PARAMETER, m.position(),
214                       "Parameter uses type parameter of unavailable type " + t.fullName() + " in "
215                       + cl.qualifiedName() + "." + m.name() + "()");
216                 }
217               }
218             }
219           }
220         }
221 
222         // annotations are handled like methods
223         for (MethodInfo m : cl.annotationElements()) {
224           if (m.isHiddenOrRemoved()) {
225             Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Reference to unavailable annotation "
226                 + m.name());
227           }
228 
229           ClassInfo returnClass = m.returnType().asClassInfo();
230           if (returnClass != null && returnClass.isHiddenOrRemoved()) {
231             Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Annotation '" + m.name()
232                 + "' returns unavailable type " + returnClass.name());
233           }
234 
235           for (ParameterInfo p :  m.parameters()) {
236             TypeInfo t = p.type();
237             if (!t.isPrimitive()) {
238               if (t.asClassInfo().isHiddenOrRemoved()) {
239                 Errors.error(Errors.UNAVAILABLE_SYMBOL, p.position(),
240                     "Reference to unavailable annotation class " + t.fullName());
241               }
242             }
243           }
244         }
245       } else if (cl.isDeprecated()) {
246         // not hidden, but deprecated
247         Errors.error(Errors.DEPRECATED, cl.position(), "Class " + cl.qualifiedName()
248             + " is deprecated");
249       }
250     }
251 
252     // packages contains all the notStrippable classes mapped by their containing packages
253     HashMap<PackageInfo, List<ClassInfo>> packages = new HashMap<PackageInfo, List<ClassInfo>>();
254     final HashSet<Pattern> stubPackageWildcards = extractWildcards(stubPackages);
255     for (ClassInfo cl : notStrippable) {
256       if (!cl.isDocOnly()) {
257         if (stubSourceOnly && !Files.exists(Paths.get(cl.position().file))) {
258           continue;
259         }
260         if (shouldWriteStub(cl.containingPackage().name(), stubPackages, stubPackageWildcards)) {
261           // write out the stubs
262           if (stubsDir != null) {
263             writeClassFile(stubsDir, notStrippable, cl, keepStubComments);
264           }
265           // build class list for api file or keep list file
266           if (apiWriter != null || dexApiWriter != null || keepListWriter != null) {
267             if (packages.containsKey(cl.containingPackage())) {
268               packages.get(cl.containingPackage()).add(cl);
269             } else {
270               ArrayList<ClassInfo> classes = new ArrayList<ClassInfo>();
271               classes.add(cl);
272               packages.put(cl.containingPackage(), classes);
273             }
274           }
275         }
276       }
277     }
278 
279     if (privateApiWriter != null || privateDexApiWriter != null || removedApiWriter != null
280             || removedDexApiWriter != null || apiMappingWriter != null) {
281       allClassesByPackage = Converter.allClasses().stream()
282           // Make sure that the files only contains information from the required packages.
283           .filter(ci -> stubPackages == null
284               || stubPackages.contains(ci.containingPackage().qualifiedName()))
285           .collect(Collectors.groupingBy(ClassInfo::containingPackage));
286     }
287 
288     final boolean ignoreShown = Doclava.showUnannotated;
289 
290     Predicate<MemberInfo> memberIsNotCloned = (x -> !x.isCloned());
291 
292     FilterPredicate apiFilter = new FilterPredicate(new ApiPredicate().setIgnoreShown(ignoreShown));
293     ApiPredicate apiReference = new ApiPredicate().setIgnoreShown(true);
294     Predicate<MemberInfo> apiEmit = apiFilter.and(new ElidingPredicate(apiReference));
295     Predicate<MemberInfo> dexApiEmit = memberIsNotCloned.and(apiFilter);
296 
297     Predicate<MemberInfo> privateEmit = memberIsNotCloned.and(apiFilter.negate());
298     Predicate<MemberInfo> privateReference = (x -> true);
299 
300     FilterPredicate removedFilter =
301         new FilterPredicate(new ApiPredicate().setIgnoreShown(ignoreShown).setMatchRemoved(true));
302     ApiPredicate removedReference = new ApiPredicate().setIgnoreShown(true).setIgnoreRemoved(true);
303     Predicate<MemberInfo> removedEmit = removedFilter.and(new ElidingPredicate(removedReference));
304     Predicate<MemberInfo> removedDexEmit = memberIsNotCloned.and(removedFilter);
305 
306     // Write out the current API
307     if (apiWriter != null) {
308       writeApi(apiWriter, packages, apiEmit, apiReference);
309       apiWriter.close();
310     }
311 
312     // Write out the current DEX API
313     if (dexApiWriter != null) {
314       writeDexApi(dexApiWriter, packages, dexApiEmit);
315       dexApiWriter.close();
316     }
317 
318     // Write out the keep list
319     if (keepListWriter != null) {
320       writeKeepList(keepListWriter, packages, notStrippable);
321       keepListWriter.close();
322     }
323 
324     // Write out the private API
325     if (privateApiWriter != null) {
326       writeApi(privateApiWriter, allClassesByPackage, privateEmit, privateReference);
327       privateApiWriter.close();
328     }
329 
330     // Write out the private API
331     if (privateDexApiWriter != null) {
332       writeDexApi(privateDexApiWriter, allClassesByPackage, privateEmit);
333       privateDexApiWriter.close();
334     }
335 
336     // Write out the API mapping
337     if (apiMappingWriter != null) {
338       writeApiMapping(apiMappingWriter, allClassesByPackage);
339       apiMappingWriter.close();
340     }
341 
342     // Write out the removed API
343     if (removedApiWriter != null) {
344       writeApi(removedApiWriter, allClassesByPackage, removedEmit, removedReference);
345       removedApiWriter.close();
346     }
347 
348     // Write out the removed DEX API
349     if (removedDexApiWriter != null) {
350       writeDexApi(removedDexApiWriter, allClassesByPackage, removedDexEmit);
351       removedDexApiWriter.close();
352     }
353   }
354 
shouldWriteStub(final String packageName, final HashSet<String> stubPackages, final HashSet<Pattern> stubPackageWildcards)355   private static boolean shouldWriteStub(final String packageName,
356           final HashSet<String> stubPackages, final HashSet<Pattern> stubPackageWildcards) {
357     if (stubPackages == null) {
358       // There aren't any stub packages set, write all stubs
359       return true;
360     }
361     if (stubPackages.contains(packageName)) {
362       // Stub packages contains package, return true
363       return true;
364     }
365     if (stubPackageWildcards != null) {
366       // Else, we will iterate through the wildcards to see if there's a match
367       for (Pattern wildcard : stubPackageWildcards) {
368         if (wildcard.matcher(packageName).matches()) {
369           return true;
370         }
371       }
372     }
373     return false;
374   }
375 
extractWildcards(HashSet<String> stubPackages)376   private static HashSet<Pattern> extractWildcards(HashSet<String> stubPackages) {
377     HashSet<Pattern> wildcards = null;
378     if (stubPackages != null) {
379       for (Iterator<String> i = stubPackages.iterator(); i.hasNext();) {
380         final String pkg = i.next();
381         if (pkg.indexOf('*') != -1) {
382           if (wildcards == null) {
383             wildcards = new HashSet<Pattern>();
384           }
385           // Add the compiled wildcard, replacing * with the regex equivalent
386           wildcards.add(Pattern.compile(pkg.replace("*", ".*?")));
387           // And remove the raw wildcard from the packages
388           i.remove();
389         }
390       }
391     }
392     return wildcards;
393   }
394 
395   /**
396    * Find references to hidden classes.
397    *
398    * <p>This finds hidden classes that are used by public parts of the API in order to ensure the
399    * API is self consistent and does not reference classes that are not included in
400    * the stubs. Any such references cause an error to be reported.
401    *
402    * <p>A reference to an imported class is not treated as an error, even though imported classes
403    * are hidden from the stub generation. That is because imported classes are, by definition,
404    * excluded from the set of classes for which stubs are required.
405    *
406    * @param ti the type information to examine for references to hidden classes.
407    * @param stubImportPackages the possibly null set of imported package names.
408    * @return a reference to a hidden class or null if there are none
409    */
findHiddenClasses(TypeInfo ti, HashSet<String> stubImportPackages)410   private static ClassInfo findHiddenClasses(TypeInfo ti, HashSet<String> stubImportPackages) {
411     ClassInfo ci = ti.asClassInfo();
412     if (ci == null) return null;
413     if (stubImportPackages != null
414         && stubImportPackages.contains(ci.containingPackage().qualifiedName())) {
415       return null;
416     }
417     if (ci.isHiddenOrRemoved()) return ci;
418     if (ti.typeArguments() != null) {
419       for (TypeInfo tii : ti.typeArguments()) {
420         // Avoid infinite recursion in the case of Foo<T extends Foo>
421         if (tii.qualifiedTypeName() != ti.qualifiedTypeName()) {
422           ClassInfo hiddenClass = findHiddenClasses(tii, stubImportPackages);
423           if (hiddenClass != null) return hiddenClass;
424         }
425       }
426     }
427     return null;
428   }
429 
cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why, HashSet<String> stubImportPackages)430   public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why,
431       HashSet<String> stubImportPackages) {
432 
433     if (stubImportPackages != null
434         && stubImportPackages.contains(cl.containingPackage().qualifiedName())) {
435       // if the package is imported then it does not need stubbing.
436       return;
437     }
438 
439     if (!notStrippable.add(cl)) {
440       // slight optimization: if it already contains cl, it already contains
441       // all of cl's parents
442       return;
443     }
444     cl.setReasonIncluded(why);
445 
446     // cant strip annotations
447     /*
448      * if (cl.annotations() != null){ for (AnnotationInstanceInfo ai : cl.annotations()){ if
449      * (ai.type() != null){ cantStripThis(ai.type(), notStrippable, "1:" + cl.qualifiedName()); } }
450      * }
451      */
452     // cant strip any public fields or their generics
453     if (cl.selfFields() != null) {
454       for (FieldInfo fInfo : cl.selfFields()) {
455         if (fInfo.type() != null) {
456           if (fInfo.type().asClassInfo() != null) {
457             cantStripThis(fInfo.type().asClassInfo(), notStrippable, "2:" + cl.qualifiedName(),
458                 stubImportPackages);
459           }
460           if (fInfo.type().typeArguments() != null) {
461             for (TypeInfo tTypeInfo : fInfo.type().typeArguments()) {
462               if (tTypeInfo.asClassInfo() != null) {
463                 cantStripThis(tTypeInfo.asClassInfo(), notStrippable, "3:" + cl.qualifiedName(),
464                     stubImportPackages);
465               }
466             }
467           }
468         }
469       }
470     }
471     // cant strip any of the type's generics
472     if (cl.asTypeInfo() != null) {
473       if (cl.asTypeInfo().typeArguments() != null) {
474         for (TypeInfo tInfo : cl.asTypeInfo().typeArguments()) {
475           if (tInfo.asClassInfo() != null) {
476             cantStripThis(tInfo.asClassInfo(), notStrippable, "4:" + cl.qualifiedName(),
477                 stubImportPackages);
478           }
479         }
480       }
481     }
482     // cant strip any of the annotation elements
483     // cantStripThis(cl.annotationElements(), notStrippable);
484     // take care of methods
485     cantStripThis(cl.allSelfMethods(), notStrippable, stubImportPackages);
486     cantStripThis(cl.allConstructors(), notStrippable, stubImportPackages);
487     // blow the outer class open if this is an inner class
488     if (cl.containingClass() != null) {
489       cantStripThis(cl.containingClass(), notStrippable, "5:" + cl.qualifiedName(),
490           stubImportPackages);
491     }
492     // blow open super class and interfaces
493     ClassInfo supr = cl.realSuperclass();
494     if (supr != null) {
495       if (supr.isHiddenOrRemoved()) {
496         // cl is a public class declared as extending a hidden superclass.
497         // this is not a desired practice but it's happened, so we deal
498         // with it by finding the first super class which passes checklevel for purposes of
499         // generating the doc & stub information, and proceeding normally.
500         ClassInfo publicSuper = cl.superclass();
501         cl.init(cl.asTypeInfo(), cl.realInterfaces(), cl.realInterfaceTypes(), cl.innerClasses(),
502             cl.allConstructors(), cl.allSelfMethods(), cl.annotationElements(), cl.allSelfFields(),
503             cl.enumConstants(), cl.containingPackage(), cl.containingClass(),
504             publicSuper, publicSuper.asTypeInfo(), cl.annotations());
505         Errors.error(Errors.HIDDEN_SUPERCLASS, cl.position(), "Public class " + cl.qualifiedName()
506             + " stripped of unavailable superclass " + supr.qualifiedName());
507       } else {
508         cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name() + cl.qualifiedName(),
509             stubImportPackages);
510         if (supr.isPrivate()) {
511           Errors.error(Errors.PRIVATE_SUPERCLASS, cl.position(), "Public class "
512               + cl.qualifiedName() + " extends private class " + supr.qualifiedName());
513         }
514       }
515     }
516   }
517 
cantStripThis(ArrayList<MethodInfo> mInfos, HashSet<ClassInfo> notStrippable, HashSet<String> stubImportPackages)518   private static void cantStripThis(ArrayList<MethodInfo> mInfos, HashSet<ClassInfo> notStrippable,
519       HashSet<String> stubImportPackages) {
520     // for each method, blow open the parameters, throws and return types. also blow open their
521     // generics
522     if (mInfos != null) {
523       for (MethodInfo mInfo : mInfos) {
524         if (mInfo.getTypeParameters() != null) {
525           for (TypeInfo tInfo : mInfo.getTypeParameters()) {
526             if (tInfo.asClassInfo() != null) {
527               cantStripThis(tInfo.asClassInfo(), notStrippable, "8:"
528                   + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
529                   stubImportPackages);
530             }
531           }
532         }
533         if (mInfo.parameters() != null) {
534           for (ParameterInfo pInfo : mInfo.parameters()) {
535             if (pInfo.type() != null && pInfo.type().asClassInfo() != null) {
536               cantStripThis(pInfo.type().asClassInfo(), notStrippable, "9:"
537                   + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
538                   stubImportPackages);
539               if (pInfo.type().typeArguments() != null) {
540                 for (TypeInfo tInfoType : pInfo.type().typeArguments()) {
541                   if (tInfoType.asClassInfo() != null) {
542                     ClassInfo tcl = tInfoType.asClassInfo();
543                     if (tcl.isHiddenOrRemoved()) {
544                       Errors
545                           .error(Errors.UNAVAILABLE_SYMBOL, mInfo.position(),
546                               "Parameter of hidden type " + tInfoType.fullName() + " in "
547                                   + mInfo.containingClass().qualifiedName() + '.' + mInfo.name()
548                                   + "()");
549                     } else {
550                       cantStripThis(tcl, notStrippable, "10:"
551                           + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
552                           stubImportPackages);
553                     }
554                   }
555                 }
556               }
557             }
558           }
559         }
560         for (ClassInfo thrown : mInfo.thrownExceptions()) {
561           cantStripThis(thrown, notStrippable, "11:" + mInfo.realContainingClass().qualifiedName()
562               + ":" + mInfo.name(), stubImportPackages);
563         }
564         if (mInfo.returnType() != null && mInfo.returnType().asClassInfo() != null) {
565           cantStripThis(mInfo.returnType().asClassInfo(), notStrippable, "12:"
566               + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
567               stubImportPackages);
568           if (mInfo.returnType().typeArguments() != null) {
569             for (TypeInfo tyInfo : mInfo.returnType().typeArguments()) {
570               if (tyInfo.asClassInfo() != null) {
571                 cantStripThis(tyInfo.asClassInfo(), notStrippable, "13:"
572                     + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
573                     stubImportPackages);
574               }
575             }
576           }
577         }
578       }
579     }
580   }
581 
javaFileName(ClassInfo cl)582   static String javaFileName(ClassInfo cl) {
583     String dir = "";
584     PackageInfo pkg = cl.containingPackage();
585     if (pkg != null) {
586       dir = pkg.name();
587       dir = dir.replace('.', '/') + '/';
588     }
589     return dir + cl.name() + ".java";
590   }
591 
writeClassFile(String stubsDir, HashSet<ClassInfo> notStrippable, ClassInfo cl, boolean keepStubComments)592   static void writeClassFile(String stubsDir, HashSet<ClassInfo> notStrippable, ClassInfo cl, boolean keepStubComments) {
593     // inner classes are written by their containing class
594     if (cl.containingClass() != null) {
595       return;
596     }
597 
598     // Work around the bogus "Array" class we invent for
599     // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505)
600     if (cl.containingPackage() != null
601         && cl.containingPackage().name().equals(PackageInfo.DEFAULT_PACKAGE)) {
602       return;
603     }
604 
605     String filename = stubsDir + '/' + javaFileName(cl);
606     File file = new File(filename);
607     ClearPage.ensureDirectory(file);
608 
609     PrintStream stream = null;
610     try {
611       stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(file)));
612       writeClassFile(stream, notStrippable, cl, keepStubComments);
613     } catch (FileNotFoundException e) {
614       System.err.println("error writing file: " + filename);
615     } finally {
616       if (stream != null) {
617         stream.close();
618       }
619     }
620   }
621 
writeClassFile(PrintStream stream, HashSet<ClassInfo> notStrippable, ClassInfo cl, boolean keepStubComments)622   static void writeClassFile(PrintStream stream, HashSet<ClassInfo> notStrippable, ClassInfo cl, boolean keepStubComments) {
623     PackageInfo pkg = cl.containingPackage();
624     if (cl.containingClass() == null) {
625         stream.print(parseLicenseHeader(cl.position()));
626     }
627     if (pkg != null) {
628       stream.println("package " + pkg.name() + ";");
629     }
630     writeClass(stream, notStrippable, cl, keepStubComments);
631   }
632 
parseLicenseHeader( SourcePositionInfo positionInfo)633   private static String parseLicenseHeader(/* @Nonnull */ SourcePositionInfo positionInfo) {
634     if (positionInfo == null) {
635       throw new NullPointerException("positionInfo == null");
636     }
637 
638     try {
639       final File sourceFile = new File(positionInfo.file);
640       if (!sourceFile.exists()) {
641         throw new IllegalArgumentException("Unable to find " + sourceFile +
642                 ". This is usually because doclava has been asked to generate stubs for a file " +
643                 "that isn't present in the list of input source files but exists in the input " +
644                 "classpath.");
645       }
646       return parseLicenseHeader(new FileInputStream(sourceFile));
647     } catch (IOException ioe) {
648       throw new RuntimeException("Unable to parse license header for: " + positionInfo.file, ioe);
649     }
650   }
651 
652   /* @VisibleForTesting */
parseLicenseHeader(InputStream input)653   static String parseLicenseHeader(InputStream input) throws IOException {
654     StringBuilder builder = new StringBuilder(8192);
655     try (Scanner scanner  = new Scanner(
656           new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)))) {
657       String line;
658       while (scanner.hasNextLine()) {
659         line = scanner.nextLine().trim();
660         // Use an extremely simple strategy for parsing license headers : assume that
661         // all file content before the first "package " or "import " directive is a license
662         // header. In some cases this might contain more than just the license header, but we
663         // don't care.
664         if (line.startsWith("package ") || line.startsWith("import ")) {
665           break;
666         }
667         builder.append(line);
668         builder.append("\n");
669       }
670 
671       // We've reached the end of the file without reaching any package or import
672       // directives.
673       if (!scanner.hasNextLine()) {
674         throw new IOException("Unable to parse license header");
675       }
676     }
677 
678     return builder.toString();
679   }
680 
writeClass(PrintStream stream, HashSet<ClassInfo> notStrippable, ClassInfo cl, boolean keepStubComments)681   static void writeClass(PrintStream stream, HashSet<ClassInfo> notStrippable, ClassInfo cl, boolean keepStubComments) {
682     if (keepStubComments) {
683       writeComment(stream, cl);
684     }
685 
686     writeAnnotations(stream, cl.annotations(), cl.isDeprecated());
687 
688     stream.print(cl.scope() + " ");
689     if (cl.isAbstract() && !cl.isAnnotation() && !cl.isInterface()) {
690       stream.print("abstract ");
691     }
692     if (cl.isStatic()) {
693       stream.print("static ");
694     }
695     if (cl.isFinal() && !cl.isEnum()) {
696       stream.print("final ");
697     }
698     if (false) {
699       stream.print("strictfp ");
700     }
701 
702     HashSet<String> classDeclTypeVars = new HashSet();
703     String leafName = cl.asTypeInfo().fullName(classDeclTypeVars);
704     int bracket = leafName.indexOf('<');
705     if (bracket < 0) bracket = leafName.length() - 1;
706     int period = leafName.lastIndexOf('.', bracket);
707     if (period < 0) period = -1;
708     leafName = leafName.substring(period + 1);
709 
710     String kind = cl.kind();
711     stream.println(kind + " " + leafName);
712 
713     TypeInfo base = cl.superclassType();
714 
715     if (!"enum".equals(kind)) {
716       if (base != null && !"java.lang.Object".equals(base.qualifiedTypeName())) {
717         stream.println("  extends " + base.fullName(classDeclTypeVars));
718       }
719     }
720 
721     List<TypeInfo> usedInterfaces = new ArrayList<TypeInfo>();
722     for (TypeInfo iface : cl.realInterfaceTypes()) {
723       if (notStrippable.contains(iface.asClassInfo()) && !iface.asClassInfo().isDocOnly()) {
724         usedInterfaces.add(iface);
725       }
726     }
727     if (usedInterfaces.size() > 0 && !cl.isAnnotation()) {
728       // can java annotations extend other ones?
729       if (cl.isInterface() || cl.isAnnotation()) {
730         stream.print("  extends ");
731       } else {
732         stream.print("  implements ");
733       }
734       String comma = "";
735       for (TypeInfo iface : usedInterfaces) {
736         stream.print(comma + iface.fullName(classDeclTypeVars));
737         comma = ", ";
738       }
739       stream.println();
740     }
741 
742     stream.println("{");
743 
744     ArrayList<FieldInfo> enumConstants = cl.enumConstants();
745     int N = enumConstants.size();
746     int i = 0;
747     for (FieldInfo field : enumConstants) {
748       writeAnnotations(stream, field.annotations(), field.isDeprecated());
749       if (!field.constantLiteralValue().equals("null")) {
750         stream.println(field.name() + "(" + field.constantLiteralValue()
751             + (i == N - 1 ? ");" : "),"));
752       } else {
753         stream.println(field.name() + "(" + (i == N - 1 ? ");" : "),"));
754       }
755       i++;
756     }
757 
758     for (ClassInfo inner : cl.getRealInnerClasses()) {
759       if (notStrippable.contains(inner) && !inner.isDocOnly()) {
760         writeClass(stream, notStrippable, inner, keepStubComments);
761       }
762     }
763 
764 
765     for (MethodInfo method : cl.constructors()) {
766       if (!method.isDocOnly()) {
767         writeMethod(stream, method, true, keepStubComments);
768       }
769     }
770 
771     boolean fieldNeedsInitialization = false;
772     boolean staticFieldNeedsInitialization = false;
773     for (FieldInfo field : cl.selfFields()) {
774       if (!field.isDocOnly()) {
775         if (!field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) {
776           fieldNeedsInitialization = true;
777         }
778         if (field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) {
779           staticFieldNeedsInitialization = true;
780         }
781       }
782     }
783 
784     // The compiler includes a default public constructor that calls the super classes
785     // default constructor in the case where there are no written constructors.
786     // So, if we hide all the constructors, java may put in a constructor
787     // that calls a nonexistent super class constructor. So, if there are no constructors,
788     // and the super class doesn't have a default constructor, write in a private constructor
789     // that works. TODO -- we generate this as protected, but we really should generate
790     // it as private unless it also exists in the real code.
791     if ((cl.constructors().isEmpty() && (!cl.getNonWrittenConstructors().isEmpty() ||
792         fieldNeedsInitialization)) && !cl.isAnnotation() && !cl.isInterface() && !cl.isEnum()) {
793       // Errors.error(Errors.HIDDEN_CONSTRUCTOR,
794       // cl.position(), "No constructors " +
795       // "found and superclass has no parameterless constructor.  A constructor " +
796       // "that calls an appropriate superclass constructor " +
797       // "was automatically written to stubs.\n");
798       stream.println(cl.leafName() + "() { " + superCtorCall(cl, null) + "throw new"
799           + " RuntimeException(\"Stub!\"); }");
800     }
801 
802     for (MethodInfo method : cl.allSelfMethods()) {
803       if (cl.isEnum()) {
804         if (("values".equals(method.name()) && "()".equals(method.signature())) ||
805             ("valueOf".equals(method.name()) &&
806             "(java.lang.String)".equals(method.signature()))) {
807           // skip these two methods on enums, because they're synthetic,
808           // although for some reason javadoc doesn't mark them as synthetic,
809           // maybe because they still want them documented
810           continue;
811         }
812       }
813       if (!method.isDocOnly()) {
814         writeMethod(stream, method, false, keepStubComments);
815       }
816     }
817     // Write all methods that are hidden or removed, but override abstract methods or interface methods.
818     // These can't be hidden.
819     List<MethodInfo> hiddenAndRemovedMethods = cl.getHiddenMethods();
820     hiddenAndRemovedMethods.addAll(cl.getRemovedMethods());
821     for (MethodInfo method : hiddenAndRemovedMethods) {
822       MethodInfo overriddenMethod =
823           method.findRealOverriddenMethod(method.name(), method.signature(), notStrippable);
824       ClassInfo classContainingMethod =
825           method.findRealOverriddenClass(method.name(), method.signature());
826       if (overriddenMethod != null && !overriddenMethod.isHiddenOrRemoved() &&
827           !overriddenMethod.isDocOnly() &&
828           (overriddenMethod.isAbstract() || overriddenMethod.containingClass().isInterface())) {
829         method.setReason("1:" + classContainingMethod.qualifiedName());
830         cl.addMethod(method);
831         writeMethod(stream, method, false, keepStubComments);
832       }
833     }
834 
835     for (MethodInfo element : cl.annotationElements()) {
836       if (!element.isDocOnly()) {
837         writeAnnotationElement(stream, element);
838       }
839     }
840 
841     for (FieldInfo field : cl.selfFields()) {
842       if (!field.isDocOnly()) {
843         writeField(stream, field, keepStubComments);
844       }
845     }
846 
847     if (staticFieldNeedsInitialization) {
848       stream.print("static { ");
849       for (FieldInfo field : cl.selfFields()) {
850         if (!field.isDocOnly() && field.isStatic() && field.isFinal() && !fieldIsInitialized(field)
851             && field.constantValue() == null) {
852           stream.print(field.name() + " = " + field.type().defaultValue() + "; ");
853         }
854       }
855       stream.println("}");
856     }
857 
858     stream.println("}");
859   }
860 
writeMethod(PrintStream stream, MethodInfo method, boolean isConstructor, boolean keepStubComments)861   static void writeMethod(PrintStream stream, MethodInfo method, boolean isConstructor, boolean keepStubComments) {
862     if (keepStubComments) {
863       writeComment(stream, method);
864     }
865     String comma;
866 
867     writeAnnotations(stream, method.annotations(), method.isDeprecated());
868 
869     if (method.isDefault()) {
870       stream.print("default ");
871     }
872     stream.print(method.scope() + " ");
873     if (method.isStatic()) {
874       stream.print("static ");
875     }
876     if (method.isFinal()) {
877       stream.print("final ");
878     }
879     if (method.isAbstract()) {
880       stream.print("abstract ");
881     }
882     if (method.isSynchronized()) {
883       stream.print("synchronized ");
884     }
885     if (method.isNative()) {
886       stream.print("native ");
887     }
888     if (false /* method.isStictFP() */) {
889       stream.print("strictfp ");
890     }
891 
892     stream.print(method.typeArgumentsName(new HashSet()) + " ");
893 
894     if (!isConstructor) {
895       stream.print(method.returnType().fullName(method.typeVariables()) + " ");
896     }
897     String n = method.name();
898     int pos = n.lastIndexOf('.');
899     if (pos >= 0) {
900       n = n.substring(pos + 1);
901     }
902     stream.print(n + "(");
903     comma = "";
904     int count = 1;
905     int size = method.parameters().size();
906     for (ParameterInfo param : method.parameters()) {
907       stream.print(comma);
908       writeAnnotations(stream, param.annotations(), false);
909       stream.print(fullParameterTypeName(method, param.type(), count == size) + " "
910           + param.name());
911       comma = ", ";
912       count++;
913     }
914     stream.print(")");
915 
916     comma = "";
917     if (method.thrownExceptions().size() > 0) {
918       stream.print(" throws ");
919       for (ClassInfo thrown : method.thrownExceptions()) {
920         stream.print(comma + thrown.qualifiedName());
921         comma = ", ";
922       }
923     }
924     if (method.isAbstract() || method.isNative() || (method.containingClass().isInterface() && (!method.isDefault() && !method.isStatic()))) {
925       stream.println(";");
926     } else {
927       stream.print(" { ");
928       if (isConstructor) {
929         stream.print(superCtorCall(method.containingClass(), method.thrownExceptions()));
930       }
931       stream.println("throw new RuntimeException(\"Stub!\"); }");
932     }
933   }
934 
writeField(PrintStream stream, FieldInfo field, boolean keepStubComments)935   static void writeField(PrintStream stream, FieldInfo field, boolean keepStubComments) {
936     if (keepStubComments) {
937       writeComment(stream, field);
938     }
939     writeAnnotations(stream, field.annotations(), field.isDeprecated());
940 
941     stream.print(field.scope() + " ");
942     if (field.isStatic()) {
943       stream.print("static ");
944     }
945     if (field.isFinal()) {
946       stream.print("final ");
947     }
948     if (field.isTransient()) {
949       stream.print("transient ");
950     }
951     if (field.isVolatile()) {
952       stream.print("volatile ");
953     }
954 
955     stream.print(field.type().fullName());
956     stream.print(" ");
957     stream.print(field.name());
958 
959     if (fieldIsInitialized(field)) {
960       stream.print(" = " + field.constantLiteralValue());
961     }
962 
963     stream.println(";");
964   }
965 
fieldIsInitialized(FieldInfo field)966   static boolean fieldIsInitialized(FieldInfo field) {
967     return (field.isFinal() && field.constantValue() != null)
968         || !field.type().dimension().equals("") || field.containingClass().isInterface();
969   }
970 
canCallMethod(ClassInfo from, MethodInfo m)971   static boolean canCallMethod(ClassInfo from, MethodInfo m) {
972     if (m.isPublic() || m.isProtected()) {
973       return true;
974     }
975     if (m.isPackagePrivate()) {
976       String fromPkg = from.containingPackage().name();
977       String pkg = m.containingClass().containingPackage().name();
978       if (fromPkg.equals(pkg)) {
979         return true;
980       }
981     }
982     return false;
983   }
984 
985   // call a constructor, any constructor on this class's superclass.
superCtorCall(ClassInfo cl, ArrayList<ClassInfo> thrownExceptions)986   static String superCtorCall(ClassInfo cl, ArrayList<ClassInfo> thrownExceptions) {
987     ClassInfo base = cl.realSuperclass();
988     if (base == null) {
989       return "";
990     }
991     HashSet<String> exceptionNames = new HashSet<String>();
992     if (thrownExceptions != null) {
993       for (ClassInfo thrown : thrownExceptions) {
994         exceptionNames.add(thrown.name());
995       }
996     }
997     ArrayList<MethodInfo> ctors = base.constructors();
998     MethodInfo ctor = null;
999     // bad exception indicates that the exceptions thrown by the super constructor
1000     // are incompatible with the constructor we're using for the sub class.
1001     Boolean badException = false;
1002     for (MethodInfo m : ctors) {
1003       if (canCallMethod(cl, m)) {
1004         if (m.thrownExceptions() != null) {
1005           for (ClassInfo thrown : m.thrownExceptions()) {
1006             if (thrownExceptions != null && !exceptionNames.contains(thrown.name())) {
1007               badException = true;
1008             }
1009           }
1010         }
1011         if (badException) {
1012           badException = false;
1013           continue;
1014         }
1015         // if it has no args, we're done
1016         if (m.parameters().isEmpty()) {
1017           return "";
1018         }
1019         ctor = m;
1020       }
1021     }
1022     if (ctor != null) {
1023       String result = "";
1024       result += "super(";
1025       ArrayList<ParameterInfo> params = ctor.parameters();
1026       for (ParameterInfo param : params) {
1027         TypeInfo t = param.type();
1028         if (t.isPrimitive() && t.dimension().equals("")) {
1029           String n = t.simpleTypeName();
1030           if (("byte".equals(n) || "short".equals(n) || "int".equals(n) || "long".equals(n)
1031               || "float".equals(n) || "double".equals(n))
1032               && t.dimension().equals("")) {
1033             result += "0";
1034           } else if ("char".equals(n)) {
1035             result += "'\\0'";
1036           } else if ("boolean".equals(n)) {
1037             result += "false";
1038           } else {
1039             result += "<<unknown-" + n + ">>";
1040           }
1041         } else {
1042           // put null in each super class method. Cast null to the correct type
1043           // to avoid collisions with other constructors. If the type is generic
1044           // don't cast it
1045           result +=
1046               (!t.isTypeVariable() ? "(" + t.qualifiedTypeName() + t.dimension() + ")" : "")
1047                   + "null";
1048         }
1049         if (param != params.get(params.size()-1)) {
1050           result += ",";
1051         }
1052       }
1053       result += "); ";
1054       return result;
1055     } else {
1056       return "";
1057     }
1058   }
1059 
1060     /**
1061      * Write out the given list of annotations. If the {@code isDeprecated}
1062      * flag is true also write out a {@code @Deprecated} annotation if it did not
1063      * already appear in the list of annotations. (This covers APIs that mention
1064      * {@code @deprecated} in their documentation but fail to add
1065      * {@code @Deprecated} as an annotation.
1066      * <p>
1067      * {@code @Override} annotations are deliberately skipped.
1068      */
writeAnnotations(PrintStream stream, List<AnnotationInstanceInfo> annotations, boolean isDeprecated)1069   static void writeAnnotations(PrintStream stream, List<AnnotationInstanceInfo> annotations,
1070           boolean isDeprecated) {
1071     assert annotations != null;
1072     for (AnnotationInstanceInfo ann : annotations) {
1073       // Skip @Override annotations: the stubs do not need it and in some cases it leads
1074       // to compilation errors with the way the stubs are generated
1075       if (ann.type() != null && ann.type().qualifiedName().equals("java.lang.Override")) {
1076         continue;
1077       }
1078       if (!ann.type().isHiddenOrRemoved()) {
1079         stream.println(ann.toString());
1080         if (isDeprecated && ann.type() != null
1081             && ann.type().qualifiedName().equals("java.lang.Deprecated")) {
1082           isDeprecated = false; // Prevent duplicate annotations
1083         }
1084       }
1085     }
1086     if (isDeprecated) {
1087       stream.println("@Deprecated");
1088     }
1089   }
1090 
writeAnnotationElement(PrintStream stream, MethodInfo ann)1091   static void writeAnnotationElement(PrintStream stream, MethodInfo ann) {
1092     stream.print(ann.returnType().fullName());
1093     stream.print(" ");
1094     stream.print(ann.name());
1095     stream.print("()");
1096     AnnotationValueInfo def = ann.defaultAnnotationElementValue();
1097     if (def != null) {
1098       stream.print(" default ");
1099       stream.print(def.valueString());
1100     }
1101     stream.println(";");
1102   }
1103 
writeComment(PrintStream stream, DocInfo doc)1104   static void writeComment(PrintStream stream, DocInfo doc) {
1105     if (!doc.isHiddenOrRemoved() && !doc.comment().isDocOnly() && !"".equals(doc.getRawCommentText())) {
1106       String newLineSeparator = System.getProperty("line.separator");
1107       stream.println("/**");
1108       stream.print(" * ");
1109       stream.println(doc.getRawCommentText().replace(newLineSeparator, newLineSeparator + " *"));
1110       stream.println(" */");
1111     }
1112   }
1113 
writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs, boolean strip)1114   public static void writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs, boolean strip) {
1115     if (strip) {
1116       Stubs.writeXml(xmlWriter, pkgs);
1117     } else {
1118       Stubs.writeXml(xmlWriter, pkgs, c -> true);
1119     }
1120   }
1121 
writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs, Predicate<ClassInfo> notStrippable)1122   public static void writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs,
1123       Predicate<ClassInfo> notStrippable) {
1124 
1125     final PackageInfo[] packages = pkgs.toArray(new PackageInfo[pkgs.size()]);
1126     Arrays.sort(packages, PackageInfo.comparator);
1127 
1128     xmlWriter.println("<api>");
1129     for (PackageInfo pkg: packages) {
1130       writePackageXML(xmlWriter, pkg, pkg.allClasses().values(), notStrippable);
1131     }
1132     xmlWriter.println("</api>");
1133   }
1134 
writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs)1135   public static void writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs) {
1136     HashSet<ClassInfo> allClasses = new HashSet<>();
1137     for (PackageInfo pkg: pkgs) {
1138       allClasses.addAll(pkg.allClasses().values());
1139     }
1140     Predicate<ClassInfo> notStrippable = allClasses::contains;
1141     writeXml(xmlWriter, pkgs, notStrippable);
1142   }
1143 
writePackageXML(PrintStream xmlWriter, PackageInfo pack, Collection<ClassInfo> classList, Predicate<ClassInfo> notStrippable)1144   static void writePackageXML(PrintStream xmlWriter, PackageInfo pack,
1145       Collection<ClassInfo> classList, Predicate<ClassInfo> notStrippable) {
1146     ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]);
1147     Arrays.sort(classes, ClassInfo.comparator);
1148     // Work around the bogus "Array" class we invent for
1149     // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505)
1150     if (pack.name().equals(PackageInfo.DEFAULT_PACKAGE)) {
1151       return;
1152     }
1153     xmlWriter.println("<package name=\"" + pack.name() + "\"\n"
1154     // + " source=\"" + pack.position() + "\"\n"
1155         + ">");
1156     for (ClassInfo cl : classes) {
1157       writeClassXML(xmlWriter, cl, notStrippable);
1158     }
1159     xmlWriter.println("</package>");
1160 
1161 
1162   }
1163 
writeClassXML(PrintStream xmlWriter, ClassInfo cl, Predicate<ClassInfo> notStrippable)1164   static void writeClassXML(PrintStream xmlWriter, ClassInfo cl, Predicate<ClassInfo> notStrippable) {
1165     String scope = cl.scope();
1166     String deprecatedString = "";
1167     String declString = (cl.isInterface()) ? "interface" : "class";
1168     if (cl.isDeprecated()) {
1169       deprecatedString = "deprecated";
1170     } else {
1171       deprecatedString = "not deprecated";
1172     }
1173     xmlWriter.println("<" + declString + " name=\"" + cl.name() + "\"");
1174     if (!cl.isInterface() && !cl.qualifiedName().equals("java.lang.Object")) {
1175       xmlWriter.println(" extends=\""
1176           + ((cl.realSuperclass() == null) ? "java.lang.Object" : cl.realSuperclass()
1177               .qualifiedName()) + "\"");
1178     }
1179     xmlWriter.println(" abstract=\"" + cl.isAbstract() + "\"\n" + " static=\"" + cl.isStatic()
1180         + "\"\n" + " final=\"" + cl.isFinal() + "\"\n" + " deprecated=\"" + deprecatedString
1181         + "\"\n" + " visibility=\"" + scope + "\"\n"
1182         // + " source=\"" + cl.position() + "\"\n"
1183         + ">");
1184 
1185     ArrayList<ClassInfo> interfaces = cl.realInterfaces();
1186     Collections.sort(interfaces, ClassInfo.comparator);
1187     for (ClassInfo iface : interfaces) {
1188       if (notStrippable.test(iface)) {
1189         xmlWriter.println("<implements name=\"" + iface.qualifiedName() + "\">");
1190         xmlWriter.println("</implements>");
1191       }
1192     }
1193 
1194     ArrayList<MethodInfo> constructors = cl.constructors();
1195     Collections.sort(constructors, MethodInfo.comparator);
1196     for (MethodInfo mi : constructors) {
1197       writeConstructorXML(xmlWriter, mi);
1198     }
1199 
1200     ArrayList<MethodInfo> methods = cl.allSelfMethods();
1201     Collections.sort(methods, MethodInfo.comparator);
1202     for (MethodInfo mi : methods) {
1203       writeMethodXML(xmlWriter, mi);
1204     }
1205 
1206     ArrayList<FieldInfo> fields = cl.selfFields();
1207     Collections.sort(fields, FieldInfo.comparator);
1208     for (FieldInfo fi : fields) {
1209       writeFieldXML(xmlWriter, fi);
1210     }
1211     xmlWriter.println("</" + declString + ">");
1212 
1213   }
1214 
writeMethodXML(PrintStream xmlWriter, MethodInfo mi)1215   static void writeMethodXML(PrintStream xmlWriter, MethodInfo mi) {
1216     String scope = mi.scope();
1217 
1218     String deprecatedString = "";
1219     if (mi.isDeprecated()) {
1220       deprecatedString = "deprecated";
1221     } else {
1222       deprecatedString = "not deprecated";
1223     }
1224     xmlWriter.println("<method name=\""
1225         + mi.name()
1226         + "\"\n"
1227         + ((mi.returnType() != null) ? " return=\""
1228             + makeXMLcompliant(fullParameterTypeName(mi, mi.returnType(), false)) + "\"\n" : "")
1229         + " abstract=\"" + mi.isAbstract() + "\"\n" + " native=\"" + mi.isNative() + "\"\n"
1230         + " synchronized=\"" + mi.isSynchronized() + "\"\n" + " static=\"" + mi.isStatic() + "\"\n"
1231         + " final=\"" + mi.isFinal() + "\"\n" + " deprecated=\"" + deprecatedString + "\"\n"
1232         + " visibility=\"" + scope + "\"\n"
1233         // + " source=\"" + mi.position() + "\"\n"
1234         + ">");
1235 
1236     // write parameters in declaration order
1237     int numParameters = mi.parameters().size();
1238     int count = 0;
1239     for (ParameterInfo pi : mi.parameters()) {
1240       count++;
1241       writeParameterXML(xmlWriter, mi, pi, count == numParameters);
1242     }
1243 
1244     // but write exceptions in canonicalized order
1245     ArrayList<ClassInfo> exceptions = mi.thrownExceptions();
1246     Collections.sort(exceptions, ClassInfo.comparator);
1247     for (ClassInfo pi : exceptions) {
1248       xmlWriter.println("<exception name=\"" + pi.name() + "\" type=\"" + pi.qualifiedName()
1249           + "\">");
1250       xmlWriter.println("</exception>");
1251     }
1252     xmlWriter.println("</method>");
1253   }
1254 
writeConstructorXML(PrintStream xmlWriter, MethodInfo mi)1255   static void writeConstructorXML(PrintStream xmlWriter, MethodInfo mi) {
1256     String scope = mi.scope();
1257     String deprecatedString = "";
1258     if (mi.isDeprecated()) {
1259       deprecatedString = "deprecated";
1260     } else {
1261       deprecatedString = "not deprecated";
1262     }
1263     xmlWriter.println("<constructor name=\"" + mi.name() + "\"\n" + " type=\""
1264         + mi.containingClass().qualifiedName() + "\"\n" + " static=\"" + mi.isStatic() + "\"\n"
1265         + " final=\"" + mi.isFinal() + "\"\n" + " deprecated=\"" + deprecatedString + "\"\n"
1266         + " visibility=\"" + scope + "\"\n"
1267         // + " source=\"" + mi.position() + "\"\n"
1268         + ">");
1269 
1270     int numParameters = mi.parameters().size();
1271     int count = 0;
1272     for (ParameterInfo pi : mi.parameters()) {
1273       count++;
1274       writeParameterXML(xmlWriter, mi, pi, count == numParameters);
1275     }
1276 
1277     ArrayList<ClassInfo> exceptions = mi.thrownExceptions();
1278     Collections.sort(exceptions, ClassInfo.comparator);
1279     for (ClassInfo pi : exceptions) {
1280       xmlWriter.println("<exception name=\"" + pi.name() + "\" type=\"" + pi.qualifiedName()
1281           + "\">");
1282       xmlWriter.println("</exception>");
1283     }
1284     xmlWriter.println("</constructor>");
1285   }
1286 
writeParameterXML(PrintStream xmlWriter, MethodInfo method, ParameterInfo pi, boolean isLast)1287   static void writeParameterXML(PrintStream xmlWriter, MethodInfo method, ParameterInfo pi,
1288       boolean isLast) {
1289     xmlWriter.println("<parameter name=\"" + pi.name() + "\" type=\""
1290         + makeXMLcompliant(fullParameterTypeName(method, pi.type(), isLast)) + "\">");
1291     xmlWriter.println("</parameter>");
1292   }
1293 
writeFieldXML(PrintStream xmlWriter, FieldInfo fi)1294   static void writeFieldXML(PrintStream xmlWriter, FieldInfo fi) {
1295     String scope = fi.scope();
1296     String deprecatedString = "";
1297     if (fi.isDeprecated()) {
1298       deprecatedString = "deprecated";
1299     } else {
1300       deprecatedString = "not deprecated";
1301     }
1302     // need to make sure value is valid XML
1303     String value = makeXMLcompliant(fi.constantLiteralValue());
1304 
1305     String fullTypeName = makeXMLcompliant(fi.type().fullName());
1306 
1307     xmlWriter.println("<field name=\"" + fi.name() + "\"\n" + " type=\"" + fullTypeName + "\"\n"
1308         + " transient=\"" + fi.isTransient() + "\"\n" + " volatile=\"" + fi.isVolatile() + "\"\n"
1309         + (fieldIsInitialized(fi) ? " value=\"" + value + "\"\n" : "") + " static=\""
1310         + fi.isStatic() + "\"\n" + " final=\"" + fi.isFinal() + "\"\n" + " deprecated=\""
1311         + deprecatedString + "\"\n" + " visibility=\"" + scope + "\"\n"
1312         // + " source=\"" + fi.position() + "\"\n"
1313         + ">");
1314     xmlWriter.println("</field>");
1315   }
1316 
makeXMLcompliant(String s)1317   static String makeXMLcompliant(String s) {
1318     String returnString = "";
1319     returnString = s.replaceAll("&", "&amp;");
1320     returnString = returnString.replaceAll("<", "&lt;");
1321     returnString = returnString.replaceAll(">", "&gt;");
1322     returnString = returnString.replaceAll("\"", "&quot;");
1323     returnString = returnString.replaceAll("'", "&apos;");
1324     return returnString;
1325   }
1326 
1327   /**
1328    * Predicate that decides if the given member should be considered part of an
1329    * API surface area. To make the most accurate decision, it searches for
1330    * signals on the member, all containing classes, and all containing packages.
1331    */
1332   public static class ApiPredicate implements Predicate<MemberInfo> {
1333     public boolean ignoreShown;
1334     public boolean ignoreRemoved;
1335     public boolean matchRemoved;
1336 
1337     /**
1338      * Set if the value of {@link MemberInfo#hasShowAnnotation()} should be
1339      * ignored. That is, this predicate will assume that all encountered members
1340      * match the "shown" requirement.
1341      * <p>
1342      * This is typically useful when generating "current.txt", when no
1343      * {@link Doclava#showAnnotations} have been defined.
1344      */
setIgnoreShown(boolean ignoreShown)1345     public ApiPredicate setIgnoreShown(boolean ignoreShown) {
1346       this.ignoreShown = ignoreShown;
1347       return this;
1348     }
1349 
1350     /**
1351      * Set if the value of {@link MemberInfo#isRemoved()} should be ignored.
1352      * That is, this predicate will assume that all encountered members match
1353      * the "removed" requirement.
1354      * <p>
1355      * This is typically useful when generating "removed.txt", when it's okay to
1356      * reference both current and removed APIs.
1357      */
setIgnoreRemoved(boolean ignoreRemoved)1358     public ApiPredicate setIgnoreRemoved(boolean ignoreRemoved) {
1359       this.ignoreRemoved = ignoreRemoved;
1360       return this;
1361     }
1362 
1363     /**
1364      * Set what the value of {@link MemberInfo#isRemoved()} must be equal to in
1365      * order for a member to match.
1366      * <p>
1367      * This is typically useful when generating "removed.txt", when you only
1368      * want to match members that have actually been removed.
1369      */
setMatchRemoved(boolean matchRemoved)1370     public ApiPredicate setMatchRemoved(boolean matchRemoved) {
1371       this.matchRemoved = matchRemoved;
1372       return this;
1373     }
1374 
containingPackage(PackageInfo pkg)1375     private static PackageInfo containingPackage(PackageInfo pkg) {
1376       String name = pkg.name();
1377       final int lastDot = name.lastIndexOf('.');
1378       if (lastDot == -1) {
1379         return null;
1380       } else {
1381         name = name.substring(0, lastDot);
1382         return Converter.obtainPackage(name);
1383       }
1384     }
1385 
1386     @Override
test(MemberInfo member)1387     public boolean test(MemberInfo member) {
1388       boolean visible = member.isPublic() || member.isProtected();
1389       boolean hasShowAnnotation = member.hasShowAnnotation();
1390       boolean hidden = member.isHidden();
1391       boolean docOnly = member.isDocOnly();
1392       boolean removed = member.isRemoved();
1393 
1394       ClassInfo clazz = member.containingClass();
1395       if (clazz != null) {
1396         PackageInfo pkg = clazz.containingPackage();
1397         while (pkg != null) {
1398           hidden |= pkg.isHidden();
1399           docOnly |= pkg.isDocOnly();
1400           removed |= pkg.isRemoved();
1401           pkg = containingPackage(pkg);
1402         }
1403       }
1404       while (clazz != null) {
1405         visible &= clazz.isPublic() || clazz.isProtected();
1406         hasShowAnnotation |= clazz.hasShowAnnotation();
1407         hidden |= clazz.isHidden();
1408         docOnly |= clazz.isDocOnly();
1409         removed |= clazz.isRemoved();
1410         clazz = clazz.containingClass();
1411       }
1412 
1413       if (ignoreShown) {
1414         hasShowAnnotation = true;
1415       }
1416       if (ignoreRemoved) {
1417         removed = matchRemoved;
1418       }
1419 
1420       return visible && hasShowAnnotation && !hidden && !docOnly && (removed == matchRemoved);
1421     }
1422   }
1423 
1424   /**
1425    * Filter that will elide exact duplicate members that are already included
1426    * in another superclass/interfaces.
1427    */
1428   public static class ElidingPredicate implements Predicate<MemberInfo> {
1429     private final Predicate<MemberInfo> wrapped;
1430 
ElidingPredicate(Predicate<MemberInfo> wrapped)1431     public ElidingPredicate(Predicate<MemberInfo> wrapped) {
1432       this.wrapped = wrapped;
1433     }
1434 
1435     @Override
test(MemberInfo member)1436     public boolean test(MemberInfo member) {
1437       // This member should be included, but if it's an exact duplicate
1438       // override then we can elide it.
1439       if (member instanceof MethodInfo) {
1440         MethodInfo method = (MethodInfo) member;
1441         if (method.returnType() != null) {  // not a constructor
1442           String methodRaw = writeMethodApiWithoutDefault(method);
1443           return (method.findPredicateOverriddenMethod(new Predicate<MemberInfo>() {
1444             @Override
1445             public boolean test(MemberInfo test) {
1446               // We're looking for included and perfect signature
1447               return (wrapped.test(test)
1448                   && writeMethodApiWithoutDefault((MethodInfo) test).equals(methodRaw));
1449             }
1450           }) == null);
1451         }
1452       }
1453       return true;
1454     }
1455   }
1456 
1457   public static class FilterPredicate implements Predicate<MemberInfo> {
1458     private final Predicate<MemberInfo> wrapped;
1459 
1460     public FilterPredicate(Predicate<MemberInfo> wrapped) {
1461       this.wrapped = wrapped;
1462     }
1463 
1464     @Override
1465     public boolean test(MemberInfo member) {
1466       if (wrapped.test(member)) {
1467         return true;
1468       } else if (member instanceof MethodInfo) {
1469         MethodInfo method = (MethodInfo) member;
1470         return method.returnType() != null &&  // not a constructor
1471                method.findPredicateOverriddenMethod(wrapped) != null;
1472       } else {
1473         return false;
1474       }
1475     }
1476   }
1477 
1478   static void writeApi(PrintStream apiWriter, Map<PackageInfo, List<ClassInfo>> classesByPackage,
1479       Predicate<MemberInfo> filterEmit, Predicate<MemberInfo> filterReference) {
1480     for (PackageInfo pkg : classesByPackage.keySet().stream().sorted(PackageInfo.comparator)
1481         .collect(Collectors.toList())) {
1482       if (pkg.name().equals(PackageInfo.DEFAULT_PACKAGE)) continue;
1483 
1484       boolean hasWrittenPackageHead = false;
1485       for (ClassInfo clazz : classesByPackage.get(pkg).stream().sorted(ClassInfo.comparator)
1486           .collect(Collectors.toList())) {
1487         hasWrittenPackageHead = writeClassApi(apiWriter, clazz, filterEmit, filterReference,
1488             hasWrittenPackageHead);
1489       }
1490 
1491       if (hasWrittenPackageHead) {
1492         apiWriter.print("}\n\n");
1493       }
1494     }
1495   }
1496 
1497   static void writeDexApi(PrintStream apiWriter, Map<PackageInfo, List<ClassInfo>> classesByPackage,
1498       Predicate<MemberInfo> filterEmit) {
1499     for (PackageInfo pkg : classesByPackage.keySet().stream().sorted(PackageInfo.comparator)
1500         .collect(Collectors.toList())) {
1501       if (pkg.name().equals(PackageInfo.DEFAULT_PACKAGE)) continue;
1502 
1503       for (ClassInfo clazz : classesByPackage.get(pkg).stream().sorted(ClassInfo.comparator)
1504           .collect(Collectors.toList())) {
1505         writeClassDexApi(apiWriter, clazz, filterEmit);
1506       }
1507     }
1508   }
1509 
1510   static void writeApiMapping(PrintStream mappingWriter,
1511       Map<PackageInfo, List<ClassInfo>> classesByPackage) {
1512 
1513     for (PackageInfo pkg : classesByPackage.keySet().stream().sorted(PackageInfo.comparator)
1514             .collect(Collectors.toList())) {
1515       if (pkg.name().equals(PackageInfo.DEFAULT_PACKAGE)) continue;
1516       for (ClassInfo cl : classesByPackage.get(pkg).stream().sorted(ClassInfo.comparator)
1517               .collect(Collectors.toList())) {
1518         cl.getExhaustiveConstructors().stream().sorted(MethodInfo.comparator).forEach(method -> {
1519           writeMethodDexApi(mappingWriter, cl, method);
1520           writeSourcePositionInfo(mappingWriter, method);
1521         });
1522         cl.getExhaustiveMethods().stream().sorted(MethodInfo.comparator).forEach(method -> {
1523           writeMethodDexApi(mappingWriter, cl, method);
1524           writeSourcePositionInfo(mappingWriter, method);
1525         });
1526         cl.getExhaustiveEnumConstants().stream().sorted(FieldInfo.comparator).forEach(enumInfo -> {
1527           writeFieldDexApi(mappingWriter, cl, enumInfo);
1528           writeSourcePositionInfo(mappingWriter, enumInfo);
1529         });
1530         cl.getExhaustiveFields().stream().sorted(FieldInfo.comparator).forEach(field -> {
1531           writeFieldDexApi(mappingWriter, cl, field);
1532           writeSourcePositionInfo(mappingWriter, field);
1533         });
1534       }
1535     }
1536   }
1537 
1538   /**
1539    * Write the removed members of the class to removed.txt
1540    */
1541   private static boolean writeClassApi(PrintStream apiWriter, ClassInfo cl,
1542       Predicate<MemberInfo> filterEmit, Predicate<MemberInfo> filterReference,
1543       boolean hasWrittenPackageHead) {
1544 
1545     List<MethodInfo> constructors = cl.getExhaustiveConstructors().stream().filter(filterEmit)
1546         .sorted(MethodInfo.comparator).collect(Collectors.toList());
1547     List<MethodInfo> methods = cl.getExhaustiveMethods().stream().filter(filterEmit)
1548         .sorted(MethodInfo.comparator).collect(Collectors.toList());
1549     List<FieldInfo> enums = cl.getExhaustiveEnumConstants().stream().filter(filterEmit)
1550         .sorted(FieldInfo.comparator).collect(Collectors.toList());
1551     List<FieldInfo> fields = cl.filteredFields(filterEmit).stream()
1552         .sorted(FieldInfo.comparator).collect(Collectors.toList());
1553 
1554     final boolean classEmpty = (constructors.isEmpty() && methods.isEmpty() && enums.isEmpty()
1555         && fields.isEmpty());
1556     final boolean emit;
1557     if (filterEmit.test(cl.asMemberInfo())) {
1558       emit = true;
1559     } else if (!classEmpty) {
1560       emit = filterReference.test(cl.asMemberInfo());
1561     } else {
1562       emit = false;
1563     }
1564     if (!emit) {
1565       return hasWrittenPackageHead;
1566     }
1567 
1568     // Look for Android @SystemApi exposed outside the normal SDK; we require
1569     // that they're protected with a system permission.
1570     if (Doclava.android && Doclava.showAnnotations.contains("android.annotation.SystemApi")) {
1571       boolean systemService = "android.content.pm.PackageManager".equals(cl.qualifiedName());
1572       for (AnnotationInstanceInfo a : cl.annotations()) {
1573         if (a.type().qualifiedNameMatches("android", "annotation.SystemService")) {
1574           systemService = true;
1575         }
1576       }
1577       if (systemService) {
1578         for (MethodInfo mi : methods) {
1579           checkSystemPermissions(mi);
1580         }
1581       }
1582     }
1583 
1584     for (MethodInfo method : methods) {
1585       checkHiddenTypes(method, filterReference);
1586     }
1587     for (FieldInfo field : fields) {
1588       checkHiddenTypes(field, filterReference);
1589     }
1590 
1591     if (!hasWrittenPackageHead) {
1592       hasWrittenPackageHead = true;
1593       apiWriter.print("package ");
1594       apiWriter.print(cl.containingPackage().qualifiedName());
1595       apiWriter.print(" {\n\n");
1596     }
1597 
1598     apiWriter.print("  ");
1599     apiWriter.print(cl.scope());
1600     if (cl.isStatic()) {
1601       apiWriter.print(" static");
1602     }
1603     if (cl.isFinal()) {
1604       apiWriter.print(" final");
1605     }
1606     if (cl.isAbstract()) {
1607       apiWriter.print(" abstract");
1608     }
1609     if (cl.isDeprecated()) {
1610       apiWriter.print(" deprecated");
1611     }
1612     apiWriter.print(" ");
1613     apiWriter.print(cl.isInterface() ? "interface" : "class");
1614     apiWriter.print(" ");
1615     apiWriter.print(cl.name());
1616     if (cl.hasTypeParameters()) {
1617       apiWriter.print(TypeInfo.typeArgumentsName(cl.asTypeInfo().typeArguments(),
1618           new HashSet<String>()));
1619     }
1620 
1621     if (!cl.isInterface()
1622         && !"java.lang.Object".equals(cl.qualifiedName())) {
1623       final ClassInfo superclass = cl.filteredSuperclass(filterReference);
1624       if (superclass != null && !"java.lang.Object".equals(superclass.qualifiedName())) {
1625         apiWriter.print(" extends ");
1626         apiWriter.print(superclass.qualifiedName());
1627       }
1628     }
1629 
1630     List<ClassInfo> interfaces = cl.filteredInterfaces(filterReference).stream()
1631         .sorted(ClassInfo.comparator).collect(Collectors.toList());
1632     boolean first = true;
1633     for (ClassInfo iface : interfaces) {
1634       if (first) {
1635         apiWriter.print(" implements");
1636         first = false;
1637       }
1638       apiWriter.print(" ");
1639       apiWriter.print(iface.qualifiedName());
1640     }
1641 
1642     apiWriter.print(" {\n");
1643 
1644     for (MethodInfo mi : constructors) {
1645       writeConstructorApi(apiWriter, mi);
1646     }
1647     for (MethodInfo mi : methods) {
1648       writeMethodApi(apiWriter, mi);
1649     }
1650     for (FieldInfo fi : enums) {
1651       writeFieldApi(apiWriter, fi, "enum_constant");
1652     }
1653     for (FieldInfo fi : fields) {
1654       writeFieldApi(apiWriter, fi, "field");
1655     }
1656 
1657     apiWriter.print("  }\n\n");
1658     return hasWrittenPackageHead;
1659   }
1660 
1661   private static void writeClassDexApi(PrintStream apiWriter, ClassInfo cl,
1662       Predicate<MemberInfo> filterEmit) {
1663     if (filterEmit.test(cl.asMemberInfo())) {
1664       apiWriter.print(toSlashFormat(cl.qualifiedName()));
1665       apiWriter.print("\n");
1666     }
1667 
1668     List<MethodInfo> constructors = cl.getExhaustiveConstructors().stream().filter(filterEmit)
1669         .sorted(MethodInfo.comparator).collect(Collectors.toList());
1670     List<MethodInfo> methods = cl.getExhaustiveMethods().stream().filter(filterEmit)
1671         .sorted(MethodInfo.comparator).collect(Collectors.toList());
1672     List<FieldInfo> enums = cl.getExhaustiveEnumConstants().stream().filter(filterEmit)
1673         .sorted(FieldInfo.comparator).collect(Collectors.toList());
1674     List<FieldInfo> fields = cl.getExhaustiveFields().stream().filter(filterEmit)
1675         .sorted(FieldInfo.comparator).collect(Collectors.toList());
1676 
1677     for (MethodInfo mi : constructors) {
1678       writeMethodDexApi(apiWriter, cl, mi);
1679     }
1680     for (MethodInfo mi : methods) {
1681       writeMethodDexApi(apiWriter, cl, mi);
1682     }
1683     for (FieldInfo fi : enums) {
1684       writeFieldDexApi(apiWriter, cl, fi);
1685     }
1686     for (FieldInfo fi : fields) {
1687       writeFieldDexApi(apiWriter, cl, fi);
1688     }
1689   }
1690 
1691   private static void checkSystemPermissions(MethodInfo mi) {
1692     boolean hasAnnotation = false;
1693     for (AnnotationInstanceInfo a : mi.annotations()) {
1694       if (a.type().qualifiedNameMatches("android", "annotation.RequiresPermission")) {
1695         hasAnnotation = true;
1696         for (AnnotationValueInfo val : a.elementValues()) {
1697           ArrayList<AnnotationValueInfo> values = new ArrayList<>();
1698           boolean any = false;
1699           switch (val.element().name()) {
1700             case "value":
1701               values.add(val);
1702               break;
1703             case "allOf":
1704               values = (ArrayList<AnnotationValueInfo>) val.value();
1705               break;
1706             case "anyOf":
1707               any = true;
1708               values = (ArrayList<AnnotationValueInfo>) val.value();
1709               break;
1710           }
1711           if (values.isEmpty()) continue;
1712 
1713           ArrayList<String> system = new ArrayList<>();
1714           ArrayList<String> nonSystem = new ArrayList<>();
1715           for (AnnotationValueInfo value : values) {
1716             final String perm = String.valueOf(value.value());
1717             final String level = Doclava.manifestPermissions.getOrDefault(perm, null);
1718             if (level == null) {
1719               Errors.error(Errors.REMOVED_FIELD, mi.position(),
1720                   "Permission '" + perm + "' is not defined by AndroidManifest.xml.");
1721               continue;
1722             }
1723             if (level.contains("normal") || level.contains("dangerous")
1724                 || level.contains("ephemeral")) {
1725               nonSystem.add(perm);
1726             } else {
1727               system.add(perm);
1728             }
1729           }
1730 
1731           if (system.isEmpty() && nonSystem.isEmpty()) {
1732             hasAnnotation = false;
1733           } else if ((any && !nonSystem.isEmpty()) || (!any && system.isEmpty())) {
1734             Errors.error(Errors.REQUIRES_PERMISSION, mi, "Method '" + mi.name()
1735                 + "' must be protected with a system permission; it currently"
1736                 + " allows non-system callers holding " + nonSystem.toString());
1737           }
1738         }
1739       }
1740     }
1741     if (!hasAnnotation) {
1742       Errors.error(Errors.REQUIRES_PERMISSION, mi, "Method '" + mi.name()
1743         + "' must be protected with a system permission.");
1744     }
1745   }
1746 
1747   private static void checkHiddenTypes(MethodInfo method, Predicate<MemberInfo> filterReference) {
1748     checkHiddenTypes(method.returnType(), method, filterReference);
1749     List<ParameterInfo> params = method.parameters();
1750     if (params != null) {
1751       for (ParameterInfo param : params) {
1752         checkHiddenTypes(param.type(), method, filterReference);
1753       }
1754     }
1755   }
1756 
1757   private static void checkHiddenTypes(FieldInfo field, Predicate<MemberInfo> filterReference) {
1758     checkHiddenTypes(field.type(), field, filterReference);
1759   }
1760 
1761   private static void checkHiddenTypes(TypeInfo type, MemberInfo member,
1762       Predicate<MemberInfo> filterReference) {
1763     if (type == null || type.isPrimitive()) {
1764       return;
1765     }
1766 
1767     ClassInfo clazz = type.asClassInfo();
1768     if (clazz == null || !filterReference.test(clazz.asMemberInfo())) {
1769       Errors.error(Errors.HIDDEN_TYPE_PARAMETER, member.position(),
1770           "Member " + member + " references hidden type " + type.qualifiedTypeName() + ".");
1771     }
1772 
1773     List<TypeInfo> args = type.typeArguments();
1774     if (args != null) {
1775       for (TypeInfo arg : args) {
1776         checkHiddenTypes(arg, member, filterReference);
1777       }
1778     }
1779   }
1780 
1781   static void writeConstructorApi(PrintStream apiWriter, MethodInfo mi) {
1782     apiWriter.print("    ctor ");
1783     apiWriter.print(mi.scope());
1784     if (mi.isDeprecated()) {
1785       apiWriter.print(" deprecated");
1786     }
1787     apiWriter.print(" ");
1788     apiWriter.print(mi.name());
1789 
1790     writeParametersApi(apiWriter, mi, mi.parameters());
1791     writeThrowsApi(apiWriter, mi.thrownExceptions());
1792     apiWriter.print(";\n");
1793   }
1794 
1795   static String writeMethodApiWithoutDefault(MethodInfo mi) {
1796     final ByteArrayOutputStream out = new ByteArrayOutputStream();
1797     writeMethodApi(new PrintStream(out), mi, false);
1798     return out.toString();
1799   }
1800 
1801   static void writeMethodApi(PrintStream apiWriter, MethodInfo mi) {
1802     writeMethodApi(apiWriter, mi, true);
1803   }
1804 
1805   static void writeMethodApi(PrintStream apiWriter, MethodInfo mi, boolean withDefault) {
1806     apiWriter.print("    method ");
1807     apiWriter.print(mi.scope());
1808     if (mi.isDefault() && withDefault) {
1809       apiWriter.print(" default");
1810     }
1811     if (mi.isStatic()) {
1812       apiWriter.print(" static");
1813     }
1814     if (mi.isFinal()) {
1815       apiWriter.print(" final");
1816     }
1817     if (mi.isAbstract()) {
1818       apiWriter.print(" abstract");
1819     }
1820     if (mi.isDeprecated()) {
1821       apiWriter.print(" deprecated");
1822     }
1823     if (mi.isSynchronized()) {
1824       apiWriter.print(" synchronized");
1825     }
1826     if (mi.hasTypeParameters()) {
1827       apiWriter.print(" " + mi.typeArgumentsName(new HashSet<String>()));
1828     }
1829     apiWriter.print(" ");
1830     if (mi.returnType() == null) {
1831       apiWriter.print("void");
1832     } else {
1833       apiWriter.print(fullParameterTypeName(mi, mi.returnType(), false));
1834     }
1835     apiWriter.print(" ");
1836     apiWriter.print(mi.name());
1837 
1838     writeParametersApi(apiWriter, mi, mi.parameters());
1839     writeThrowsApi(apiWriter, mi.thrownExceptions());
1840 
1841     apiWriter.print(";\n");
1842   }
1843 
1844   static void writeSourcePositionInfo(PrintStream writer, DocInfo docInfo) {
1845     SourcePositionInfo pos = docInfo.position();
1846     writer.println(pos);
1847   }
1848 
1849   static void writeMethodDexApi(PrintStream apiWriter, ClassInfo cl, MethodInfo mi) {
1850     apiWriter.print(toSlashFormat(cl.qualifiedName()));
1851     apiWriter.print("->");
1852     if (mi.returnType() == null) {
1853       apiWriter.print("<init>");
1854     } else {
1855       apiWriter.print(mi.name());
1856     }
1857     writeParametersDexApi(apiWriter, mi, mi.parameters());
1858     if (mi.returnType() == null) {  // constructor
1859       apiWriter.print("V");
1860     } else {
1861       apiWriter.print(toSlashFormat(mi.returnType().dexName()));
1862     }
1863     apiWriter.print("\n");
1864   }
1865 
1866   static void writeParametersApi(PrintStream apiWriter, MethodInfo method,
1867       ArrayList<ParameterInfo> params) {
1868     apiWriter.print("(");
1869 
1870     for (ParameterInfo pi : params) {
1871       if (pi != params.get(0)) {
1872         apiWriter.print(", ");
1873       }
1874       apiWriter.print(fullParameterTypeName(method, pi.type(), pi == params.get(params.size()-1)));
1875       // turn on to write the names too
1876       if (false) {
1877         apiWriter.print(" ");
1878         apiWriter.print(pi.name());
1879       }
1880     }
1881 
1882     apiWriter.print(")");
1883   }
1884 
1885   static void writeParametersDexApi(PrintStream apiWriter, MethodInfo method,
1886       ArrayList<ParameterInfo> params) {
1887     apiWriter.print("(");
1888     for (ParameterInfo pi : params) {
1889       String typeName = pi.type().dexName();
1890       if (method.isVarArgs() && pi == params.get(params.size() - 1)) {
1891         typeName += "[]";
1892       }
1893       apiWriter.print(toSlashFormat(typeName));
1894     }
1895     apiWriter.print(")");
1896   }
1897 
1898   static void writeThrowsApi(PrintStream apiWriter, ArrayList<ClassInfo> exceptions) {
1899     // write in a canonical order
1900     exceptions = (ArrayList<ClassInfo>) exceptions.clone();
1901     Collections.sort(exceptions, ClassInfo.comparator);
1902     //final int N = exceptions.length;
1903     boolean first = true;
1904     for (ClassInfo ex : exceptions) {
1905       // Turn this off, b/c we need to regenrate the old xml files.
1906       if (true || !"java.lang.RuntimeException".equals(ex.qualifiedName())
1907           && !ex.isDerivedFrom("java.lang.RuntimeException")) {
1908         if (first) {
1909           apiWriter.print(" throws ");
1910           first = false;
1911         } else {
1912           apiWriter.print(", ");
1913         }
1914         apiWriter.print(ex.qualifiedName());
1915       }
1916     }
1917   }
1918 
1919   static void writeFieldApi(PrintStream apiWriter, FieldInfo fi, String label) {
1920     apiWriter.print("    ");
1921     apiWriter.print(label);
1922     apiWriter.print(" ");
1923     apiWriter.print(fi.scope());
1924     if (fi.isStatic()) {
1925       apiWriter.print(" static");
1926     }
1927     if (fi.isFinal()) {
1928       apiWriter.print(" final");
1929     }
1930     if (fi.isDeprecated()) {
1931       apiWriter.print(" deprecated");
1932     }
1933     if (fi.isTransient()) {
1934       apiWriter.print(" transient");
1935     }
1936     if (fi.isVolatile()) {
1937       apiWriter.print(" volatile");
1938     }
1939 
1940     apiWriter.print(" ");
1941     apiWriter.print(fi.type().fullName(fi.typeVariables()));
1942 
1943     apiWriter.print(" ");
1944     apiWriter.print(fi.name());
1945 
1946     Object val = null;
1947     if (fi.isConstant() && fieldIsInitialized(fi)) {
1948       apiWriter.print(" = ");
1949       apiWriter.print(fi.constantLiteralValue());
1950       val = fi.constantValue();
1951     }
1952 
1953     apiWriter.print(";");
1954 
1955     if (val != null) {
1956       if (val instanceof Integer && "char".equals(fi.type().qualifiedTypeName())) {
1957         apiWriter.format(" // 0x%04x '%s'", val,
1958             FieldInfo.javaEscapeString("" + ((char)((Integer)val).intValue())));
1959       } else if (val instanceof Byte || val instanceof Short || val instanceof Integer) {
1960         apiWriter.format(" // 0x%x", val);
1961       } else if (val instanceof Long) {
1962         apiWriter.format(" // 0x%xL", val);
1963       }
1964     }
1965 
1966     apiWriter.print("\n");
1967   }
1968 
1969   static void writeFieldDexApi(PrintStream apiWriter, ClassInfo cl, FieldInfo fi) {
1970     apiWriter.print(toSlashFormat(cl.qualifiedName()));
1971     apiWriter.print("->");
1972     apiWriter.print(fi.name());
1973     apiWriter.print(":");
1974     apiWriter.print(toSlashFormat(fi.type().dexName()));
1975     apiWriter.print("\n");
1976   }
1977 
1978   static void writeKeepList(PrintStream keepListWriter,
1979       HashMap<PackageInfo, List<ClassInfo>> allClasses, HashSet<ClassInfo> notStrippable) {
1980     // extract the set of packages, sort them by name, and write them out in that order
1981     Set<PackageInfo> allClassKeys = allClasses.keySet();
1982     PackageInfo[] allPackages = allClassKeys.toArray(new PackageInfo[allClassKeys.size()]);
1983     Arrays.sort(allPackages, PackageInfo.comparator);
1984 
1985     for (PackageInfo pack : allPackages) {
1986       writePackageKeepList(keepListWriter, pack, allClasses.get(pack), notStrippable);
1987     }
1988   }
1989 
1990   static void writePackageKeepList(PrintStream keepListWriter, PackageInfo pack,
1991       Collection<ClassInfo> classList, HashSet<ClassInfo> notStrippable) {
1992     // Work around the bogus "Array" class we invent for
1993     // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505)
1994     if (pack.name().equals(PackageInfo.DEFAULT_PACKAGE)) {
1995       return;
1996     }
1997 
1998     ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]);
1999     Arrays.sort(classes, ClassInfo.comparator);
2000     for (ClassInfo cl : classes) {
2001       writeClassKeepList(keepListWriter, cl, notStrippable);
2002     }
2003   }
2004 
2005   static void writeClassKeepList(PrintStream keepListWriter, ClassInfo cl,
2006       HashSet<ClassInfo> notStrippable) {
2007     keepListWriter.print("-keep class ");
2008     keepListWriter.print(to$Class(cl.qualifiedName()));
2009 
2010     keepListWriter.print(" {\n");
2011 
2012     ArrayList<MethodInfo> constructors = cl.constructors();
2013     Collections.sort(constructors, MethodInfo.comparator);
2014     for (MethodInfo mi : constructors) {
2015       writeConstructorKeepList(keepListWriter, mi);
2016     }
2017 
2018     keepListWriter.print("\n");
2019 
2020     ArrayList<MethodInfo> methods = cl.allSelfMethods();
2021     Collections.sort(methods, MethodInfo.comparator);
2022     for (MethodInfo mi : methods) {
2023       // allSelfMethods is the non-hidden and visible methods. See Doclava.checkLevel.
2024       writeMethodKeepList(keepListWriter, mi);
2025     }
2026 
2027     keepListWriter.print("\n");
2028 
2029     ArrayList<FieldInfo> enums = cl.enumConstants();
2030     Collections.sort(enums, FieldInfo.comparator);
2031     for (FieldInfo fi : enums) {
2032       writeFieldKeepList(keepListWriter, fi);
2033     }
2034 
2035     keepListWriter.print("\n");
2036 
2037     ArrayList<FieldInfo> fields = cl.selfFields();
2038     Collections.sort(fields, FieldInfo.comparator);
2039     for (FieldInfo fi : fields) {
2040       writeFieldKeepList(keepListWriter, fi);
2041     }
2042 
2043     keepListWriter.print("}\n\n");
2044   }
2045 
2046   static void writeConstructorKeepList(PrintStream keepListWriter, MethodInfo mi) {
2047     keepListWriter.print("    ");
2048     keepListWriter.print("<init>");
2049 
2050     writeParametersKeepList(keepListWriter, mi, mi.parameters());
2051     keepListWriter.print(";\n");
2052   }
2053 
2054   static void writeMethodKeepList(PrintStream keepListWriter, MethodInfo mi) {
2055     keepListWriter.print("    ");
2056     keepListWriter.print(mi.scope());
2057     if (mi.isStatic()) {
2058       keepListWriter.print(" static");
2059     }
2060     if (mi.isAbstract()) {
2061       keepListWriter.print(" abstract");
2062     }
2063     if (mi.isSynchronized()) {
2064       keepListWriter.print(" synchronized");
2065     }
2066     keepListWriter.print(" ");
2067     if (mi.returnType() == null) {
2068       keepListWriter.print("void");
2069     } else {
2070       keepListWriter.print(getCleanTypeName(mi.returnType()));
2071     }
2072     keepListWriter.print(" ");
2073     keepListWriter.print(mi.name());
2074 
2075     writeParametersKeepList(keepListWriter, mi, mi.parameters());
2076 
2077     keepListWriter.print(";\n");
2078   }
2079 
2080   static void writeParametersKeepList(PrintStream keepListWriter, MethodInfo method,
2081       ArrayList<ParameterInfo> params) {
2082     keepListWriter.print("(");
2083 
2084     for (ParameterInfo pi : params) {
2085       if (pi != params.get(0)) {
2086         keepListWriter.print(", ");
2087       }
2088       keepListWriter.print(getCleanTypeName(pi.type()));
2089     }
2090 
2091     keepListWriter.print(")");
2092   }
2093 
2094   static void writeFieldKeepList(PrintStream keepListWriter, FieldInfo fi) {
2095     keepListWriter.print("    ");
2096     keepListWriter.print(fi.scope());
2097     if (fi.isStatic()) {
2098       keepListWriter.print(" static");
2099     }
2100     if (fi.isTransient()) {
2101       keepListWriter.print(" transient");
2102     }
2103     if (fi.isVolatile()) {
2104       keepListWriter.print(" volatile");
2105     }
2106 
2107     keepListWriter.print(" ");
2108     keepListWriter.print(getCleanTypeName(fi.type()));
2109 
2110     keepListWriter.print(" ");
2111     keepListWriter.print(fi.name());
2112 
2113     keepListWriter.print(";\n");
2114   }
2115 
2116   static String fullParameterTypeName(MethodInfo method, TypeInfo type, boolean isLast) {
2117     String fullTypeName = type.fullName(method.typeVariables());
2118     if (isLast && method.isVarArgs()) {
2119       // TODO: note that this does not attempt to handle hypothetical
2120       // vararg methods whose last parameter is a list of arrays, e.g.
2121       // "Object[]...".
2122       fullTypeName = type.fullNameNoDimension(method.typeVariables()) + "...";
2123     }
2124     return fullTypeName;
2125   }
2126 
2127   static String to$Class(String name) {
2128     int pos = 0;
2129     while ((pos = name.indexOf('.', pos)) > 0) {
2130       String n = name.substring(0, pos);
2131       if (Converter.obtainClass(n) != null) {
2132         return n + (name.substring(pos).replace('.', '$'));
2133       }
2134       pos = pos + 1;
2135     }
2136     return name;
2137   }
2138 
2139   static String toSlashFormat(String name) {
2140     String dimension = "";
2141     while (name.endsWith("[]")) {
2142       dimension += "[";
2143       name = name.substring(0, name.length() - 2);
2144     }
2145 
2146     final String base;
2147     if (name.equals("void")) {
2148       base = "V";
2149     } else if (name.equals("byte")) {
2150       base = "B";
2151     } else if (name.equals("boolean")) {
2152       base = "Z";
2153     } else if (name.equals("char")) {
2154       base = "C";
2155     } else if (name.equals("short")) {
2156       base = "S";
2157     } else if (name.equals("int")) {
2158       base = "I";
2159     } else if (name.equals("long")) {
2160       base = "J";
2161     } else if (name.equals("float")) {
2162       base = "F";
2163     } else if (name.equals("double")) {
2164       base = "D";
2165     } else {
2166       base = "L" + to$Class(name).replace(".", "/") + ";";
2167     }
2168 
2169     return dimension + base;
2170   }
2171 
2172   static String getCleanTypeName(TypeInfo t) {
2173       return t.isPrimitive() ? t.simpleTypeName() + t.dimension() :
2174               to$Class(t.asClassInfo().qualifiedName() + t.dimension());
2175   }
2176 }
2177