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