• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.doclava;
18 
19 import java.io.BufferedOutputStream;
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.PrintStream;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Set;
32 
33 public class Stubs {
writeStubsAndApi(String stubsDir, String apiFile, String keepListFile, HashSet<String> stubPackages)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 
cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why)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 
cantStripThis(ArrayList<MethodInfo> mInfos, HashSet<ClassInfo> notStrippable)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 
javaFileName(ClassInfo cl)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 
writeClassFile(String stubsDir, HashSet<ClassInfo> notStrippable, ClassInfo cl)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 
writeClassFile(PrintStream stream, HashSet<ClassInfo> notStrippable, ClassInfo cl)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 
writeClass(PrintStream stream, HashSet<ClassInfo> notStrippable, ClassInfo cl)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 
writeMethod(PrintStream stream, MethodInfo method, boolean isConstructor)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 
writeField(PrintStream stream, FieldInfo field)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 
fieldIsInitialized(FieldInfo field)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.
methodIsOverride(HashSet<ClassInfo> notStrippable, MethodInfo mi)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 
canCallMethod(ClassInfo from, MethodInfo m)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.
superCtorCall(ClassInfo cl, ArrayList<ClassInfo> thrownExceptions)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      */
writeAnnotations(PrintStream stream, List<AnnotationInstanceInfo> annotations, boolean isDeprecated)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 
writeAnnotationElement(PrintStream stream, MethodInfo ann)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 
writeXML(PrintStream xmlWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses, HashSet<ClassInfo> notStrippable)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 
writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs)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 
writePackageXML(PrintStream xmlWriter, PackageInfo pack, Collection<ClassInfo> classList, HashSet<ClassInfo> notStrippable)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 
writeClassXML(PrintStream xmlWriter, ClassInfo cl, HashSet<ClassInfo> notStrippable)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 
writeMethodXML(PrintStream xmlWriter, MethodInfo mi)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 
writeConstructorXML(PrintStream xmlWriter, MethodInfo mi)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 
writeParameterXML(PrintStream xmlWriter, MethodInfo method, ParameterInfo pi, boolean isLast)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 
writeFieldXML(PrintStream xmlWriter, FieldInfo fi)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 
makeXMLcompliant(String s)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 
writeApi(PrintStream apiWriter, Collection<PackageInfo> pkgs)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 
writeApi(PrintStream apiWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses, HashSet<ClassInfo> notStrippable)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 
writePackageApi(PrintStream apiWriter, PackageInfo pack, Collection<ClassInfo> classList, HashSet<ClassInfo> notStrippable)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 
writeClassApi(PrintStream apiWriter, ClassInfo cl, HashSet<ClassInfo> notStrippable)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 
writeConstructorApi(PrintStream apiWriter, MethodInfo mi)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 
writeMethodApi(PrintStream apiWriter, MethodInfo mi)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 
writeParametersApi(PrintStream apiWriter, MethodInfo method, ArrayList<ParameterInfo> params)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 
writeThrowsApi(PrintStream apiWriter, ArrayList<ClassInfo> exceptions)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 
writeFieldApi(PrintStream apiWriter, FieldInfo fi, String label)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 
writeKeepList(PrintStream keepListWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses, HashSet<ClassInfo> notStrippable)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 
writePackageKeepList(PrintStream keepListWriter, PackageInfo pack, Collection<ClassInfo> classList, HashSet<ClassInfo> notStrippable)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 
writeClassKeepList(PrintStream keepListWriter, ClassInfo cl, HashSet<ClassInfo> notStrippable)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 
writeConstructorKeepList(PrintStream keepListWriter, MethodInfo mi)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 
writeMethodKeepList(PrintStream keepListWriter, MethodInfo mi)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 
writeParametersKeepList(PrintStream keepListWriter, MethodInfo method, ArrayList<ParameterInfo> params)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 
writeFieldKeepList(PrintStream keepListWriter, FieldInfo fi)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 
fullParameterTypeName(MethodInfo method, TypeInfo type, boolean isLast)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 
to$Class(String name)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 
getCleanTypeName(TypeInfo t)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