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