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