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