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 
  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
 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
 115     public void initDocLint(Collection<String> opts, Collection<String> customTagNames) {
 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 + "html5");
 141 
 142         JavacTask t = BasicJavacTask.instance(toolEnv.context);
 143         doclint = new DocLint();
 144         doclint.init(t, doclintOpts.toArray(new String[doclintOpts.size()]), false);
 145     }
 146 
 147     // TODO: fix this up correctly
 148     public boolean haveDocLint() {
 149         return (doclint == null);
 150     }
 151 
 152     /*
 153      * TODO: This method exists because of a bug in javac which does not
 154      * handle "@deprecated tag in package-info.java", when this issue
 155      * is fixed this method and its uses must be jettisoned.
 156      */
 157     public boolean isDeprecated0(Element e) {
 158         if (!utils.getDeprecatedTrees(e).isEmpty()) {
 159             return true;
 160         }
 161         JavacTypes jctypes = ((DocEnvImpl)configuration.docEnv).toolEnv.typeutils;
 162         TypeMirror deprecatedType = utils.getDeprecatedType();
 163         for (AnnotationMirror anno : e.getAnnotationMirrors()) {
 164             if (jctypes.isSameType(anno.getAnnotationType().asElement().asType(), deprecatedType))
 165                 return true;
 166         }
 167         return false;
 168     }
 169 
 170     // TODO: fix jx.l.m add this method.
 171     public boolean isSynthesized(AnnotationMirror aDesc) {
 172         return ((Attribute)aDesc).isSynthesized();
 173     }
 174 
 175     // TODO: fix the caller
 176     public Object getConstValue(VariableElement ve) {
 177         return ((VarSymbol)ve).getConstValue();
 178     }
 179 
 180     // TODO: DocTrees: Trees.getPath(Element e) is slow a factor 4-5 times.
 181     public Map<Element, TreePath> getElementToTreePath() {
 182         return toolEnv.elementToTreePath;
 183     }
 184 
 185     // TODO: we need ElementUtils.getPackage to cope with input strings
 186     // to return the proper unnamedPackage for all supported releases.
 187     PackageElement getUnnamedPackage() {
 188         return (Feature.MODULES.allowedInSource(toolEnv.source))
 189                 ? toolEnv.syms.unnamedModule.unnamedPackage
 190                 : toolEnv.syms.noModule.unnamedPackage;
 191     }
 192 
 193     // TODO: implement in either jx.l.m API (preferred) or DocletEnvironment.
 194     FileObject getJavaFileObject(PackageElement packageElement) {
 195         return ((PackageSymbol)packageElement).sourcefile;
 196     }
 197 
 198     // TODO: needs to ported to jx.l.m.
 199     public TypeElement searchClass(TypeElement klass, String className) {
 200         TypeElement te;
 201 
 202         // search by qualified name in current module first
 203         ModuleElement me = utils.containingModule(klass);
 204         if (me != null) {
 205             te = configuration.docEnv.getElementUtils().getTypeElement(me, className);
 206             if (te != null) {
 207                 return te;
 208             }
 209         }
 210 
 211         // search inner classes
 212         for (TypeElement ite : utils.getClasses(klass)) {
 213             TypeElement innerClass = searchClass(ite, className);
 214             if (innerClass != null) {
 215                 return innerClass;
 216             }
 217         }
 218 
 219         // check in this package
 220         te = utils.findClassInPackageElement(utils.containingPackage(klass), className);
 221         if (te != null) {
 222             return te;
 223         }
 224 
 225         ClassSymbol tsym = (ClassSymbol)klass;
 226         // make sure that this symbol has been completed
 227         // TODO: do we need this anymore ?
 228         if (tsym.completer != null) {
 229             tsym.complete();
 230         }
 231 
 232         // search imports
 233         if (tsym.sourcefile != null) {
 234 
 235             //### This information is available only for source classes.
 236             Env<AttrContext> compenv = toolEnv.getEnv(tsym);
 237             if (compenv == null) {
 238                 return null;
 239             }
 240             Names names = tsym.name.table.names;
 241             Scope s = compenv.toplevel.namedImportScope;
 242             for (Symbol sym : s.getSymbolsByName(names.fromString(className))) {
 243                 if (sym.kind == TYP) {
 244                     return (TypeElement)sym;
 245                 }
 246             }
 247 
 248             s = compenv.toplevel.starImportScope;
 249             for (Symbol sym : s.getSymbolsByName(names.fromString(className))) {
 250                 if (sym.kind == TYP) {
 251                     return (TypeElement)sym;
 252                 }
 253             }
 254         }
 255 
 256         // finally, search by qualified name in all modules
 257         te = configuration.docEnv.getElementUtils().getTypeElement(className);
 258         if (te != null) {
 259             return te;
 260         }
 261 
 262         return null; // not found
 263     }
 264 
 265     // TODO:  need to re-implement this using j.l.m. correctly!, this has
 266     // implications on testInterface, the note here is that javac's supertype
 267     // does the right thing returning Parameters in scope.
 268     /**
 269      * Return the type containing the method that this method overrides.
 270      * It may be a <code>TypeElement</code> or a <code>TypeParameterElement</code>.
 271      * @param method target
 272      * @return a type
 273      */
 274     public TypeMirror overriddenType(ExecutableElement method) {
 275         if (utils.isStatic(method)) {
 276             return null;
 277         }
 278         MethodSymbol sym = (MethodSymbol)method;
 279         ClassSymbol origin = (ClassSymbol) sym.owner;
 280         for (com.sun.tools.javac.code.Type t = toolEnv.getTypes().supertype(origin.type);
 281                 t.hasTag(TypeTag.CLASS);
 282                 t = toolEnv.getTypes().supertype(t)) {
 283             ClassSymbol c = (ClassSymbol) t.tsym;
 284             for (com.sun.tools.javac.code.Symbol sym2 : c.members().getSymbolsByName(sym.name)) {
 285                 if (sym.overrides(sym2, origin, toolEnv.getTypes(), true)) {
 286                     // Ignore those methods that may be a simple override
 287                     // and allow the real API method to be found.
 288                     if (sym2.type.hasTag(TypeTag.METHOD) &&
 289                             utils.isSimpleOverride((MethodSymbol)sym2)) {
 290                         continue;
 291                     }
 292                     return t;
 293                 }
 294             }
 295         }
 296         return null;
 297     }
 298 
 299     // TODO: the method jx.l.m.Elements::overrides does not check
 300     // the return type, see JDK-8174840 until that is resolved,
 301     // use a  copy of the same method, with a return type check.
 302 
 303     // Note: the rider.overrides call in this method *must* be consistent
 304     // with the call in overrideType(....), the method above.
 305     public boolean overrides(ExecutableElement e1, ExecutableElement e2, TypeElement cls) {
 306         MethodSymbol rider = (MethodSymbol)e1;
 307         MethodSymbol ridee = (MethodSymbol)e2;
 308         ClassSymbol origin = (ClassSymbol)cls;
 309 
 310         return rider.name == ridee.name &&
 311 
 312                // not reflexive as per JLS
 313                rider != ridee &&
 314 
 315                // we don't care if ridee is static, though that wouldn't
 316                // compile
 317                !rider.isStatic() &&
 318 
 319                // Symbol.overrides assumes the following
 320                ridee.isMemberOf(origin, toolEnv.getTypes()) &&
 321 
 322                // check access, signatures and check return types
 323                rider.overrides(ridee, origin, toolEnv.getTypes(), true);
 324     }
 325 
 326     // TODO: jx.l.m ?
 327     public Location getLocationForModule(ModuleElement mdle) {
 328         ModuleSymbol msym = (ModuleSymbol)mdle;
 329         return msym.sourceLocation != null
 330                 ? msym.sourceLocation
 331                 : msym.classLocation;
 332     }
 333 
 334     //------------------Start of Serializable Implementation---------------------//
 335     private final static Map<TypeElement, NewSerializedForm> serializedForms = new HashMap<>();
 336 
 337     public SortedSet<VariableElement> getSerializableFields(Utils utils, TypeElement klass) {
 338         NewSerializedForm sf = serializedForms.get(klass);
 339         if (sf == null) {
 340             sf = new NewSerializedForm(utils, configuration.docEnv.getElementUtils(), klass);
 341             serializedForms.put(klass, sf);
 342         }
 343         return sf.fields;
 344     }
 345 
 346     public SortedSet<ExecutableElement>  getSerializationMethods(Utils utils, TypeElement klass) {
 347         NewSerializedForm sf = serializedForms.get(klass);
 348         if (sf == null) {
 349             sf = new NewSerializedForm(utils, configuration.docEnv.getElementUtils(), klass);
 350             serializedForms.put(klass, sf);
 351         }
 352         return sf.methods;
 353     }
 354 
 355     public boolean definesSerializableFields(Utils utils, TypeElement klass) {
 356         if (!utils.isSerializable(klass) || utils.isExternalizable(klass)) {
 357             return false;
 358         } else {
 359             NewSerializedForm sf = serializedForms.get(klass);
 360             if (sf == null) {
 361                 sf = new NewSerializedForm(utils, configuration.docEnv.getElementUtils(), klass);
 362                 serializedForms.put(klass, sf);
 363             }
 364             return sf.definesSerializableFields;
 365         }
 366     }
 367 
 368     /* TODO we need a clean port to jx.l.m
 369      * The serialized form is the specification of a class' serialization state.
 370      * <p>
 371      *
 372      * It consists of the following information:
 373      * <p>
 374      *
 375      * <pre>
 376      * 1. Whether class is Serializable or Externalizable.
 377      * 2. Javadoc for serialization methods.
 378      *    a. For Serializable, the optional readObject, writeObject,
 379      *       readResolve and writeReplace.
 380      *       serialData tag describes, in prose, the sequence and type
 381      *       of optional data written by writeObject.
 382      *    b. For Externalizable, writeExternal and readExternal.
 383      *       serialData tag describes, in prose, the sequence and type
 384      *       of optional data written by writeExternal.
 385      * 3. Javadoc for serialization data layout.
 386      *    a. For Serializable, the name,type and description
 387      *       of each Serializable fields.
 388      *    b. For Externalizable, data layout is described by 2(b).
 389      * </pre>
 390      *
 391      */
 392     static class NewSerializedForm {
 393 
 394         final Utils utils;
 395         final Elements elements;
 396 
 397         final SortedSet<ExecutableElement> methods;
 398 
 399         /* List of FieldDocImpl - Serializable fields.
 400          * Singleton list if class defines Serializable fields explicitly.
 401          * Otherwise, list of default serializable fields.
 402          * 0 length list for Externalizable.
 403          */
 404         final SortedSet<VariableElement> fields;
 405 
 406         /* True if class specifies serializable fields explicitly.
 407          * using special static member, serialPersistentFields.
 408          */
 409         boolean definesSerializableFields = false;
 410 
 411         // Specially treated field/method names defined by Serialization.
 412         private static final String SERIALIZABLE_FIELDS = "serialPersistentFields";
 413         private static final String READOBJECT = "readObject";
 414         private static final String WRITEOBJECT = "writeObject";
 415         private static final String READRESOLVE = "readResolve";
 416         private static final String WRITEREPLACE = "writeReplace";
 417         private static final String READOBJECTNODATA = "readObjectNoData";
 418 
 419         NewSerializedForm(Utils utils, Elements elements, TypeElement te) {
 420             this.utils = utils;
 421             this.elements = elements;
 422             methods = new TreeSet<>(utils.makeGeneralPurposeComparator());
 423             fields = new TreeSet<>(utils.makeGeneralPurposeComparator());
 424             if (utils.isExternalizable(te)) {
 425                 /* look up required public accessible methods,
 426                  *   writeExternal and readExternal.
 427                  */
 428                 String[] readExternalParamArr = {"java.io.ObjectInput"};
 429                 String[] writeExternalParamArr = {"java.io.ObjectOutput"};
 430 
 431                 ExecutableElement md = findMethod(te, "readExternal", Arrays.asList(readExternalParamArr));
 432                 if (md != null) {
 433                     methods.add(md);
 434                 }
 435                 md = findMethod((ClassSymbol) te, "writeExternal", Arrays.asList(writeExternalParamArr));
 436                 if (md != null) {
 437                     methods.add(md);
 438                 }
 439             } else if (utils.isSerializable(te)) {
 440                 VarSymbol dsf = getDefinedSerializableFields((ClassSymbol) te);
 441                 if (dsf != null) {
 442                     /* Define serializable fields with array of ObjectStreamField.
 443                      * Each ObjectStreamField should be documented by a
 444                      * serialField tag.
 445                      */
 446                     definesSerializableFields = true;
 447                     fields.add((VariableElement) dsf);
 448                 } else {
 449 
 450                     /* Calculate default Serializable fields as all
 451                      * non-transient, non-static fields.
 452                      * Fields should be documented by serial tag.
 453                      */
 454                     computeDefaultSerializableFields((ClassSymbol) te);
 455                 }
 456 
 457                 /* Check for optional customized readObject, writeObject,
 458                  * readResolve and writeReplace, which can all contain
 459                  * the serialData tag.        */
 460                 addMethodIfExist((ClassSymbol) te, READOBJECT);
 461                 addMethodIfExist((ClassSymbol) te, WRITEOBJECT);
 462                 addMethodIfExist((ClassSymbol) te, READRESOLVE);
 463                 addMethodIfExist((ClassSymbol) te, WRITEREPLACE);
 464                 addMethodIfExist((ClassSymbol) te, READOBJECTNODATA);
 465             }
 466         }
 467 
 468         private VarSymbol getDefinedSerializableFields(ClassSymbol def) {
 469             Names names = def.name.table.names;
 470 
 471             /* SERIALIZABLE_FIELDS can be private,
 472              */
 473             for (Symbol sym : def.members().getSymbolsByName(names.fromString(SERIALIZABLE_FIELDS))) {
 474                 if (sym.kind == VAR) {
 475                     VarSymbol f = (VarSymbol) sym;
 476                     if ((f.flags() & Flags.STATIC) != 0
 477                             && (f.flags() & Flags.PRIVATE) != 0) {
 478                         return f;
 479                     }
 480                 }
 481             }
 482             return null;
 483         }
 484 
 485         /*
 486          * Catalog Serializable method if it exists in current ClassSymbol.
 487          * Do not look for method in superclasses.
 488          *
 489          * Serialization requires these methods to be non-static.
 490          *
 491          * @param method should be an unqualified Serializable method
 492          *               name either READOBJECT, WRITEOBJECT, READRESOLVE
 493          *               or WRITEREPLACE.
 494          * @param visibility the visibility flag for the given method.
 495          */
 496         private void addMethodIfExist(ClassSymbol def, String methodName) {
 497             Names names = def.name.table.names;
 498 
 499             for (Symbol sym : def.members().getSymbolsByName(names.fromString(methodName))) {
 500                 if (sym.kind == MTH) {
 501                     MethodSymbol md = (MethodSymbol) sym;
 502                     if ((md.flags() & Flags.STATIC) == 0) {
 503                         /*
 504                          * WARNING: not robust if unqualifiedMethodName is overloaded
 505                          *          method. Signature checking could make more robust.
 506                          * READOBJECT takes a single parameter, java.io.ObjectInputStream.
 507                          * WRITEOBJECT takes a single parameter, java.io.ObjectOutputStream.
 508                          */
 509                         methods.add(md);
 510                     }
 511                 }
 512             }
 513         }
 514 
 515         /*
 516          * Compute default Serializable fields from all members of ClassSymbol.
 517          *
 518          * must walk over all members of ClassSymbol.
 519          */
 520         private void computeDefaultSerializableFields(ClassSymbol te) {
 521             for (Symbol sym : te.members().getSymbols(NON_RECURSIVE)) {
 522                 if (sym != null && sym.kind == VAR) {
 523                     VarSymbol f = (VarSymbol) sym;
 524                     if ((f.flags() & Flags.STATIC) == 0
 525                             && (f.flags() & Flags.TRANSIENT) == 0) {
 526                         //### No modifier filtering applied here.
 527                         //### Add to beginning.
 528                         //### Preserve order used by old 'javadoc'.
 529                         fields.add(f);
 530                     }
 531                 }
 532             }
 533         }
 534 
 535         /**
 536          * Find a method in this class scope. Search order: this class, interfaces, superclasses,
 537          * outerclasses. Note that this is not necessarily what the compiler would do!
 538          *
 539          * @param methodName the unqualified name to search for.
 540          * @param paramTypes the array of Strings for method parameter types.
 541          * @return the first MethodDocImpl which matches, null if not found.
 542          */
 543         public ExecutableElement findMethod(TypeElement te, String methodName,
 544                 List<String> paramTypes) {
 545             List<? extends Element> allMembers = this.elements.getAllMembers(te);
 546             loop:
 547             for (Element e : allMembers) {
 548                 if (e.getKind() != METHOD) {
 549                     continue;
 550                 }
 551                 ExecutableElement ee = (ExecutableElement) e;
 552                 if (!ee.getSimpleName().contentEquals(methodName)) {
 553                     continue;
 554                 }
 555                 List<? extends VariableElement> parameters = ee.getParameters();
 556                 if (paramTypes.size() != parameters.size()) {
 557                     continue;
 558                 }
 559                 for (int i = 0; i < parameters.size(); i++) {
 560                     VariableElement ve = parameters.get(i);
 561                     if (!ve.asType().toString().equals(paramTypes.get(i))) {
 562                         break loop;
 563                     }
 564                 }
 565                 return ee;
 566             }
 567             TypeElement encl = utils.getEnclosingTypeElement(te);
 568             if (encl == null) {
 569                 return null;
 570             }
 571             return findMethod(encl, methodName, paramTypes);
 572         }
 573     }
 574 
 575     // TODO: we need to eliminate this, as it is hacky.
 576     /**
 577      * Returns a representation of the package truncated to two levels.
 578      * For instance if the given package represents foo.bar.baz will return
 579      * a representation of foo.bar
 580      * @param pkg the PackageElement
 581      * @return an abbreviated PackageElement
 582      */
 583     public PackageElement getAbbreviatedPackageElement(PackageElement pkg) {
 584         String parsedPackageName = utils.parsePackageName(pkg);
 585         ModuleElement encl = (ModuleElement) pkg.getEnclosingElement();
 586         PackageElement abbrevPkg = encl == null
 587                 ? utils.elementUtils.getPackageElement(parsedPackageName)
 588                 : ((JavacElements) utils.elementUtils).getPackageElement(encl, parsedPackageName);
 589         return abbrevPkg;
 590     }
 591 }