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