Home | History | Annotate | Download | only in doclava
      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 {
     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 
    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 
    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    */
    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 
    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 
    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 
    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 
    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 
    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 
    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 */
    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 
    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 
    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 
    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 
    937   static boolean fieldIsInitialized(FieldInfo field) {
    938     return (field.isFinal() && field.constantValue() != null)
    939         || !field.type().dimension().equals("") || field.containingClass().isInterface();
    940   }
    941 
    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.
    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      */
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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      */
   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      */
   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      */
   1331     public ApiPredicate setMatchRemoved(boolean matchRemoved) {
   1332       this.matchRemoved = matchRemoved;
   1333       return this;
   1334     }
   1335 
   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
   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 
   1392     public ElidingPredicate(Predicate<MemberInfo> wrapped) {
   1393       this.wrapped = wrapped;
   1394     }
   1395 
   1396     @Override
   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