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