WorkArounds.java revision 3233:b5d08bc0d224
1/* 2 * Copyright (c) 2015, 2016, 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 26package jdk.javadoc.internal.doclets.toolkit; 27 28import java.util.ArrayList; 29import java.util.Arrays; 30import java.util.Collection; 31import java.util.Collections; 32import java.util.HashMap; 33import java.util.List; 34import java.util.Map; 35import java.util.SortedSet; 36import java.util.TreeSet; 37 38import javax.lang.model.element.AnnotationMirror; 39import javax.lang.model.element.Element; 40import javax.lang.model.element.ExecutableElement; 41import javax.lang.model.element.PackageElement; 42import javax.lang.model.element.TypeElement; 43import javax.lang.model.element.VariableElement; 44import javax.lang.model.type.TypeMirror; 45import javax.lang.model.util.Elements; 46import javax.tools.JavaFileObject; 47 48import com.sun.source.tree.CompilationUnitTree; 49import com.sun.source.util.JavacTask; 50import com.sun.source.util.TreePath; 51import com.sun.tools.doclint.DocLint; 52import com.sun.tools.javac.api.BasicJavacTask; 53import com.sun.tools.javac.code.Attribute; 54import com.sun.tools.javac.code.Flags; 55import com.sun.tools.javac.code.Scope; 56import com.sun.tools.javac.code.Symbol; 57import com.sun.tools.javac.code.Symbol.ClassSymbol; 58import com.sun.tools.javac.code.Symbol.MethodSymbol; 59import com.sun.tools.javac.code.Symbol.VarSymbol; 60import com.sun.tools.javac.comp.AttrContext; 61import com.sun.tools.javac.comp.Env; 62import com.sun.tools.javac.model.JavacTypes; 63import com.sun.tools.javac.util.Names; 64 65import jdk.javadoc.internal.doclets.toolkit.util.Utils; 66import jdk.javadoc.internal.tool.DocEnv; 67import jdk.javadoc.internal.tool.RootDocImpl; 68 69import static com.sun.tools.javac.code.Kinds.Kind.*; 70import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE; 71import static javax.lang.model.element.ElementKind.*; 72 73/** 74 * A quarantine class to isolate all the workarounds and bridges to 75 * a locality. This class should eventually disappear once all the 76 * standard APIs support the needed interfaces. 77 * 78 * 79 * <p><b>This is NOT part of any supported API. 80 * If you write code that depends on this, you do so at your own risk. 81 * This code and its internal interfaces are subject to change or 82 * deletion without notice.</b> 83 */ 84public class WorkArounds { 85 86 public final Configuration configuration; 87 public final DocEnv env; 88 public final Utils utils; 89 90 private DocLint doclint; 91 92 public WorkArounds(Configuration configuration) { 93 this.configuration = configuration; 94 this.utils = this.configuration.utils; 95 this.env = ((RootDocImpl)this.configuration.root).env; 96 } 97 98 Map<CompilationUnitTree, Boolean> shouldCheck = new HashMap<>(); 99 // TODO: fix this up correctly 100 public void runDocLint(TreePath path) { 101 CompilationUnitTree unit = path.getCompilationUnit(); 102 if (doclint != null && shouldCheck.computeIfAbsent(unit, doclint::shouldCheck)) { 103 doclint.scan(path); 104 } 105 } 106 107 // TODO: fix this up correctly 108 public void initDocLint(Collection<String> opts, Collection<String> customTagNames, String htmlVersion) { 109 ArrayList<String> doclintOpts = new ArrayList<>(); 110 boolean msgOptionSeen = false; 111 112 for (String opt : opts) { 113 if (opt.startsWith(DocLint.XMSGS_OPTION)) { 114 if (opt.equals(DocLint.XMSGS_CUSTOM_PREFIX + "none")) 115 return; 116 msgOptionSeen = true; 117 } 118 doclintOpts.add(opt); 119 } 120 121 if (!msgOptionSeen) { 122 doclintOpts.add(DocLint.XMSGS_OPTION); 123 } 124 125 String sep = ""; 126 StringBuilder customTags = new StringBuilder(); 127 for (String customTag : customTagNames) { 128 customTags.append(sep); 129 customTags.append(customTag); 130 sep = DocLint.SEPARATOR; 131 } 132 doclintOpts.add(DocLint.XCUSTOM_TAGS_PREFIX + customTags.toString()); 133 doclintOpts.add(DocLint.XHTML_VERSION_PREFIX + htmlVersion); 134 135 JavacTask t = BasicJavacTask.instance(env.context); 136 doclint = new DocLint(); 137 // standard doclet normally generates H1, H2 138 doclintOpts.add(DocLint.XIMPLICIT_HEADERS + "2"); 139 doclint.init(t, doclintOpts.toArray(new String[doclintOpts.size()]), false); 140 } 141 142 // TODO: fix this up correctly 143 public boolean haveDocLint() { 144 return (doclint == null); 145 } 146 147 // TODO: jx.l.m directSuperTypes don't work for things like Enum, 148 // so we use javac directly, investigate why jx.l.m is not cutting it. 149 public List<TypeMirror> interfaceTypesOf(TypeMirror type) { 150 com.sun.tools.javac.util.List<com.sun.tools.javac.code.Type> interfaces = 151 ((RootDocImpl)configuration.root).env.getTypes().interfaces((com.sun.tools.javac.code.Type)type); 152 if (interfaces.isEmpty()) { 153 return Collections.emptyList(); 154 } 155 List<TypeMirror> list = new ArrayList<>(interfaces.size()); 156 for (com.sun.tools.javac.code.Type t : interfaces) { 157 list.add((TypeMirror)t); 158 } 159 return list; 160 } 161 162 /* 163 * TODO: This method exists because of a bug in javac which does not 164 * handle "@deprecated tag in package-info.java", when this issue 165 * is fixed this method and its uses must be jettisoned. 166 */ 167 public boolean isDeprecated0(Element e) { 168 if (!utils.getDeprecatedTrees(e).isEmpty()) { 169 return true; 170 } 171 JavacTypes jctypes = ((RootDocImpl)configuration.root).env.typeutils; 172 TypeMirror deprecatedType = utils.getDeprecatedType(); 173 for (AnnotationMirror anno : e.getAnnotationMirrors()) { 174 if (jctypes.isSameType(anno.getAnnotationType().asElement().asType(), deprecatedType)) 175 return true; 176 } 177 return false; 178 } 179 180 // TODO: fix jx.l.m add this method. 181 public boolean isSynthesized(AnnotationMirror aDesc) { 182 return ((Attribute)aDesc).isSynthesized(); 183 } 184 185 // TODO: implement using jx.l.model 186 public boolean isVisible(TypeElement te) { 187 return env.isVisible((ClassSymbol)te); 188 } 189 190 // TODO: fix the caller 191 public Object getConstValue(VariableElement ve) { 192 return ((VarSymbol)ve).getConstValue(); 193 } 194 195 //TODO: DocTrees: Trees.getPath(Element e) is slow a factor 4-5 times. 196 public Map<Element, TreePath> getElementToTreePath() { 197 return env.elementToTreePath; 198 } 199 200 // TODO: needs to ported to jx.l.m. 201 public TypeElement searchClass(TypeElement klass, String className) { 202 // search by qualified name first 203 TypeElement te = configuration.root.getElementUtils().getTypeElement(className); 204 if (te != null) { 205 return te; 206 } 207 208 // search inner classes 209 for (TypeElement ite : utils.getClasses(klass)) { 210 TypeElement innerClass = searchClass(ite, className); 211 if (innerClass != null) { 212 return innerClass; 213 } 214 } 215 216 // check in this package 217 te = utils.findClassInPackageElement(utils.containingPackage(klass), className); 218 if (te != null) { 219 return te; 220 } 221 222 ClassSymbol tsym = (ClassSymbol)klass; 223 // make sure that this symbol has been completed 224 // TODO: do we need this anymore ? 225 if (tsym.completer != null) { 226 tsym.complete(); 227 } 228 229 // search imports 230 if (tsym.sourcefile != null) { 231 232 //### This information is available only for source classes. 233 Env<AttrContext> compenv = env.getEnv(tsym); 234 if (compenv == null) { 235 return null; 236 } 237 Names names = tsym.name.table.names; 238 Scope s = compenv.toplevel.namedImportScope; 239 for (Symbol sym : s.getSymbolsByName(names.fromString(className))) { 240 if (sym.kind == TYP) { 241 return (TypeElement)sym; 242 } 243 } 244 245 s = compenv.toplevel.starImportScope; 246 for (Symbol sym : s.getSymbolsByName(names.fromString(className))) { 247 if (sym.kind == TYP) { 248 return (TypeElement)sym; 249 } 250 } 251 } 252 253 return null; // not found 254 } 255 256 // TODO: need to re-implement this using j.l.m. correctly!, this has 257 // implications on testInterface, the note here is that javac's supertype 258 // does the right thing returning Parameters in scope. 259 /** 260 * Return the type containing the method that this method overrides. 261 * It may be a <code>TypeElement</code> or a <code>TypeParameterElement</code>. 262 * @param method target 263 * @return a type 264 */ 265 public TypeMirror overriddenType(ExecutableElement method) { 266 if (utils.isStatic(method)) { 267 return null; 268 } 269 MethodSymbol sym = (MethodSymbol)method; 270 ClassSymbol origin = (ClassSymbol) sym.owner; 271 for (com.sun.tools.javac.code.Type t = env.getTypes().supertype(origin.type); 272 t.hasTag(com.sun.tools.javac.code.TypeTag.CLASS); 273 t = env.getTypes().supertype(t)) { 274 ClassSymbol c = (ClassSymbol) t.tsym; 275 for (com.sun.tools.javac.code.Symbol sym2 : c.members().getSymbolsByName(sym.name)) { 276 if (sym.overrides(sym2, origin, env.getTypes(), true)) { 277 return t; 278 } 279 } 280 } 281 return null; 282 } 283 284 // TODO: investigate and reimplement without javac dependencies. 285 public boolean shouldDocument(Element e) { 286 return env.shouldDocument(e); 287 } 288 289 //------------------Start of Serializable Implementation---------------------// 290 private final static Map<TypeElement, NewSerializedForm> serializedForms = new HashMap<>(); 291 292 public SortedSet<VariableElement> getSerializableFields(Utils utils, TypeElement klass) { 293 NewSerializedForm sf = serializedForms.get(klass); 294 if (sf == null) { 295 sf = new NewSerializedForm(utils, configuration.root.getElementUtils(), klass); 296 serializedForms.put(klass, sf); 297 } 298 return sf.fields; 299 } 300 301 public SortedSet<ExecutableElement> getSerializationMethods(Utils utils, TypeElement klass) { 302 NewSerializedForm sf = serializedForms.get(klass); 303 if (sf == null) { 304 sf = new NewSerializedForm(utils, configuration.root.getElementUtils(), klass); 305 serializedForms.put(klass, sf); 306 } 307 return sf.methods; 308 } 309 310 public boolean definesSerializableFields(Utils utils, TypeElement klass) { 311 if (!utils.isSerializable(klass) || utils.isExternalizable(klass)) { 312 return false; 313 } else { 314 NewSerializedForm sf = serializedForms.get(klass); 315 if (sf == null) { 316 sf = new NewSerializedForm(utils, configuration.root.getElementUtils(), klass); 317 serializedForms.put(klass, sf); 318 } 319 return sf.definesSerializableFields; 320 } 321 } 322 323 /* TODO we need a clean port to jx.l.m 324 * The serialized form is the specification of a class' serialization state. 325 * <p> 326 * 327 * It consists of the following information: 328 * <p> 329 * 330 * <pre> 331 * 1. Whether class is Serializable or Externalizable. 332 * 2. Javadoc for serialization methods. 333 * a. For Serializable, the optional readObject, writeObject, 334 * readResolve and writeReplace. 335 * serialData tag describes, in prose, the sequence and type 336 * of optional data written by writeObject. 337 * b. For Externalizable, writeExternal and readExternal. 338 * serialData tag describes, in prose, the sequence and type 339 * of optional data written by writeExternal. 340 * 3. Javadoc for serialization data layout. 341 * a. For Serializable, the name,type and description 342 * of each Serializable fields. 343 * b. For Externalizable, data layout is described by 2(b). 344 * </pre> 345 * 346 */ 347 static class NewSerializedForm { 348 349 final Utils utils; 350 final Elements elements; 351 352 final SortedSet<ExecutableElement> methods; 353 354 /* List of FieldDocImpl - Serializable fields. 355 * Singleton list if class defines Serializable fields explicitly. 356 * Otherwise, list of default serializable fields. 357 * 0 length list for Externalizable. 358 */ 359 final SortedSet<VariableElement> fields; 360 361 /* True if class specifies serializable fields explicitly. 362 * using special static member, serialPersistentFields. 363 */ 364 boolean definesSerializableFields = false; 365 366 // Specially treated field/method names defined by Serialization. 367 private static final String SERIALIZABLE_FIELDS = "serialPersistentFields"; 368 private static final String READOBJECT = "readObject"; 369 private static final String WRITEOBJECT = "writeObject"; 370 private static final String READRESOLVE = "readResolve"; 371 private static final String WRITEREPLACE = "writeReplace"; 372 private static final String READOBJECTNODATA = "readObjectNoData"; 373 374 NewSerializedForm(Utils utils, Elements elements, TypeElement te) { 375 this.utils = utils; 376 this.elements = elements; 377 methods = new TreeSet<>(utils.makeGeneralPurposeComparator()); 378 fields = new TreeSet<>(utils.makeGeneralPurposeComparator()); 379 if (utils.isExternalizable(te)) { 380 /* look up required public accessible methods, 381 * writeExternal and readExternal. 382 */ 383 String[] readExternalParamArr = {"java.io.ObjectInput"}; 384 String[] writeExternalParamArr = {"java.io.ObjectOutput"}; 385 386 ExecutableElement md = findMethod(te, "readExternal", Arrays.asList(readExternalParamArr)); 387 if (md != null) { 388 methods.add(md); 389 } 390 md = findMethod((ClassSymbol) te, "writeExternal", Arrays.asList(writeExternalParamArr)); 391 if (md != null) { 392 methods.add(md); 393 } 394 } else if (utils.isSerializable(te)) { 395 VarSymbol dsf = getDefinedSerializableFields((ClassSymbol) te); 396 if (dsf != null) { 397 /* Define serializable fields with array of ObjectStreamField. 398 * Each ObjectStreamField should be documented by a 399 * serialField tag. 400 */ 401 definesSerializableFields = true; 402 fields.add((VariableElement) dsf); 403 } else { 404 405 /* Calculate default Serializable fields as all 406 * non-transient, non-static fields. 407 * Fields should be documented by serial tag. 408 */ 409 computeDefaultSerializableFields((ClassSymbol) te); 410 } 411 412 /* Check for optional customized readObject, writeObject, 413 * readResolve and writeReplace, which can all contain 414 * the serialData tag. */ 415 addMethodIfExist((ClassSymbol) te, READOBJECT); 416 addMethodIfExist((ClassSymbol) te, WRITEOBJECT); 417 addMethodIfExist((ClassSymbol) te, READRESOLVE); 418 addMethodIfExist((ClassSymbol) te, WRITEREPLACE); 419 addMethodIfExist((ClassSymbol) te, READOBJECTNODATA); 420 } 421 } 422 423 private VarSymbol getDefinedSerializableFields(ClassSymbol def) { 424 Names names = def.name.table.names; 425 426 /* SERIALIZABLE_FIELDS can be private, 427 */ 428 for (Symbol sym : def.members().getSymbolsByName(names.fromString(SERIALIZABLE_FIELDS))) { 429 if (sym.kind == VAR) { 430 VarSymbol f = (VarSymbol) sym; 431 if ((f.flags() & Flags.STATIC) != 0 432 && (f.flags() & Flags.PRIVATE) != 0) { 433 return f; 434 } 435 } 436 } 437 return null; 438 } 439 440 /* 441 * Catalog Serializable method if it exists in current ClassSymbol. 442 * Do not look for method in superclasses. 443 * 444 * Serialization requires these methods to be non-static. 445 * 446 * @param method should be an unqualified Serializable method 447 * name either READOBJECT, WRITEOBJECT, READRESOLVE 448 * or WRITEREPLACE. 449 * @param visibility the visibility flag for the given method. 450 */ 451 private void addMethodIfExist(ClassSymbol def, String methodName) { 452 Names names = def.name.table.names; 453 454 for (Symbol sym : def.members().getSymbolsByName(names.fromString(methodName))) { 455 if (sym.kind == MTH) { 456 MethodSymbol md = (MethodSymbol) sym; 457 if ((md.flags() & Flags.STATIC) == 0) { 458 /* 459 * WARNING: not robust if unqualifiedMethodName is overloaded 460 * method. Signature checking could make more robust. 461 * READOBJECT takes a single parameter, java.io.ObjectInputStream. 462 * WRITEOBJECT takes a single parameter, java.io.ObjectOutputStream. 463 */ 464 methods.add(md); 465 } 466 } 467 } 468 } 469 470 /* 471 * Compute default Serializable fields from all members of ClassSymbol. 472 * 473 * must walk over all members of ClassSymbol. 474 */ 475 private void computeDefaultSerializableFields(ClassSymbol te) { 476 for (Symbol sym : te.members().getSymbols(NON_RECURSIVE)) { 477 if (sym != null && sym.kind == VAR) { 478 VarSymbol f = (VarSymbol) sym; 479 if ((f.flags() & Flags.STATIC) == 0 480 && (f.flags() & Flags.TRANSIENT) == 0) { 481 //### No modifier filtering applied here. 482 //### Add to beginning. 483 //### Preserve order used by old 'javadoc'. 484 fields.add(f); 485 } 486 } 487 } 488 } 489 490 /** 491 * Find a method in this class scope. Search order: this class, interfaces, superclasses, 492 * outerclasses. Note that this is not necessarily what the compiler would do! 493 * 494 * @param methodName the unqualified name to search for. 495 * @param paramTypes the array of Strings for method parameter types. 496 * @return the first MethodDocImpl which matches, null if not found. 497 */ 498 public ExecutableElement findMethod(TypeElement te, String methodName, 499 List<String> paramTypes) { 500 List<? extends Element> allMembers = this.elements.getAllMembers(te); 501 loop: 502 for (Element e : allMembers) { 503 if (e.getKind() != METHOD) { 504 continue; 505 } 506 ExecutableElement ee = (ExecutableElement) e; 507 if (!ee.getSimpleName().contentEquals(methodName)) { 508 continue; 509 } 510 List<? extends VariableElement> parameters = ee.getParameters(); 511 if (paramTypes.size() != parameters.size()) { 512 continue; 513 } 514 for (int i = 0; i < parameters.size(); i++) { 515 VariableElement ve = parameters.get(i); 516 if (!ve.asType().toString().equals(paramTypes.get(i))) { 517 break loop; 518 } 519 } 520 return ee; 521 } 522 TypeElement encl = utils.getEnclosingTypeElement(te); 523 if (encl == null) { 524 return null; 525 } 526 return findMethod(encl, methodName, paramTypes); 527 } 528 } 529 530 // TODO: this is a fast way to get the JavaFileObject for 531 // a package.html file, however we need to eliminate this. 532 public JavaFileObject getJavaFileObject(PackageElement pe) { 533 return env.pkgToJavaFOMap.get(pe); 534 } 535} 536