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