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