1 /* 2 * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.javadoc.internal.doclets.toolkit; 27 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collection; 31 import java.util.HashMap; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.SortedSet; 35 import java.util.TreeSet; 36 37 import javax.lang.model.element.AnnotationMirror; 38 import javax.lang.model.element.Element; 39 import javax.lang.model.element.ExecutableElement; 40 import javax.lang.model.element.ModuleElement; 41 import javax.lang.model.element.PackageElement; 42 import javax.lang.model.element.TypeElement; 43 import javax.lang.model.element.VariableElement; 44 import javax.lang.model.type.TypeMirror; 45 import javax.lang.model.util.Elements; 46 import javax.tools.FileObject; 47 import javax.tools.JavaFileManager.Location; 48 49 import com.sun.source.tree.CompilationUnitTree; 50 import com.sun.source.util.JavacTask; 51 import com.sun.source.util.TreePath; 52 import com.sun.tools.doclint.DocLint; 53 import com.sun.tools.javac.api.BasicJavacTask; 54 import com.sun.tools.javac.code.Attribute; 55 import com.sun.tools.javac.code.Flags; 56 import com.sun.tools.javac.code.Scope; 57 import com.sun.tools.javac.code.Source.Feature; 58 import com.sun.tools.javac.code.Symbol; 59 import com.sun.tools.javac.code.Symbol.ClassSymbol; 60 import com.sun.tools.javac.code.Symbol.MethodSymbol; 61 import com.sun.tools.javac.code.Symbol.ModuleSymbol; 62 import com.sun.tools.javac.code.Symbol.PackageSymbol; 63 import com.sun.tools.javac.code.Symbol.VarSymbol; 64 import com.sun.tools.javac.code.TypeTag; 65 import com.sun.tools.javac.comp.AttrContext; 66 import com.sun.tools.javac.comp.Env; 67 import com.sun.tools.javac.model.JavacElements; 68 import com.sun.tools.javac.model.JavacTypes; 69 import com.sun.tools.javac.util.Names; 70 71 import jdk.javadoc.internal.doclets.toolkit.util.Utils; 72 import jdk.javadoc.internal.tool.ToolEnvironment; 73 import jdk.javadoc.internal.tool.DocEnvImpl; 74 75 import static com.sun.tools.javac.code.Kinds.Kind.*; 76 import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE; 77 78 import static javax.lang.model.element.ElementKind.*; 79 80 /** 81 * A quarantine class to isolate all the workarounds and bridges to 82 * a locality. This class should eventually disappear once all the 83 * standard APIs support the needed interfaces. 84 * 85 * 86 * <p><b>This is NOT part of any supported API. 87 * If you write code that depends on this, you do so at your own risk. 88 * This code and its internal interfaces are subject to change or 89 * deletion without notice.</b> 90 */ 91 public class WorkArounds { 92 93 public final BaseConfiguration configuration; 94 public final ToolEnvironment toolEnv; 95 public final Utils utils; 96 97 private DocLint doclint; 98 WorkArounds(BaseConfiguration configuration)99 public WorkArounds(BaseConfiguration configuration) { 100 this.configuration = configuration; 101 this.utils = this.configuration.utils; 102 this.toolEnv = ((DocEnvImpl)this.configuration.docEnv).toolEnv; 103 } 104 105 Map<CompilationUnitTree, Boolean> shouldCheck = new HashMap<>(); 106 // TODO: fix this up correctly runDocLint(TreePath path)107 public void runDocLint(TreePath path) { 108 CompilationUnitTree unit = path.getCompilationUnit(); 109 if (doclint != null && shouldCheck.computeIfAbsent(unit, doclint::shouldCheck)) { 110 doclint.scan(path); 111 } 112 } 113 114 // TODO: fix this up correctly initDocLint(Collection<String> opts, Collection<String> customTagNames, String htmlVersion)115 public void initDocLint(Collection<String> opts, Collection<String> customTagNames, String htmlVersion) { 116 ArrayList<String> doclintOpts = new ArrayList<>(); 117 boolean msgOptionSeen = false; 118 119 for (String opt : opts) { 120 if (opt.startsWith(DocLint.XMSGS_OPTION)) { 121 if (opt.equals(DocLint.XMSGS_CUSTOM_PREFIX + "none")) 122 return; 123 msgOptionSeen = true; 124 } 125 doclintOpts.add(opt); 126 } 127 128 if (!msgOptionSeen) { 129 doclintOpts.add(DocLint.XMSGS_OPTION); 130 } 131 132 String sep = ""; 133 StringBuilder customTags = new StringBuilder(); 134 for (String customTag : customTagNames) { 135 customTags.append(sep); 136 customTags.append(customTag); 137 sep = DocLint.SEPARATOR; 138 } 139 doclintOpts.add(DocLint.XCUSTOM_TAGS_PREFIX + customTags.toString()); 140 doclintOpts.add(DocLint.XHTML_VERSION_PREFIX + htmlVersion); 141 142 JavacTask t = BasicJavacTask.instance(toolEnv.context); 143 doclint = new DocLint(); 144 // standard doclet normally generates H1, H2 145 doclintOpts.add(DocLint.XIMPLICIT_HEADERS + "2"); 146 doclint.init(t, doclintOpts.toArray(new String[doclintOpts.size()]), false); 147 } 148 149 // TODO: fix this up correctly haveDocLint()150 public boolean haveDocLint() { 151 return (doclint == null); 152 } 153 154 /* 155 * TODO: This method exists because of a bug in javac which does not 156 * handle "@deprecated tag in package-info.java", when this issue 157 * is fixed this method and its uses must be jettisoned. 158 */ isDeprecated0(Element e)159 public boolean isDeprecated0(Element e) { 160 if (!utils.getDeprecatedTrees(e).isEmpty()) { 161 return true; 162 } 163 JavacTypes jctypes = ((DocEnvImpl)configuration.docEnv).toolEnv.typeutils; 164 TypeMirror deprecatedType = utils.getDeprecatedType(); 165 for (AnnotationMirror anno : e.getAnnotationMirrors()) { 166 if (jctypes.isSameType(anno.getAnnotationType().asElement().asType(), deprecatedType)) 167 return true; 168 } 169 return false; 170 } 171 172 // TODO: fix jx.l.m add this method. isSynthesized(AnnotationMirror aDesc)173 public boolean isSynthesized(AnnotationMirror aDesc) { 174 return ((Attribute)aDesc).isSynthesized(); 175 } 176 177 // TODO: fix the caller getConstValue(VariableElement ve)178 public Object getConstValue(VariableElement ve) { 179 return ((VarSymbol)ve).getConstValue(); 180 } 181 182 // TODO: DocTrees: Trees.getPath(Element e) is slow a factor 4-5 times. getElementToTreePath()183 public Map<Element, TreePath> getElementToTreePath() { 184 return toolEnv.elementToTreePath; 185 } 186 187 // TODO: we need ElementUtils.getPackage to cope with input strings 188 // to return the proper unnamedPackage for all supported releases. getUnnamedPackage()189 PackageElement getUnnamedPackage() { 190 return (Feature.MODULES.allowedInSource(toolEnv.source)) 191 ? toolEnv.syms.unnamedModule.unnamedPackage 192 : toolEnv.syms.noModule.unnamedPackage; 193 } 194 195 // TODO: implement in either jx.l.m API (preferred) or DocletEnvironment. getJavaFileObject(PackageElement packageElement)196 FileObject getJavaFileObject(PackageElement packageElement) { 197 return ((PackageSymbol)packageElement).sourcefile; 198 } 199 200 // TODO: needs to ported to jx.l.m. searchClass(TypeElement klass, String className)201 public TypeElement searchClass(TypeElement klass, String className) { 202 TypeElement te; 203 204 // search by qualified name in current module first 205 ModuleElement me = utils.containingModule(klass); 206 if (me != null) { 207 te = configuration.docEnv.getElementUtils().getTypeElement(me, className); 208 if (te != null) { 209 return te; 210 } 211 } 212 213 // search inner classes 214 for (TypeElement ite : utils.getClasses(klass)) { 215 TypeElement innerClass = searchClass(ite, className); 216 if (innerClass != null) { 217 return innerClass; 218 } 219 } 220 221 // check in this package 222 te = utils.findClassInPackageElement(utils.containingPackage(klass), className); 223 if (te != null) { 224 return te; 225 } 226 227 ClassSymbol tsym = (ClassSymbol)klass; 228 // make sure that this symbol has been completed 229 // TODO: do we need this anymore ? 230 if (tsym.completer != null) { 231 tsym.complete(); 232 } 233 234 // search imports 235 if (tsym.sourcefile != null) { 236 237 //### This information is available only for source classes. 238 Env<AttrContext> compenv = toolEnv.getEnv(tsym); 239 if (compenv == null) { 240 return null; 241 } 242 Names names = tsym.name.table.names; 243 Scope s = compenv.toplevel.namedImportScope; 244 for (Symbol sym : s.getSymbolsByName(names.fromString(className))) { 245 if (sym.kind == TYP) { 246 return (TypeElement)sym; 247 } 248 } 249 250 s = compenv.toplevel.starImportScope; 251 for (Symbol sym : s.getSymbolsByName(names.fromString(className))) { 252 if (sym.kind == TYP) { 253 return (TypeElement)sym; 254 } 255 } 256 } 257 258 // finally, search by qualified name in all modules 259 te = configuration.docEnv.getElementUtils().getTypeElement(className); 260 if (te != null) { 261 return te; 262 } 263 264 return null; // not found 265 } 266 267 // TODO: need to re-implement this using j.l.m. correctly!, this has 268 // implications on testInterface, the note here is that javac's supertype 269 // does the right thing returning Parameters in scope. 270 /** 271 * Return the type containing the method that this method overrides. 272 * It may be a <code>TypeElement</code> or a <code>TypeParameterElement</code>. 273 * @param method target 274 * @return a type 275 */ overriddenType(ExecutableElement method)276 public TypeMirror overriddenType(ExecutableElement method) { 277 if (utils.isStatic(method)) { 278 return null; 279 } 280 MethodSymbol sym = (MethodSymbol)method; 281 ClassSymbol origin = (ClassSymbol) sym.owner; 282 for (com.sun.tools.javac.code.Type t = toolEnv.getTypes().supertype(origin.type); 283 t.hasTag(TypeTag.CLASS); 284 t = toolEnv.getTypes().supertype(t)) { 285 ClassSymbol c = (ClassSymbol) t.tsym; 286 for (com.sun.tools.javac.code.Symbol sym2 : c.members().getSymbolsByName(sym.name)) { 287 if (sym.overrides(sym2, origin, toolEnv.getTypes(), true)) { 288 // Ignore those methods that may be a simple override 289 // and allow the real API method to be found. 290 if (sym2.type.hasTag(TypeTag.METHOD) && 291 utils.isSimpleOverride((MethodSymbol)sym2)) { 292 continue; 293 } 294 return t; 295 } 296 } 297 } 298 return null; 299 } 300 301 // TODO: the method jx.l.m.Elements::overrides does not check 302 // the return type, see JDK-8174840 until that is resolved, 303 // use a copy of the same method, with a return type check. 304 305 // Note: the rider.overrides call in this method *must* be consistent 306 // with the call in overrideType(....), the method above. overrides(ExecutableElement e1, ExecutableElement e2, TypeElement cls)307 public boolean overrides(ExecutableElement e1, ExecutableElement e2, TypeElement cls) { 308 MethodSymbol rider = (MethodSymbol)e1; 309 MethodSymbol ridee = (MethodSymbol)e2; 310 ClassSymbol origin = (ClassSymbol)cls; 311 312 return rider.name == ridee.name && 313 314 // not reflexive as per JLS 315 rider != ridee && 316 317 // we don't care if ridee is static, though that wouldn't 318 // compile 319 !rider.isStatic() && 320 321 // Symbol.overrides assumes the following 322 ridee.isMemberOf(origin, toolEnv.getTypes()) && 323 324 // check access, signatures and check return types 325 rider.overrides(ridee, origin, toolEnv.getTypes(), true); 326 } 327 328 // TODO: jx.l.m ? getLocationForModule(ModuleElement mdle)329 public Location getLocationForModule(ModuleElement mdle) { 330 ModuleSymbol msym = (ModuleSymbol)mdle; 331 return msym.sourceLocation != null 332 ? msym.sourceLocation 333 : msym.classLocation; 334 } 335 336 //------------------Start of Serializable Implementation---------------------// 337 private final static Map<TypeElement, NewSerializedForm> serializedForms = new HashMap<>(); 338 getSerializableFields(Utils utils, TypeElement klass)339 public SortedSet<VariableElement> getSerializableFields(Utils utils, TypeElement klass) { 340 NewSerializedForm sf = serializedForms.get(klass); 341 if (sf == null) { 342 sf = new NewSerializedForm(utils, configuration.docEnv.getElementUtils(), klass); 343 serializedForms.put(klass, sf); 344 } 345 return sf.fields; 346 } 347 getSerializationMethods(Utils utils, TypeElement klass)348 public SortedSet<ExecutableElement> getSerializationMethods(Utils utils, TypeElement klass) { 349 NewSerializedForm sf = serializedForms.get(klass); 350 if (sf == null) { 351 sf = new NewSerializedForm(utils, configuration.docEnv.getElementUtils(), klass); 352 serializedForms.put(klass, sf); 353 } 354 return sf.methods; 355 } 356 definesSerializableFields(Utils utils, TypeElement klass)357 public boolean definesSerializableFields(Utils utils, TypeElement klass) { 358 if (!utils.isSerializable(klass) || utils.isExternalizable(klass)) { 359 return false; 360 } else { 361 NewSerializedForm sf = serializedForms.get(klass); 362 if (sf == null) { 363 sf = new NewSerializedForm(utils, configuration.docEnv.getElementUtils(), klass); 364 serializedForms.put(klass, sf); 365 } 366 return sf.definesSerializableFields; 367 } 368 } 369 370 /* TODO we need a clean port to jx.l.m 371 * The serialized form is the specification of a class' serialization state. 372 * <p> 373 * 374 * It consists of the following information: 375 * <p> 376 * 377 * <pre> 378 * 1. Whether class is Serializable or Externalizable. 379 * 2. Javadoc for serialization methods. 380 * a. For Serializable, the optional readObject, writeObject, 381 * readResolve and writeReplace. 382 * serialData tag describes, in prose, the sequence and type 383 * of optional data written by writeObject. 384 * b. For Externalizable, writeExternal and readExternal. 385 * serialData tag describes, in prose, the sequence and type 386 * of optional data written by writeExternal. 387 * 3. Javadoc for serialization data layout. 388 * a. For Serializable, the name,type and description 389 * of each Serializable fields. 390 * b. For Externalizable, data layout is described by 2(b). 391 * </pre> 392 * 393 */ 394 static class NewSerializedForm { 395 396 final Utils utils; 397 final Elements elements; 398 399 final SortedSet<ExecutableElement> methods; 400 401 /* List of FieldDocImpl - Serializable fields. 402 * Singleton list if class defines Serializable fields explicitly. 403 * Otherwise, list of default serializable fields. 404 * 0 length list for Externalizable. 405 */ 406 final SortedSet<VariableElement> fields; 407 408 /* True if class specifies serializable fields explicitly. 409 * using special static member, serialPersistentFields. 410 */ 411 boolean definesSerializableFields = false; 412 413 // Specially treated field/method names defined by Serialization. 414 private static final String SERIALIZABLE_FIELDS = "serialPersistentFields"; 415 private static final String READOBJECT = "readObject"; 416 private static final String WRITEOBJECT = "writeObject"; 417 private static final String READRESOLVE = "readResolve"; 418 private static final String WRITEREPLACE = "writeReplace"; 419 private static final String READOBJECTNODATA = "readObjectNoData"; 420 NewSerializedForm(Utils utils, Elements elements, TypeElement te)421 NewSerializedForm(Utils utils, Elements elements, TypeElement te) { 422 this.utils = utils; 423 this.elements = elements; 424 methods = new TreeSet<>(utils.makeGeneralPurposeComparator()); 425 fields = new TreeSet<>(utils.makeGeneralPurposeComparator()); 426 if (utils.isExternalizable(te)) { 427 /* look up required public accessible methods, 428 * writeExternal and readExternal. 429 */ 430 String[] readExternalParamArr = {"java.io.ObjectInput"}; 431 String[] writeExternalParamArr = {"java.io.ObjectOutput"}; 432 433 ExecutableElement md = findMethod(te, "readExternal", Arrays.asList(readExternalParamArr)); 434 if (md != null) { 435 methods.add(md); 436 } 437 md = findMethod((ClassSymbol) te, "writeExternal", Arrays.asList(writeExternalParamArr)); 438 if (md != null) { 439 methods.add(md); 440 } 441 } else if (utils.isSerializable(te)) { 442 VarSymbol dsf = getDefinedSerializableFields((ClassSymbol) te); 443 if (dsf != null) { 444 /* Define serializable fields with array of ObjectStreamField. 445 * Each ObjectStreamField should be documented by a 446 * serialField tag. 447 */ 448 definesSerializableFields = true; 449 fields.add((VariableElement) dsf); 450 } else { 451 452 /* Calculate default Serializable fields as all 453 * non-transient, non-static fields. 454 * Fields should be documented by serial tag. 455 */ 456 computeDefaultSerializableFields((ClassSymbol) te); 457 } 458 459 /* Check for optional customized readObject, writeObject, 460 * readResolve and writeReplace, which can all contain 461 * the serialData tag. */ 462 addMethodIfExist((ClassSymbol) te, READOBJECT); 463 addMethodIfExist((ClassSymbol) te, WRITEOBJECT); 464 addMethodIfExist((ClassSymbol) te, READRESOLVE); 465 addMethodIfExist((ClassSymbol) te, WRITEREPLACE); 466 addMethodIfExist((ClassSymbol) te, READOBJECTNODATA); 467 } 468 } 469 getDefinedSerializableFields(ClassSymbol def)470 private VarSymbol getDefinedSerializableFields(ClassSymbol def) { 471 Names names = def.name.table.names; 472 473 /* SERIALIZABLE_FIELDS can be private, 474 */ 475 for (Symbol sym : def.members().getSymbolsByName(names.fromString(SERIALIZABLE_FIELDS))) { 476 if (sym.kind == VAR) { 477 VarSymbol f = (VarSymbol) sym; 478 if ((f.flags() & Flags.STATIC) != 0 479 && (f.flags() & Flags.PRIVATE) != 0) { 480 return f; 481 } 482 } 483 } 484 return null; 485 } 486 487 /* 488 * Catalog Serializable method if it exists in current ClassSymbol. 489 * Do not look for method in superclasses. 490 * 491 * Serialization requires these methods to be non-static. 492 * 493 * @param method should be an unqualified Serializable method 494 * name either READOBJECT, WRITEOBJECT, READRESOLVE 495 * or WRITEREPLACE. 496 * @param visibility the visibility flag for the given method. 497 */ addMethodIfExist(ClassSymbol def, String methodName)498 private void addMethodIfExist(ClassSymbol def, String methodName) { 499 Names names = def.name.table.names; 500 501 for (Symbol sym : def.members().getSymbolsByName(names.fromString(methodName))) { 502 if (sym.kind == MTH) { 503 MethodSymbol md = (MethodSymbol) sym; 504 if ((md.flags() & Flags.STATIC) == 0) { 505 /* 506 * WARNING: not robust if unqualifiedMethodName is overloaded 507 * method. Signature checking could make more robust. 508 * READOBJECT takes a single parameter, java.io.ObjectInputStream. 509 * WRITEOBJECT takes a single parameter, java.io.ObjectOutputStream. 510 */ 511 methods.add(md); 512 } 513 } 514 } 515 } 516 517 /* 518 * Compute default Serializable fields from all members of ClassSymbol. 519 * 520 * must walk over all members of ClassSymbol. 521 */ computeDefaultSerializableFields(ClassSymbol te)522 private void computeDefaultSerializableFields(ClassSymbol te) { 523 for (Symbol sym : te.members().getSymbols(NON_RECURSIVE)) { 524 if (sym != null && sym.kind == VAR) { 525 VarSymbol f = (VarSymbol) sym; 526 if ((f.flags() & Flags.STATIC) == 0 527 && (f.flags() & Flags.TRANSIENT) == 0) { 528 //### No modifier filtering applied here. 529 //### Add to beginning. 530 //### Preserve order used by old 'javadoc'. 531 fields.add(f); 532 } 533 } 534 } 535 } 536 537 /** 538 * Find a method in this class scope. Search order: this class, interfaces, superclasses, 539 * outerclasses. Note that this is not necessarily what the compiler would do! 540 * 541 * @param methodName the unqualified name to search for. 542 * @param paramTypes the array of Strings for method parameter types. 543 * @return the first MethodDocImpl which matches, null if not found. 544 */ findMethod(TypeElement te, String methodName, List<String> paramTypes)545 public ExecutableElement findMethod(TypeElement te, String methodName, 546 List<String> paramTypes) { 547 List<? extends Element> allMembers = this.elements.getAllMembers(te); 548 loop: 549 for (Element e : allMembers) { 550 if (e.getKind() != METHOD) { 551 continue; 552 } 553 ExecutableElement ee = (ExecutableElement) e; 554 if (!ee.getSimpleName().contentEquals(methodName)) { 555 continue; 556 } 557 List<? extends VariableElement> parameters = ee.getParameters(); 558 if (paramTypes.size() != parameters.size()) { 559 continue; 560 } 561 for (int i = 0; i < parameters.size(); i++) { 562 VariableElement ve = parameters.get(i); 563 if (!ve.asType().toString().equals(paramTypes.get(i))) { 564 break loop; 565 } 566 } 567 return ee; 568 } 569 TypeElement encl = utils.getEnclosingTypeElement(te); 570 if (encl == null) { 571 return null; 572 } 573 return findMethod(encl, methodName, paramTypes); 574 } 575 } 576 577 // TODO: we need to eliminate this, as it is hacky. 578 /** 579 * Returns a representation of the package truncated to two levels. 580 * For instance if the given package represents foo.bar.baz will return 581 * a representation of foo.bar 582 * @param pkg the PackageElement 583 * @return an abbreviated PackageElement 584 */ getAbbreviatedPackageElement(PackageElement pkg)585 public PackageElement getAbbreviatedPackageElement(PackageElement pkg) { 586 String parsedPackageName = utils.parsePackageName(pkg); 587 ModuleElement encl = (ModuleElement) pkg.getEnclosingElement(); 588 PackageElement abbrevPkg = encl == null 589 ? utils.elementUtils.getPackageElement(parsedPackageName) 590 : ((JavacElements) utils.elementUtils).getPackageElement(encl, parsedPackageName); 591 return abbrevPkg; 592 } 593 } 594