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