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