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("&", "&"); 1220 returnString = returnString.replaceAll("<", "<"); 1221 returnString = returnString.replaceAll(">", ">"); 1222 returnString = returnString.replaceAll("\"", """); 1223 returnString = returnString.replaceAll("'", "'"); 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