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