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