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