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.File;
     21 import java.io.FileNotFoundException;
     22 import java.io.FileOutputStream;
     23 import java.io.PrintStream;
     24 import java.util.ArrayList;
     25 import java.util.Arrays;
     26 import java.util.Collection;
     27 import java.util.Collections;
     28 import java.util.HashMap;
     29 import java.util.HashSet;
     30 import java.util.Iterator;
     31 import java.util.List;
     32 import java.util.Set;
     33 import java.util.regex.Pattern;
     34 
     35 public class Stubs {
     36   public static void writeStubsAndApi(String stubsDir, String apiFile, String keepListFile,
     37       String removedApiFile, HashSet<String> stubPackages) {
     38     // figure out which classes we need
     39     final HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
     40     ClassInfo[] all = Converter.allClasses();
     41     PrintStream apiWriter = null;
     42     PrintStream keepListWriter = null;
     43     PrintStream removedApiWriter = null;
     44 
     45     if (apiFile != null) {
     46       try {
     47         File xml = new File(apiFile);
     48         xml.getParentFile().mkdirs();
     49         apiWriter = new PrintStream(new BufferedOutputStream(new FileOutputStream(xml)));
     50       } catch (FileNotFoundException e) {
     51         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(apiFile, 0, 0),
     52             "Cannot open file for write.");
     53       }
     54     }
     55     if (keepListFile != null) {
     56       try {
     57         File keepList = new File(keepListFile);
     58         keepList.getParentFile().mkdirs();
     59         keepListWriter = new PrintStream(new BufferedOutputStream(new FileOutputStream(keepList)));
     60       } catch (FileNotFoundException e) {
     61         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(keepListFile, 0, 0),
     62             "Cannot open file for write.");
     63       }
     64     }
     65     if (removedApiFile != null) {
     66       try {
     67         File removedApi = new File(removedApiFile);
     68         removedApi.getParentFile().mkdirs();
     69         removedApiWriter = new PrintStream(
     70             new BufferedOutputStream(new FileOutputStream(removedApi)));
     71       } catch (FileNotFoundException e) {
     72         Errors.error(Errors.IO_ERROR, new SourcePositionInfo(removedApiFile, 0, 0),
     73             "Cannot open file for write");
     74       }
     75     }
     76     // If a class is public or protected, not hidden, and marked as included,
     77     // then we can't strip it
     78     for (ClassInfo cl : all) {
     79       if (cl.checkLevel() && cl.isIncluded()) {
     80         cantStripThis(cl, notStrippable, "0:0");
     81       }
     82     }
     83 
     84     // complain about anything that looks includeable but is not supposed to
     85     // be written, e.g. hidden things
     86     for (ClassInfo cl : notStrippable) {
     87       if (!cl.isHiddenOrRemoved()) {
     88         for (MethodInfo m : cl.selfMethods()) {
     89           if (m.isHiddenOrRemoved()) {
     90             Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Reference to unavailable method "
     91                 + m.name());
     92           } else if (m.isDeprecated()) {
     93             // don't bother reporting deprecated methods
     94             // unless they are public
     95             Errors.error(Errors.DEPRECATED, m.position(), "Method " + cl.qualifiedName() + "."
     96                 + m.name() + " is deprecated");
     97           }
     98 
     99           ClassInfo hiddenClass = findHiddenClasses(m.returnType());
    100           if (null != hiddenClass) {
    101             if (hiddenClass.qualifiedName() == m.returnType().asClassInfo().qualifiedName()) {
    102               // Return type is hidden
    103               Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Method " + cl.qualifiedName()
    104                   + "." + m.name() + " returns unavailable type " + hiddenClass.name());
    105             } else {
    106               // Return type contains a generic parameter
    107               Errors.error(Errors.HIDDEN_TYPE_PARAMETER, m.position(), "Method " + cl.qualifiedName()
    108                   + "." + m.name() + " returns unavailable type " + hiddenClass.name()
    109                   + " as a type parameter");
    110             }
    111           }
    112 
    113           for (ParameterInfo p :  m.parameters()) {
    114             TypeInfo t = p.type();
    115             if (!t.isPrimitive()) {
    116               hiddenClass = findHiddenClasses(t);
    117               if (null != hiddenClass) {
    118                 if (hiddenClass.qualifiedName() == t.asClassInfo().qualifiedName()) {
    119                   // Parameter type is hidden
    120                   Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(),
    121                       "Parameter of unavailable type " + t.fullName() + " in " + cl.qualifiedName()
    122                       + "." + m.name() + "()");
    123                 } else {
    124                   // Parameter type contains a generic parameter
    125                   Errors.error(Errors.HIDDEN_TYPE_PARAMETER, m.position(),
    126                       "Parameter uses type parameter of unavailable type " + t.fullName() + " in "
    127                       + cl.qualifiedName() + "." + m.name() + "()");
    128                 }
    129               }
    130             }
    131           }
    132         }
    133 
    134         // annotations are handled like methods
    135         for (MethodInfo m : cl.annotationElements()) {
    136           if (m.isHiddenOrRemoved()) {
    137             Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Reference to unavailable annotation "
    138                 + m.name());
    139           }
    140 
    141           ClassInfo returnClass = m.returnType().asClassInfo();
    142           if (returnClass != null && returnClass.isHiddenOrRemoved()) {
    143             Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Annotation '" + m.name()
    144                 + "' returns unavailable type " + returnClass.name());
    145           }
    146 
    147           for (ParameterInfo p :  m.parameters()) {
    148             TypeInfo t = p.type();
    149             if (!t.isPrimitive()) {
    150               if (t.asClassInfo().isHiddenOrRemoved()) {
    151                 Errors.error(Errors.UNAVAILABLE_SYMBOL, p.position(),
    152                     "Reference to unavailable annotation class " + t.fullName());
    153               }
    154             }
    155           }
    156         }
    157       } else if (cl.isDeprecated()) {
    158         // not hidden, but deprecated
    159         Errors.error(Errors.DEPRECATED, cl.position(), "Class " + cl.qualifiedName()
    160             + " is deprecated");
    161       }
    162     }
    163 
    164     // packages contains all the notStrippable classes mapped by their containing packages
    165     HashMap<PackageInfo, List<ClassInfo>> packages = new HashMap<PackageInfo, List<ClassInfo>>();
    166     final HashSet<Pattern> stubPackageWildcards = extractWildcards(stubPackages);
    167     for (ClassInfo cl : notStrippable) {
    168       if (!cl.isDocOnly()) {
    169         if (shouldWriteStub(cl.containingPackage().name(), stubPackages, stubPackageWildcards)) {
    170           // write out the stubs
    171           if (stubsDir != null) {
    172             writeClassFile(stubsDir, notStrippable, cl);
    173           }
    174           // build class list for api file or keep list file
    175           if (apiWriter != null || keepListWriter != null) {
    176             if (packages.containsKey(cl.containingPackage())) {
    177               packages.get(cl.containingPackage()).add(cl);
    178             } else {
    179               ArrayList<ClassInfo> classes = new ArrayList<ClassInfo>();
    180               classes.add(cl);
    181               packages.put(cl.containingPackage(), classes);
    182             }
    183           }
    184         }
    185       }
    186     }
    187     // write out the Api
    188     if (apiWriter != null) {
    189       writeApi(apiWriter, packages, notStrippable);
    190       apiWriter.close();
    191     }
    192 
    193     // write out the keep list
    194     if (keepListWriter != null) {
    195       writeKeepList(keepListWriter, packages, notStrippable);
    196       keepListWriter.close();
    197     }
    198 
    199     HashMap<PackageInfo, List<ClassInfo>> allPackageClassMap =
    200         new HashMap<PackageInfo, List<ClassInfo>>();
    201     for (ClassInfo cl : Converter.allClasses()) {
    202       if (allPackageClassMap.containsKey(cl.containingPackage())) {
    203         allPackageClassMap.get(cl.containingPackage()).add(cl);
    204       } else {
    205         ArrayList<ClassInfo> classes = new ArrayList<ClassInfo>();
    206         classes.add(cl);
    207         allPackageClassMap.put(cl.containingPackage(), classes);
    208       }
    209     }
    210     // write out the removed Api
    211     if (removedApiWriter != null) {
    212       writeRemovedApi(removedApiWriter, allPackageClassMap, notStrippable);
    213       removedApiWriter.close();
    214     }
    215   }
    216 
    217   private static boolean shouldWriteStub(final String packageName,
    218           final HashSet<String> stubPackages, final HashSet<Pattern> stubPackageWildcards) {
    219     if (stubPackages == null) {
    220       // There aren't any stub packages set, write all stubs
    221       return true;
    222     }
    223     if (stubPackages.contains(packageName)) {
    224       // Stub packages contains package, return true
    225       return true;
    226     }
    227     if (stubPackageWildcards != null) {
    228       // Else, we will iterate through the wildcards to see if there's a match
    229       for (Pattern wildcard : stubPackageWildcards) {
    230         if (wildcard.matcher(packageName).matches()) {
    231           return true;
    232         }
    233       }
    234     }
    235     return false;
    236   }
    237 
    238   private static HashSet<Pattern> extractWildcards(HashSet<String> stubPackages) {
    239     HashSet<Pattern> wildcards = null;
    240     if (stubPackages != null) {
    241       for (Iterator<String> i = stubPackages.iterator(); i.hasNext();) {
    242         final String pkg = i.next();
    243         if (pkg.indexOf('*') != -1) {
    244           if (wildcards == null) {
    245             wildcards = new HashSet<Pattern>();
    246           }
    247           // Add the compiled wildcard, replacing * with the regex equivalent
    248           wildcards.add(Pattern.compile(pkg.replace("*", ".*?")));
    249           // And remove the raw wildcard from the packages
    250           i.remove();
    251         }
    252       }
    253     }
    254     return wildcards;
    255   }
    256 
    257   private static ClassInfo findHiddenClasses(TypeInfo ti) {
    258     ClassInfo ci = ti.asClassInfo();
    259     if (ci == null) return null;
    260     if (ci.isHiddenOrRemoved()) return ci;
    261     if (ti.typeArguments() != null) {
    262       for (TypeInfo tii : ti.typeArguments()) {
    263         // Avoid infinite recursion in the case of Foo<T extends Foo>
    264         if (tii.qualifiedTypeName() != ti.qualifiedTypeName()) {
    265           ClassInfo hiddenClass = findHiddenClasses(tii);
    266           if (hiddenClass != null) return hiddenClass;
    267         }
    268       }
    269     }
    270     return null;
    271   }
    272 
    273   public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why) {
    274 
    275     if (!notStrippable.add(cl)) {
    276       // slight optimization: if it already contains cl, it already contains
    277       // all of cl's parents
    278       return;
    279     }
    280     cl.setReasonIncluded(why);
    281 
    282     // cant strip annotations
    283     /*
    284      * if (cl.annotations() != null){ for (AnnotationInstanceInfo ai : cl.annotations()){ if
    285      * (ai.type() != null){ cantStripThis(ai.type(), notStrippable, "1:" + cl.qualifiedName()); } }
    286      * }
    287      */
    288     // cant strip any public fields or their generics
    289     if (cl.selfFields() != null) {
    290       for (FieldInfo fInfo : cl.selfFields()) {
    291         if (fInfo.type() != null) {
    292           if (fInfo.type().asClassInfo() != null) {
    293             cantStripThis(fInfo.type().asClassInfo(), notStrippable, "2:" + cl.qualifiedName());
    294           }
    295           if (fInfo.type().typeArguments() != null) {
    296             for (TypeInfo tTypeInfo : fInfo.type().typeArguments()) {
    297               if (tTypeInfo.asClassInfo() != null) {
    298                 cantStripThis(tTypeInfo.asClassInfo(), notStrippable, "3:" + cl.qualifiedName());
    299               }
    300             }
    301           }
    302         }
    303       }
    304     }
    305     // cant strip any of the type's generics
    306     if (cl.asTypeInfo() != null) {
    307       if (cl.asTypeInfo().typeArguments() != null) {
    308         for (TypeInfo tInfo : cl.asTypeInfo().typeArguments()) {
    309           if (tInfo.asClassInfo() != null) {
    310             cantStripThis(tInfo.asClassInfo(), notStrippable, "4:" + cl.qualifiedName());
    311           }
    312         }
    313       }
    314     }
    315     // cant strip any of the annotation elements
    316     // cantStripThis(cl.annotationElements(), notStrippable);
    317     // take care of methods
    318     cantStripThis(cl.allSelfMethods(), notStrippable);
    319     cantStripThis(cl.allConstructors(), notStrippable);
    320     // blow the outer class open if this is an inner class
    321     if (cl.containingClass() != null) {
    322       cantStripThis(cl.containingClass(), notStrippable, "5:" + cl.qualifiedName());
    323     }
    324     // blow open super class and interfaces
    325     ClassInfo supr = cl.realSuperclass();
    326     if (supr != null) {
    327       if (supr.isHiddenOrRemoved()) {
    328         // cl is a public class declared as extending a hidden superclass.
    329         // this is not a desired practice but it's happened, so we deal
    330         // with it by finding the first super class which passes checklevel for purposes of
    331         // generating the doc & stub information, and proceeding normally.
    332         cl.init(cl.asTypeInfo(), cl.realInterfaces(), cl.realInterfaceTypes(), cl.innerClasses(),
    333             cl.allConstructors(), cl.allSelfMethods(), cl.annotationElements(), cl.allSelfFields(),
    334             cl.enumConstants(), cl.containingPackage(), cl.containingClass(),
    335             supr.superclass(), supr.superclassType(), cl.annotations());
    336         Errors.error(Errors.HIDDEN_SUPERCLASS, cl.position(), "Public class " + cl.qualifiedName()
    337             + " stripped of unavailable superclass " + supr.qualifiedName());
    338       } else {
    339         cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name() + cl.qualifiedName());
    340         if (supr.isPrivate()) {
    341           Errors.error(Errors.PRIVATE_SUPERCLASS, cl.position(), "Public class "
    342               + cl.qualifiedName() + " extends private class " + supr.qualifiedName());
    343         }
    344       }
    345     }
    346   }
    347 
    348   private static void cantStripThis(ArrayList<MethodInfo> mInfos, HashSet<ClassInfo> notStrippable) {
    349     // for each method, blow open the parameters, throws and return types. also blow open their
    350     // generics
    351     if (mInfos != null) {
    352       for (MethodInfo mInfo : mInfos) {
    353         if (mInfo.getTypeParameters() != null) {
    354           for (TypeInfo tInfo : mInfo.getTypeParameters()) {
    355             if (tInfo.asClassInfo() != null) {
    356               cantStripThis(tInfo.asClassInfo(), notStrippable, "8:"
    357                   + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
    358             }
    359           }
    360         }
    361         if (mInfo.parameters() != null) {
    362           for (ParameterInfo pInfo : mInfo.parameters()) {
    363             if (pInfo.type() != null && pInfo.type().asClassInfo() != null) {
    364               cantStripThis(pInfo.type().asClassInfo(), notStrippable, "9:"
    365                   + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
    366               if (pInfo.type().typeArguments() != null) {
    367                 for (TypeInfo tInfoType : pInfo.type().typeArguments()) {
    368                   if (tInfoType.asClassInfo() != null) {
    369                     ClassInfo tcl = tInfoType.asClassInfo();
    370                     if (tcl.isHiddenOrRemoved()) {
    371                       Errors
    372                           .error(Errors.UNAVAILABLE_SYMBOL, mInfo.position(),
    373                               "Parameter of hidden type " + tInfoType.fullName() + " in "
    374                                   + mInfo.containingClass().qualifiedName() + '.' + mInfo.name()
    375                                   + "()");
    376                     } else {
    377                       cantStripThis(tcl, notStrippable, "10:"
    378                           + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
    379                     }
    380                   }
    381                 }
    382               }
    383             }
    384           }
    385         }
    386         for (ClassInfo thrown : mInfo.thrownExceptions()) {
    387           cantStripThis(thrown, notStrippable, "11:" + mInfo.realContainingClass().qualifiedName()
    388               + ":" + mInfo.name());
    389         }
    390         if (mInfo.returnType() != null && mInfo.returnType().asClassInfo() != null) {
    391           cantStripThis(mInfo.returnType().asClassInfo(), notStrippable, "12:"
    392               + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
    393           if (mInfo.returnType().typeArguments() != null) {
    394             for (TypeInfo tyInfo : mInfo.returnType().typeArguments()) {
    395               if (tyInfo.asClassInfo() != null) {
    396                 cantStripThis(tyInfo.asClassInfo(), notStrippable, "13:"
    397                     + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
    398               }
    399             }
    400           }
    401         }
    402       }
    403     }
    404   }
    405 
    406   static String javaFileName(ClassInfo cl) {
    407     String dir = "";
    408     PackageInfo pkg = cl.containingPackage();
    409     if (pkg != null) {
    410       dir = pkg.name();
    411       dir = dir.replace('.', '/') + '/';
    412     }
    413     return dir + cl.name() + ".java";
    414   }
    415 
    416   static void writeClassFile(String stubsDir, HashSet<ClassInfo> notStrippable, ClassInfo cl) {
    417     // inner classes are written by their containing class
    418     if (cl.containingClass() != null) {
    419       return;
    420     }
    421 
    422     // Work around the bogus "Array" class we invent for
    423     // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505)
    424     if (cl.containingPackage() != null
    425         && cl.containingPackage().name().equals(PackageInfo.DEFAULT_PACKAGE)) {
    426       return;
    427     }
    428 
    429     String filename = stubsDir + '/' + javaFileName(cl);
    430     File file = new File(filename);
    431     ClearPage.ensureDirectory(file);
    432 
    433     PrintStream stream = null;
    434     try {
    435       stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(file)));
    436       writeClassFile(stream, notStrippable, cl);
    437     } catch (FileNotFoundException e) {
    438       System.err.println("error writing file: " + filename);
    439     } finally {
    440       if (stream != null) {
    441         stream.close();
    442       }
    443     }
    444   }
    445 
    446   static void writeClassFile(PrintStream stream, HashSet<ClassInfo> notStrippable, ClassInfo cl) {
    447     PackageInfo pkg = cl.containingPackage();
    448     if (pkg != null) {
    449       stream.println("package " + pkg.name() + ";");
    450     }
    451     writeClass(stream, notStrippable, cl);
    452   }
    453 
    454   static void writeClass(PrintStream stream, HashSet<ClassInfo> notStrippable, ClassInfo cl) {
    455     writeAnnotations(stream, cl.annotations(), cl.isDeprecated());
    456 
    457     stream.print(cl.scope() + " ");
    458     if (cl.isAbstract() && !cl.isAnnotation() && !cl.isInterface()) {
    459       stream.print("abstract ");
    460     }
    461     if (cl.isStatic()) {
    462       stream.print("static ");
    463     }
    464     if (cl.isFinal() && !cl.isEnum()) {
    465       stream.print("final ");
    466     }
    467     if (false) {
    468       stream.print("strictfp ");
    469     }
    470 
    471     HashSet<String> classDeclTypeVars = new HashSet();
    472     String leafName = cl.asTypeInfo().fullName(classDeclTypeVars);
    473     int bracket = leafName.indexOf('<');
    474     if (bracket < 0) bracket = leafName.length() - 1;
    475     int period = leafName.lastIndexOf('.', bracket);
    476     if (period < 0) period = -1;
    477     leafName = leafName.substring(period + 1);
    478 
    479     String kind = cl.kind();
    480     stream.println(kind + " " + leafName);
    481 
    482     TypeInfo base = cl.superclassType();
    483 
    484     if (!"enum".equals(kind)) {
    485       if (base != null && !"java.lang.Object".equals(base.qualifiedTypeName())) {
    486         stream.println("  extends " + base.fullName(classDeclTypeVars));
    487       }
    488     }
    489 
    490     List<TypeInfo> usedInterfaces = new ArrayList<TypeInfo>();
    491     for (TypeInfo iface : cl.realInterfaceTypes()) {
    492       if (notStrippable.contains(iface.asClassInfo()) && !iface.asClassInfo().isDocOnly()) {
    493         usedInterfaces.add(iface);
    494       }
    495     }
    496     if (usedInterfaces.size() > 0 && !cl.isAnnotation()) {
    497       // can java annotations extend other ones?
    498       if (cl.isInterface() || cl.isAnnotation()) {
    499         stream.print("  extends ");
    500       } else {
    501         stream.print("  implements ");
    502       }
    503       String comma = "";
    504       for (TypeInfo iface : usedInterfaces) {
    505         stream.print(comma + iface.fullName(classDeclTypeVars));
    506         comma = ", ";
    507       }
    508       stream.println();
    509     }
    510 
    511     stream.println("{");
    512 
    513     ArrayList<FieldInfo> enumConstants = cl.enumConstants();
    514     int N = enumConstants.size();
    515     int i = 0;
    516     for (FieldInfo field : enumConstants) {
    517       if (!field.constantLiteralValue().equals("null")) {
    518         stream.println(field.name() + "(" + field.constantLiteralValue()
    519             + (i == N - 1 ? ");" : "),"));
    520       } else {
    521         stream.println(field.name() + "(" + (i == N - 1 ? ");" : "),"));
    522       }
    523       i++;
    524     }
    525 
    526     for (ClassInfo inner : cl.getRealInnerClasses()) {
    527       if (notStrippable.contains(inner) && !inner.isDocOnly()) {
    528         writeClass(stream, notStrippable, inner);
    529       }
    530     }
    531 
    532 
    533     for (MethodInfo method : cl.constructors()) {
    534       if (!method.isDocOnly()) {
    535         writeMethod(stream, method, true);
    536       }
    537     }
    538 
    539     boolean fieldNeedsInitialization = false;
    540     boolean staticFieldNeedsInitialization = false;
    541     for (FieldInfo field : cl.selfFields()) {
    542       if (!field.isDocOnly()) {
    543         if (!field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) {
    544           fieldNeedsInitialization = true;
    545         }
    546         if (field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) {
    547           staticFieldNeedsInitialization = true;
    548         }
    549       }
    550     }
    551 
    552     // The compiler includes a default public constructor that calls the super classes
    553     // default constructor in the case where there are no written constructors.
    554     // So, if we hide all the constructors, java may put in a constructor
    555     // that calls a nonexistent super class constructor. So, if there are no constructors,
    556     // and the super class doesn't have a default constructor, write in a private constructor
    557     // that works. TODO -- we generate this as protected, but we really should generate
    558     // it as private unless it also exists in the real code.
    559     if ((cl.constructors().isEmpty() && (!cl.getNonWrittenConstructors().isEmpty() ||
    560         fieldNeedsInitialization)) && !cl.isAnnotation() && !cl.isInterface() && !cl.isEnum()) {
    561       // Errors.error(Errors.HIDDEN_CONSTRUCTOR,
    562       // cl.position(), "No constructors " +
    563       // "found and superclass has no parameterless constructor.  A constructor " +
    564       // "that calls an appropriate superclass constructor " +
    565       // "was automatically written to stubs.\n");
    566       stream.println(cl.leafName() + "() { " + superCtorCall(cl, null) + "throw new"
    567           + " RuntimeException(\"Stub!\"); }");
    568     }
    569 
    570     for (MethodInfo method : cl.allSelfMethods()) {
    571       if (cl.isEnum()) {
    572         if (("values".equals(method.name()) && "()".equals(method.signature())) ||
    573             ("valueOf".equals(method.name()) &&
    574             "(java.lang.String)".equals(method.signature()))) {
    575           // skip these two methods on enums, because they're synthetic,
    576           // although for some reason javadoc doesn't mark them as synthetic,
    577           // maybe because they still want them documented
    578           continue;
    579         }
    580       }
    581       if (!method.isDocOnly()) {
    582         writeMethod(stream, method, false);
    583       }
    584     }
    585     // Write all methods that are hidden or removed, but override abstract methods or interface methods.
    586     // These can't be hidden.
    587     List<MethodInfo> hiddenAndRemovedMethods = cl.getHiddenMethods();
    588     hiddenAndRemovedMethods.addAll(cl.getRemovedMethods());
    589     for (MethodInfo method : hiddenAndRemovedMethods) {
    590       MethodInfo overriddenMethod =
    591           method.findRealOverriddenMethod(method.name(), method.signature(), notStrippable);
    592       ClassInfo classContainingMethod =
    593           method.findRealOverriddenClass(method.name(), method.signature());
    594       if (overriddenMethod != null && !overriddenMethod.isHiddenOrRemoved() &&
    595           !overriddenMethod.isDocOnly() &&
    596           (overriddenMethod.isAbstract() || overriddenMethod.containingClass().isInterface())) {
    597         method.setReason("1:" + classContainingMethod.qualifiedName());
    598         cl.addMethod(method);
    599         writeMethod(stream, method, false);
    600       }
    601     }
    602 
    603     for (MethodInfo element : cl.annotationElements()) {
    604       if (!element.isDocOnly()) {
    605         writeAnnotationElement(stream, element);
    606       }
    607     }
    608 
    609     for (FieldInfo field : cl.selfFields()) {
    610       if (!field.isDocOnly()) {
    611         writeField(stream, field);
    612       }
    613     }
    614 
    615     if (staticFieldNeedsInitialization) {
    616       stream.print("static { ");
    617       for (FieldInfo field : cl.selfFields()) {
    618         if (!field.isDocOnly() && field.isStatic() && field.isFinal() && !fieldIsInitialized(field)
    619             && field.constantValue() == null) {
    620           stream.print(field.name() + " = " + field.type().defaultValue() + "; ");
    621         }
    622       }
    623       stream.println("}");
    624     }
    625 
    626     stream.println("}");
    627   }
    628 
    629 
    630   static void writeMethod(PrintStream stream, MethodInfo method, boolean isConstructor) {
    631     String comma;
    632 
    633     writeAnnotations(stream, method.annotations(), method.isDeprecated());
    634 
    635     stream.print(method.scope() + " ");
    636     if (method.isStatic()) {
    637       stream.print("static ");
    638     }
    639     if (method.isFinal()) {
    640       stream.print("final ");
    641     }
    642     if (method.isAbstract()) {
    643       stream.print("abstract ");
    644     }
    645     if (method.isSynchronized()) {
    646       stream.print("synchronized ");
    647     }
    648     if (method.isNative()) {
    649       stream.print("native ");
    650     }
    651     if (false /* method.isStictFP() */) {
    652       stream.print("strictfp ");
    653     }
    654 
    655     stream.print(method.typeArgumentsName(new HashSet()) + " ");
    656 
    657     if (!isConstructor) {
    658       stream.print(method.returnType().fullName(method.typeVariables()) + " ");
    659     }
    660     String n = method.name();
    661     int pos = n.lastIndexOf('.');
    662     if (pos >= 0) {
    663       n = n.substring(pos + 1);
    664     }
    665     stream.print(n + "(");
    666     comma = "";
    667     int count = 1;
    668     int size = method.parameters().size();
    669     for (ParameterInfo param : method.parameters()) {
    670       stream.print(comma + fullParameterTypeName(method, param.type(), count == size) + " "
    671           + param.name());
    672       comma = ", ";
    673       count++;
    674     }
    675     stream.print(")");
    676 
    677     comma = "";
    678     if (method.thrownExceptions().size() > 0) {
    679       stream.print(" throws ");
    680       for (ClassInfo thrown : method.thrownExceptions()) {
    681         stream.print(comma + thrown.qualifiedName());
    682         comma = ", ";
    683       }
    684     }
    685     if (method.isAbstract() || method.isNative() || method.containingClass().isInterface()) {
    686       stream.println(";");
    687     } else {
    688       stream.print(" { ");
    689       if (isConstructor) {
    690         stream.print(superCtorCall(method.containingClass(), method.thrownExceptions()));
    691       }
    692       stream.println("throw new RuntimeException(\"Stub!\"); }");
    693     }
    694   }
    695 
    696   static void writeField(PrintStream stream, FieldInfo field) {
    697     writeAnnotations(stream, field.annotations(), field.isDeprecated());
    698 
    699     stream.print(field.scope() + " ");
    700     if (field.isStatic()) {
    701       stream.print("static ");
    702     }
    703     if (field.isFinal()) {
    704       stream.print("final ");
    705     }
    706     if (field.isTransient()) {
    707       stream.print("transient ");
    708     }
    709     if (field.isVolatile()) {
    710       stream.print("volatile ");
    711     }
    712 
    713     stream.print(field.type().fullName());
    714     stream.print(" ");
    715     stream.print(field.name());
    716 
    717     if (fieldIsInitialized(field)) {
    718       stream.print(" = " + field.constantLiteralValue());
    719     }
    720 
    721     stream.println(";");
    722   }
    723 
    724   static boolean fieldIsInitialized(FieldInfo field) {
    725     return (field.isFinal() && field.constantValue() != null)
    726         || !field.type().dimension().equals("") || field.containingClass().isInterface();
    727   }
    728 
    729   // Returns 'true' if the method is an @Override of a visible parent
    730   // method implementation, and thus does not affect the API.
    731   static boolean methodIsOverride(HashSet<ClassInfo> notStrippable, MethodInfo mi) {
    732     // Abstract/static/final methods are always listed in the API description
    733     if (mi.isAbstract() || mi.isStatic() || mi.isFinal()) {
    734       return false;
    735     }
    736 
    737     // Find any relevant ancestor declaration and inspect it
    738     MethodInfo om = mi.findSuperclassImplementation(notStrippable);
    739     if (om != null) {
    740       // Visibility mismatch is an API change, so check for it
    741       if (mi.mIsPrivate == om.mIsPrivate && mi.mIsPublic == om.mIsPublic
    742           && mi.mIsProtected == om.mIsProtected) {
    743         // Look only for overrides of an ancestor class implementation,
    744         // not of e.g. an abstract or interface method declaration
    745         if (!om.isAbstract()) {
    746           // If the parent is hidden or removed, we can't rely on it to provide
    747           // the API
    748           if (!om.isHiddenOrRemoved()) {
    749             // If the only "override" turns out to be in our own class
    750             // (which sometimes happens in concrete subclasses of
    751             // abstract base classes), it's not really an override
    752             if (!mi.mContainingClass.equals(om.mContainingClass)) {
    753               return true;
    754             }
    755           }
    756         }
    757       }
    758     }
    759     return false;
    760   }
    761 
    762   static boolean canCallMethod(ClassInfo from, MethodInfo m) {
    763     if (m.isPublic() || m.isProtected()) {
    764       return true;
    765     }
    766     if (m.isPackagePrivate()) {
    767       String fromPkg = from.containingPackage().name();
    768       String pkg = m.containingClass().containingPackage().name();
    769       if (fromPkg.equals(pkg)) {
    770         return true;
    771       }
    772     }
    773     return false;
    774   }
    775 
    776   // call a constructor, any constructor on this class's superclass.
    777   static String superCtorCall(ClassInfo cl, ArrayList<ClassInfo> thrownExceptions) {
    778     ClassInfo base = cl.realSuperclass();
    779     if (base == null) {
    780       return "";
    781     }
    782     HashSet<String> exceptionNames = new HashSet<String>();
    783     if (thrownExceptions != null) {
    784       for (ClassInfo thrown : thrownExceptions) {
    785         exceptionNames.add(thrown.name());
    786       }
    787     }
    788     ArrayList<MethodInfo> ctors = base.constructors();
    789     MethodInfo ctor = null;
    790     // bad exception indicates that the exceptions thrown by the super constructor
    791     // are incompatible with the constructor we're using for the sub class.
    792     Boolean badException = false;
    793     for (MethodInfo m : ctors) {
    794       if (canCallMethod(cl, m)) {
    795         if (m.thrownExceptions() != null) {
    796           for (ClassInfo thrown : m.thrownExceptions()) {
    797             if (!exceptionNames.contains(thrown.name())) {
    798               badException = true;
    799             }
    800           }
    801         }
    802         if (badException) {
    803           badException = false;
    804           continue;
    805         }
    806         // if it has no args, we're done
    807         if (m.parameters().isEmpty()) {
    808           return "";
    809         }
    810         ctor = m;
    811       }
    812     }
    813     if (ctor != null) {
    814       String result = "";
    815       result += "super(";
    816       ArrayList<ParameterInfo> params = ctor.parameters();
    817       for (ParameterInfo param : params) {
    818         TypeInfo t = param.type();
    819         if (t.isPrimitive() && t.dimension().equals("")) {
    820           String n = t.simpleTypeName();
    821           if (("byte".equals(n) || "short".equals(n) || "int".equals(n) || "long".equals(n)
    822               || "float".equals(n) || "double".equals(n))
    823               && t.dimension().equals("")) {
    824             result += "0";
    825           } else if ("char".equals(n)) {
    826             result += "'\\0'";
    827           } else if ("boolean".equals(n)) {
    828             result += "false";
    829           } else {
    830             result += "<<unknown-" + n + ">>";
    831           }
    832         } else {
    833           // put null in each super class method. Cast null to the correct type
    834           // to avoid collisions with other constructors. If the type is generic
    835           // don't cast it
    836           result +=
    837               (!t.isTypeVariable() ? "(" + t.qualifiedTypeName() + t.dimension() + ")" : "")
    838                   + "null";
    839         }
    840         if (param != params.get(params.size()-1)) {
    841           result += ",";
    842         }
    843       }
    844       result += "); ";
    845       return result;
    846     } else {
    847       return "";
    848     }
    849   }
    850 
    851     /**
    852      * Write out the given list of annotations. If the {@code isDeprecated}
    853      * flag is true also write out a {@code @Deprecated} annotation if it did not
    854      * already appear in the list of annotations. (This covers APIs that mention
    855      * {@code @deprecated} in their documentation but fail to add
    856      * {@code @Deprecated} as an annotation.
    857      * <p>
    858      * {@code @Override} annotations are deliberately skipped.
    859      */
    860   static void writeAnnotations(PrintStream stream, List<AnnotationInstanceInfo> annotations,
    861           boolean isDeprecated) {
    862     assert annotations != null;
    863     for (AnnotationInstanceInfo ann : annotations) {
    864       // Skip @Override annotations: the stubs do not need it and in some cases it leads
    865       // to compilation errors with the way the stubs are generated
    866       if (ann.type() != null && ann.type().qualifiedName().equals("java.lang.Override")) {
    867         continue;
    868       }
    869       if (!ann.type().isHiddenOrRemoved()) {
    870         stream.println(ann.toString());
    871         if (isDeprecated && ann.type() != null
    872             && ann.type().qualifiedName().equals("java.lang.Deprecated")) {
    873           isDeprecated = false; // Prevent duplicate annotations
    874         }
    875       }
    876     }
    877     if (isDeprecated) {
    878       stream.println("@Deprecated");
    879     }
    880   }
    881 
    882   static void writeAnnotationElement(PrintStream stream, MethodInfo ann) {
    883     stream.print(ann.returnType().fullName());
    884     stream.print(" ");
    885     stream.print(ann.name());
    886     stream.print("()");
    887     AnnotationValueInfo def = ann.defaultAnnotationElementValue();
    888     if (def != null) {
    889       stream.print(" default ");
    890       stream.print(def.valueString());
    891     }
    892     stream.println(";");
    893   }
    894 
    895   static void writeXML(PrintStream xmlWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses,
    896       HashSet<ClassInfo> notStrippable) {
    897     // extract the set of packages, sort them by name, and write them out in that order
    898     Set<PackageInfo> allClassKeys = allClasses.keySet();
    899     PackageInfo[] allPackages = allClassKeys.toArray(new PackageInfo[allClassKeys.size()]);
    900     Arrays.sort(allPackages, PackageInfo.comparator);
    901 
    902     xmlWriter.println("<api>");
    903     for (PackageInfo pack : allPackages) {
    904       writePackageXML(xmlWriter, pack, allClasses.get(pack), notStrippable);
    905     }
    906     xmlWriter.println("</api>");
    907   }
    908 
    909   public static void writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs) {
    910     final PackageInfo[] packages = pkgs.toArray(new PackageInfo[pkgs.size()]);
    911     Arrays.sort(packages, PackageInfo.comparator);
    912 
    913     HashSet<ClassInfo> notStrippable = new HashSet();
    914     for (PackageInfo pkg: packages) {
    915       for (ClassInfo cl: pkg.allClasses().values()) {
    916         notStrippable.add(cl);
    917       }
    918     }
    919     xmlWriter.println("<api>");
    920     for (PackageInfo pkg: packages) {
    921       writePackageXML(xmlWriter, pkg, pkg.allClasses().values(), notStrippable);
    922     }
    923     xmlWriter.println("</api>");
    924   }
    925 
    926   static void writePackageXML(PrintStream xmlWriter, PackageInfo pack,
    927       Collection<ClassInfo> classList, HashSet<ClassInfo> notStrippable) {
    928     ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]);
    929     Arrays.sort(classes, ClassInfo.comparator);
    930     // Work around the bogus "Array" class we invent for
    931     // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505)
    932     if (pack.name().equals(PackageInfo.DEFAULT_PACKAGE)) {
    933       return;
    934     }
    935     xmlWriter.println("<package name=\"" + pack.name() + "\"\n"
    936     // + " source=\"" + pack.position() + "\"\n"
    937         + ">");
    938     for (ClassInfo cl : classes) {
    939       writeClassXML(xmlWriter, cl, notStrippable);
    940     }
    941     xmlWriter.println("</package>");
    942 
    943 
    944   }
    945 
    946   static void writeClassXML(PrintStream xmlWriter, ClassInfo cl, HashSet<ClassInfo> notStrippable) {
    947     String scope = cl.scope();
    948     String deprecatedString = "";
    949     String declString = (cl.isInterface()) ? "interface" : "class";
    950     if (cl.isDeprecated()) {
    951       deprecatedString = "deprecated";
    952     } else {
    953       deprecatedString = "not deprecated";
    954     }
    955     xmlWriter.println("<" + declString + " name=\"" + cl.name() + "\"");
    956     if (!cl.isInterface() && !cl.qualifiedName().equals("java.lang.Object")) {
    957       xmlWriter.println(" extends=\""
    958           + ((cl.realSuperclass() == null) ? "java.lang.Object" : cl.realSuperclass()
    959               .qualifiedName()) + "\"");
    960     }
    961     xmlWriter.println(" abstract=\"" + cl.isAbstract() + "\"\n" + " static=\"" + cl.isStatic()
    962         + "\"\n" + " final=\"" + cl.isFinal() + "\"\n" + " deprecated=\"" + deprecatedString
    963         + "\"\n" + " visibility=\"" + scope + "\"\n"
    964         // + " source=\"" + cl.position() + "\"\n"
    965         + ">");
    966 
    967     ArrayList<ClassInfo> interfaces = cl.realInterfaces();
    968     Collections.sort(interfaces, ClassInfo.comparator);
    969     for (ClassInfo iface : interfaces) {
    970       if (notStrippable.contains(iface)) {
    971         xmlWriter.println("<implements name=\"" + iface.qualifiedName() + "\">");
    972         xmlWriter.println("</implements>");
    973       }
    974     }
    975 
    976     ArrayList<MethodInfo> constructors = cl.constructors();
    977     Collections.sort(constructors, MethodInfo.comparator);
    978     for (MethodInfo mi : constructors) {
    979       writeConstructorXML(xmlWriter, mi);
    980     }
    981 
    982     ArrayList<MethodInfo> methods = cl.allSelfMethods();
    983     Collections.sort(methods, MethodInfo.comparator);
    984     for (MethodInfo mi : methods) {
    985       if (!methodIsOverride(notStrippable, mi)) {
    986         writeMethodXML(xmlWriter, mi);
    987       }
    988     }
    989 
    990     ArrayList<FieldInfo> fields = cl.selfFields();
    991     Collections.sort(fields, FieldInfo.comparator);
    992     for (FieldInfo fi : fields) {
    993       writeFieldXML(xmlWriter, fi);
    994     }
    995     xmlWriter.println("</" + declString + ">");
    996 
    997   }
    998 
    999   static void writeMethodXML(PrintStream xmlWriter, MethodInfo mi) {
   1000     String scope = mi.scope();
   1001 
   1002     String deprecatedString = "";
   1003     if (mi.isDeprecated()) {
   1004       deprecatedString = "deprecated";
   1005     } else {
   1006       deprecatedString = "not deprecated";
   1007     }
   1008     xmlWriter.println("<method name=\""
   1009         + mi.name()
   1010         + "\"\n"
   1011         + ((mi.returnType() != null) ? " return=\""
   1012             + makeXMLcompliant(fullParameterTypeName(mi, mi.returnType(), false)) + "\"\n" : "")
   1013         + " abstract=\"" + mi.isAbstract() + "\"\n" + " native=\"" + mi.isNative() + "\"\n"
   1014         + " synchronized=\"" + mi.isSynchronized() + "\"\n" + " static=\"" + mi.isStatic() + "\"\n"
   1015         + " final=\"" + mi.isFinal() + "\"\n" + " deprecated=\"" + deprecatedString + "\"\n"
   1016         + " visibility=\"" + scope + "\"\n"
   1017         // + " source=\"" + mi.position() + "\"\n"
   1018         + ">");
   1019 
   1020     // write parameters in declaration order
   1021     int numParameters = mi.parameters().size();
   1022     int count = 0;
   1023     for (ParameterInfo pi : mi.parameters()) {
   1024       count++;
   1025       writeParameterXML(xmlWriter, mi, pi, count == numParameters);
   1026     }
   1027 
   1028     // but write exceptions in canonicalized order
   1029     ArrayList<ClassInfo> exceptions = mi.thrownExceptions();
   1030     Collections.sort(exceptions, ClassInfo.comparator);
   1031     for (ClassInfo pi : exceptions) {
   1032       xmlWriter.println("<exception name=\"" + pi.name() + "\" type=\"" + pi.qualifiedName()
   1033           + "\">");
   1034       xmlWriter.println("</exception>");
   1035     }
   1036     xmlWriter.println("</method>");
   1037   }
   1038 
   1039   static void writeConstructorXML(PrintStream xmlWriter, MethodInfo mi) {
   1040     String scope = mi.scope();
   1041     String deprecatedString = "";
   1042     if (mi.isDeprecated()) {
   1043       deprecatedString = "deprecated";
   1044     } else {
   1045       deprecatedString = "not deprecated";
   1046     }
   1047     xmlWriter.println("<constructor name=\"" + mi.name() + "\"\n" + " type=\""
   1048         + mi.containingClass().qualifiedName() + "\"\n" + " static=\"" + mi.isStatic() + "\"\n"
   1049         + " final=\"" + mi.isFinal() + "\"\n" + " deprecated=\"" + deprecatedString + "\"\n"
   1050         + " visibility=\"" + scope + "\"\n"
   1051         // + " source=\"" + mi.position() + "\"\n"
   1052         + ">");
   1053 
   1054     int numParameters = mi.parameters().size();
   1055     int count = 0;
   1056     for (ParameterInfo pi : mi.parameters()) {
   1057       count++;
   1058       writeParameterXML(xmlWriter, mi, pi, count == numParameters);
   1059     }
   1060 
   1061     ArrayList<ClassInfo> exceptions = mi.thrownExceptions();
   1062     Collections.sort(exceptions, ClassInfo.comparator);
   1063     for (ClassInfo pi : exceptions) {
   1064       xmlWriter.println("<exception name=\"" + pi.name() + "\" type=\"" + pi.qualifiedName()
   1065           + "\">");
   1066       xmlWriter.println("</exception>");
   1067     }
   1068     xmlWriter.println("</constructor>");
   1069   }
   1070 
   1071   static void writeParameterXML(PrintStream xmlWriter, MethodInfo method, ParameterInfo pi,
   1072       boolean isLast) {
   1073     xmlWriter.println("<parameter name=\"" + pi.name() + "\" type=\""
   1074         + makeXMLcompliant(fullParameterTypeName(method, pi.type(), isLast)) + "\">");
   1075     xmlWriter.println("</parameter>");
   1076   }
   1077 
   1078   static void writeFieldXML(PrintStream xmlWriter, FieldInfo fi) {
   1079     String scope = fi.scope();
   1080     String deprecatedString = "";
   1081     if (fi.isDeprecated()) {
   1082       deprecatedString = "deprecated";
   1083     } else {
   1084       deprecatedString = "not deprecated";
   1085     }
   1086     // need to make sure value is valid XML
   1087     String value = makeXMLcompliant(fi.constantLiteralValue());
   1088 
   1089     String fullTypeName = makeXMLcompliant(fi.type().fullName());
   1090 
   1091     xmlWriter.println("<field name=\"" + fi.name() + "\"\n" + " type=\"" + fullTypeName + "\"\n"
   1092         + " transient=\"" + fi.isTransient() + "\"\n" + " volatile=\"" + fi.isVolatile() + "\"\n"
   1093         + (fieldIsInitialized(fi) ? " value=\"" + value + "\"\n" : "") + " static=\""
   1094         + fi.isStatic() + "\"\n" + " final=\"" + fi.isFinal() + "\"\n" + " deprecated=\""
   1095         + deprecatedString + "\"\n" + " visibility=\"" + scope + "\"\n"
   1096         // + " source=\"" + fi.position() + "\"\n"
   1097         + ">");
   1098     xmlWriter.println("</field>");
   1099   }
   1100 
   1101   static String makeXMLcompliant(String s) {
   1102     String returnString = "";
   1103     returnString = s.replaceAll("&", "&amp;");
   1104     returnString = returnString.replaceAll("<", "&lt;");
   1105     returnString = returnString.replaceAll(">", "&gt;");
   1106     returnString = returnString.replaceAll("\"", "&quot;");
   1107     returnString = returnString.replaceAll("'", "&apos;");
   1108     return returnString;
   1109   }
   1110 
   1111   static void writeRemovedApi(PrintStream apiWriter, HashMap<PackageInfo,
   1112       List<ClassInfo>> allPackageClassMap, Set<ClassInfo> notStrippable) {
   1113     final PackageInfo[] packages = allPackageClassMap.keySet().toArray(new PackageInfo[0]);
   1114     Arrays.sort(packages, PackageInfo.comparator);
   1115     for (PackageInfo pkg : packages) {
   1116       // beware that pkg.allClasses() has no class in it at the moment
   1117       final List<ClassInfo> classes = allPackageClassMap.get(pkg);
   1118       Collections.sort(classes, ClassInfo.comparator);
   1119       boolean hasWrittenPackageHead = false;
   1120       for (ClassInfo cl : classes) {
   1121         if (cl.hasRemovedSelfMembers()) {
   1122           if (!hasWrittenPackageHead) {
   1123             hasWrittenPackageHead = true;
   1124             apiWriter.print("package ");
   1125             apiWriter.print(pkg.qualifiedName());
   1126             apiWriter.print(" {\n\n");
   1127           }
   1128           writeClassRemovedSelfMembers(apiWriter, cl, notStrippable);
   1129         }
   1130       }
   1131 
   1132       // the package contains some classes with some removed members
   1133       if (hasWrittenPackageHead) {
   1134         apiWriter.print("}\n\n");
   1135       }
   1136     }
   1137   }
   1138 
   1139   /**
   1140    * Write the removed members of the class to removed.txt
   1141    */
   1142   private static void writeClassRemovedSelfMembers(PrintStream apiWriter, ClassInfo cl,
   1143       Set<ClassInfo> notStrippable) {
   1144     apiWriter.print("  ");
   1145     apiWriter.print(cl.scope());
   1146     if (cl.isStatic()) {
   1147       apiWriter.print(" static");
   1148     }
   1149     if (cl.isFinal()) {
   1150       apiWriter.print(" final");
   1151     }
   1152     if (cl.isAbstract()) {
   1153       apiWriter.print(" abstract");
   1154     }
   1155     if (cl.isDeprecated()) {
   1156       apiWriter.print(" deprecated");
   1157     }
   1158     apiWriter.print(" ");
   1159     apiWriter.print(cl.isInterface() ? "interface" : "class");
   1160     apiWriter.print(" ");
   1161     apiWriter.print(cl.name());
   1162 
   1163     if (!cl.isInterface()
   1164         && !"java.lang.Object".equals(cl.qualifiedName())
   1165         && cl.realSuperclass() != null
   1166         && !"java.lang.Object".equals(cl.realSuperclass().qualifiedName())) {
   1167       apiWriter.print(" extends ");
   1168       apiWriter.print(cl.realSuperclass().qualifiedName());
   1169     }
   1170 
   1171     ArrayList<ClassInfo> interfaces = cl.realInterfaces();
   1172     Collections.sort(interfaces, ClassInfo.comparator);
   1173     boolean first = true;
   1174     for (ClassInfo iface : interfaces) {
   1175       if (notStrippable.contains(iface)) {
   1176         if (first) {
   1177           apiWriter.print(" implements");
   1178           first = false;
   1179         }
   1180         apiWriter.print(" ");
   1181         apiWriter.print(iface.qualifiedName());
   1182       }
   1183     }
   1184 
   1185     apiWriter.print(" {\n");
   1186 
   1187     List<MethodInfo> constructors = cl.getRemovedConstructors();
   1188     for (MethodInfo mi : constructors) {
   1189       writeConstructorApi(apiWriter, mi);
   1190     }
   1191 
   1192     List<MethodInfo> methods = cl.getRemovedSelfMethods();
   1193     for (MethodInfo mi : methods) {
   1194       writeMethodApi(apiWriter, mi);
   1195     }
   1196 
   1197     List<FieldInfo> enums = cl.getRemovedSelfEnumConstants();
   1198     for (FieldInfo fi : enums) {
   1199       writeFieldApi(apiWriter, fi, "enum_constant");
   1200     }
   1201 
   1202     List<FieldInfo> fields = cl.getRemovedSelfFields();
   1203     for (FieldInfo fi : fields) {
   1204       writeFieldApi(apiWriter, fi, "field");
   1205     }
   1206 
   1207     apiWriter.print("  }\n\n");
   1208   }
   1209 
   1210   public static void writeApi(PrintStream apiWriter, Collection<PackageInfo> pkgs) {
   1211     final PackageInfo[] packages = pkgs.toArray(new PackageInfo[pkgs.size()]);
   1212     Arrays.sort(packages, PackageInfo.comparator);
   1213 
   1214     HashSet<ClassInfo> notStrippable = new HashSet();
   1215     for (PackageInfo pkg: packages) {
   1216       for (ClassInfo cl: pkg.allClasses().values()) {
   1217         notStrippable.add(cl);
   1218       }
   1219     }
   1220     for (PackageInfo pkg: packages) {
   1221       writePackageApi(apiWriter, pkg, pkg.allClasses().values(), notStrippable);
   1222     }
   1223   }
   1224 
   1225   static void writeApi(PrintStream apiWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses,
   1226       HashSet<ClassInfo> notStrippable) {
   1227     // extract the set of packages, sort them by name, and write them out in that order
   1228     Set<PackageInfo> allClassKeys = allClasses.keySet();
   1229     PackageInfo[] allPackages = allClassKeys.toArray(new PackageInfo[allClassKeys.size()]);
   1230     Arrays.sort(allPackages, PackageInfo.comparator);
   1231 
   1232     for (PackageInfo pack : allPackages) {
   1233       writePackageApi(apiWriter, pack, allClasses.get(pack), notStrippable);
   1234     }
   1235   }
   1236 
   1237   static void writePackageApi(PrintStream apiWriter, PackageInfo pack,
   1238       Collection<ClassInfo> classList, HashSet<ClassInfo> notStrippable) {
   1239     // Work around the bogus "Array" class we invent for
   1240     // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505)
   1241     if (pack.name().equals(PackageInfo.DEFAULT_PACKAGE)) {
   1242       return;
   1243     }
   1244 
   1245     apiWriter.print("package ");
   1246     apiWriter.print(pack.qualifiedName());
   1247     apiWriter.print(" {\n\n");
   1248 
   1249     ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]);
   1250     Arrays.sort(classes, ClassInfo.comparator);
   1251     for (ClassInfo cl : classes) {
   1252       writeClassApi(apiWriter, cl, notStrippable);
   1253     }
   1254 
   1255     apiWriter.print("}\n\n");
   1256   }
   1257 
   1258   static void writeClassApi(PrintStream apiWriter, ClassInfo cl, HashSet<ClassInfo> notStrippable) {
   1259     boolean first;
   1260 
   1261     apiWriter.print("  ");
   1262     apiWriter.print(cl.scope());
   1263     if (cl.isStatic()) {
   1264       apiWriter.print(" static");
   1265     }
   1266     if (cl.isFinal()) {
   1267       apiWriter.print(" final");
   1268     }
   1269     if (cl.isAbstract()) {
   1270       apiWriter.print(" abstract");
   1271     }
   1272     if (cl.isDeprecated()) {
   1273       apiWriter.print(" deprecated");
   1274     }
   1275     apiWriter.print(" ");
   1276     apiWriter.print(cl.isInterface() ? "interface" : "class");
   1277     apiWriter.print(" ");
   1278     apiWriter.print(cl.name());
   1279 
   1280     if (!cl.isInterface()
   1281         && !"java.lang.Object".equals(cl.qualifiedName())
   1282         && cl.realSuperclass() != null
   1283         && !"java.lang.Object".equals(cl.realSuperclass().qualifiedName())) {
   1284       apiWriter.print(" extends ");
   1285       apiWriter.print(cl.realSuperclass().qualifiedName());
   1286     }
   1287 
   1288     ArrayList<ClassInfo> interfaces = cl.realInterfaces();
   1289     Collections.sort(interfaces, ClassInfo.comparator);
   1290     first = true;
   1291     for (ClassInfo iface : interfaces) {
   1292       if (notStrippable.contains(iface)) {
   1293         if (first) {
   1294           apiWriter.print(" implements");
   1295           first = false;
   1296         }
   1297         apiWriter.print(" ");
   1298         apiWriter.print(iface.qualifiedName());
   1299       }
   1300     }
   1301 
   1302     apiWriter.print(" {\n");
   1303 
   1304     ArrayList<MethodInfo> constructors = cl.constructors();
   1305     Collections.sort(constructors, MethodInfo.comparator);
   1306     for (MethodInfo mi : constructors) {
   1307       writeConstructorApi(apiWriter, mi);
   1308     }
   1309 
   1310     ArrayList<MethodInfo> methods = cl.allSelfMethods();
   1311     Collections.sort(methods, MethodInfo.comparator);
   1312     for (MethodInfo mi : methods) {
   1313       if (!methodIsOverride(notStrippable, mi)) {
   1314         writeMethodApi(apiWriter, mi);
   1315       }
   1316     }
   1317 
   1318     ArrayList<FieldInfo> enums = cl.enumConstants();
   1319     Collections.sort(enums, FieldInfo.comparator);
   1320     for (FieldInfo fi : enums) {
   1321       writeFieldApi(apiWriter, fi, "enum_constant");
   1322     }
   1323 
   1324     ArrayList<FieldInfo> fields = cl.selfFields();
   1325     Collections.sort(fields, FieldInfo.comparator);
   1326     for (FieldInfo fi : fields) {
   1327       writeFieldApi(apiWriter, fi, "field");
   1328     }
   1329 
   1330     apiWriter.print("  }\n\n");
   1331   }
   1332 
   1333   static void writeConstructorApi(PrintStream apiWriter, MethodInfo mi) {
   1334     apiWriter.print("    ctor ");
   1335     apiWriter.print(mi.scope());
   1336     if (mi.isDeprecated()) {
   1337       apiWriter.print(" deprecated");
   1338     }
   1339     apiWriter.print(" ");
   1340     apiWriter.print(mi.name());
   1341 
   1342     writeParametersApi(apiWriter, mi, mi.parameters());
   1343     writeThrowsApi(apiWriter, mi.thrownExceptions());
   1344     apiWriter.print(";\n");
   1345   }
   1346 
   1347   static void writeMethodApi(PrintStream apiWriter, MethodInfo mi) {
   1348     apiWriter.print("    method ");
   1349     apiWriter.print(mi.scope());
   1350     if (mi.isStatic()) {
   1351       apiWriter.print(" static");
   1352     }
   1353     if (mi.isFinal()) {
   1354       apiWriter.print(" final");
   1355     }
   1356     if (mi.isAbstract()) {
   1357       apiWriter.print(" abstract");
   1358     }
   1359     if (mi.isDeprecated()) {
   1360       apiWriter.print(" deprecated");
   1361     }
   1362     if (mi.isSynchronized()) {
   1363       apiWriter.print(" synchronized");
   1364     }
   1365     apiWriter.print(" ");
   1366     if (mi.returnType() == null) {
   1367       apiWriter.print("void");
   1368     } else {
   1369       apiWriter.print(fullParameterTypeName(mi, mi.returnType(), false));
   1370     }
   1371     apiWriter.print(" ");
   1372     apiWriter.print(mi.name());
   1373 
   1374     writeParametersApi(apiWriter, mi, mi.parameters());
   1375     writeThrowsApi(apiWriter, mi.thrownExceptions());
   1376 
   1377     apiWriter.print(";\n");
   1378   }
   1379 
   1380   static void writeParametersApi(PrintStream apiWriter, MethodInfo method,
   1381       ArrayList<ParameterInfo> params) {
   1382     apiWriter.print("(");
   1383 
   1384     for (ParameterInfo pi : params) {
   1385       if (pi != params.get(0)) {
   1386         apiWriter.print(", ");
   1387       }
   1388       apiWriter.print(fullParameterTypeName(method, pi.type(), pi == params.get(params.size()-1)));
   1389       // turn on to write the names too
   1390       if (false) {
   1391         apiWriter.print(" ");
   1392         apiWriter.print(pi.name());
   1393       }
   1394     }
   1395 
   1396     apiWriter.print(")");
   1397   }
   1398 
   1399   static void writeThrowsApi(PrintStream apiWriter, ArrayList<ClassInfo> exceptions) {
   1400     // write in a canonical order
   1401     exceptions = (ArrayList<ClassInfo>) exceptions.clone();
   1402     Collections.sort(exceptions, ClassInfo.comparator);
   1403     //final int N = exceptions.length;
   1404     boolean first = true;
   1405     for (ClassInfo ex : exceptions) {
   1406       // Turn this off, b/c we need to regenrate the old xml files.
   1407       if (true || !"java.lang.RuntimeException".equals(ex.qualifiedName())
   1408           && !ex.isDerivedFrom("java.lang.RuntimeException")) {
   1409         if (first) {
   1410           apiWriter.print(" throws ");
   1411           first = false;
   1412         } else {
   1413           apiWriter.print(", ");
   1414         }
   1415         apiWriter.print(ex.qualifiedName());
   1416       }
   1417     }
   1418   }
   1419 
   1420   static void writeFieldApi(PrintStream apiWriter, FieldInfo fi, String label) {
   1421     apiWriter.print("    ");
   1422     apiWriter.print(label);
   1423     apiWriter.print(" ");
   1424     apiWriter.print(fi.scope());
   1425     if (fi.isStatic()) {
   1426       apiWriter.print(" static");
   1427     }
   1428     if (fi.isFinal()) {
   1429       apiWriter.print(" final");
   1430     }
   1431     if (fi.isDeprecated()) {
   1432       apiWriter.print(" deprecated");
   1433     }
   1434     if (fi.isTransient()) {
   1435       apiWriter.print(" transient");
   1436     }
   1437     if (fi.isVolatile()) {
   1438       apiWriter.print(" volatile");
   1439     }
   1440 
   1441     apiWriter.print(" ");
   1442     apiWriter.print(fi.type().fullName());
   1443 
   1444     apiWriter.print(" ");
   1445     apiWriter.print(fi.name());
   1446 
   1447     Object val = null;
   1448     if (fi.isConstant() && fieldIsInitialized(fi)) {
   1449       apiWriter.print(" = ");
   1450       apiWriter.print(fi.constantLiteralValue());
   1451       val = fi.constantValue();
   1452     }
   1453 
   1454     apiWriter.print(";");
   1455 
   1456     if (val != null) {
   1457       if (val instanceof Integer && "char".equals(fi.type().qualifiedTypeName())) {
   1458         apiWriter.format(" // 0x%04x '%s'", val,
   1459             FieldInfo.javaEscapeString("" + ((char)((Integer)val).intValue())));
   1460       } else if (val instanceof Byte || val instanceof Short || val instanceof Integer) {
   1461         apiWriter.format(" // 0x%x", val);
   1462       } else if (val instanceof Long) {
   1463         apiWriter.format(" // 0x%xL", val);
   1464       }
   1465     }
   1466 
   1467     apiWriter.print("\n");
   1468   }
   1469 
   1470   static void writeKeepList(PrintStream keepListWriter,
   1471       HashMap<PackageInfo, List<ClassInfo>> allClasses, HashSet<ClassInfo> notStrippable) {
   1472     // extract the set of packages, sort them by name, and write them out in that order
   1473     Set<PackageInfo> allClassKeys = allClasses.keySet();
   1474     PackageInfo[] allPackages = allClassKeys.toArray(new PackageInfo[allClassKeys.size()]);
   1475     Arrays.sort(allPackages, PackageInfo.comparator);
   1476 
   1477     for (PackageInfo pack : allPackages) {
   1478       writePackageKeepList(keepListWriter, pack, allClasses.get(pack), notStrippable);
   1479     }
   1480   }
   1481 
   1482   static void writePackageKeepList(PrintStream keepListWriter, PackageInfo pack,
   1483       Collection<ClassInfo> classList, HashSet<ClassInfo> notStrippable) {
   1484     // Work around the bogus "Array" class we invent for
   1485     // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505)
   1486     if (pack.name().equals(PackageInfo.DEFAULT_PACKAGE)) {
   1487       return;
   1488     }
   1489 
   1490     ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]);
   1491     Arrays.sort(classes, ClassInfo.comparator);
   1492     for (ClassInfo cl : classes) {
   1493       writeClassKeepList(keepListWriter, cl, notStrippable);
   1494     }
   1495   }
   1496 
   1497   static void writeClassKeepList(PrintStream keepListWriter, ClassInfo cl,
   1498       HashSet<ClassInfo> notStrippable) {
   1499     keepListWriter.print("-keep class ");
   1500     keepListWriter.print(to$Class(cl.qualifiedName()));
   1501 
   1502     keepListWriter.print(" {\n");
   1503 
   1504     ArrayList<MethodInfo> constructors = cl.constructors();
   1505     Collections.sort(constructors, MethodInfo.comparator);
   1506     for (MethodInfo mi : constructors) {
   1507       writeConstructorKeepList(keepListWriter, mi);
   1508     }
   1509 
   1510     keepListWriter.print("\n");
   1511 
   1512     ArrayList<MethodInfo> methods = cl.allSelfMethods();
   1513     Collections.sort(methods, MethodInfo.comparator);
   1514     for (MethodInfo mi : methods) {
   1515       // allSelfMethods is the non-hidden and visible methods. See Doclava.checkLevel.
   1516       writeMethodKeepList(keepListWriter, mi);
   1517     }
   1518 
   1519     keepListWriter.print("\n");
   1520 
   1521     ArrayList<FieldInfo> enums = cl.enumConstants();
   1522     Collections.sort(enums, FieldInfo.comparator);
   1523     for (FieldInfo fi : enums) {
   1524       writeFieldKeepList(keepListWriter, fi);
   1525     }
   1526 
   1527     keepListWriter.print("\n");
   1528 
   1529     ArrayList<FieldInfo> fields = cl.selfFields();
   1530     Collections.sort(fields, FieldInfo.comparator);
   1531     for (FieldInfo fi : fields) {
   1532       writeFieldKeepList(keepListWriter, fi);
   1533     }
   1534 
   1535     keepListWriter.print("}\n\n");
   1536   }
   1537 
   1538   static void writeConstructorKeepList(PrintStream keepListWriter, MethodInfo mi) {
   1539     keepListWriter.print("    ");
   1540     String name = mi.name();
   1541     name = name.replace(".", "$");
   1542     keepListWriter.print(name);
   1543 
   1544     writeParametersKeepList(keepListWriter, mi, mi.parameters());
   1545     keepListWriter.print(";\n");
   1546   }
   1547 
   1548   static void writeMethodKeepList(PrintStream keepListWriter, MethodInfo mi) {
   1549     keepListWriter.print("    ");
   1550     keepListWriter.print(mi.scope());
   1551     if (mi.isStatic()) {
   1552       keepListWriter.print(" static");
   1553     }
   1554     if (mi.isAbstract()) {
   1555       keepListWriter.print(" abstract");
   1556     }
   1557     if (mi.isSynchronized()) {
   1558       keepListWriter.print(" synchronized");
   1559     }
   1560     keepListWriter.print(" ");
   1561     if (mi.returnType() == null) {
   1562       keepListWriter.print("void");
   1563     } else {
   1564       keepListWriter.print(getCleanTypeName(mi.returnType()));
   1565     }
   1566     keepListWriter.print(" ");
   1567     keepListWriter.print(mi.name());
   1568 
   1569     writeParametersKeepList(keepListWriter, mi, mi.parameters());
   1570 
   1571     keepListWriter.print(";\n");
   1572   }
   1573 
   1574   static void writeParametersKeepList(PrintStream keepListWriter, MethodInfo method,
   1575       ArrayList<ParameterInfo> params) {
   1576     keepListWriter.print("(");
   1577 
   1578     for (ParameterInfo pi : params) {
   1579       if (pi != params.get(0)) {
   1580         keepListWriter.print(", ");
   1581       }
   1582       keepListWriter.print(getCleanTypeName(pi.type()));
   1583     }
   1584 
   1585     keepListWriter.print(")");
   1586   }
   1587 
   1588   static void writeFieldKeepList(PrintStream keepListWriter, FieldInfo fi) {
   1589     keepListWriter.print("    ");
   1590     keepListWriter.print(fi.scope());
   1591     if (fi.isStatic()) {
   1592       keepListWriter.print(" static");
   1593     }
   1594     if (fi.isTransient()) {
   1595       keepListWriter.print(" transient");
   1596     }
   1597     if (fi.isVolatile()) {
   1598       keepListWriter.print(" volatile");
   1599     }
   1600 
   1601     keepListWriter.print(" ");
   1602     keepListWriter.print(getCleanTypeName(fi.type()));
   1603 
   1604     keepListWriter.print(" ");
   1605     keepListWriter.print(fi.name());
   1606 
   1607     keepListWriter.print(";\n");
   1608   }
   1609 
   1610   static String fullParameterTypeName(MethodInfo method, TypeInfo type, boolean isLast) {
   1611     String fullTypeName = type.fullName(method.typeVariables());
   1612     if (isLast && method.isVarArgs()) {
   1613       // TODO: note that this does not attempt to handle hypothetical
   1614       // vararg methods whose last parameter is a list of arrays, e.g.
   1615       // "Object[]...".
   1616       fullTypeName = type.fullNameNoDimension(method.typeVariables()) + "...";
   1617     }
   1618     return fullTypeName;
   1619   }
   1620 
   1621   static String to$Class(String name) {
   1622     int pos = 0;
   1623     while ((pos = name.indexOf('.', pos)) > 0) {
   1624       String n = name.substring(0, pos);
   1625       if (Converter.obtainClass(n) != null) {
   1626         return n + (name.substring(pos).replace('.', '$'));
   1627       }
   1628       pos = pos + 1;
   1629     }
   1630     return name;
   1631   }
   1632 
   1633   static String getCleanTypeName(TypeInfo t) {
   1634       return t.isPrimitive() ? t.simpleTypeName() + t.dimension() :
   1635               to$Class(t.asClassInfo().qualifiedName() + t.dimension());
   1636   }
   1637 }
   1638