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