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