001/*
002 * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package org.jdrupes.mdoclet.internal.doclets.toolkit;
027
028import java.util.Arrays;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.SortedSet;
033import java.util.TreeSet;
034
035import javax.lang.model.element.AnnotationMirror;
036import javax.lang.model.element.AnnotationValue;
037import javax.lang.model.element.Element;
038import javax.lang.model.element.ExecutableElement;
039import javax.lang.model.element.ModuleElement;
040import javax.lang.model.element.PackageElement;
041import javax.lang.model.element.TypeElement;
042import javax.lang.model.element.VariableElement;
043import javax.lang.model.type.DeclaredType;
044import javax.lang.model.type.TypeMirror;
045import javax.lang.model.util.Elements;
046import javax.lang.model.util.Types;
047import javax.tools.FileObject;
048import javax.tools.JavaFileManager.Location;
049
050import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils;
051import jdk.javadoc.internal.tool.DocEnvImpl;
052import jdk.javadoc.internal.tool.ToolEnvironment;
053
054import com.sun.source.util.TreePath;
055import com.sun.tools.javac.code.Flags;
056import com.sun.tools.javac.code.Scope;
057import com.sun.tools.javac.code.Symbol;
058import com.sun.tools.javac.code.Symbol.ClassSymbol;
059import com.sun.tools.javac.code.Symbol.MethodSymbol;
060import com.sun.tools.javac.code.Symbol.ModuleSymbol;
061import com.sun.tools.javac.code.Symbol.PackageSymbol;
062import com.sun.tools.javac.code.Symbol.VarSymbol;
063import com.sun.tools.javac.code.Type;
064import com.sun.tools.javac.code.TypeTag;
065import com.sun.tools.javac.comp.AttrContext;
066import com.sun.tools.javac.comp.Env;
067import com.sun.tools.javac.model.JavacElements;
068import com.sun.tools.javac.util.Names;
069import com.sun.tools.javac.util.Options;
070
071import static com.sun.tools.javac.code.Kinds.Kind.*;
072import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
073
074import static javax.lang.model.element.ElementKind.*;
075
076/**
077 * A quarantine class to isolate all the workarounds and bridges to
078 * a locality. This class should eventually disappear once all the
079 * standard APIs support the needed interfaces.
080 */
081public class WorkArounds {
082
083    public final BaseConfiguration configuration;
084    public final ToolEnvironment toolEnv;
085    public final Utils utils;
086    public final Elements elementUtils;
087    public final Types typeUtils;
088    public final com.sun.tools.javac.code.Types javacTypes;
089
090    public WorkArounds(BaseConfiguration configuration) {
091        this.configuration = configuration;
092        this.utils = this.configuration.utils;
093
094        elementUtils = configuration.docEnv.getElementUtils();
095        typeUtils = configuration.docEnv.getTypeUtils();
096
097        // Note: this one use of DocEnvImpl is what prevents us tunnelling extra
098        // info from a doclet to its taglets via a doclet-specific subtype of
099        // DocletEnvironment.
100        toolEnv = ((DocEnvImpl) this.configuration.docEnv).toolEnv;
101        javacTypes = toolEnv.getTypes();
102    }
103
104    /*
105     * TODO: This method exists because of a bug in javac which does not
106     * handle "@deprecated tag in package-info.java", when this issue
107     * is fixed this method and its uses must be jettisoned.
108     */
109    public boolean isDeprecated0(Element e) {
110        if (!utils.getDeprecatedTrees(e).isEmpty()) {
111            return true;
112        }
113        TypeMirror deprecatedType = utils.getDeprecatedType();
114        for (AnnotationMirror anno : e.getAnnotationMirrors()) {
115            if (typeUtils.isSameType(
116                anno.getAnnotationType().asElement().asType(), deprecatedType))
117                return true;
118        }
119        return false;
120    }
121
122    public boolean isMandated(AnnotationMirror aDesc) {
123        return elementUtils.getOrigin(null, aDesc) == Elements.Origin.MANDATED;
124    }
125
126    // TODO: DocTrees: Trees.getPath(Element e) is slow a factor 4-5 times.
127    public Map<Element, TreePath> getElementToTreePath() {
128        return toolEnv.elementToTreePath;
129    }
130
131    // TODO: implement in either jx.l.m API (preferred) or DocletEnvironment.
132    FileObject getJavaFileObject(PackageElement packageElement) {
133        return ((PackageSymbol) packageElement).sourcefile;
134    }
135
136    // TODO: needs to ported to jx.l.m.
137    public TypeElement searchClass(TypeElement klass, String className) {
138        TypeElement te;
139
140        // search by qualified name in current module first
141        ModuleElement me = utils.containingModule(klass);
142        if (me != null) {
143            te = elementUtils.getTypeElement(me, className);
144            if (te != null) {
145                return te;
146            }
147        }
148
149        // search inner classes
150        for (TypeElement ite : utils.getClasses(klass)) {
151            TypeElement innerClass = searchClass(ite, className);
152            if (innerClass != null) {
153                return innerClass;
154            }
155        }
156
157        // check in this package
158        te = utils.findClassInPackageElement(utils.containingPackage(klass),
159            className);
160        if (te != null) {
161            return te;
162        }
163
164        ClassSymbol tsym = (ClassSymbol) klass;
165        // make sure that this symbol has been completed
166        // TODO: do we need this anymore ?
167        if (tsym.completer != null) {
168            tsym.complete();
169        }
170
171        // search imports
172        if (tsym.sourcefile != null) {
173
174            // ### This information is available only for source classes.
175            Env<AttrContext> compenv = toolEnv.getEnv(tsym);
176            if (compenv == null) {
177                return null;
178            }
179            Names names = tsym.name.table.names;
180            Scope s = compenv.toplevel.namedImportScope;
181            for (Symbol sym : s.getSymbolsByName(names.fromString(className))) {
182                if (sym.kind == TYP) {
183                    return (TypeElement) sym;
184                }
185            }
186
187            s = compenv.toplevel.starImportScope;
188            for (Symbol sym : s.getSymbolsByName(names.fromString(className))) {
189                if (sym.kind == TYP) {
190                    return (TypeElement) sym;
191                }
192            }
193        }
194
195        // finally, search by qualified name in all modules
196        return elementUtils.getTypeElement(className);
197    }
198
199    // TODO: jx.l.m ?
200    public Location getLocationForModule(ModuleElement mdle) {
201        ModuleSymbol msym = (ModuleSymbol) mdle;
202        return msym.sourceLocation != null
203            ? msym.sourceLocation
204            : msym.classLocation;
205    }
206
207    // ------------------Start of Serializable
208    // Implementation---------------------//
209    private final Map<TypeElement, NewSerializedForm> serializedForms
210        = new HashMap<>();
211
212    private NewSerializedForm getSerializedForm(TypeElement typeElem) {
213        return serializedForms.computeIfAbsent(typeElem,
214            te -> new NewSerializedForm(utils,
215                configuration.docEnv.getElementUtils(), te));
216    }
217
218    public SortedSet<VariableElement>
219            getSerializableFields(TypeElement typeElem) {
220        return getSerializedForm(typeElem).fields;
221    }
222
223    public SortedSet<ExecutableElement>
224            getSerializationMethods(TypeElement typeElem) {
225        return getSerializedForm(typeElem).methods;
226    }
227
228    public boolean definesSerializableFields(TypeElement typeElem) {
229        if (!utils.isSerializable(typeElem)
230            || utils.isExternalizable(typeElem)) {
231            return false;
232        } else {
233            return getSerializedForm(typeElem).definesSerializableFields;
234        }
235    }
236
237    /*
238     * TODO we need a clean port to jx.l.m
239     * The serialized form is the specification of a class' serialization state.
240     * <p>
241     *
242     * It consists of the following information:
243     * <p>
244     *
245     * <pre>
246     * 1. Whether class is Serializable or Externalizable.
247     * 2. Javadoc for serialization methods.
248     * a. For Serializable, the optional readObject, writeObject,
249     * readResolve and writeReplace.
250     * serialData tag describes, in prose, the sequence and type
251     * of optional data written by writeObject.
252     * b. For Externalizable, writeExternal and readExternal.
253     * serialData tag describes, in prose, the sequence and type
254     * of optional data written by writeExternal.
255     * 3. Javadoc for serialization data layout.
256     * a. For Serializable, the name,type and description
257     * of each Serializable fields.
258     * b. For Externalizable, data layout is described by 2(b).
259     * </pre>
260     *
261     */
262    static class NewSerializedForm {
263
264        final Utils utils;
265        final Elements elements;
266
267        final SortedSet<ExecutableElement> methods;
268
269        /*
270         * List of FieldDocImpl - Serializable fields.
271         * Singleton list if class defines Serializable fields explicitly.
272         * Otherwise, list of default serializable fields.
273         * 0 length list for Externalizable.
274         */
275        final SortedSet<VariableElement> fields;
276
277        /*
278         * True if class specifies serializable fields explicitly.
279         * using special static member, serialPersistentFields.
280         */
281        boolean definesSerializableFields = false;
282
283        // Specially treated field/method names defined by Serialization.
284        private static final String SERIALIZABLE_FIELDS
285            = "serialPersistentFields";
286        private static final String READOBJECT = "readObject";
287        private static final String WRITEOBJECT = "writeObject";
288        private static final String READRESOLVE = "readResolve";
289        private static final String WRITEREPLACE = "writeReplace";
290        private static final String READOBJECTNODATA = "readObjectNoData";
291
292        NewSerializedForm(Utils utils, Elements elements, TypeElement te) {
293            this.utils = utils;
294            this.elements = elements;
295            methods = new TreeSet<>(
296                utils.comparators.makeGeneralPurposeComparator());
297            fields = new TreeSet<>(
298                utils.comparators.makeGeneralPurposeComparator());
299            if (utils.isExternalizable(te)) {
300                /*
301                 * look up required public accessible methods,
302                 * writeExternal and readExternal.
303                 */
304                String[] readExternalParamArr = { "java.io.ObjectInput" };
305                String[] writeExternalParamArr = { "java.io.ObjectOutput" };
306
307                ExecutableElement md = findMethod(te, "readExternal",
308                    Arrays.asList(readExternalParamArr));
309                if (md != null) {
310                    methods.add(md);
311                }
312                md = findMethod(te, "writeExternal",
313                    Arrays.asList(writeExternalParamArr));
314                if (md != null) {
315                    methods.add(md);
316                }
317            } else if (utils.isSerializable(te)) {
318                VarSymbol dsf = getDefinedSerializableFields((ClassSymbol) te);
319                if (dsf != null) {
320                    /*
321                     * Define serializable fields with array of
322                     * ObjectStreamField.
323                     * Each ObjectStreamField should be documented by a
324                     * serialField tag.
325                     */
326                    definesSerializableFields = true;
327                    fields.add(dsf);
328                } else {
329
330                    /*
331                     * Calculate default Serializable fields as all
332                     * non-transient, non-static fields.
333                     * Fields should be documented by serial tag.
334                     */
335                    computeDefaultSerializableFields((ClassSymbol) te);
336                }
337
338                /*
339                 * Check for optional customized readObject, writeObject,
340                 * readResolve and writeReplace, which can all contain
341                 * the serialData tag.
342                 */
343                addMethodIfExist((ClassSymbol) te, READOBJECT);
344                addMethodIfExist((ClassSymbol) te, WRITEOBJECT);
345                addMethodIfExist((ClassSymbol) te, READRESOLVE);
346                addMethodIfExist((ClassSymbol) te, WRITEREPLACE);
347                addMethodIfExist((ClassSymbol) te, READOBJECTNODATA);
348            }
349        }
350
351        private VarSymbol getDefinedSerializableFields(ClassSymbol def) {
352            Names names = def.name.table.names;
353
354            /*
355             * SERIALIZABLE_FIELDS can be private,
356             */
357            for (Symbol sym : def.members()
358                .getSymbolsByName(names.fromString(SERIALIZABLE_FIELDS))) {
359                if (sym.kind == VAR) {
360                    VarSymbol f = (VarSymbol) sym;
361                    if ((f.flags() & Flags.STATIC) != 0
362                        && (f.flags() & Flags.PRIVATE) != 0) {
363                        return f;
364                    }
365                }
366            }
367            return null;
368        }
369
370        /*
371         * Catalog Serializable method if it exists in current ClassSymbol.
372         * Do not look for method in superclasses.
373         *
374         * Serialization requires these methods to be non-static.
375         *
376         * @param method should be an unqualified Serializable method
377         * name either READOBJECT, WRITEOBJECT, READRESOLVE
378         * or WRITEREPLACE.
379         * 
380         * @param visibility the visibility flag for the given method.
381         */
382        private void addMethodIfExist(ClassSymbol def, String methodName) {
383            Names names = def.name.table.names;
384
385            for (Symbol sym : def.members()
386                .getSymbolsByName(names.fromString(methodName))) {
387                if (sym.kind == MTH) {
388                    MethodSymbol md = (MethodSymbol) sym;
389                    if ((md.flags() & Flags.STATIC) == 0) {
390                        /*
391                         * WARNING: not robust if unqualifiedMethodName is
392                         * overloaded
393                         * method. Signature checking could make more robust.
394                         * READOBJECT takes a single parameter,
395                         * java.io.ObjectInputStream.
396                         * WRITEOBJECT takes a single parameter,
397                         * java.io.ObjectOutputStream.
398                         */
399                        methods.add(md);
400                    }
401                }
402            }
403        }
404
405        /*
406         * Compute default Serializable fields from all members of ClassSymbol.
407         *
408         * must walk over all members of ClassSymbol.
409         */
410        private void computeDefaultSerializableFields(ClassSymbol te) {
411            for (Symbol sym : te.members().getSymbols(NON_RECURSIVE)) {
412                if (sym != null && sym.kind == VAR) {
413                    VarSymbol f = (VarSymbol) sym;
414                    if ((f.flags() & Flags.STATIC) == 0
415                        && (f.flags() & Flags.TRANSIENT) == 0) {
416                        // ### No modifier filtering applied here.
417                        // ### Add to beginning.
418                        // ### Preserve order used by old 'javadoc'.
419                        fields.add(f);
420                    }
421                }
422            }
423        }
424
425        /**
426         * Find a method in this class scope. Search order: this class, interfaces, superclasses,
427         * outerclasses. Note that this is not necessarily what the compiler would do!
428         *
429         * @param methodName the unqualified name to search for.
430         * @param paramTypes the array of Strings for method parameter types.
431         * @return the first MethodDocImpl which matches, null if not found.
432         */
433        public ExecutableElement findMethod(TypeElement te, String methodName,
434                List<String> paramTypes) {
435            List<? extends Element> allMembers
436                = this.elements.getAllMembers(te);
437            loop: for (Element e : allMembers) {
438                if (e.getKind() != METHOD) {
439                    continue;
440                }
441                ExecutableElement ee = (ExecutableElement) e;
442                if (!ee.getSimpleName().contentEquals(methodName)) {
443                    continue;
444                }
445                List<? extends VariableElement> parameters = ee.getParameters();
446                if (paramTypes.size() != parameters.size()) {
447                    continue;
448                }
449                for (int i = 0; i < parameters.size(); i++) {
450                    VariableElement ve = parameters.get(i);
451                    if (!ve.asType().toString().equals(paramTypes.get(i))) {
452                        break loop;
453                    }
454                }
455                return ee;
456            }
457            TypeElement encl = utils.getEnclosingTypeElement(te);
458            if (encl == null) {
459                return null;
460            }
461            return findMethod(encl, methodName, paramTypes);
462        }
463    }
464
465    public boolean isPreviewAPI(Element el) {
466        Symbol sym = (Symbol) el;
467        return (sym.flags() & Flags.PREVIEW_API) != 0;
468    }
469
470    public boolean isReflectivePreviewAPI(Element el) {
471        Symbol sym = (Symbol) el;
472        return (sym.flags() & Flags.PREVIEW_REFLECTIVE) != 0;
473    }
474
475    /**
476     * Returns whether or not to permit dynamically loaded components to access
477     * part of the javadoc internal API. The flag is the same (hidden) compiler
478     * option that allows javac plugins and annotation processors to access
479     * javac internal API.
480     *
481     * As with all workarounds, it is better to consider updating the public API,
482     * rather than relying on undocumented features like this, that may be withdrawn
483     * at any time, without notice.
484     *
485     * @return true if access is permitted to internal API
486     */
487    public boolean accessInternalAPI() {
488        Options compilerOptions = Options.instance(toolEnv.context);
489        return compilerOptions.isSet("accessInternalAPI");
490    }
491
492    /**
493     * Returns a map containing {@code jdk.internal.javac.PreviewFeature.JEP} element values associated with the
494     * {@code jdk.internal.javac.PreviewFeature.Feature} enum constant identified by {@code feature}.
495     *
496     * This method uses internal javac features (although only reflectively).
497     *
498     * @param feature the name of the PreviewFeature.Feature enum value
499     * @return the map of PreviewFeature.JEP annotation element values, or an empty map
500     */
501    public Map<? extends ExecutableElement, ? extends AnnotationValue>
502            getJepInfo(String feature) {
503        TypeElement featureType = elementUtils
504            .getTypeElement("jdk.internal.javac.PreviewFeature.Feature");
505        TypeElement jepType = elementUtils
506            .getTypeElement("jdk.internal.javac.PreviewFeature.JEP");
507        var featureVar = featureType.getEnclosedElements().stream()
508            .filter(e -> feature.equals(e.getSimpleName().toString()))
509            .findFirst();
510        if (featureVar.isPresent()) {
511            for (AnnotationMirror anno : featureVar.get()
512                .getAnnotationMirrors()) {
513                if (anno.getAnnotationType().asElement().equals(jepType)) {
514                    return anno.getElementValues();
515                }
516            }
517        }
518        return Map.of();
519    }
520
521}