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