001/* 002 * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.toolkit; 027 028import java.util.Arrays; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.SortedSet; 033import java.util.TreeSet; 034 035import javax.lang.model.element.AnnotationMirror; 036import javax.lang.model.element.AnnotationValue; 037import javax.lang.model.element.Element; 038import javax.lang.model.element.ExecutableElement; 039import javax.lang.model.element.ModuleElement; 040import javax.lang.model.element.PackageElement; 041import javax.lang.model.element.TypeElement; 042import javax.lang.model.element.VariableElement; 043import javax.lang.model.type.DeclaredType; 044import javax.lang.model.type.TypeMirror; 045import javax.lang.model.util.Elements; 046import javax.lang.model.util.Types; 047import javax.tools.FileObject; 048import javax.tools.JavaFileManager.Location; 049 050import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 051import jdk.javadoc.internal.tool.DocEnvImpl; 052import jdk.javadoc.internal.tool.ToolEnvironment; 053 054import com.sun.source.util.TreePath; 055import com.sun.tools.javac.code.Flags; 056import com.sun.tools.javac.code.Scope; 057import com.sun.tools.javac.code.Symbol; 058import com.sun.tools.javac.code.Symbol.ClassSymbol; 059import com.sun.tools.javac.code.Symbol.MethodSymbol; 060import com.sun.tools.javac.code.Symbol.ModuleSymbol; 061import com.sun.tools.javac.code.Symbol.PackageSymbol; 062import com.sun.tools.javac.code.Symbol.VarSymbol; 063import com.sun.tools.javac.code.Type; 064import com.sun.tools.javac.code.TypeTag; 065import com.sun.tools.javac.comp.AttrContext; 066import com.sun.tools.javac.comp.Env; 067import com.sun.tools.javac.model.JavacElements; 068import com.sun.tools.javac.util.Names; 069import com.sun.tools.javac.util.Options; 070 071import static com.sun.tools.javac.code.Kinds.Kind.*; 072import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE; 073 074import static javax.lang.model.element.ElementKind.*; 075 076/** 077 * A quarantine class to isolate all the workarounds and bridges to 078 * a locality. This class should eventually disappear once all the 079 * standard APIs support the needed interfaces. 080 */ 081public class WorkArounds { 082 083 public final BaseConfiguration configuration; 084 public final ToolEnvironment toolEnv; 085 public final Utils utils; 086 public final Elements elementUtils; 087 public final Types typeUtils; 088 public final com.sun.tools.javac.code.Types javacTypes; 089 090 public WorkArounds(BaseConfiguration configuration) { 091 this.configuration = configuration; 092 this.utils = this.configuration.utils; 093 094 elementUtils = configuration.docEnv.getElementUtils(); 095 typeUtils = configuration.docEnv.getTypeUtils(); 096 097 // Note: this one use of DocEnvImpl is what prevents us tunnelling extra 098 // info from a doclet to its taglets via a doclet-specific subtype of 099 // DocletEnvironment. 100 toolEnv = ((DocEnvImpl) this.configuration.docEnv).toolEnv; 101 javacTypes = toolEnv.getTypes(); 102 } 103 104 /* 105 * TODO: This method exists because of a bug in javac which does not 106 * handle "@deprecated tag in package-info.java", when this issue 107 * is fixed this method and its uses must be jettisoned. 108 */ 109 public boolean isDeprecated0(Element e) { 110 if (!utils.getDeprecatedTrees(e).isEmpty()) { 111 return true; 112 } 113 TypeMirror deprecatedType = utils.getDeprecatedType(); 114 for (AnnotationMirror anno : e.getAnnotationMirrors()) { 115 if (typeUtils.isSameType( 116 anno.getAnnotationType().asElement().asType(), deprecatedType)) 117 return true; 118 } 119 return false; 120 } 121 122 public boolean isMandated(AnnotationMirror aDesc) { 123 return elementUtils.getOrigin(null, aDesc) == Elements.Origin.MANDATED; 124 } 125 126 // TODO: DocTrees: Trees.getPath(Element e) is slow a factor 4-5 times. 127 public Map<Element, TreePath> getElementToTreePath() { 128 return toolEnv.elementToTreePath; 129 } 130 131 // TODO: implement in either jx.l.m API (preferred) or DocletEnvironment. 132 FileObject getJavaFileObject(PackageElement packageElement) { 133 return ((PackageSymbol) packageElement).sourcefile; 134 } 135 136 // TODO: needs to ported to jx.l.m. 137 public TypeElement searchClass(TypeElement klass, String className) { 138 TypeElement te; 139 140 // search by qualified name in current module first 141 ModuleElement me = utils.containingModule(klass); 142 if (me != null) { 143 te = elementUtils.getTypeElement(me, className); 144 if (te != null) { 145 return te; 146 } 147 } 148 149 // search inner classes 150 for (TypeElement ite : utils.getClasses(klass)) { 151 TypeElement innerClass = searchClass(ite, className); 152 if (innerClass != null) { 153 return innerClass; 154 } 155 } 156 157 // check in this package 158 te = utils.findClassInPackageElement(utils.containingPackage(klass), 159 className); 160 if (te != null) { 161 return te; 162 } 163 164 ClassSymbol tsym = (ClassSymbol) klass; 165 // make sure that this symbol has been completed 166 // TODO: do we need this anymore ? 167 if (tsym.completer != null) { 168 tsym.complete(); 169 } 170 171 // search imports 172 if (tsym.sourcefile != null) { 173 174 // ### This information is available only for source classes. 175 Env<AttrContext> compenv = toolEnv.getEnv(tsym); 176 if (compenv == null) { 177 return null; 178 } 179 Names names = tsym.name.table.names; 180 Scope s = compenv.toplevel.namedImportScope; 181 for (Symbol sym : s.getSymbolsByName(names.fromString(className))) { 182 if (sym.kind == TYP) { 183 return (TypeElement) sym; 184 } 185 } 186 187 s = compenv.toplevel.starImportScope; 188 for (Symbol sym : s.getSymbolsByName(names.fromString(className))) { 189 if (sym.kind == TYP) { 190 return (TypeElement) sym; 191 } 192 } 193 } 194 195 // finally, search by qualified name in all modules 196 return elementUtils.getTypeElement(className); 197 } 198 199 // TODO: jx.l.m ? 200 public Location getLocationForModule(ModuleElement mdle) { 201 ModuleSymbol msym = (ModuleSymbol) mdle; 202 return msym.sourceLocation != null 203 ? msym.sourceLocation 204 : msym.classLocation; 205 } 206 207 // ------------------Start of Serializable 208 // Implementation---------------------// 209 private final Map<TypeElement, NewSerializedForm> serializedForms 210 = new HashMap<>(); 211 212 private NewSerializedForm getSerializedForm(TypeElement typeElem) { 213 return serializedForms.computeIfAbsent(typeElem, 214 te -> new NewSerializedForm(utils, 215 configuration.docEnv.getElementUtils(), te)); 216 } 217 218 public SortedSet<VariableElement> 219 getSerializableFields(TypeElement typeElem) { 220 return getSerializedForm(typeElem).fields; 221 } 222 223 public SortedSet<ExecutableElement> 224 getSerializationMethods(TypeElement typeElem) { 225 return getSerializedForm(typeElem).methods; 226 } 227 228 public boolean definesSerializableFields(TypeElement typeElem) { 229 if (!utils.isSerializable(typeElem) 230 || utils.isExternalizable(typeElem)) { 231 return false; 232 } else { 233 return getSerializedForm(typeElem).definesSerializableFields; 234 } 235 } 236 237 /* 238 * TODO we need a clean port to jx.l.m 239 * The serialized form is the specification of a class' serialization state. 240 * <p> 241 * 242 * It consists of the following information: 243 * <p> 244 * 245 * <pre> 246 * 1. Whether class is Serializable or Externalizable. 247 * 2. Javadoc for serialization methods. 248 * a. For Serializable, the optional readObject, writeObject, 249 * readResolve and writeReplace. 250 * serialData tag describes, in prose, the sequence and type 251 * of optional data written by writeObject. 252 * b. For Externalizable, writeExternal and readExternal. 253 * serialData tag describes, in prose, the sequence and type 254 * of optional data written by writeExternal. 255 * 3. Javadoc for serialization data layout. 256 * a. For Serializable, the name,type and description 257 * of each Serializable fields. 258 * b. For Externalizable, data layout is described by 2(b). 259 * </pre> 260 * 261 */ 262 static class NewSerializedForm { 263 264 final Utils utils; 265 final Elements elements; 266 267 final SortedSet<ExecutableElement> methods; 268 269 /* 270 * List of FieldDocImpl - Serializable fields. 271 * Singleton list if class defines Serializable fields explicitly. 272 * Otherwise, list of default serializable fields. 273 * 0 length list for Externalizable. 274 */ 275 final SortedSet<VariableElement> fields; 276 277 /* 278 * True if class specifies serializable fields explicitly. 279 * using special static member, serialPersistentFields. 280 */ 281 boolean definesSerializableFields = false; 282 283 // Specially treated field/method names defined by Serialization. 284 private static final String SERIALIZABLE_FIELDS 285 = "serialPersistentFields"; 286 private static final String READOBJECT = "readObject"; 287 private static final String WRITEOBJECT = "writeObject"; 288 private static final String READRESOLVE = "readResolve"; 289 private static final String WRITEREPLACE = "writeReplace"; 290 private static final String READOBJECTNODATA = "readObjectNoData"; 291 292 NewSerializedForm(Utils utils, Elements elements, TypeElement te) { 293 this.utils = utils; 294 this.elements = elements; 295 methods = new TreeSet<>( 296 utils.comparators.makeGeneralPurposeComparator()); 297 fields = new TreeSet<>( 298 utils.comparators.makeGeneralPurposeComparator()); 299 if (utils.isExternalizable(te)) { 300 /* 301 * look up required public accessible methods, 302 * writeExternal and readExternal. 303 */ 304 String[] readExternalParamArr = { "java.io.ObjectInput" }; 305 String[] writeExternalParamArr = { "java.io.ObjectOutput" }; 306 307 ExecutableElement md = findMethod(te, "readExternal", 308 Arrays.asList(readExternalParamArr)); 309 if (md != null) { 310 methods.add(md); 311 } 312 md = findMethod(te, "writeExternal", 313 Arrays.asList(writeExternalParamArr)); 314 if (md != null) { 315 methods.add(md); 316 } 317 } else if (utils.isSerializable(te)) { 318 VarSymbol dsf = getDefinedSerializableFields((ClassSymbol) te); 319 if (dsf != null) { 320 /* 321 * Define serializable fields with array of 322 * ObjectStreamField. 323 * Each ObjectStreamField should be documented by a 324 * serialField tag. 325 */ 326 definesSerializableFields = true; 327 fields.add(dsf); 328 } else { 329 330 /* 331 * Calculate default Serializable fields as all 332 * non-transient, non-static fields. 333 * Fields should be documented by serial tag. 334 */ 335 computeDefaultSerializableFields((ClassSymbol) te); 336 } 337 338 /* 339 * Check for optional customized readObject, writeObject, 340 * readResolve and writeReplace, which can all contain 341 * the serialData tag. 342 */ 343 addMethodIfExist((ClassSymbol) te, READOBJECT); 344 addMethodIfExist((ClassSymbol) te, WRITEOBJECT); 345 addMethodIfExist((ClassSymbol) te, READRESOLVE); 346 addMethodIfExist((ClassSymbol) te, WRITEREPLACE); 347 addMethodIfExist((ClassSymbol) te, READOBJECTNODATA); 348 } 349 } 350 351 private VarSymbol getDefinedSerializableFields(ClassSymbol def) { 352 Names names = def.name.table.names; 353 354 /* 355 * SERIALIZABLE_FIELDS can be private, 356 */ 357 for (Symbol sym : def.members() 358 .getSymbolsByName(names.fromString(SERIALIZABLE_FIELDS))) { 359 if (sym.kind == VAR) { 360 VarSymbol f = (VarSymbol) sym; 361 if ((f.flags() & Flags.STATIC) != 0 362 && (f.flags() & Flags.PRIVATE) != 0) { 363 return f; 364 } 365 } 366 } 367 return null; 368 } 369 370 /* 371 * Catalog Serializable method if it exists in current ClassSymbol. 372 * Do not look for method in superclasses. 373 * 374 * Serialization requires these methods to be non-static. 375 * 376 * @param method should be an unqualified Serializable method 377 * name either READOBJECT, WRITEOBJECT, READRESOLVE 378 * or WRITEREPLACE. 379 * 380 * @param visibility the visibility flag for the given method. 381 */ 382 private void addMethodIfExist(ClassSymbol def, String methodName) { 383 Names names = def.name.table.names; 384 385 for (Symbol sym : def.members() 386 .getSymbolsByName(names.fromString(methodName))) { 387 if (sym.kind == MTH) { 388 MethodSymbol md = (MethodSymbol) sym; 389 if ((md.flags() & Flags.STATIC) == 0) { 390 /* 391 * WARNING: not robust if unqualifiedMethodName is 392 * overloaded 393 * method. Signature checking could make more robust. 394 * READOBJECT takes a single parameter, 395 * java.io.ObjectInputStream. 396 * WRITEOBJECT takes a single parameter, 397 * java.io.ObjectOutputStream. 398 */ 399 methods.add(md); 400 } 401 } 402 } 403 } 404 405 /* 406 * Compute default Serializable fields from all members of ClassSymbol. 407 * 408 * must walk over all members of ClassSymbol. 409 */ 410 private void computeDefaultSerializableFields(ClassSymbol te) { 411 for (Symbol sym : te.members().getSymbols(NON_RECURSIVE)) { 412 if (sym != null && sym.kind == VAR) { 413 VarSymbol f = (VarSymbol) sym; 414 if ((f.flags() & Flags.STATIC) == 0 415 && (f.flags() & Flags.TRANSIENT) == 0) { 416 // ### No modifier filtering applied here. 417 // ### Add to beginning. 418 // ### Preserve order used by old 'javadoc'. 419 fields.add(f); 420 } 421 } 422 } 423 } 424 425 /** 426 * Find a method in this class scope. Search order: this class, interfaces, superclasses, 427 * outerclasses. Note that this is not necessarily what the compiler would do! 428 * 429 * @param methodName the unqualified name to search for. 430 * @param paramTypes the array of Strings for method parameter types. 431 * @return the first MethodDocImpl which matches, null if not found. 432 */ 433 public ExecutableElement findMethod(TypeElement te, String methodName, 434 List<String> paramTypes) { 435 List<? extends Element> allMembers 436 = this.elements.getAllMembers(te); 437 loop: for (Element e : allMembers) { 438 if (e.getKind() != METHOD) { 439 continue; 440 } 441 ExecutableElement ee = (ExecutableElement) e; 442 if (!ee.getSimpleName().contentEquals(methodName)) { 443 continue; 444 } 445 List<? extends VariableElement> parameters = ee.getParameters(); 446 if (paramTypes.size() != parameters.size()) { 447 continue; 448 } 449 for (int i = 0; i < parameters.size(); i++) { 450 VariableElement ve = parameters.get(i); 451 if (!ve.asType().toString().equals(paramTypes.get(i))) { 452 break loop; 453 } 454 } 455 return ee; 456 } 457 TypeElement encl = utils.getEnclosingTypeElement(te); 458 if (encl == null) { 459 return null; 460 } 461 return findMethod(encl, methodName, paramTypes); 462 } 463 } 464 465 public boolean isPreviewAPI(Element el) { 466 Symbol sym = (Symbol) el; 467 return (sym.flags() & Flags.PREVIEW_API) != 0; 468 } 469 470 public boolean isReflectivePreviewAPI(Element el) { 471 Symbol sym = (Symbol) el; 472 return (sym.flags() & Flags.PREVIEW_REFLECTIVE) != 0; 473 } 474 475 /** 476 * Returns whether or not to permit dynamically loaded components to access 477 * part of the javadoc internal API. The flag is the same (hidden) compiler 478 * option that allows javac plugins and annotation processors to access 479 * javac internal API. 480 * 481 * As with all workarounds, it is better to consider updating the public API, 482 * rather than relying on undocumented features like this, that may be withdrawn 483 * at any time, without notice. 484 * 485 * @return true if access is permitted to internal API 486 */ 487 public boolean accessInternalAPI() { 488 Options compilerOptions = Options.instance(toolEnv.context); 489 return compilerOptions.isSet("accessInternalAPI"); 490 } 491 492 /** 493 * Returns a map containing {@code jdk.internal.javac.PreviewFeature.JEP} element values associated with the 494 * {@code jdk.internal.javac.PreviewFeature.Feature} enum constant identified by {@code feature}. 495 * 496 * This method uses internal javac features (although only reflectively). 497 * 498 * @param feature the name of the PreviewFeature.Feature enum value 499 * @return the map of PreviewFeature.JEP annotation element values, or an empty map 500 */ 501 public Map<? extends ExecutableElement, ? extends AnnotationValue> 502 getJepInfo(String feature) { 503 TypeElement featureType = elementUtils 504 .getTypeElement("jdk.internal.javac.PreviewFeature.Feature"); 505 TypeElement jepType = elementUtils 506 .getTypeElement("jdk.internal.javac.PreviewFeature.JEP"); 507 var featureVar = featureType.getEnclosedElements().stream() 508 .filter(e -> feature.equals(e.getSimpleName().toString())) 509 .findFirst(); 510 if (featureVar.isPresent()) { 511 for (AnnotationMirror anno : featureVar.get() 512 .getAnnotationMirrors()) { 513 if (anno.getAnnotationType().asElement().equals(jepType)) { 514 return anno.getElementValues(); 515 } 516 } 517 } 518 return Map.of(); 519 } 520 521}