1 /* 2 * Copyright (C) 2008 The Android Open Source Project 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 import java.util.HashMap; 18 import java.util.HashSet; 19 import java.util.List; 20 import java.util.ArrayList; 21 import java.util.Arrays; 22 import java.util.Set; 23 import java.util.Comparator; 24 import java.io.File; 25 import java.io.FileNotFoundException; 26 import java.io.PrintStream; 27 28 public class Stubs { 29 private static HashSet<ClassInfo> notStrippable; writeStubs(String stubsDir, Boolean writeXML, String xmlFile, HashSet<String> stubPackages)30 public static void writeStubs(String stubsDir, Boolean writeXML, String xmlFile, 31 HashSet<String> stubPackages) { 32 // figure out which classes we need 33 notStrippable = new HashSet(); 34 ClassInfo[] all = Converter.allClasses(); 35 File xml = new File(xmlFile); 36 xml.getParentFile().mkdirs(); 37 PrintStream xmlWriter = null; 38 if (writeXML) { 39 try { 40 xmlWriter = new PrintStream(xml); 41 } catch (FileNotFoundException e) { 42 Errors.error(Errors.IO_ERROR, new SourcePositionInfo(xmlFile, 0, 0), 43 "Cannot open file for write."); 44 } 45 } 46 // If a class is public or protected, not hidden, and marked as included, 47 // then we can't strip it 48 for (ClassInfo cl: all) { 49 if (cl.checkLevel() && cl.isIncluded()) { 50 cantStripThis(cl, notStrippable, "0:0"); 51 } 52 } 53 54 // complain about anything that looks includeable but is not supposed to 55 // be written, e.g. hidden things 56 for (ClassInfo cl: notStrippable) { 57 if (!cl.isHidden()) { 58 MethodInfo[] methods = cl.selfMethods(); 59 for (MethodInfo m: methods) { 60 if (m.isHidden()) { 61 Errors.error(Errors.UNAVAILABLE_SYMBOL, 62 m.position(), "Reference to hidden method " 63 + m.name()); 64 } else if (m.isDeprecated()) { 65 // don't bother reporting deprecated methods 66 // unless they are public 67 Errors.error(Errors.DEPRECATED, 68 m.position(), "Method " 69 + cl.qualifiedName() + "." + m.name() 70 + " is deprecated"); 71 } 72 73 ClassInfo returnClass = m.returnType().asClassInfo(); 74 if (returnClass != null && returnClass.isHidden()) { 75 Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), 76 "Method " + cl.qualifiedName() + "." + m.name() 77 + " returns unavailable type " + returnClass.name()); 78 } 79 80 ParameterInfo[] params = m.parameters(); 81 for (ParameterInfo p: params) { 82 TypeInfo t = p.type(); 83 if (!t.isPrimitive()) { 84 if (t.asClassInfo().isHidden()) { 85 Errors.error(Errors.UNAVAILABLE_SYMBOL, 86 m.position(), "Parameter of hidden type " 87 + t.fullName() + " in " 88 + cl.qualifiedName() + "." + m.name() + "()"); 89 } 90 } 91 } 92 } 93 94 // annotations are handled like methods 95 methods = cl.annotationElements(); 96 for (MethodInfo m: methods) { 97 if (m.isHidden()) { 98 Errors.error(Errors.UNAVAILABLE_SYMBOL, 99 m.position(), "Reference to hidden annotation " 100 + m.name()); 101 } 102 103 ClassInfo returnClass = m.returnType().asClassInfo(); 104 if (returnClass != null && returnClass.isHidden()) { 105 Errors.error(Errors.UNAVAILABLE_SYMBOL, 106 m.position(), "Annotation '" + m.name() 107 + "' returns unavailable type " + returnClass.name()); 108 } 109 110 ParameterInfo[] params = m.parameters(); 111 for (ParameterInfo p: params) { 112 TypeInfo t = p.type(); 113 if (!t.isPrimitive()) { 114 if (t.asClassInfo().isHidden()) { 115 Errors.error(Errors.UNAVAILABLE_SYMBOL, 116 p.position(), "Reference to unavailable annotation class " 117 + t.fullName()); 118 } 119 } 120 } 121 } 122 } else if (cl.isDeprecated()) { 123 // not hidden, but deprecated 124 Errors.error(Errors.DEPRECATED, 125 cl.position(), "Class " + cl.qualifiedName() 126 + " is deprecated"); 127 } 128 } 129 130 // write out the stubs 131 HashMap<PackageInfo, List<ClassInfo>> packages = new HashMap<PackageInfo, List<ClassInfo>>(); 132 for (ClassInfo cl: notStrippable) { 133 if (!cl.isDocOnly()) { 134 if (stubPackages == null || stubPackages.contains(cl.containingPackage().name())) { 135 writeClassFile(stubsDir, cl); 136 if (packages.containsKey(cl.containingPackage())) { 137 packages.get(cl.containingPackage()).add(cl); 138 } else { 139 ArrayList<ClassInfo> classes = new ArrayList<ClassInfo>(); 140 classes.add(cl); 141 packages.put(cl.containingPackage(), classes); 142 } 143 } 144 } 145 } 146 147 // write out the XML 148 if (writeXML && xmlWriter != null) { 149 writeXML(xmlWriter, packages, notStrippable); 150 } 151 152 if (xmlWriter != null) { 153 xmlWriter.close(); 154 } 155 156 } 157 cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why)158 public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why) { 159 160 if (!notStrippable.add(cl)) { 161 // slight optimization: if it already contains cl, it already contains 162 // all of cl's parents 163 return; 164 } 165 cl.setReasonIncluded(why); 166 167 // cant strip annotations 168 /*if (cl.annotations() != null){ 169 for (AnnotationInstanceInfo ai : cl.annotations()){ 170 if (ai.type() != null){ 171 cantStripThis(ai.type(), notStrippable, "1:" + cl.qualifiedName()); 172 } 173 } 174 }*/ 175 // cant strip any public fields or their generics 176 if (cl.allSelfFields() != null){ 177 for (FieldInfo fInfo : cl.allSelfFields()){ 178 if (fInfo.type() != null){ 179 if (fInfo.type().asClassInfo() != null){ 180 cantStripThis(fInfo.type().asClassInfo(), notStrippable, 181 "2:" + cl.qualifiedName()); 182 } 183 if (fInfo.type().typeArguments() != null){ 184 for (TypeInfo tTypeInfo : fInfo.type().typeArguments()){ 185 if (tTypeInfo.asClassInfo() != null){ 186 cantStripThis(tTypeInfo.asClassInfo(), notStrippable, 187 "3:" + cl.qualifiedName()); 188 } 189 } 190 } 191 } 192 } 193 } 194 //cant strip any of the type's generics 195 if (cl.asTypeInfo() != null){ 196 if (cl.asTypeInfo().typeArguments() != null){ 197 for (TypeInfo tInfo : cl.asTypeInfo().typeArguments()){ 198 if (tInfo.asClassInfo() != null){ 199 cantStripThis(tInfo.asClassInfo(), notStrippable, "4:" + cl.qualifiedName()); 200 } 201 } 202 } 203 } 204 //cant strip any of the annotation elements 205 //cantStripThis(cl.annotationElements(), notStrippable); 206 // take care of methods 207 cantStripThis(cl.allSelfMethods(), notStrippable); 208 cantStripThis(cl.allConstructors(), notStrippable); 209 // blow the outer class open if this is an inner class 210 if(cl.containingClass() != null){ 211 cantStripThis(cl.containingClass(), notStrippable, "5:" + cl.qualifiedName()); 212 } 213 // blow open super class and interfaces 214 ClassInfo supr = cl.realSuperclass(); 215 if (supr != null) { 216 if (supr.isHidden()) { 217 // cl is a public class declared as extending a hidden superclass. 218 // this is not a desired practice but it's happened, so we deal 219 // with it by stripping off the superclass relation for purposes of 220 // generating the doc & stub information, and proceeding normally. 221 cl.init(cl.asTypeInfo(), cl.realInterfaces(), cl.realInterfaceTypes(), 222 cl.innerClasses(), cl.allConstructors(), cl.allSelfMethods(), 223 cl.annotationElements(), cl.allSelfFields(), cl.enumConstants(), 224 cl.containingPackage(), cl.containingClass(), 225 null, null, cl.annotations()); 226 Errors.error(Errors.HIDDEN_SUPERCLASS, 227 cl.position(), "Public class " + cl.qualifiedName() 228 + " stripped of unavailable superclass " 229 + supr.qualifiedName()); 230 } else { 231 cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name() 232 + cl.qualifiedName()); 233 } 234 } 235 } 236 cantStripThis(MethodInfo[] mInfos , HashSet<ClassInfo> notStrippable)237 private static void cantStripThis(MethodInfo[] mInfos , HashSet<ClassInfo> notStrippable) { 238 //for each method, blow open the parameters, throws and return types. also blow open their generics 239 if (mInfos != null){ 240 for (MethodInfo mInfo : mInfos){ 241 if (mInfo.getTypeParameters() != null){ 242 for (TypeInfo tInfo : mInfo.getTypeParameters()){ 243 if (tInfo.asClassInfo() != null){ 244 cantStripThis(tInfo.asClassInfo(), notStrippable, "8:" + 245 mInfo.realContainingClass().qualifiedName() + ":" + 246 mInfo.name()); 247 } 248 } 249 } 250 if (mInfo.parameters() != null){ 251 for (ParameterInfo pInfo : mInfo.parameters()){ 252 if (pInfo.type() != null && pInfo.type().asClassInfo() != null){ 253 cantStripThis(pInfo.type().asClassInfo(), notStrippable, 254 "9:"+ mInfo.realContainingClass().qualifiedName() 255 + ":" + mInfo.name()); 256 if (pInfo.type().typeArguments() != null){ 257 for (TypeInfo tInfoType : pInfo.type().typeArguments()){ 258 if (tInfoType.asClassInfo() != null){ 259 ClassInfo tcl = tInfoType.asClassInfo(); 260 if (tcl.isHidden()) { 261 Errors.error(Errors.UNAVAILABLE_SYMBOL, mInfo.position(), 262 "Parameter of hidden type " 263 + tInfoType.fullName() + " in " 264 + mInfo.containingClass().qualifiedName() 265 + '.' + mInfo.name() + "()"); 266 } else { 267 cantStripThis(tcl, notStrippable, 268 "10:" + 269 mInfo.realContainingClass().qualifiedName() + ":" + 270 mInfo.name()); 271 } 272 } 273 } 274 } 275 } 276 } 277 } 278 for (ClassInfo thrown : mInfo.thrownExceptions()){ 279 cantStripThis(thrown, notStrippable, "11:" + 280 mInfo.realContainingClass().qualifiedName() 281 +":" + mInfo.name()); 282 } 283 if (mInfo.returnType() != null && mInfo.returnType().asClassInfo() != null){ 284 cantStripThis(mInfo.returnType().asClassInfo(), notStrippable, 285 "12:" + mInfo.realContainingClass().qualifiedName() + 286 ":" + mInfo.name()); 287 if (mInfo.returnType().typeArguments() != null){ 288 for (TypeInfo tyInfo: mInfo.returnType().typeArguments() ){ 289 if (tyInfo.asClassInfo() != null){ 290 cantStripThis(tyInfo.asClassInfo(), notStrippable, 291 "13:" + 292 mInfo.realContainingClass().qualifiedName() 293 + ":" + mInfo.name()); 294 } 295 } 296 } 297 } 298 } 299 } 300 } 301 javaFileName(ClassInfo cl)302 static String javaFileName(ClassInfo cl) { 303 String dir = ""; 304 PackageInfo pkg = cl.containingPackage(); 305 if (pkg != null) { 306 dir = pkg.name(); 307 dir = dir.replace('.', '/') + '/'; 308 } 309 return dir + cl.name() + ".java"; 310 } 311 writeClassFile(String stubsDir, ClassInfo cl)312 static void writeClassFile(String stubsDir, ClassInfo cl) { 313 // inner classes are written by their containing class 314 if (cl.containingClass() != null) { 315 return; 316 } 317 318 // Work around the bogus "Array" class we invent for 319 // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505) 320 if (cl.containingPackage() != null && cl.containingPackage().name().equals("")) { 321 return; 322 } 323 324 String filename = stubsDir + '/' + javaFileName(cl); 325 File file = new File(filename); 326 ClearPage.ensureDirectory(file); 327 328 PrintStream stream = null; 329 try { 330 stream = new PrintStream(file); 331 writeClassFile(stream, cl); 332 } 333 catch (FileNotFoundException e) { 334 System.err.println("error writing file: " + filename); 335 } 336 finally { 337 if (stream != null) { 338 stream.close(); 339 } 340 } 341 } 342 writeClassFile(PrintStream stream, ClassInfo cl)343 static void writeClassFile(PrintStream stream, ClassInfo cl) { 344 PackageInfo pkg = cl.containingPackage(); 345 if (pkg != null) { 346 stream.println("package " + pkg.name() + ";"); 347 } 348 writeClass(stream, cl); 349 } 350 writeClass(PrintStream stream, ClassInfo cl)351 static void writeClass(PrintStream stream, ClassInfo cl) { 352 writeAnnotations(stream, cl.annotations()); 353 354 stream.print(DroidDoc.scope(cl) + " "); 355 if (cl.isAbstract() && !cl.isAnnotation() && !cl.isInterface()) { 356 stream.print("abstract "); 357 } 358 if (cl.isStatic()){ 359 stream.print("static "); 360 } 361 if (cl.isFinal() && !cl.isEnum()) { 362 stream.print("final "); 363 } 364 if (false) { 365 stream.print("strictfp "); 366 } 367 368 HashSet<String> classDeclTypeVars = new HashSet(); 369 String leafName = cl.asTypeInfo().fullName(classDeclTypeVars); 370 int bracket = leafName.indexOf('<'); 371 if (bracket < 0) bracket = leafName.length() - 1; 372 int period = leafName.lastIndexOf('.', bracket); 373 if (period < 0) period = -1; 374 leafName = leafName.substring(period+1); 375 376 String kind = cl.kind(); 377 stream.println(kind + " " + leafName); 378 379 TypeInfo base = cl.superclassType(); 380 381 if (!"enum".equals(kind)) { 382 if (base != null && !"java.lang.Object".equals(base.qualifiedTypeName())) { 383 stream.println(" extends " + base.fullName(classDeclTypeVars)); 384 } 385 } 386 387 TypeInfo[] interfaces = cl.realInterfaceTypes(); 388 List<TypeInfo> usedInterfaces = new ArrayList<TypeInfo>(); 389 for (TypeInfo iface : interfaces) { 390 if (notStrippable.contains(iface.asClassInfo()) 391 && !iface.asClassInfo().isDocOnly()) { 392 usedInterfaces.add(iface); 393 } 394 } 395 if (usedInterfaces.size() > 0 && !cl.isAnnotation()) { 396 // can java annotations extend other ones? 397 if (cl.isInterface() || cl.isAnnotation()) { 398 stream.print(" extends "); 399 } else { 400 stream.print(" implements "); 401 } 402 String comma = ""; 403 for (TypeInfo iface: usedInterfaces) { 404 stream.print(comma + iface.fullName(classDeclTypeVars)); 405 comma = ", "; 406 } 407 stream.println(); 408 } 409 410 stream.println("{"); 411 412 FieldInfo[] enumConstants = cl.enumConstants(); 413 int N = enumConstants.length; 414 for (int i=0; i<N; i++) { 415 FieldInfo field = enumConstants[i]; 416 if (!field.constantLiteralValue().equals("null")){ 417 stream.println(field.name() + "(" + field.constantLiteralValue() 418 + (i==N-1 ? ");" : "),")); 419 }else{ 420 stream.println(field.name() + "(" + (i==N-1 ? ");" : "),")); 421 } 422 } 423 424 for (ClassInfo inner: cl.getRealInnerClasses()) { 425 if (notStrippable.contains(inner) 426 && !inner.isDocOnly()){ 427 writeClass(stream, inner); 428 } 429 } 430 431 432 for (MethodInfo method: cl.constructors()) { 433 if (!method.isDocOnly()) { 434 writeMethod(stream, method, true); 435 } 436 } 437 438 boolean fieldNeedsInitialization = false; 439 boolean staticFieldNeedsInitialization = false; 440 for (FieldInfo field: cl.allSelfFields()) { 441 if (!field.isDocOnly()) { 442 if (!field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) { 443 fieldNeedsInitialization = true; 444 } 445 if (field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) { 446 staticFieldNeedsInitialization = true; 447 } 448 } 449 } 450 451 // The compiler includes a default public constructor that calls the super classes 452 // default constructor in the case where there are no written constructors. 453 // So, if we hide all the constructors, java may put in a constructor 454 // that calls a nonexistent super class constructor. So, if there are no constructors, 455 // and the super class doesn't have a default constructor, write in a private constructor 456 // that works. TODO -- we generate this as protected, but we really should generate 457 // it as private unless it also exists in the real code. 458 if ((cl.constructors().length == 0 && (cl.getNonWrittenConstructors().length != 0 459 || fieldNeedsInitialization)) 460 && !cl.isAnnotation() 461 && !cl.isInterface() 462 && !cl.isEnum() ) { 463 //Errors.error(Errors.HIDDEN_CONSTRUCTOR, 464 // cl.position(), "No constructors " + 465 // "found and superclass has no parameterless constructor. A constructor " + 466 // "that calls an appropriate superclass constructor " + 467 // "was automatically written to stubs.\n"); 468 stream.println(cl.leafName() 469 + "() { " + superCtorCall(cl,null) 470 + "throw new" + " RuntimeException(\"Stub!\"); }"); 471 } 472 473 for (MethodInfo method: cl.allSelfMethods()) { 474 if (cl.isEnum()) { 475 if (("values".equals(method.name()) 476 && "()".equals(method.signature())) 477 || ("valueOf".equals(method.name()) 478 && "(java.lang.String)".equals(method.signature()))) { 479 // skip these two methods on enums, because they're synthetic, 480 // although for some reason javadoc doesn't mark them as synthetic, 481 // maybe because they still want them documented 482 continue; 483 } 484 } 485 if (!method.isDocOnly()) { 486 writeMethod(stream, method, false); 487 } 488 } 489 //Write all methods that are hidden, but override abstract methods or interface methods. 490 //These can't be hidden. 491 for (MethodInfo method : cl.getHiddenMethods()){ 492 MethodInfo overriddenMethod = method.findRealOverriddenMethod(method.name(), method.signature(), notStrippable); 493 ClassInfo classContainingMethod = method.findRealOverriddenClass(method.name(), 494 method.signature()); 495 if (overriddenMethod != null && !overriddenMethod.isHidden() 496 && !overriddenMethod.isDocOnly() && 497 (overriddenMethod.isAbstract() || 498 overriddenMethod.containingClass().isInterface())) { 499 method.setReason("1:" + classContainingMethod.qualifiedName()); 500 cl.addMethod(method); 501 writeMethod(stream, method, false); 502 } 503 } 504 505 for (MethodInfo element: cl.annotationElements()) { 506 if (!element.isDocOnly()) { 507 writeAnnotationElement(stream, element); 508 } 509 } 510 511 for (FieldInfo field: cl.allSelfFields()) { 512 if (!field.isDocOnly()) { 513 writeField(stream, field); 514 } 515 } 516 517 if (staticFieldNeedsInitialization) { 518 stream.print("static { "); 519 for (FieldInfo field: cl.allSelfFields()) { 520 if (!field.isDocOnly() && field.isStatic() && field.isFinal() 521 && !fieldIsInitialized(field) && field.constantValue() == null) { 522 stream.print(field.name() + " = " + field.type().defaultValue() 523 + "; "); 524 } 525 } 526 stream.println("}"); 527 } 528 529 stream.println("}"); 530 } 531 532 writeMethod(PrintStream stream, MethodInfo method, boolean isConstructor)533 static void writeMethod(PrintStream stream, MethodInfo method, boolean isConstructor) { 534 String comma; 535 536 stream.print(DroidDoc.scope(method) + " "); 537 if (method.isStatic()) { 538 stream.print("static "); 539 } 540 if (method.isFinal()) { 541 stream.print("final "); 542 } 543 if (method.isAbstract()) { 544 stream.print("abstract "); 545 } 546 if (method.isSynchronized()) { 547 stream.print("synchronized "); 548 } 549 if (method.isNative()) { 550 stream.print("native "); 551 } 552 if (false /*method.isStictFP()*/) { 553 stream.print("strictfp "); 554 } 555 556 stream.print(method.typeArgumentsName(new HashSet()) + " "); 557 558 if (!isConstructor) { 559 stream.print(method.returnType().fullName(method.typeVariables()) + " "); 560 } 561 String n = method.name(); 562 int pos = n.lastIndexOf('.'); 563 if (pos >= 0) { 564 n = n.substring(pos + 1); 565 } 566 stream.print(n + "("); 567 comma = ""; 568 int count = 1; 569 int size = method.parameters().length; 570 for (ParameterInfo param: method.parameters()) { 571 stream.print(comma + fullParameterTypeName(method, param.type(), count == size) 572 + " " + param.name()); 573 comma = ", "; 574 count++; 575 } 576 stream.print(")"); 577 578 comma = ""; 579 if (method.thrownExceptions().length > 0) { 580 stream.print(" throws "); 581 for (ClassInfo thrown: method.thrownExceptions()) { 582 stream.print(comma + thrown.qualifiedName()); 583 comma = ", "; 584 } 585 } 586 if (method.isAbstract() || method.isNative() || method.containingClass().isInterface()) { 587 stream.println(";"); 588 } else { 589 stream.print(" { "); 590 if (isConstructor) { 591 stream.print(superCtorCall(method.containingClass(), method.thrownExceptions())); 592 } 593 stream.println("throw new RuntimeException(\"Stub!\"); }"); 594 } 595 } 596 writeField(PrintStream stream, FieldInfo field)597 static void writeField(PrintStream stream, FieldInfo field) { 598 stream.print(DroidDoc.scope(field) + " "); 599 if (field.isStatic()) { 600 stream.print("static "); 601 } 602 if (field.isFinal()) { 603 stream.print("final "); 604 } 605 if (field.isTransient()) { 606 stream.print("transient "); 607 } 608 if (field.isVolatile()) { 609 stream.print("volatile "); 610 } 611 612 stream.print(field.type().fullName()); 613 stream.print(" "); 614 stream.print(field.name()); 615 616 if (fieldIsInitialized(field)) { 617 stream.print(" = " + field.constantLiteralValue()); 618 } 619 620 stream.println(";"); 621 } 622 fieldIsInitialized(FieldInfo field)623 static boolean fieldIsInitialized(FieldInfo field) { 624 return (field.isFinal() && field.constantValue() != null) 625 || !field.type().dimension().equals("") 626 || field.containingClass().isInterface(); 627 } 628 629 // Returns 'true' if the method is an @Override of a visible parent 630 // method implementation, and thus does not affect the API. methodIsOverride(MethodInfo mi)631 static boolean methodIsOverride(MethodInfo mi) { 632 // Abstract/static/final methods are always listed in the API description 633 if (mi.isAbstract() || mi.isStatic() || mi.isFinal()) { 634 return false; 635 } 636 637 // Find any relevant ancestor declaration and inspect it 638 MethodInfo om = mi.findSuperclassImplementation(notStrippable); 639 if (om != null) { 640 // Visibility mismatch is an API change, so check for it 641 if (mi.mIsPrivate == om.mIsPrivate 642 && mi.mIsPublic == om.mIsPublic 643 && mi.mIsProtected == om.mIsProtected) { 644 // Look only for overrides of an ancestor class implementation, 645 // not of e.g. an abstract or interface method declaration 646 if (!om.isAbstract()) { 647 // If the parent is hidden, we can't rely on it to provide 648 // the API 649 if (!om.isHidden()) { 650 // If the only "override" turns out to be in our own class 651 // (which sometimes happens in concrete subclasses of 652 // abstract base classes), it's not really an override 653 if (!mi.mContainingClass.equals(om.mContainingClass)) { 654 return true; 655 } 656 } 657 } 658 } 659 } 660 return false; 661 } 662 canCallMethod(ClassInfo from, MethodInfo m)663 static boolean canCallMethod(ClassInfo from, MethodInfo m) { 664 if (m.isPublic() || m.isProtected()) { 665 return true; 666 } 667 if (m.isPackagePrivate()) { 668 String fromPkg = from.containingPackage().name(); 669 String pkg = m.containingClass().containingPackage().name(); 670 if (fromPkg.equals(pkg)) { 671 return true; 672 } 673 } 674 return false; 675 } 676 677 // call a constructor, any constructor on this class's superclass. superCtorCall(ClassInfo cl, ClassInfo[] thrownExceptions)678 static String superCtorCall(ClassInfo cl, ClassInfo[] thrownExceptions) { 679 ClassInfo base = cl.realSuperclass(); 680 if (base == null) { 681 return ""; 682 } 683 HashSet<String> exceptionNames = new HashSet<String>(); 684 if (thrownExceptions != null ){ 685 for (ClassInfo thrown : thrownExceptions){ 686 exceptionNames.add(thrown.name()); 687 } 688 } 689 MethodInfo[] ctors = base.constructors(); 690 MethodInfo ctor = null; 691 //bad exception indicates that the exceptions thrown by the super constructor 692 //are incompatible with the constructor we're using for the sub class. 693 Boolean badException = false; 694 for (MethodInfo m: ctors) { 695 if (canCallMethod(cl, m)) { 696 if (m.thrownExceptions() != null){ 697 for (ClassInfo thrown : m.thrownExceptions()){ 698 if (!exceptionNames.contains(thrown.name())){ 699 badException = true; 700 } 701 } 702 } 703 if (badException){ 704 badException = false; 705 continue; 706 } 707 // if it has no args, we're done 708 if (m.parameters().length == 0) { 709 return ""; 710 } 711 ctor = m; 712 } 713 } 714 if (ctor != null) { 715 String result = ""; 716 result+= "super("; 717 ParameterInfo[] params = ctor.parameters(); 718 int N = params.length; 719 for (int i=0; i<N; i++) { 720 TypeInfo t = params[i].type(); 721 if (t.isPrimitive() && t.dimension().equals("")) { 722 String n = t.simpleTypeName(); 723 if (("byte".equals(n) 724 || "short".equals(n) 725 || "int".equals(n) 726 || "long".equals(n) 727 || "float".equals(n) 728 || "double".equals(n)) && t.dimension().equals("")) { 729 result += "0"; 730 } 731 else if ("char".equals(n)) { 732 result += "'\\0'"; 733 } 734 else if ("boolean".equals(n)) { 735 result += "false"; 736 } 737 else { 738 result += "<<unknown-" + n + ">>"; 739 } 740 } else { 741 //put null in each super class method. Cast null to the correct type 742 //to avoid collisions with other constructors. If the type is generic 743 //don't cast it 744 result += (!t.isTypeVariable() ? "(" + t.qualifiedTypeName() + t.dimension() + 745 ")" : "") + "null"; 746 } 747 if (i != N-1) { 748 result += ","; 749 } 750 } 751 result += "); "; 752 return result; 753 } else { 754 return ""; 755 } 756 } 757 writeAnnotations(PrintStream stream, AnnotationInstanceInfo[] annotations)758 static void writeAnnotations(PrintStream stream, AnnotationInstanceInfo[] annotations) { 759 for (AnnotationInstanceInfo ann: annotations) { 760 if (!ann.type().isHidden()) { 761 stream.println(ann.toString()); 762 } 763 } 764 } 765 writeAnnotationElement(PrintStream stream, MethodInfo ann)766 static void writeAnnotationElement(PrintStream stream, MethodInfo ann) { 767 stream.print(ann.returnType().fullName()); 768 stream.print(" "); 769 stream.print(ann.name()); 770 stream.print("()"); 771 AnnotationValueInfo def = ann.defaultAnnotationElementValue(); 772 if (def != null) { 773 stream.print(" default "); 774 stream.print(def.valueString()); 775 } 776 stream.println(";"); 777 } 778 writeXML(PrintStream xmlWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses, HashSet notStrippable)779 static void writeXML(PrintStream xmlWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses, 780 HashSet notStrippable) { 781 // extract the set of packages, sort them by name, and write them out in that order 782 Set<PackageInfo> allClassKeys = allClasses.keySet(); 783 PackageInfo[] allPackages = allClassKeys.toArray(new PackageInfo[allClassKeys.size()]); 784 Arrays.sort(allPackages, PackageInfo.comparator); 785 786 xmlWriter.println("<api>"); 787 for (PackageInfo pack : allPackages) { 788 writePackageXML(xmlWriter, pack, allClasses.get(pack), notStrippable); 789 } 790 xmlWriter.println("</api>"); 791 } 792 writePackageXML(PrintStream xmlWriter, PackageInfo pack, List<ClassInfo> classList, HashSet notStrippable)793 static void writePackageXML(PrintStream xmlWriter, PackageInfo pack, List<ClassInfo> classList, 794 HashSet notStrippable) { 795 ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]); 796 Arrays.sort(classes, ClassInfo.comparator); 797 // Work around the bogus "Array" class we invent for 798 // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505) 799 if (pack.name().equals("")) { 800 return; 801 } 802 xmlWriter.println("<package name=\"" + pack.name() + "\"\n" 803 //+ " source=\"" + pack.position() + "\"\n" 804 + ">"); 805 for (ClassInfo cl : classes) { 806 writeClassXML(xmlWriter, cl, notStrippable); 807 } 808 xmlWriter.println("</package>"); 809 810 811 } 812 writeClassXML(PrintStream xmlWriter, ClassInfo cl, HashSet notStrippable)813 static void writeClassXML(PrintStream xmlWriter, ClassInfo cl, HashSet notStrippable) { 814 String scope = DroidDoc.scope(cl); 815 String deprecatedString = ""; 816 String declString = (cl.isInterface()) ? "interface" : "class"; 817 if (cl.isDeprecated()) { 818 deprecatedString = "deprecated"; 819 } else { 820 deprecatedString = "not deprecated"; 821 } 822 xmlWriter.println("<" + declString + " name=\"" + cl.name() + "\""); 823 if (!cl.isInterface() && !cl.qualifiedName().equals("java.lang.Object")) { 824 xmlWriter.println(" extends=\"" + ((cl.realSuperclass() == null) 825 ? "java.lang.Object" 826 : cl.realSuperclass().qualifiedName()) + "\""); 827 } 828 xmlWriter.println(" abstract=\"" + cl.isAbstract() + "\"\n" 829 + " static=\"" + cl.isStatic() + "\"\n" 830 + " final=\"" + cl.isFinal() + "\"\n" 831 + " deprecated=\"" + deprecatedString + "\"\n" 832 + " visibility=\"" + scope + "\"\n" 833 //+ " source=\"" + cl.position() + "\"\n" 834 + ">"); 835 836 ClassInfo[] interfaces = cl.realInterfaces(); 837 Arrays.sort(interfaces, ClassInfo.comparator); 838 for (ClassInfo iface : interfaces) { 839 if (notStrippable.contains(iface)) { 840 xmlWriter.println("<implements name=\"" + iface.qualifiedName() + "\">"); 841 xmlWriter.println("</implements>"); 842 } 843 } 844 845 MethodInfo[] constructors = cl.constructors(); 846 Arrays.sort(constructors, MethodInfo.comparator); 847 for (MethodInfo mi : constructors) { 848 writeConstructorXML(xmlWriter, mi); 849 } 850 851 MethodInfo[] methods = cl.allSelfMethods(); 852 Arrays.sort(methods, MethodInfo.comparator); 853 for (MethodInfo mi : methods) { 854 if (!methodIsOverride(mi)) { 855 writeMethodXML(xmlWriter, mi); 856 } 857 } 858 859 FieldInfo[] fields = cl.allSelfFields(); 860 Arrays.sort(fields, FieldInfo.comparator); 861 for (FieldInfo fi : fields) { 862 writeFieldXML(xmlWriter, fi); 863 } 864 xmlWriter.println("</" + declString + ">"); 865 866 } 867 writeMethodXML(PrintStream xmlWriter, MethodInfo mi)868 static void writeMethodXML(PrintStream xmlWriter, MethodInfo mi) { 869 String scope = DroidDoc.scope(mi); 870 871 String deprecatedString = ""; 872 if (mi.isDeprecated()) { 873 deprecatedString = "deprecated"; 874 } else { 875 deprecatedString = "not deprecated"; 876 } 877 xmlWriter.println("<method name=\"" + mi.name() + "\"\n" 878 + ((mi.returnType() != null) 879 ? " return=\"" + makeXMLcompliant(fullParameterTypeName(mi, mi.returnType(), false)) + "\"\n" 880 : "") 881 + " abstract=\"" + mi.isAbstract() + "\"\n" 882 + " native=\"" + mi.isNative() + "\"\n" 883 + " synchronized=\"" + mi.isSynchronized() + "\"\n" 884 + " static=\"" + mi.isStatic() + "\"\n" 885 + " final=\"" + mi.isFinal() + "\"\n" 886 + " deprecated=\""+ deprecatedString + "\"\n" 887 + " visibility=\"" + scope + "\"\n" 888 //+ " source=\"" + mi.position() + "\"\n" 889 + ">"); 890 891 // write parameters in declaration order 892 int numParameters = mi.parameters().length; 893 int count = 0; 894 for (ParameterInfo pi : mi.parameters()) { 895 count++; 896 writeParameterXML(xmlWriter, mi, pi, count == numParameters); 897 } 898 899 // but write exceptions in canonicalized order 900 ClassInfo[] exceptions = mi.thrownExceptions(); 901 Arrays.sort(exceptions, ClassInfo.comparator); 902 for (ClassInfo pi : exceptions) { 903 xmlWriter.println("<exception name=\"" + pi.name() +"\" type=\"" + pi.qualifiedName() 904 + "\">"); 905 xmlWriter.println("</exception>"); 906 } 907 xmlWriter.println("</method>"); 908 } 909 writeConstructorXML(PrintStream xmlWriter, MethodInfo mi)910 static void writeConstructorXML(PrintStream xmlWriter, MethodInfo mi) { 911 String scope = DroidDoc.scope(mi); 912 String deprecatedString = ""; 913 if (mi.isDeprecated()) { 914 deprecatedString = "deprecated"; 915 } else { 916 deprecatedString = "not deprecated"; 917 } 918 xmlWriter.println("<constructor name=\"" + mi.name() + "\"\n" 919 + " type=\"" + mi.containingClass().qualifiedName() + "\"\n" 920 + " static=\"" + mi.isStatic() + "\"\n" 921 + " final=\"" + mi.isFinal() + "\"\n" 922 + " deprecated=\"" + deprecatedString + "\"\n" 923 + " visibility=\"" + scope +"\"\n" 924 //+ " source=\"" + mi.position() + "\"\n" 925 + ">"); 926 927 int numParameters = mi.parameters().length; 928 int count = 0; 929 for (ParameterInfo pi : mi.parameters()) { 930 count++; 931 writeParameterXML(xmlWriter, mi, pi, count == numParameters); 932 } 933 934 ClassInfo[] exceptions = mi.thrownExceptions(); 935 Arrays.sort(exceptions, ClassInfo.comparator); 936 for (ClassInfo pi : exceptions) { 937 xmlWriter.println("<exception name=\"" + pi.name() +"\" type=\"" + pi.qualifiedName() 938 + "\">"); 939 xmlWriter.println("</exception>"); 940 } 941 xmlWriter.println("</constructor>"); 942 } 943 writeParameterXML(PrintStream xmlWriter, MethodInfo method, ParameterInfo pi, boolean isLast)944 static void writeParameterXML(PrintStream xmlWriter, MethodInfo method, 945 ParameterInfo pi, boolean isLast) { 946 xmlWriter.println("<parameter name=\"" + pi.name() + "\" type=\"" + 947 makeXMLcompliant(fullParameterTypeName(method, pi.type(), isLast)) + "\">"); 948 xmlWriter.println("</parameter>"); 949 } 950 writeFieldXML(PrintStream xmlWriter, FieldInfo fi)951 static void writeFieldXML(PrintStream xmlWriter, FieldInfo fi) { 952 String scope = DroidDoc.scope(fi); 953 String deprecatedString = ""; 954 if (fi.isDeprecated()) { 955 deprecatedString = "deprecated"; 956 } else { 957 deprecatedString = "not deprecated"; 958 } 959 //need to make sure value is valid XML 960 String value = makeXMLcompliant(fi.constantLiteralValue()); 961 962 String fullTypeName = makeXMLcompliant(fi.type().qualifiedTypeName()) 963 + fi.type().dimension(); 964 965 xmlWriter.println("<field name=\"" + fi.name() +"\"\n" 966 + " type=\"" + fullTypeName + "\"\n" 967 + " transient=\"" + fi.isTransient() + "\"\n" 968 + " volatile=\"" + fi.isVolatile() + "\"\n" 969 + (fieldIsInitialized(fi) ? " value=\"" + value + "\"\n" : "") 970 + " static=\"" + fi.isStatic() + "\"\n" 971 + " final=\"" + fi.isFinal() + "\"\n" 972 + " deprecated=\"" + deprecatedString + "\"\n" 973 + " visibility=\"" + scope + "\"\n" 974 //+ " source=\"" + fi.position() + "\"\n" 975 + ">"); 976 xmlWriter.println("</field>"); 977 } 978 makeXMLcompliant(String s)979 static String makeXMLcompliant(String s) { 980 String returnString = ""; 981 returnString = s.replaceAll("&", "&"); 982 returnString = returnString.replaceAll("<", "<"); 983 returnString = returnString.replaceAll(">", ">"); 984 returnString = returnString.replaceAll("\"", """); 985 returnString = returnString.replaceAll("'", "&pos;"); 986 return returnString; 987 } 988 fullParameterTypeName(MethodInfo method, TypeInfo type, boolean isLast)989 static String fullParameterTypeName(MethodInfo method, TypeInfo type, boolean isLast) { 990 String fullTypeName = type.fullName(method.typeVariables()); 991 if (isLast && method.isVarArgs()) { 992 // TODO: note that this does not attempt to handle hypothetical 993 // vararg methods whose last parameter is a list of arrays, e.g. 994 // "Object[]...". 995 fullTypeName = type.fullNameNoDimension(method.typeVariables()) + "..."; 996 } 997 return fullTypeName; 998 } 999 } 1000