001/*
002 * Copyright (c) 1999, 2023, 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.util;
027
028import java.lang.annotation.Documented;
029import java.lang.ref.SoftReference;
030import java.net.URI;
031import java.text.CollationKey;
032import java.text.Collator;
033import java.text.ParseException;
034import java.text.RuleBasedCollator;
035import java.util.ArrayDeque;
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.Deque;
039import java.util.EnumSet;
040import java.util.HashMap;
041import java.util.HashSet;
042import java.util.Iterator;
043import java.util.LinkedHashMap;
044import java.util.LinkedHashSet;
045import java.util.List;
046import java.util.Locale;
047import java.util.Map;
048import java.util.Map.Entry;
049import java.util.Objects;
050import java.util.Set;
051import java.util.SortedSet;
052import java.util.TreeMap;
053import java.util.TreeSet;
054import java.util.function.Predicate;
055
056import javax.lang.model.AnnotatedConstruct;
057import javax.lang.model.SourceVersion;
058import javax.lang.model.element.AnnotationMirror;
059import javax.lang.model.element.AnnotationValue;
060import javax.lang.model.element.Element;
061import javax.lang.model.element.ElementKind;
062import javax.lang.model.element.ExecutableElement;
063import javax.lang.model.element.Modifier;
064import javax.lang.model.element.ModuleElement;
065import javax.lang.model.element.ModuleElement.RequiresDirective;
066import javax.lang.model.element.PackageElement;
067import javax.lang.model.element.RecordComponentElement;
068import javax.lang.model.element.TypeElement;
069import javax.lang.model.element.TypeParameterElement;
070import javax.lang.model.element.VariableElement;
071import javax.lang.model.type.ArrayType;
072import javax.lang.model.type.DeclaredType;
073import javax.lang.model.type.ErrorType;
074import javax.lang.model.type.ExecutableType;
075import javax.lang.model.type.PrimitiveType;
076import javax.lang.model.type.TypeMirror;
077import javax.lang.model.type.TypeVariable;
078import javax.lang.model.type.WildcardType;
079import javax.lang.model.util.ElementFilter;
080import javax.lang.model.util.Elements;
081import javax.lang.model.util.SimpleAnnotationValueVisitor14;
082import javax.lang.model.util.SimpleElementVisitor14;
083import javax.lang.model.util.SimpleTypeVisitor14;
084import javax.lang.model.util.TypeKindVisitor9;
085import javax.lang.model.util.Types;
086import javax.tools.FileObject;
087import javax.tools.JavaFileManager;
088import javax.tools.JavaFileManager.Location;
089
090import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseConfiguration;
091import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseOptions;
092import org.jdrupes.mdoclet.internal.doclets.toolkit.CommentUtils;
093import org.jdrupes.mdoclet.internal.doclets.toolkit.Resources;
094import org.jdrupes.mdoclet.internal.doclets.toolkit.CommentUtils.DocCommentInfo;
095import org.jdrupes.mdoclet.internal.doclets.toolkit.taglets.BaseTaglet;
096import org.jdrupes.mdoclet.internal.doclets.toolkit.taglets.Taglet;
097
098import javax.tools.StandardLocation;
099
100import com.sun.source.doctree.BlockTagTree;
101import com.sun.source.doctree.DeprecatedTree;
102import com.sun.source.doctree.DocCommentTree;
103import com.sun.source.doctree.DocTree;
104import com.sun.source.doctree.DocTree.Kind;
105import com.sun.source.doctree.EndElementTree;
106import com.sun.source.doctree.ParamTree;
107import com.sun.source.doctree.ProvidesTree;
108import com.sun.source.doctree.ReturnTree;
109import com.sun.source.doctree.SeeTree;
110import com.sun.source.doctree.SerialDataTree;
111import com.sun.source.doctree.SerialFieldTree;
112import com.sun.source.doctree.SerialTree;
113import com.sun.source.doctree.SpecTree;
114import com.sun.source.doctree.StartElementTree;
115import com.sun.source.doctree.TextTree;
116import com.sun.source.doctree.ThrowsTree;
117import com.sun.source.doctree.UsesTree;
118import com.sun.source.tree.CompilationUnitTree;
119import com.sun.source.tree.LineMap;
120import com.sun.source.util.DocSourcePositions;
121import com.sun.source.util.DocTrees;
122import com.sun.source.util.TreePath;
123
124import static javax.lang.model.element.ElementKind.*;
125import static javax.lang.model.type.TypeKind.*;
126
127import static com.sun.source.doctree.DocTree.Kind.*;
128
129/**
130 * Utilities Class for Doclets.
131 */
132public class Utils {
133    public final BaseConfiguration configuration;
134    private final BaseOptions options;
135    private final Resources resources;
136    public final DocTrees docTrees;
137    public final Elements elementUtils;
138    public final Types typeUtils;
139    public final Comparators comparators;
140    private final JavaScriptScanner javaScriptScanner;
141    private final DocFinder docFinder = newDocFinder();
142
143    public Utils(BaseConfiguration c) {
144        configuration = c;
145        options = configuration.getOptions();
146        resources = configuration.getDocResources();
147        elementUtils = c.docEnv.getElementUtils();
148        typeUtils = c.docEnv.getTypeUtils();
149        docTrees = c.docEnv.getDocTrees();
150        javaScriptScanner
151            = c.isAllowScriptInComments() ? null : new JavaScriptScanner();
152        comparators = new Comparators(this);
153    }
154
155    // our own little symbol table
156    private final Map<String, TypeMirror> symtab = new HashMap<>();
157
158    public TypeMirror getSymbol(String signature) {
159        return symtab.computeIfAbsent(signature, s -> {
160            var typeElement = elementUtils.getTypeElement(s);
161            return typeElement == null ? null : typeElement.asType();
162        });
163    }
164
165    public TypeMirror getObjectType() {
166        return getSymbol("java.lang.Object");
167    }
168
169    public TypeMirror getThrowableType() {
170        return getSymbol("java.lang.Throwable");
171    }
172
173    public TypeMirror getSerializableType() {
174        return getSymbol("java.io.Serializable");
175    }
176
177    public TypeMirror getExternalizableType() {
178        return getSymbol("java.io.Externalizable");
179    }
180
181    public TypeMirror getDeprecatedType() {
182        return getSymbol("java.lang.Deprecated");
183    }
184
185    public TypeMirror getFunctionalInterface() {
186        return getSymbol("java.lang.FunctionalInterface");
187    }
188
189    /**
190     * According to <cite>The Java Language Specification</cite>,
191     * all the outer classes and static inner classes are core classes.
192     */
193    public boolean isCoreClass(TypeElement e) {
194        return getEnclosingTypeElement(e) == null || isStatic(e);
195    }
196
197    public Location getLocationForPackage(PackageElement pd) {
198        ModuleElement mdle
199            = configuration.docEnv.getElementUtils().getModuleOf(pd);
200
201        if (mdle == null)
202            return defaultLocation();
203
204        return getLocationForModule(mdle);
205    }
206
207    public Location getLocationForModule(ModuleElement mdle) {
208        Location loc = configuration.workArounds.getLocationForModule(mdle);
209        if (loc != null)
210            return loc;
211
212        return defaultLocation();
213    }
214
215    private Location defaultLocation() {
216        JavaFileManager fm = configuration.docEnv.getJavaFileManager();
217        return fm.hasLocation(StandardLocation.SOURCE_PATH)
218            ? StandardLocation.SOURCE_PATH
219            : StandardLocation.CLASS_PATH;
220    }
221
222    public boolean isAnnotated(TypeMirror e) {
223        return !e.getAnnotationMirrors().isEmpty();
224    }
225
226    public boolean isAnnotationInterface(Element e) {
227        return e.getKind() == ANNOTATION_TYPE;
228    }
229
230    // Note that e.getKind().isClass() is not the same as e.getKind() == CLASS
231    public boolean isClass(Element e) {
232        return e.getKind().isClass();
233    }
234
235    // Note that e.getKind().isInterface() is not the same as e.getKind() ==
236    // INTERFACE
237    // See Also: isPlainInterface(Element)
238    public boolean isInterface(Element e) {
239        return e.getKind().isInterface();
240    }
241
242    public boolean isConstructor(Element e) {
243        return e.getKind() == CONSTRUCTOR;
244    }
245
246    public boolean isEnum(Element e) {
247        return e.getKind() == ENUM;
248    }
249
250    public boolean isField(Element e) {
251        return e.getKind() == FIELD;
252    }
253
254    public boolean isPlainInterface(Element e) {
255        return e.getKind() == INTERFACE;
256    }
257
258    public boolean isMethod(Element e) {
259        return e.getKind() == METHOD;
260    }
261
262    public boolean isModule(Element e) {
263        return e.getKind() == ElementKind.MODULE;
264    }
265
266    public boolean isPackage(Element e) {
267        return e.getKind() == ElementKind.PACKAGE;
268    }
269
270    public boolean isAbstract(Element e) {
271        return e.getModifiers().contains(Modifier.ABSTRACT);
272    }
273
274    public boolean isDefault(Element e) {
275        return e.getModifiers().contains(Modifier.DEFAULT);
276    }
277
278    public boolean isFinal(Element e) {
279        return e.getModifiers().contains(Modifier.FINAL);
280    }
281
282    /*
283     * A contemporary JLS term for "package private" or "default access" is
284     * "package access". For example: "a member is declared with package
285     * access" or "a member has package access".
286     *
287     * This is to avoid confusion with unrelated _default_ methods which
288     * appeared in JDK 8.
289     */
290    public boolean isPackagePrivate(Element e) {
291        var m = e.getModifiers();
292        return !m.contains(Modifier.PUBLIC)
293            && !m.contains(Modifier.PROTECTED)
294            && !m.contains(Modifier.PRIVATE);
295    }
296
297    public boolean isPrivate(Element e) {
298        return e.getModifiers().contains(Modifier.PRIVATE);
299    }
300
301    public boolean isProtected(Element e) {
302        return e.getModifiers().contains(Modifier.PROTECTED);
303    }
304
305    public boolean isPublic(Element e) {
306        return e.getModifiers().contains(Modifier.PUBLIC);
307    }
308
309    public boolean isProperty(String name) {
310        return options.javafx() && name.endsWith("Property");
311    }
312
313    public String getPropertyName(String name) {
314        return isProperty(name)
315            ? name.substring(0, name.length() - "Property".length())
316            : name;
317    }
318
319    public String getPropertyLabel(String name) {
320        return name.substring(0, name.lastIndexOf("Property"));
321    }
322
323    public boolean isOverviewElement(Element e) {
324        return e.getKind() == ElementKind.OTHER;
325    }
326
327    public boolean isStatic(Element e) {
328        return e.getModifiers().contains(Modifier.STATIC);
329    }
330
331    public boolean isSerializable(TypeElement e) {
332        return typeUtils.isSubtype(e.asType(), getSerializableType());
333    }
334
335    public boolean isExternalizable(TypeElement e) {
336        return typeUtils.isSubtype(e.asType(), getExternalizableType());
337    }
338
339    public boolean isRecord(TypeElement e) {
340        return e.getKind() == ElementKind.RECORD;
341    }
342
343    public boolean isCanonicalRecordConstructor(ExecutableElement ee) {
344        TypeElement te = (TypeElement) ee.getEnclosingElement();
345        List<? extends RecordComponentElement> stateComps
346            = te.getRecordComponents();
347        List<? extends VariableElement> params = ee.getParameters();
348        if (stateComps.size() != params.size()) {
349            return false;
350        }
351
352        Iterator<? extends RecordComponentElement> stateIter
353            = stateComps.iterator();
354        Iterator<? extends VariableElement> paramIter = params.iterator();
355        while (paramIter.hasNext() && stateIter.hasNext()) {
356            VariableElement param = paramIter.next();
357            RecordComponentElement comp = stateIter.next();
358            if (!Objects.equals(param.getSimpleName(), comp.getSimpleName())
359                || !typeUtils.isSameType(param.asType(), comp.asType())) {
360                return false;
361            }
362        }
363
364        return true;
365    }
366
367    public SortedSet<VariableElement> serializableFields(TypeElement aclass) {
368        return configuration.workArounds.getSerializableFields(aclass);
369    }
370
371    public SortedSet<ExecutableElement>
372            serializationMethods(TypeElement aclass) {
373        return configuration.workArounds.getSerializationMethods(aclass);
374    }
375
376    public boolean definesSerializableFields(TypeElement aclass) {
377        return configuration.workArounds.definesSerializableFields(aclass);
378    }
379
380    public boolean isFunctionalInterface(AnnotationMirror amirror) {
381        return typeUtils.isSameType(amirror.getAnnotationType(),
382            getFunctionalInterface()) &&
383            configuration.docEnv.getSourceVersion()
384                .compareTo(SourceVersion.RELEASE_8) >= 0;
385    }
386
387    public boolean isUndocumentedEnclosure(TypeElement enclosingTypeElement) {
388        return (isPackagePrivate(enclosingTypeElement)
389            || isPrivate(enclosingTypeElement)
390            || hasHiddenTag(enclosingTypeElement))
391            && !isLinkable(enclosingTypeElement);
392    }
393
394    public boolean isNonThrowableClass(TypeElement te) {
395        return te.getKind() == CLASS && !isThrowable(te);
396    }
397
398    public boolean isThrowable(TypeElement te) {
399        return te.getKind() == CLASS
400            && typeUtils.isSubtype(te.asType(), getThrowableType());
401    }
402
403    public boolean isExecutableElement(Element e) {
404        return e.getKind().isExecutable();
405    }
406
407    public boolean isVariableElement(Element e) {
408        return e.getKind().isVariable();
409    }
410
411    public boolean isTypeElement(Element e) {
412        return e.getKind().isDeclaredType();
413    }
414
415    /**
416     * Get the signature of an executable element with qualified parameter types
417     * in the context of type element {@code site}.
418     * For instance, for a method {@code mymethod(String x, int y)},
419     * it will return {@code (java.lang.String,int)}.
420     *
421     * @param e the executable element
422     * @param site the contextual site
423     * @return String signature with qualified parameter types
424     */
425    public String signature(ExecutableElement e, TypeElement site) {
426        return makeSignature(e, site, true);
427    }
428
429    /**
430     * Get the flat signature of an executable element with simple (unqualified)
431     * parameter types in the context of type element {@code site}.
432     * For instance, for a method {@code mymethod(String x, int y)},
433     * it will return {@code (String, int)}.
434     *
435     * @param e the executable element
436     * @param site the contextual site
437     * @return signature with simple (unqualified) parameter types
438     */
439    public String flatSignature(ExecutableElement e, TypeElement site) {
440        return makeSignature(e, site, false);
441    }
442
443    public String makeSignature(ExecutableElement e, TypeElement site,
444            boolean full) {
445        return makeSignature(e, site, full, false);
446    }
447
448    public String makeSignature(ExecutableElement e, TypeElement site,
449            boolean full, boolean ignoreTypeParameters) {
450        StringBuilder result = new StringBuilder();
451        result.append("(");
452        ExecutableType executableType = asInstantiatedMethodType(site, e);
453        Iterator<? extends TypeMirror> iterator
454            = executableType.getParameterTypes().iterator();
455        while (iterator.hasNext()) {
456            TypeMirror type = iterator.next();
457            result.append(getTypeSignature(type, full, ignoreTypeParameters));
458            if (iterator.hasNext()) {
459                result.append(", ");
460            }
461        }
462        if (e.isVarArgs()) {
463            int len = result.length();
464            result.replace(len - 2, len, "...");
465        }
466        result.append(")");
467        return result.toString();
468    }
469
470    public String getTypeSignature(TypeMirror t, boolean qualifiedName,
471            boolean noTypeParameters) {
472        return new SimpleTypeVisitor14<StringBuilder, Void>() {
473            final StringBuilder sb = new StringBuilder();
474
475            @Override
476            public StringBuilder visitArray(ArrayType t, Void p) {
477                TypeMirror componentType = t.getComponentType();
478                visit(componentType);
479                sb.append("[]");
480                return sb;
481            }
482
483            @Override
484            public StringBuilder visitDeclared(DeclaredType t, Void p) {
485                Element e = t.asElement();
486                sb.append(qualifiedName ? getFullyQualifiedName(e)
487                    : getSimpleName(e));
488                List<? extends TypeMirror> typeArguments = t.getTypeArguments();
489                if (typeArguments.isEmpty() || noTypeParameters) {
490                    return sb;
491                }
492                sb.append("<");
493                Iterator<? extends TypeMirror> iterator
494                    = typeArguments.iterator();
495                while (iterator.hasNext()) {
496                    TypeMirror ta = iterator.next();
497                    visit(ta);
498                    if (iterator.hasNext()) {
499                        sb.append(", ");
500                    }
501                }
502                sb.append(">");
503                return sb;
504            }
505
506            @Override
507            public StringBuilder visitPrimitive(PrimitiveType t, Void p) {
508                sb.append(t.getKind().toString().toLowerCase(Locale.ROOT));
509                return sb;
510            }
511
512            @Override
513            public StringBuilder visitTypeVariable(TypeVariable t, Void p) {
514                Element e = t.asElement();
515                sb.append(qualifiedName ? getFullyQualifiedName(e, false)
516                    : getSimpleName(e));
517                return sb;
518            }
519
520            @Override
521            public StringBuilder visitWildcard(WildcardType t, Void p) {
522                sb.append("?");
523                TypeMirror upperBound = t.getExtendsBound();
524                if (upperBound != null) {
525                    sb.append(" extends ");
526                    visit(upperBound);
527                }
528                TypeMirror superBound = t.getSuperBound();
529                if (superBound != null) {
530                    sb.append(" super ");
531                    visit(superBound);
532                }
533                return sb;
534            }
535
536            @Override
537            protected StringBuilder defaultAction(TypeMirror e, Void p) {
538                return sb.append(e);
539            }
540        }.visit(t).toString();
541    }
542
543    public boolean isArrayType(TypeMirror t) {
544        return t.getKind() == ARRAY;
545    }
546
547    public boolean isDeclaredType(TypeMirror t) {
548        return t.getKind() == DECLARED;
549    }
550
551    public boolean isTypeParameterElement(Element e) {
552        return e.getKind() == TYPE_PARAMETER;
553    }
554
555    public boolean isTypeVariable(TypeMirror t) {
556        return t.getKind() == TYPEVAR;
557    }
558
559    public boolean isVoid(TypeMirror t) {
560        return t.getKind() == VOID;
561    }
562
563    public boolean ignoreBounds(TypeMirror bound) {
564        return typeUtils.isSameType(bound, getObjectType())
565            && !isAnnotated(bound);
566    }
567
568    /*
569     * a direct port of TypeVariable.getBounds
570     */
571    public List<? extends TypeMirror> getBounds(TypeParameterElement tpe) {
572        List<? extends TypeMirror> bounds = tpe.getBounds();
573        if (!bounds.isEmpty()) {
574            TypeMirror upperBound = bounds.get(bounds.size() - 1);
575            if (ignoreBounds(upperBound)) {
576                return List.of();
577            }
578        }
579        return bounds;
580    }
581
582    /**
583     * Returns the TypeMirror of the ExecutableElement if it is a method, or null
584     * if it is a constructor.
585     * @param site the contextual type
586     * @param ee the ExecutableElement
587     * @return the return type
588     */
589    public TypeMirror getReturnType(TypeElement site, ExecutableElement ee) {
590        return ee.getKind() == CONSTRUCTOR ? null
591            : asInstantiatedMethodType(site, ee).getReturnType();
592    }
593
594    /**
595     * Returns the ExecutableType corresponding to the type of the method declaration seen as a
596     * member of a given declared type. This might cause type-variable substitution to kick in.
597     * @param site the contextual type.
598     * @param ee the method declaration.
599     * @return the instantiated method type.
600     */
601    public ExecutableType asInstantiatedMethodType(TypeElement site,
602            ExecutableElement ee) {
603        return shouldInstantiate(site, ee)
604            ? (ExecutableType) typeUtils
605                .asMemberOf((DeclaredType) site.asType(), ee)
606            : (ExecutableType) ee.asType();
607    }
608
609    /**
610     * Returns the TypeMirror corresponding to the type of the field declaration seen as a
611     * member of a given declared type. This might cause type-variable substitution to kick in.
612     * @param site the contextual type.
613     * @param ve the field declaration.
614     * @return the instantiated field type.
615     */
616    public TypeMirror asInstantiatedFieldType(TypeElement site,
617            VariableElement ve) {
618        return shouldInstantiate(site, ve)
619            ? typeUtils.asMemberOf((DeclaredType) site.asType(), ve)
620            : ve.asType();
621    }
622
623    /*
624     * We should not instantiate if (i) there's no contextual type declaration,
625     * (ii) the declaration
626     * to which the member belongs to is the same as the one under
627     * consideration, (iii) if the
628     * declaration to which the member belongs to is not generic.
629     */
630    private boolean shouldInstantiate(TypeElement site, Element e) {
631        return site != null &&
632            site != e.getEnclosingElement() &&
633            !((DeclaredType) e.getEnclosingElement().asType())
634                .getTypeArguments().isEmpty();
635    }
636
637    /*
638     * The record is used to pass the method along with the type where that
639     * method is visible.
640     * Passing the type explicitly allows to preserve a complete type
641     * information, including
642     * parameterization.
643     */
644    public record OverrideInfo(ExecutableElement overriddenMethod,
645            DeclaredType overriddenMethodOwner) {
646    }
647
648    /*
649     * Returns the closest superclass (not the superinterface) that contains
650     * a method that is both:
651     *
652     * - overridden by the specified method, and
653     * - is not itself a *simple* override
654     *
655     * If no such class can be found, returns null.
656     *
657     * If the specified method belongs to an interface, the only considered
658     * superclass is java.lang.Object no matter how many other interfaces
659     * that interface extends.
660     */
661    public OverrideInfo overriddenMethod(ExecutableElement method) {
662        var t = method.getEnclosingElement().asType();
663        // in this context, consider java.lang.Object to be the superclass of an
664        // interface
665        while (true) {
666            var supertypes = typeUtils.directSupertypes(t);
667            if (supertypes.isEmpty()) {
668                // reached the top of the hierarchy
669                assert typeUtils.isSameType(getObjectType(), t);
670                return null;
671            }
672            t = supertypes.get(0);
673            // if non-empty, the first element is always the superclass
674            var te = (TypeElement) ((DeclaredType) t).asElement();
675            assert te.getKind().isClass();
676            VisibleMemberTable vmt = configuration.getVisibleMemberTable(te);
677            for (Element e : vmt.getMembers(VisibleMemberTable.Kind.METHODS)) {
678                var ee = (ExecutableElement) e;
679                if (elementUtils.overrides(method, ee,
680                    (TypeElement) method.getEnclosingElement()) &&
681                    !isSimpleOverride(ee)) {
682                    return new OverrideInfo(ee, (DeclaredType) t);
683                }
684            }
685        }
686    }
687
688    public SortedSet<TypeElement>
689            getTypeElementsAsSortedSet(Iterable<TypeElement> typeElements) {
690        SortedSet<TypeElement> set
691            = new TreeSet<>(comparators.makeGeneralPurposeComparator());
692        typeElements.forEach(set::add);
693        return set;
694    }
695
696    public List<? extends SerialDataTree>
697            getSerialDataTrees(ExecutableElement member) {
698        return getBlockTags(member, SERIAL_DATA, SerialDataTree.class);
699    }
700
701    public FileObject getFileObject(TypeElement te) {
702        return docTrees.getPath(te).getCompilationUnit().getSourceFile();
703    }
704
705    public TypeMirror getDeclaredType(TypeElement enclosing,
706            TypeMirror target) {
707        return getDeclaredType(List.of(), enclosing, target);
708    }
709
710    /**
711     * Finds the declaration of the enclosing's type parameter.
712     *
713     * @param values
714     * @param enclosing a TypeElement whose type arguments  we desire
715     * @param target the TypeMirror of the type as described by the enclosing
716     * @return
717     */
718    public TypeMirror getDeclaredType(Collection<TypeMirror> values,
719            TypeElement enclosing, TypeMirror target) {
720        TypeElement targetElement = asTypeElement(target);
721        List<? extends TypeParameterElement> targetTypeArgs
722            = targetElement.getTypeParameters();
723        if (targetTypeArgs.isEmpty()) {
724            return target;
725        }
726
727        List<? extends TypeParameterElement> enclosingTypeArgs
728            = enclosing.getTypeParameters();
729        List<TypeMirror> targetTypeArgTypes
730            = new ArrayList<>(targetTypeArgs.size());
731
732        if (enclosingTypeArgs.isEmpty()) {
733            for (TypeMirror te : values) {
734                List<? extends TypeMirror> typeArguments
735                    = ((DeclaredType) te).getTypeArguments();
736                if (typeArguments.size() >= targetTypeArgs.size()) {
737                    for (int i = 0; i < targetTypeArgs.size(); i++) {
738                        targetTypeArgTypes.add(typeArguments.get(i));
739                    }
740                    break;
741                }
742            }
743            // we found no matches in the hierarchy
744            if (targetTypeArgTypes.isEmpty()) {
745                return target;
746            }
747        } else {
748            if (targetTypeArgs.size() > enclosingTypeArgs.size()) {
749                return target;
750            }
751            for (int i = 0; i < targetTypeArgs.size(); i++) {
752                TypeParameterElement tpe = enclosingTypeArgs.get(i);
753                targetTypeArgTypes.add(tpe.asType());
754            }
755        }
756        TypeMirror dt = typeUtils.getDeclaredType(targetElement,
757            targetTypeArgTypes
758                .toArray(new TypeMirror[targetTypeArgTypes.size()]));
759        return dt;
760    }
761
762    /**
763     * Returns all the implemented superinterfaces of a given type,
764     * in the case of classes, include all the superinterfaces of
765     * the supertype. The superinterfaces are collected before the
766     * superinterfaces of the supertype.
767     *
768     * @param  te the type element to get the superinterfaces for.
769     * @return the list of superinterfaces.
770     */
771    public Set<TypeMirror> getAllInterfaces(TypeElement te) {
772        Set<TypeMirror> results = new LinkedHashSet<>();
773        addSuperInterfaces(te.asType(), results, new HashSet<>());
774        assert noSameTypes(results);
775        return results;
776    }
777
778    private boolean noSameTypes(Set<TypeMirror> results) {
779        for (TypeMirror t1 : results) {
780            for (TypeMirror t2 : results) {
781                if (t1 == t2) {
782                    continue;
783                }
784                if (typeUtils.isSameType(t1, t2)) {
785                    return false;
786                }
787            }
788        }
789        return true;
790    }
791
792    /*
793     * Instances of TypeMirror should be compared using
794     * Types.isSameType. However, there's no hash function
795     * consistent with that method. This makes it problematic to
796     * store TypeMirror in a collection that relies on hashing.
797     *
798     * To work around that, along with accumulating the resulting set of type
799     * mirrors, we also maintain a set of elements that correspond to those
800     * type mirrors. Element provides strong equals and hashCode. We only add
801     * a type mirror into the result set if we don't already have an element
802     * that corresponds to this type mirror in the set of seen elements.
803     *
804     * Although this might seem wrong, as an instance of Element corresponds
805     * to multiple instances of TypeMirror (one-to-many), in an
806     * inheritance hierarchy the correspondence is effectively one-to-one.
807     * This is because it is NOT possible for a type to be a subtype
808     * of different generic invocations of the same supertype; e.g.,
809     *
810     * interface X extends G<A>, G<B>
811     */
812    private void addSuperInterfaces(TypeMirror type, Set<TypeMirror> results,
813            Set<Element> visited) {
814        TypeMirror superType = null;
815        for (TypeMirror t : typeUtils.directSupertypes(type)) {
816            if (typeUtils.isSameType(t, getObjectType()))
817                continue;
818            TypeElement e = asTypeElement(t);
819            if (isPlainInterface(e)) {
820                if (!visited.add(e)) {
821                    continue; // seen it before
822                }
823                if (isPublic(e) || isLinkable(e)) {
824                    results.add(t);
825                }
826                addSuperInterfaces(t, results, visited);
827            } else {
828                // there can be at most one superclass and it is not null
829                assert superType == null && t != null : superType;
830                // Save the supertype for later.
831                superType = t;
832            }
833        }
834        // Collect the super-interfaces of the supertype.
835        if (superType != null)
836            addSuperInterfaces(superType, results, visited);
837    }
838
839    /**
840     * Lookup for a class within this package.
841     *
842     * @return TypeElement of found class, or null if not found.
843     */
844    public TypeElement findClassInPackageElement(PackageElement pkg,
845            String className) {
846        for (TypeElement c : getAllClasses(pkg)) {
847            if (getSimpleName(c).equals(className)) {
848                return c;
849            }
850        }
851        return null;
852    }
853
854    /**
855     * Returns true if {@code type} or any of its enclosing types has non-empty type arguments.
856     * @param type the type
857     * @return {@code true} if type arguments were found
858     */
859    public boolean isGenericType(TypeMirror type) {
860        while (type instanceof DeclaredType dt) {
861            if (!dt.getTypeArguments().isEmpty()) {
862                return true;
863            }
864            type = dt.getEnclosingType();
865        }
866        return false;
867    }
868
869    /**
870     * TODO: FIXME: port to javax.lang.model
871     * Find a class within the context of this class. Search order: qualified name, in this class
872     * (inner), in this package, in the class imports, in the package imports. Return the
873     * TypeElement if found, null if not found.
874     */
875    // ### The specified search order is not the normal rule the
876    // ### compiler would use. Leave as specified or change it?
877    public TypeElement findClass(Element element, String className) {
878        TypeElement encl = getEnclosingTypeElement(element);
879        TypeElement searchResult
880            = configuration.workArounds.searchClass(encl, className);
881        if (searchResult == null) {
882            encl = getEnclosingTypeElement(encl);
883            // Expand search space to include enclosing class.
884            while (encl != null && getEnclosingTypeElement(encl) != null) {
885                encl = getEnclosingTypeElement(encl);
886            }
887            searchResult = encl == null
888                ? null
889                : configuration.workArounds.searchClass(encl, className);
890        }
891        return searchResult;
892    }
893
894    /**
895     * Given an annotation, return true if it should be documented and false
896     * otherwise.
897     *
898     * @param annotation the annotation to check.
899     *
900     * @return true return true if it should be documented and false otherwise.
901     */
902    public boolean isDocumentedAnnotation(TypeElement annotation) {
903        for (AnnotationMirror anno : annotation.getAnnotationMirrors()) {
904            if (getFullyQualifiedName(anno.getAnnotationType().asElement())
905                .equals(
906                    Documented.class.getName())) {
907                return true;
908            }
909        }
910        return false;
911    }
912
913    /**
914     * Returns true if this class is linkable and false if we can't link to it.
915     *
916     * <p>
917     * <b>NOTE:</b>  You can only link to external classes if they are public or
918     * protected.
919     *
920     * @return true if this class is linkable and false if we can't link to the
921     * desired class.
922     */
923    public boolean isLinkable(TypeElement typeElem) {
924        return typeElem != null &&
925            ((isIncluded(typeElem) && configuration.isGeneratedDoc(typeElem) &&
926                !hasHiddenTag(typeElem)) ||
927                (configuration.extern.isExternal(typeElem) &&
928                    (isPublic(typeElem) || isProtected(typeElem))));
929    }
930
931    /**
932     * Returns true if an element is linkable in the context of a given type element.
933     *
934     * If the element is a type element, it delegates to {@link #isLinkable(TypeElement)}.
935     * Otherwise, the element is linkable if any of the following are true:
936     * <ul>
937     * <li>it is "included" (see {@link jdk.javadoc.doclet})
938     * <li>it is inherited from an undocumented supertype
939     * <li>it is a public or protected member of an external API
940     * </ul>
941     *
942     * @param typeElem the type element
943     * @param elem the element
944     * @return whether or not the element is linkable
945     */
946    public boolean isLinkable(TypeElement typeElem, Element elem) {
947        if (isTypeElement(elem)) {
948            return isLinkable((TypeElement) elem); // defer to existing behavior
949        }
950
951        if (isIncluded(elem) && !hasHiddenTag(elem)) {
952            return true;
953        }
954
955        // Allow for the behavior that members of undocumented supertypes
956        // may be included in documented types
957        if (isUndocumentedEnclosure(getEnclosingTypeElement(elem))) {
958            return true;
959        }
960
961        // Allow for external members
962        return isLinkable(typeElem)
963            && configuration.extern.isExternal(typeElem)
964            && (isPublic(elem) || isProtected(elem));
965    }
966
967    /**
968     * Return this type as a {@code TypeElement} if it represents a class
969     * interface or annotation.  Array dimensions are ignored.
970     * If this type {@code ParameterizedType} or {@code WildcardType}, return
971     * the {@code TypeElement} of the type's erasure.  If this is an
972     * annotation, return this as a {@code TypeElement}.
973     * If this is a primitive type, return null.
974     *
975     * @return the {@code TypeElement} of this type,
976     *         or null if it is a primitive type.
977     */
978    public TypeElement asTypeElement(TypeMirror t) {
979        return new SimpleTypeVisitor14<TypeElement, Void>() {
980
981            @Override
982            public TypeElement visitDeclared(DeclaredType t, Void p) {
983                return (TypeElement) t.asElement();
984            }
985
986            @Override
987            public TypeElement visitArray(ArrayType t, Void p) {
988                return visit(t.getComponentType());
989            }
990
991            @Override
992            public TypeElement visitTypeVariable(TypeVariable t, Void p) {
993                /*
994                 * TODO, this may not be an optimal fix.
995                 * if we have an annotated type @DA T, then erasure returns a
996                 * none, in this case we use asElement instead.
997                 */
998                if (isAnnotated(t)) {
999                    return visit(typeUtils.asElement(t).asType());
1000                }
1001                return visit(typeUtils.erasure(t));
1002            }
1003
1004            @Override
1005            public TypeElement visitWildcard(WildcardType t, Void p) {
1006                return visit(typeUtils.erasure(t));
1007            }
1008
1009            @Override
1010            public TypeElement visitError(ErrorType t, Void p) {
1011                return (TypeElement) t.asElement();
1012            }
1013
1014            @Override
1015            protected TypeElement defaultAction(TypeMirror e, Void p) {
1016                return super.defaultAction(e, p);
1017            }
1018        }.visit(t);
1019    }
1020
1021    public TypeMirror getComponentType(TypeMirror t) {
1022        while (isArrayType(t)) {
1023            t = ((ArrayType) t).getComponentType();
1024        }
1025        return t;
1026    }
1027
1028    /**
1029     * Return the type's dimension information, as a string.
1030     * <p>
1031     * For example, a two dimensional array of String returns "{@code [][]}".
1032     *
1033     * @return the type's dimension information as a string.
1034     */
1035    public String getDimension(TypeMirror t) {
1036        return new SimpleTypeVisitor14<String, Void>() {
1037            StringBuilder dimension = new StringBuilder();
1038
1039            @Override
1040            public String visitArray(ArrayType t, Void p) {
1041                dimension.append("[]");
1042                return visit(t.getComponentType());
1043            }
1044
1045            @Override
1046            protected String defaultAction(TypeMirror e, Void p) {
1047                return dimension.toString();
1048            }
1049
1050        }.visit(t);
1051    }
1052
1053    private boolean checkType(TypeElement te) {
1054        return isInterface(te)
1055            || typeUtils.isSameType(te.asType(), getObjectType());
1056    }
1057
1058    public TypeElement getFirstVisibleSuperClassAsTypeElement(TypeElement te) {
1059        if (checkType(te)) {
1060            return null;
1061        }
1062        TypeMirror firstVisibleSuperClass = getFirstVisibleSuperClass(te);
1063        return firstVisibleSuperClass == null ? null
1064            : asTypeElement(firstVisibleSuperClass);
1065    }
1066
1067    /**
1068     * Given a class, return the closest visible superclass.
1069     * @param type the TypeMirror to be interrogated
1070     * @return  the closest visible superclass.  Return null if it cannot
1071     *          be found.
1072     */
1073    public TypeMirror getFirstVisibleSuperClass(TypeMirror type) {
1074        // TODO: this computation should be eventually delegated to
1075        // VisibleMemberTable
1076        Set<TypeElement> alreadySeen = null;
1077        // create a set iff assertions are enabled, to assert that no class
1078        // appears more than once in a superclass hierarchy
1079        assert (alreadySeen = new HashSet<>()) != null;
1080        for (var t = type;;) {
1081            var supertypes = typeUtils.directSupertypes(t);
1082            if (supertypes.isEmpty()) { // end of hierarchy
1083                return null;
1084            }
1085            t = supertypes.get(0); // if non-empty, the first element is always
1086                                   // the superclass
1087            var te = asTypeElement(t);
1088            assert alreadySeen.add(te); // it should be the first time we see
1089                                        // `te`
1090            if (!hasHiddenTag(te) && (isPublic(te) || isLinkable(te))) {
1091                return t;
1092            }
1093        }
1094    }
1095
1096    /**
1097     * Given a class, return the closest visible superclass.
1098     *
1099     * @param te the TypeElement to be interrogated
1100     * @return the closest visible superclass.  Return null if it cannot
1101     *         be found.
1102     */
1103    public TypeMirror getFirstVisibleSuperClass(TypeElement te) {
1104        return getFirstVisibleSuperClass(te.asType());
1105    }
1106
1107    /**
1108     * Returns the name of the kind of a type element (Class, Interface, etc.).
1109     *
1110     * @param te the type element
1111     * @param lowerCaseOnly true if you want the name returned in lower case;
1112     *                      if false, the first letter of the name is capitalized
1113     * @return the name
1114     */
1115    public String getTypeElementKindName(TypeElement te,
1116            boolean lowerCaseOnly) {
1117        String kindName = switch (te.getKind()) {
1118        case ANNOTATION_TYPE -> "doclet.AnnotationType";
1119        case ENUM -> "doclet.Enum";
1120        case INTERFACE -> "doclet.Interface";
1121        case RECORD -> "doclet.RecordClass";
1122        case CLASS -> isThrowable(te) ? "doclet.ExceptionClass"
1123            : "doclet.Class";
1124        default -> throw new IllegalArgumentException(te.getKind().toString());
1125        };
1126        kindName = lowerCaseOnly ? toLowerCase(kindName) : kindName;
1127        return kindNameMap.computeIfAbsent(kindName, resources::getText);
1128    }
1129
1130    private final Map<String, String> kindNameMap = new HashMap<>();
1131
1132    public String getTypeName(TypeMirror t, boolean fullyQualified) {
1133        return new SimpleTypeVisitor14<String, Void>() {
1134
1135            @Override
1136            public String visitArray(ArrayType t, Void p) {
1137                return visit(t.getComponentType());
1138            }
1139
1140            @Override
1141            public String visitDeclared(DeclaredType t, Void p) {
1142                TypeElement te = asTypeElement(t);
1143                return fullyQualified
1144                    ? te.getQualifiedName().toString()
1145                    : getSimpleName(te);
1146            }
1147
1148            @Override
1149            public String visitExecutable(ExecutableType t, Void p) {
1150                return t.toString();
1151            }
1152
1153            @Override
1154            public String visitPrimitive(PrimitiveType t, Void p) {
1155                return t.toString();
1156            }
1157
1158            @Override
1159            public String visitTypeVariable(
1160                    javax.lang.model.type.TypeVariable t, Void p) {
1161                return getSimpleName(t.asElement());
1162            }
1163
1164            @Override
1165            public String visitWildcard(javax.lang.model.type.WildcardType t,
1166                    Void p) {
1167                return t.toString();
1168            }
1169
1170            @Override
1171            protected String defaultAction(TypeMirror e, Void p) {
1172                return e.toString();
1173            }
1174        }.visit(t);
1175    }
1176
1177    /**
1178     * Replace all tabs in a string with the appropriate number of spaces.
1179     * The string may be a multi-line string.
1180     * @param text the text for which the tabs should be expanded
1181     * @return the text with all tabs expanded
1182     */
1183    public String replaceTabs(String text) {
1184        if (!text.contains("\t"))
1185            return text;
1186
1187        final int tabLength = options.sourceTabSize();
1188        final String whitespace = " ".repeat(tabLength);
1189        final int textLength = text.length();
1190        StringBuilder result = new StringBuilder(textLength);
1191        int pos = 0;
1192        int lineLength = 0;
1193        for (int i = 0; i < textLength; i++) {
1194            char ch = text.charAt(i);
1195            switch (ch) {
1196            case '\n', '\r' -> lineLength = 0;
1197
1198            case '\t' -> {
1199                result.append(text, pos, i);
1200                int spaceCount = tabLength - lineLength % tabLength;
1201                result.append(whitespace, 0, spaceCount);
1202                lineLength += spaceCount;
1203                pos = i + 1;
1204            }
1205
1206            default -> lineLength++;
1207            }
1208        }
1209        result.append(text, pos, textLength);
1210        return result.toString();
1211    }
1212
1213    /**
1214     * Returns a locale independent lower cased String. That is, it
1215     * always uses US locale, this is a clone of the one in StringUtils.
1216     * @param s to convert
1217     * @return converted String
1218     */
1219    public static String toLowerCase(String s) {
1220        return s.toLowerCase(Locale.US);
1221    }
1222
1223    /**
1224     * Return true if the given Element is deprecated.
1225     *
1226     * @param e the Element to check.
1227     * @return true if the given Element is deprecated.
1228     */
1229    public boolean isDeprecated(Element e) {
1230        if (isPackage(e)) {
1231            return configuration.workArounds.isDeprecated0(e);
1232        }
1233        return elementUtils.isDeprecated(e);
1234    }
1235
1236    /**
1237     * Returns true if the given Element is deprecated for removal.
1238     *
1239     * @param e the Element to check.
1240     * @return true if the given Element is deprecated for removal.
1241     */
1242    public boolean isDeprecatedForRemoval(Element e) {
1243        Object forRemoval
1244            = getAnnotationElement(e, getDeprecatedType(), "forRemoval");
1245        return forRemoval != null && (boolean) forRemoval;
1246    }
1247
1248    /**
1249     * Returns the value of the {@code Deprecated.since} element if it is set on the given Element.
1250     *
1251     * @param e the Element to check.
1252     * @return the Deprecated.since value for e, or null.
1253     */
1254    public String getDeprecatedSince(Element e) {
1255        return (String) getAnnotationElement(e, getDeprecatedType(), "since");
1256    }
1257
1258    /**
1259     * Returns the value of the internal {@code PreviewFeature.feature} element.
1260     *
1261     * @param e the Element to check
1262     * @return the PreviewFeature.feature for e, or null
1263     */
1264    public Object getPreviewFeature(Element e) {
1265        return getAnnotationElement(e,
1266            getSymbol("jdk.internal.javac.PreviewFeature"), "feature");
1267    }
1268
1269    /**
1270     * Returns the Deprecated annotation element value of the given element, or null.
1271     */
1272    private Object getAnnotationElement(Element e, TypeMirror annotationType,
1273            String annotationElementName) {
1274        List<? extends AnnotationMirror> annotationList
1275            = e.getAnnotationMirrors();
1276        for (AnnotationMirror anno : annotationList) {
1277            if (typeUtils.isSameType(anno.getAnnotationType(),
1278                annotationType)) {
1279                Map<? extends ExecutableElement,
1280                        ? extends AnnotationValue> pairs
1281                            = anno.getElementValues();
1282                if (!pairs.isEmpty()) {
1283                    for (ExecutableElement element : pairs.keySet()) {
1284                        if (element.getSimpleName()
1285                            .contentEquals(annotationElementName)) {
1286                            return (pairs.get(element)).getValue();
1287                        }
1288                    }
1289                }
1290            }
1291        }
1292        return null;
1293    }
1294
1295    /**
1296     * A convenience method to get property name from the name of the
1297     * getter or setter method.
1298     * @param e the input method.
1299     * @return the name of the property of the given setter of getter.
1300     */
1301    public String propertyName(ExecutableElement e) {
1302        String name = getSimpleName(e);
1303        String propertyName = null;
1304        if (name.startsWith("get") || name.startsWith("set")) {
1305            propertyName = name.substring(3);
1306        } else if (name.startsWith("is")) {
1307            propertyName = name.substring(2);
1308        }
1309        if ((propertyName == null) || propertyName.isEmpty()) {
1310            return "";
1311        }
1312        return propertyName.substring(0, 1)
1313            .toLowerCase(configuration.getLocale())
1314            + propertyName.substring(1);
1315    }
1316
1317    /**
1318     * Returns true if the element is included or selected, contains &#64;hidden tag,
1319     * or if javafx flag is present and element contains &#64;treatAsPrivate
1320     * tag.
1321     * @param e the queried element
1322     * @return true if it exists, false otherwise
1323     */
1324    public boolean hasHiddenTag(Element e) {
1325        // Non-included elements may still be visible via "transclusion" from
1326        // undocumented enclosures,
1327        // but we don't want to run doclint on them, possibly causing warnings
1328        // or errors.
1329        if (!isIncluded(e)) {
1330            return hasBlockTagUnchecked(e, HIDDEN);
1331        }
1332        if (options.javafx() &&
1333            hasBlockTag(e, DocTree.Kind.UNKNOWN_BLOCK_TAG, "treatAsPrivate")) {
1334            return true;
1335        }
1336        return hasBlockTag(e, DocTree.Kind.HIDDEN);
1337    }
1338
1339    /*
1340     * Returns true if the passed method does not change the specification it
1341     * inherited.
1342     *
1343     * If the passed method is not deprecated and has either no comment or a
1344     * comment consisting of single {@inheritDoc} tag, the inherited
1345     * specification is deemed unchanged and this method returns true;
1346     * otherwise this method returns false.
1347     */
1348    public boolean isSimpleOverride(ExecutableElement m) {
1349        if (!options.summarizeOverriddenMethods() || !isIncluded(m)) {
1350            return false;
1351        }
1352
1353        if (!getBlockTags(m).isEmpty() || isDeprecated(m))
1354            return false;
1355
1356        List<? extends DocTree> fullBody = getFullBody(m);
1357        return fullBody.isEmpty() ||
1358            (fullBody.size() == 1
1359                && fullBody.get(0).getKind().equals(Kind.INHERIT_DOC));
1360    }
1361
1362    /**
1363     * In case of JavaFX mode on, filters out classes that are private,
1364     * package private, these are not documented in JavaFX mode, also
1365     * remove those classes that have &#64;hidden or &#64;treatAsPrivate comment tag.
1366     *
1367     * @param classlist a collection of TypeElements
1368     * @param javafx set to true if in JavaFX mode.
1369     * @return list of filtered classes.
1370     */
1371    public SortedSet<TypeElement> filterOutPrivateClasses(
1372            Iterable<TypeElement> classlist,
1373            boolean javafx) {
1374        SortedSet<TypeElement> filteredOutClasses
1375            = new TreeSet<>(comparators.makeGeneralPurposeComparator());
1376        if (!javafx) {
1377            for (TypeElement te : classlist) {
1378                if (!hasHiddenTag(te)) {
1379                    filteredOutClasses.add(te);
1380                }
1381            }
1382            return filteredOutClasses;
1383        }
1384        for (TypeElement e : classlist) {
1385            if (isPrivate(e) || isPackagePrivate(e) || hasHiddenTag(e)) {
1386                continue;
1387            }
1388            filteredOutClasses.add(e);
1389        }
1390        return filteredOutClasses;
1391    }
1392
1393    /**
1394     * A general purpose case insensitive String comparator, which compares
1395     * two Strings using a Collator strength of "TERTIARY".
1396     *
1397     * @param s1 first String to compare.
1398     * @param s2 second String to compare.
1399     * @return a negative integer, zero, or a positive integer as the first
1400     *         argument is less than, equal to, or greater than the second.
1401     */
1402    public int compareStrings(String s1, String s2) {
1403        return compareStrings(true, s1, s2);
1404    }
1405
1406    private DocCollator tertiaryCollator = null;
1407    private DocCollator secondaryCollator = null;
1408
1409    int compareStrings(boolean caseSensitive, String s1, String s2) {
1410        if (caseSensitive) {
1411            if (tertiaryCollator == null) {
1412                tertiaryCollator
1413                    = new DocCollator(configuration.locale, Collator.TERTIARY);
1414            }
1415            return tertiaryCollator.compare(s1, s2);
1416        }
1417        if (secondaryCollator == null) {
1418            secondaryCollator
1419                = new DocCollator(configuration.locale, Collator.SECONDARY);
1420        }
1421        return secondaryCollator.compare(s1, s2);
1422    }
1423
1424    public String getHTMLTitle(Element element) {
1425        List<? extends DocTree> preamble = getPreamble(element);
1426        StringBuilder sb = new StringBuilder();
1427        boolean titleFound = false;
1428        loop: for (DocTree dt : preamble) {
1429            switch (dt.getKind()) {
1430            case START_ELEMENT -> {
1431                StartElementTree nodeStart = (StartElementTree) dt;
1432                if (Utils.toLowerCase(nodeStart.getName().toString())
1433                    .equals("title")) {
1434                    titleFound = true;
1435                }
1436            }
1437            case END_ELEMENT -> {
1438                EndElementTree nodeEnd = (EndElementTree) dt;
1439                if (Utils.toLowerCase(nodeEnd.getName().toString())
1440                    .equals("title")) {
1441                    break loop;
1442                }
1443            }
1444            case TEXT -> {
1445                TextTree nodeText = (TextTree) dt;
1446                if (titleFound)
1447                    sb.append(nodeText.getBody());
1448            }
1449            default -> {
1450            }
1451            // do nothing
1452            }
1453        }
1454        return sb.toString().trim();
1455    }
1456
1457    private static class DocCollator {
1458        private final Map<String, CollationKey> keys;
1459        private final Collator instance;
1460        private final int MAX_SIZE = 1000;
1461
1462        private DocCollator(Locale locale, int strength) {
1463            instance = createCollator(locale);
1464            instance.setStrength(strength);
1465
1466            keys = new LinkedHashMap<>(MAX_SIZE + 1, 0.75f, true) {
1467                private static final long serialVersionUID = 1L;
1468
1469                @Override
1470                protected boolean
1471                        removeEldestEntry(Entry<String, CollationKey> eldest) {
1472                    return size() > MAX_SIZE;
1473                }
1474            };
1475        }
1476
1477        CollationKey getKey(String s) {
1478            return keys.computeIfAbsent(s, instance::getCollationKey);
1479        }
1480
1481        public int compare(String s1, String s2) {
1482            return getKey(s1).compareTo(getKey(s2));
1483        }
1484
1485        private Collator createCollator(Locale locale) {
1486            Collator baseCollator = Collator.getInstance(locale);
1487            if (baseCollator instanceof RuleBasedCollator rbc) {
1488                // Extend collator to sort signatures with additional args and
1489                // var-args in a well-defined order:
1490                // () < (int) < (int, int) < (int...)
1491                try {
1492                    return new RuleBasedCollator(rbc.getRules()
1493                        + "& ')' < ',' < '.','['");
1494                } catch (ParseException e) {
1495                    throw new RuntimeException(e);
1496                }
1497            }
1498            return baseCollator;
1499        }
1500    }
1501
1502    /**
1503     * Get the qualified type name of a TypeMirror compatible with the Element's
1504     * getQualified name, returns  the qualified name of the Reference type
1505     * otherwise the primitive name.
1506     * @param t the type whose name is to be obtained.
1507     * @return the fully qualified name of Reference type or the primitive name
1508     */
1509    public String getQualifiedTypeName(TypeMirror t) {
1510        return new SimpleTypeVisitor14<String, Void>() {
1511            @Override
1512            public String visitDeclared(DeclaredType t, Void p) {
1513                return getFullyQualifiedName(t.asElement());
1514            }
1515
1516            @Override
1517            public String visitArray(ArrayType t, Void p) {
1518                return visit(t.getComponentType());
1519            }
1520
1521            @Override
1522            public String visitTypeVariable(
1523                    javax.lang.model.type.TypeVariable t, Void p) {
1524                // The knee jerk reaction is to do this but don't!, as we would
1525                // like
1526                // it to be compatible with the old world, now if we decide to
1527                // do so
1528                // care must be taken to avoid collisions.
1529                // return getFullyQualifiedName(t.asElement());
1530                return t.toString();
1531            }
1532
1533            @Override
1534            protected String defaultAction(TypeMirror t, Void p) {
1535                return t.toString();
1536            }
1537
1538        }.visit(t);
1539    }
1540
1541    /**
1542     * A generic utility which returns the fully qualified names of an entity,
1543     * if the entity is not qualifiable then its enclosing entity, it is up to
1544     * the caller to add the elements name as required.
1545     * @param e the element to get FQN for.
1546     * @return the name
1547     */
1548    public String getFullyQualifiedName(Element e) {
1549        return getFullyQualifiedName(e, true);
1550    }
1551
1552    public String getFullyQualifiedName(Element e, final boolean outer) {
1553        return new SimpleElementVisitor14<String, Void>() {
1554            @Override
1555            public String visitModule(ModuleElement e, Void p) {
1556                return e.getQualifiedName().toString();
1557            }
1558
1559            @Override
1560            public String visitPackage(PackageElement e, Void p) {
1561                return e.getQualifiedName().toString();
1562            }
1563
1564            @Override
1565            public String visitType(TypeElement e, Void p) {
1566                return e.getQualifiedName().toString();
1567            }
1568
1569            @Override
1570            protected String defaultAction(Element e, Void p) {
1571                return outer ? visit(e.getEnclosingElement())
1572                    : e.getSimpleName().toString();
1573            }
1574        }.visit(e);
1575    }
1576
1577    /**
1578     * Returns the recursively enclosed documented type elements in a package
1579     *
1580     * @param pkg the package
1581     * @return the elements
1582     */
1583    public Iterable<TypeElement> getEnclosedTypeElements(PackageElement pkg) {
1584        return getItems(pkg, false, this::isTypeElement, TypeElement.class);
1585    }
1586
1587    // Element related methods
1588
1589    /**
1590     * Returns the fields and methods declared in an annotation interface.
1591     *
1592     * @param te the annotation interface
1593     * @return the fields and methods
1594     */
1595    public List<Element> getAnnotationMembers(TypeElement te) {
1596        return getItems(te, false, e_ -> switch (e_.getKind()) {
1597        case FIELD, METHOD -> shouldDocument(e_);
1598        default -> false;
1599        },
1600            Element.class);
1601
1602    }
1603
1604    /**
1605     * Returns the documented fields in a type element.
1606     *
1607     * @param te the element
1608     * @return the fields
1609     */
1610    public List<VariableElement> getFields(TypeElement te) {
1611        return getDocumentedItems(te, FIELD, VariableElement.class);
1612    }
1613
1614    /**
1615     * Returns the fields in a type element.
1616     *
1617     * @param te the element
1618     * @return the fields
1619     */
1620    public List<VariableElement> getFieldsUnfiltered(TypeElement te) {
1621        return getAllItems(te, FIELD, VariableElement.class);
1622    }
1623
1624    /**
1625     * Returns the documented classes in an element,
1626     * such as a package element or type element.
1627     *
1628     * @param e the element
1629     * @return the classes
1630     */
1631    public List<TypeElement> getClasses(Element e) {
1632        return getDocumentedItems(e, CLASS, TypeElement.class);
1633    }
1634
1635    /**
1636     * Returns the documented constructors in a type element.
1637     *
1638     * @param te the type element
1639     * @return the constructors
1640     */
1641    public List<ExecutableElement> getConstructors(TypeElement te) {
1642        return getDocumentedItems(te, CONSTRUCTOR, ExecutableElement.class);
1643    }
1644
1645    /**
1646     * Returns the documented methods in a type element.
1647     *
1648     * @param te the type element
1649     * @return the methods
1650     */
1651    public List<ExecutableElement> getMethods(TypeElement te) {
1652        return getDocumentedItems(te, METHOD, ExecutableElement.class);
1653    }
1654
1655    private Map<ModuleElement, Set<PackageElement>> modulePackageMap = null;
1656
1657    public Map<ModuleElement, Set<PackageElement>> getModulePackageMap() {
1658        if (modulePackageMap == null) {
1659            modulePackageMap = new HashMap<>();
1660            Set<PackageElement> pkgs
1661                = configuration.getIncludedPackageElements();
1662            pkgs.forEach(pkg -> {
1663                ModuleElement mod = elementUtils.getModuleOf(pkg);
1664                modulePackageMap.computeIfAbsent(mod, m -> new HashSet<>())
1665                    .add(pkg);
1666            });
1667        }
1668        return modulePackageMap;
1669    }
1670
1671    public Map<ModuleElement, String> getDependentModules(ModuleElement mdle) {
1672        Map<ModuleElement, String> result
1673            = new TreeMap<>(comparators.makeModuleComparator());
1674        Deque<ModuleElement> queue = new ArrayDeque<>();
1675        // get all the requires for the element in question
1676        for (RequiresDirective rd : ElementFilter
1677            .requiresIn(mdle.getDirectives())) {
1678            ModuleElement dep = rd.getDependency();
1679            // add the dependency to work queue
1680            if (!result.containsKey(dep)) {
1681                if (rd.isTransitive()) {
1682                    queue.addLast(dep);
1683                }
1684            }
1685            // add all exports for the primary module
1686            result.put(rd.getDependency(), getModifiers(rd));
1687        }
1688
1689        // add only requires public for subsequent module dependencies
1690        for (ModuleElement m = queue.poll(); m != null; m = queue.poll()) {
1691            for (RequiresDirective rd : ElementFilter
1692                .requiresIn(m.getDirectives())) {
1693                ModuleElement dep = rd.getDependency();
1694                if (!result.containsKey(dep)) {
1695                    if (rd.isTransitive()) {
1696                        result.put(dep, getModifiers(rd));
1697                        queue.addLast(dep);
1698                    }
1699                }
1700            }
1701        }
1702        return result;
1703    }
1704
1705    public String getModifiers(RequiresDirective rd) {
1706        StringBuilder modifiers = new StringBuilder();
1707        String sep = "";
1708        if (rd.isTransitive()) {
1709            modifiers.append("transitive");
1710            sep = " ";
1711        }
1712        if (rd.isStatic()) {
1713            modifiers.append(sep);
1714            modifiers.append("static");
1715        }
1716        return (modifiers.length() == 0) ? " " : modifiers.toString();
1717    }
1718
1719    public long getLineNumber(Element e) {
1720        TreePath path = getTreePath(e);
1721        if (path == null) { // maybe null if synthesized
1722            TypeElement encl = getEnclosingTypeElement(e);
1723            path = getTreePath(encl);
1724        }
1725        CompilationUnitTree cu = path.getCompilationUnit();
1726        LineMap lineMap = cu.getLineMap();
1727        DocSourcePositions spos = docTrees.getSourcePositions();
1728        long pos = spos.getStartPosition(cu, path.getLeaf());
1729        return lineMap.getLineNumber(pos);
1730    }
1731
1732    /**
1733     * Returns the documented enum constants in a type element.
1734     *
1735     * @param te the element
1736     * @return the interfaces
1737     */
1738    public List<VariableElement> getEnumConstants(TypeElement te) {
1739        return getDocumentedItems(te, ENUM_CONSTANT, VariableElement.class);
1740    }
1741
1742    /**
1743     * Returns all the classes in a package.
1744     *
1745     * @param pkg the package
1746     * @return the interfaces
1747     */
1748    public SortedSet<TypeElement> getAllClassesUnfiltered(PackageElement pkg) {
1749        SortedSet<TypeElement> set
1750            = new TreeSet<>(comparators.makeGeneralPurposeComparator());
1751        set.addAll(getItems(pkg, true, this::isTypeElement, TypeElement.class));
1752        return set;
1753    }
1754
1755    private final HashMap<Element, SortedSet<TypeElement>> cachedClasses
1756        = new HashMap<>();
1757
1758    /**
1759     * Returns a sorted set containing the documented classes and interfaces in a package.
1760     *
1761     * @param pkg the element
1762     * @return the classes and interfaces
1763     */
1764    public SortedSet<TypeElement> getAllClasses(PackageElement pkg) {
1765        return cachedClasses.computeIfAbsent(pkg, p_ -> {
1766            List<TypeElement> clist
1767                = getItems(pkg, false, this::isTypeElement, TypeElement.class);
1768            SortedSet<TypeElement> oset
1769                = new TreeSet<>(comparators.makeGeneralPurposeComparator());
1770            oset.addAll(clist);
1771            return oset;
1772        });
1773    }
1774
1775    /**
1776     * Returns a list of documented elements of a given type with a given kind.
1777     * If the root of the search is a package, the search is recursive.
1778     *
1779     * @param e      the element, such as a package element or type element
1780     * @param kind   the element kind
1781     * @param clazz  the class of the filtered members
1782     * @param <T>    the class of the filtered members
1783     *
1784     * @return the list of enclosed elements
1785     */
1786    private <T extends Element> List<T> getDocumentedItems(Element e,
1787            ElementKind kind, Class<T> clazz) {
1788        return getItems(e, false,
1789            e_ -> e_.getKind() == kind && shouldDocument(e_), clazz);
1790    }
1791
1792    /**
1793     * Returns a list of elements of a given type with a given kind.
1794     * If the root of the search is a package, the search is recursive.
1795     *
1796     * @param e      the element, such as a package element or type element
1797     * @param kind   the element kind
1798     * @param clazz  the class of the filtered members
1799     * @param <T>    the class of the filtered members
1800     *
1801     * @return the list of enclosed elements
1802     */
1803    private <T extends Element> List<T> getAllItems(Element e, ElementKind kind,
1804            Class<T> clazz) {
1805        return getItems(e, true, e_ -> e_.getKind() == kind, clazz);
1806    }
1807
1808    /**
1809     * Returns a list of elements of a given type that match a predicate.
1810     * If the root of the search is a package, the search is recursive through packages
1811     * and classes.
1812     *
1813     * @param e      the element, such as a package element or type element
1814     * @param all    whether to search through all packages and classes, or just documented ones
1815     * @param select the predicate to select members
1816     * @param clazz  the class of the filtered members
1817     * @param <T>    the class of the filtered members
1818     *
1819     * @return the list of enclosed elements
1820     */
1821    private <T extends Element> List<T> getItems(Element e, boolean all,
1822            Predicate<Element> select, Class<T> clazz) {
1823        if (e.getKind() == ElementKind.PACKAGE) {
1824            List<T> elements = new ArrayList<>();
1825            recursiveGetItems(elements, e, all, select, clazz);
1826            return elements;
1827        } else {
1828            return getItems0(e, all, select, clazz);
1829        }
1830    }
1831
1832    /**
1833     * Searches for a list of recursively enclosed elements of a given class that match a predicate.
1834     * The recursion is through nested types.
1835     *
1836     * @param e      the element, such as a package element or type element
1837     * @param all    whether to search all packages and classes, or just documented ones
1838     * @param filter the filter
1839     * @param clazz  the class of the filtered members
1840     * @param <T>    the class of the filtered members
1841     */
1842    private <T extends Element> void recursiveGetItems(Collection<T> list,
1843            Element e, boolean all, Predicate<Element> filter, Class<T> clazz) {
1844        list.addAll(getItems0(e, all, filter, clazz));
1845        List<TypeElement> classes
1846            = getItems0(e, all, this::isTypeElement, TypeElement.class);
1847        for (TypeElement c : classes) {
1848            recursiveGetItems(list, c, all, filter, clazz);
1849        }
1850    }
1851
1852    /**
1853     * Returns a list of immediately enclosed elements of a given class that match a predicate.
1854     *
1855     * @param e      the element, such as a package element or type element
1856     * @param all    whether to search all packages and classes, or just documented ones
1857     * @param select the predicate for the selected members
1858     * @param clazz  the class of the filtered members
1859     * @param <T>    the class of the filtered members
1860     *
1861     * @return the list of enclosed elements
1862     */
1863    private <T extends Element> List<T> getItems0(Element e, boolean all,
1864            Predicate<Element> select, Class<T> clazz) {
1865        return e.getEnclosedElements().stream()
1866            .filter(e_ -> select.test(e_) && (all || shouldDocument(e_)))
1867            .map(clazz::cast)
1868            .toList();
1869    }
1870
1871    private SimpleElementVisitor14<Boolean, Void> shouldDocumentVisitor = null;
1872
1873    public boolean shouldDocument(Element e) {
1874        if (shouldDocumentVisitor == null) {
1875            shouldDocumentVisitor = new SimpleElementVisitor14<>() {
1876                private boolean hasSource(TypeElement e) {
1877                    return configuration.docEnv.getFileKind(
1878                        e) == javax.tools.JavaFileObject.Kind.SOURCE;
1879                }
1880
1881                // handle types
1882                @Override
1883                public Boolean visitType(TypeElement e, Void p) {
1884                    // treat inner classes etc. as members
1885                    if (e.getNestingKind().isNested()) {
1886                        return defaultAction(e, p);
1887                    }
1888                    return configuration.docEnv.isSelected(e) && hasSource(e);
1889                }
1890
1891                // handle everything else
1892                @Override
1893                protected Boolean defaultAction(Element e, Void p) {
1894                    return configuration.docEnv.isSelected(e);
1895                }
1896
1897                @Override
1898                public Boolean visitUnknown(Element e, Void p) {
1899                    throw new AssertionError("unknown element: " + e);
1900                }
1901            };
1902        }
1903        return shouldDocumentVisitor.visit(e);
1904    }
1905
1906    /*
1907     * nameCache is maintained for improving the comparator
1908     * performance, noting that the Collator used by the comparators
1909     * use Strings, as of this writing.
1910     * TODO: when those APIs handle charSequences, the use of
1911     * this nameCache must be re-investigated and removed.
1912     */
1913    private final Map<Element, String> nameCache = new LinkedHashMap<>();
1914
1915    /**
1916     * Returns the name of the element after the last dot of the package name.
1917     * This emulates the behavior of the old doclet.
1918     * @param e an element whose name is required
1919     * @return the name
1920     */
1921    public String getSimpleName(Element e) {
1922        return nameCache.computeIfAbsent(e, this::getSimpleName0);
1923    }
1924
1925    private SimpleElementVisitor14<String, Void> snvisitor = null;
1926
1927    // If `e` is a static nested class, this method will return e's simple name
1928    // preceded by `.` and an outer type; this is not how JLS defines "simple
1929    // name". See "Simple Name", "Qualified Name", "Fully Qualified Name".
1930    private String getSimpleName0(Element e) {
1931        if (snvisitor == null) {
1932            snvisitor = new SimpleElementVisitor14<>() {
1933                @Override
1934                public String visitModule(ModuleElement e, Void p) {
1935                    return e.getQualifiedName().toString();  // temp fix for
1936                                                             // 8182736
1937                }
1938
1939                @Override
1940                public String visitType(TypeElement e, Void p) {
1941                    StringBuilder sb
1942                        = new StringBuilder(e.getSimpleName().toString());
1943                    Element enclosed = e.getEnclosingElement();
1944                    while (enclosed != null
1945                        && (enclosed.getKind().isDeclaredType())) {
1946                        sb.insert(0, enclosed.getSimpleName() + ".");
1947                        enclosed = enclosed.getEnclosingElement();
1948                    }
1949                    return sb.toString();
1950                }
1951
1952                @Override
1953                public String visitExecutable(ExecutableElement e, Void p) {
1954                    if (e.getKind() == CONSTRUCTOR
1955                        || e.getKind() == STATIC_INIT) {
1956                        return e.getEnclosingElement().getSimpleName()
1957                            .toString();
1958                    }
1959                    return e.getSimpleName().toString();
1960                }
1961
1962                @Override
1963                protected String defaultAction(Element e, Void p) {
1964                    return e.getSimpleName().toString();
1965                }
1966            };
1967        }
1968        return snvisitor.visit(e);
1969    }
1970
1971    public TypeElement getEnclosingTypeElement(Element e) {
1972        if (isPackage(e) || isModule(e)) {
1973            return null;
1974        }
1975        Element encl = e.getEnclosingElement();
1976        if (isPackage(encl)) {
1977            return null;
1978        }
1979        ElementKind kind = encl.getKind();
1980        while (!(kind.isClass() || kind.isInterface())) {
1981            encl = encl.getEnclosingElement();
1982            kind = encl.getKind();
1983        }
1984        return (TypeElement) encl;
1985    }
1986
1987    private ConstantValueExpression cve = null;
1988
1989    public String constantValueExpression(VariableElement ve) {
1990        if (cve == null)
1991            cve = new ConstantValueExpression();
1992        return cve.visit(ve.asType(), ve.getConstantValue());
1993    }
1994
1995    // We could also use Elements.getConstantValueExpression, which provides
1996    // similar functionality, but which also includes casts to provide valid
1997    // compilable constants: e.g. (byte) 0x7f
1998    private static class ConstantValueExpression
1999            extends TypeKindVisitor9<String, Object> {
2000        @Override
2001        public String visitPrimitiveAsBoolean(PrimitiveType t, Object val) {
2002            return ((boolean) val) ? "true" : "false";
2003        }
2004
2005        @Override
2006        public String visitPrimitiveAsByte(PrimitiveType t, Object val) {
2007            return "0x" + Integer.toString(((Byte) val) & 0xff, 16);
2008        }
2009
2010        @Override
2011        public String visitPrimitiveAsChar(PrimitiveType t, Object val) {
2012            StringBuilder buf = new StringBuilder(8);
2013            buf.append('\'');
2014            sourceChar((char) val, buf);
2015            buf.append('\'');
2016            return buf.toString();
2017        }
2018
2019        @Override
2020        public String visitPrimitiveAsDouble(PrimitiveType t, Object val) {
2021            return sourceForm(((Double) val), 'd');
2022        }
2023
2024        @Override
2025        public String visitPrimitiveAsFloat(PrimitiveType t, Object val) {
2026            return sourceForm(((Float) val).doubleValue(), 'f');
2027        }
2028
2029        @Override
2030        public String visitPrimitiveAsLong(PrimitiveType t, Object val) {
2031            return val + "L";
2032        }
2033
2034        @Override
2035        protected String defaultAction(TypeMirror e, Object val) {
2036            if (val == null)
2037                return null;
2038            else if (val instanceof String s)
2039                return sourceForm(s);
2040            return val.toString(); // covers int, short
2041        }
2042
2043        private String sourceForm(double v, char suffix) {
2044            if (Double.isNaN(v))
2045                return "0" + suffix + "/0" + suffix;
2046            if (v == Double.POSITIVE_INFINITY)
2047                return "1" + suffix + "/0" + suffix;
2048            if (v == Double.NEGATIVE_INFINITY)
2049                return "-1" + suffix + "/0" + suffix;
2050            return v + (suffix == 'f' || suffix == 'F' ? "" + suffix : "");
2051        }
2052
2053        private String sourceForm(String s) {
2054            StringBuilder buf = new StringBuilder(s.length() + 5);
2055            buf.append('\"');
2056            for (int i = 0; i < s.length(); i++) {
2057                char c = s.charAt(i);
2058                sourceChar(c, buf);
2059            }
2060            buf.append('\"');
2061            return buf.toString();
2062        }
2063
2064        private void sourceChar(char c, StringBuilder buf) {
2065            switch (c) {
2066            case '\b' -> buf.append("\\b");
2067            case '\t' -> buf.append("\\t");
2068            case '\n' -> buf.append("\\n");
2069            case '\f' -> buf.append("\\f");
2070            case '\r' -> buf.append("\\r");
2071            case '\"' -> buf.append("\\\"");
2072            case '\'' -> buf.append("\\\'");
2073            case '\\' -> buf.append("\\\\");
2074            default -> {
2075                if (isPrintableAscii(c)) {
2076                    buf.append(c);
2077                    return;
2078                }
2079                unicodeEscape(c, buf);
2080            }
2081            }
2082        }
2083
2084        private void unicodeEscape(char c, StringBuilder buf) {
2085            final String chars = "0123456789abcdef";
2086            buf.append("\\u");
2087            buf.append(chars.charAt(15 & (c >> 12)));
2088            buf.append(chars.charAt(15 & (c >> 8)));
2089            buf.append(chars.charAt(15 & (c >> 4)));
2090            buf.append(chars.charAt(15 & (c >> 0)));
2091        }
2092
2093        private boolean isPrintableAscii(char c) {
2094            return c >= ' ' && c <= '~';
2095        }
2096    }
2097
2098    public boolean isEnclosingPackageIncluded(TypeElement te) {
2099        return isIncluded(containingPackage(te));
2100    }
2101
2102    public boolean isIncluded(Element e) {
2103        return configuration.docEnv.isIncluded(e);
2104    }
2105
2106    private SimpleElementVisitor14<Boolean, Void> specifiedVisitor = null;
2107
2108    public boolean isSpecified(Element e) {
2109        if (specifiedVisitor == null) {
2110            specifiedVisitor = new SimpleElementVisitor14<>() {
2111                @Override
2112                public Boolean visitModule(ModuleElement e, Void p) {
2113                    return configuration.getSpecifiedModuleElements()
2114                        .contains(e);
2115                }
2116
2117                @Override
2118                public Boolean visitPackage(PackageElement e, Void p) {
2119                    return configuration.getSpecifiedPackageElements()
2120                        .contains(e);
2121                }
2122
2123                @Override
2124                public Boolean visitType(TypeElement e, Void p) {
2125                    return configuration.getSpecifiedTypeElements().contains(e);
2126                }
2127
2128                @Override
2129                protected Boolean defaultAction(Element e, Void p) {
2130                    return false;
2131                }
2132            };
2133        }
2134        return specifiedVisitor.visit(e);
2135    }
2136
2137    /**
2138     * Get the package name for a given package element. An unnamed package is returned as &lt;Unnamed&gt;
2139     * Use {@link org.jdrupes.mdoclet.internal.doclets.formats.html.HtmlDocletWriter#getLocalizedPackageName(PackageElement)}
2140     * to get a localized string for the unnamed package instead.
2141     *
2142     * @param pkg
2143     * @return
2144     */
2145    public String getPackageName(PackageElement pkg) {
2146        if (pkg == null || pkg.isUnnamed()) {
2147            return DocletConstants.DEFAULT_ELEMENT_NAME;
2148        }
2149        return pkg.getQualifiedName().toString();
2150    }
2151
2152    /**
2153     * Get the module name for a given module element. An unnamed module is returned as &lt;Unnamed&gt;
2154     *
2155     * @param mdle a ModuleElement
2156     * @return
2157     */
2158    public String getModuleName(ModuleElement mdle) {
2159        if (mdle == null || mdle.isUnnamed()) {
2160            return DocletConstants.DEFAULT_ELEMENT_NAME;
2161        }
2162        return mdle.getQualifiedName().toString();
2163    }
2164
2165    private final CommentHelperCache commentHelperCache
2166        = new CommentHelperCache(this);
2167
2168    public CommentHelper getCommentHelper(Element element) {
2169        return commentHelperCache.computeIfAbsent(element);
2170    }
2171
2172    public void removeCommentHelper(Element element) {
2173        commentHelperCache.remove(element);
2174    }
2175
2176    public List<? extends DocTree> getBlockTags(Element element) {
2177        return getBlockTags(getDocCommentTree(element));
2178    }
2179
2180    public List<? extends DocTree> getBlockTags(DocCommentTree dcTree) {
2181        return dcTree == null ? List.of() : dcTree.getBlockTags();
2182    }
2183
2184    public List<? extends DocTree> getBlockTags(Element element,
2185            Predicate<DocTree> filter) {
2186        return getBlockTags(element).stream()
2187            .filter(t -> t.getKind() != ERRONEOUS)
2188            .filter(filter)
2189            .toList();
2190    }
2191
2192    public <T extends DocTree> List<T> getBlockTags(Element element,
2193            Predicate<DocTree> filter, Class<T> tClass) {
2194        return getBlockTags(element).stream()
2195            .filter(t -> t.getKind() != ERRONEOUS)
2196            .filter(filter)
2197            .map(tClass::cast)
2198            .toList();
2199    }
2200
2201    public List<? extends DocTree> getBlockTags(Element element,
2202            DocTree.Kind kind) {
2203        return getBlockTags(element, t -> t.getKind() == kind);
2204    }
2205
2206    public <T extends DocTree> List<? extends T> getBlockTags(Element element,
2207            DocTree.Kind kind, Class<T> tClass) {
2208        return getBlockTags(element, t -> t.getKind() == kind, tClass);
2209    }
2210
2211    public List<? extends DocTree> getBlockTags(Element element,
2212            Taglet taglet) {
2213        return getBlockTags(element, t -> {
2214            if (taglet instanceof BaseTaglet baseTaglet) {
2215                return baseTaglet.accepts(t);
2216            } else if (t instanceof BlockTagTree blockTagTree) {
2217                return blockTagTree.getTagName().equals(taglet.getName());
2218            } else {
2219                return false;
2220            }
2221        });
2222    }
2223
2224    public boolean hasBlockTag(Element element, DocTree.Kind kind) {
2225        return hasBlockTag(element, kind, null);
2226    }
2227
2228    public boolean hasBlockTag(Element element, DocTree.Kind kind,
2229            final String tagName) {
2230        if (hasDocCommentTree(element)) {
2231            CommentHelper ch = getCommentHelper(element);
2232            for (DocTree dt : getBlockTags(ch.dcTree)) {
2233                if (dt.getKind() == kind
2234                    && (tagName == null || ch.getTagName(dt).equals(tagName))) {
2235                    return true;
2236                }
2237            }
2238        }
2239        return false;
2240    }
2241
2242    /*
2243     * Tests whether an element's doc comment contains a block tag without
2244     * caching it or
2245     * running doclint on it. This is done by using getDocCommentInfo(Element)
2246     * to retrieve
2247     * the doc comment info.
2248     */
2249    boolean hasBlockTagUnchecked(Element element, DocTree.Kind kind) {
2250        DocCommentInfo dcInfo = getDocCommentInfo(element);
2251        if (dcInfo != null && dcInfo.dcTree != null) {
2252            for (DocTree dt : getBlockTags(dcInfo.dcTree)) {
2253                if (dt.getKind() == kind) {
2254                    return true;
2255                }
2256            }
2257        }
2258        return false;
2259    }
2260
2261    /**
2262     * Gets a TreePath for an Element. Note this method is called very
2263     * frequently, care must be taken to ensure this method is lithe
2264     * and efficient.
2265     * @param e an Element
2266     * @return TreePath
2267     */
2268    public TreePath getTreePath(Element e) {
2269        DocCommentInfo info = dcTreeCache.get(e);
2270        if (info != null && info.treePath != null) {
2271            return info.treePath;
2272        }
2273        info = configuration.cmtUtils.getSyntheticCommentInfo(e);
2274        if (info != null && info.treePath != null) {
2275            return info.treePath;
2276        }
2277        Map<Element, TreePath> elementToTreePath
2278            = configuration.workArounds.getElementToTreePath();
2279        TreePath path = elementToTreePath.get(e);
2280        if (path != null || elementToTreePath.containsKey(e)) {
2281            // expedite the path and one that is a null
2282            return path;
2283        } else {
2284            var p = docTrees.getPath(e);
2285            // if docTrees.getPath itself has put a path for e into
2286            // elementToTreePath
2287            // (see 8304878), we assume that the path already in the map is
2288            // equivalent
2289            // to the path we are about to put: hence, no harm if replaced
2290            elementToTreePath.put(e, p);
2291            return p;
2292        }
2293    }
2294
2295    /**
2296     * A cache of doc comment info objects for elements.
2297     * The entries may come from the AST and DocCommentParser, or may be automatically
2298     * generated comments for mandated elements and JavaFX properties.
2299     *
2300     * @see CommentUtils#dcInfoMap
2301     */
2302    private final Map<Element, DocCommentInfo> dcTreeCache
2303        = new LinkedHashMap<>();
2304
2305    /**
2306     * Checks whether an element has an associated doc comment.
2307     * @param element the element
2308     * @return {@code true} if the element has a comment, and false otherwise
2309     */
2310    public boolean hasDocCommentTree(Element element) {
2311        DocCommentInfo info = getDocCommentInfo(element);
2312        return info != null && info.dcTree != null;
2313    }
2314
2315    /**
2316     * Retrieves the doc comments for a given element.
2317     * @param element the element
2318     * @return DocCommentTree for the Element
2319     */
2320    public DocCommentTree getDocCommentTree0(Element element) {
2321
2322        DocCommentInfo info = getDocCommentInfo(element);
2323
2324        DocCommentTree docCommentTree = info == null ? null : info.dcTree;
2325        if (!dcTreeCache.containsKey(element)) {
2326            TreePath path = info == null ? null : info.treePath;
2327            if (path != null) {
2328                if (docCommentTree != null
2329                    && !configuration.isAllowScriptInComments()) {
2330                    try {
2331                        javaScriptScanner.scan(docCommentTree, path, p -> {
2332                            throw new JavaScriptScanner.Fault();
2333                        });
2334                    } catch (JavaScriptScanner.Fault jsf) {
2335                        String text
2336                            = resources.getText("doclet.JavaScript_in_comment");
2337                        throw new UncheckedDocletException(
2338                            new SimpleDocletException(text, jsf));
2339                    }
2340                }
2341                // run doclint even if docCommentTree is null, to trigger checks
2342                // for missing comments
2343                configuration.runDocLint(path);
2344            }
2345            dcTreeCache.put(element, info);
2346        }
2347        return docCommentTree;
2348    }
2349
2350    private DocCommentInfo getDocCommentInfo(Element element) {
2351        DocCommentInfo info = null;
2352
2353        ElementKind kind = element.getKind();
2354        if (kind == ElementKind.PACKAGE || kind == ElementKind.OTHER) {
2355            info = dcTreeCache.get(element); // local cache
2356            if (info == null && kind == ElementKind.PACKAGE) {
2357                // package-info.java
2358                info = getDocCommentInfo0(element);
2359            }
2360            if (info == null) {
2361                // package.html or overview.html
2362                info = configuration.cmtUtils.getHtmlCommentInfo(element); // html
2363                                                                           // source
2364            }
2365        } else {
2366            info = configuration.cmtUtils.getSyntheticCommentInfo(element);
2367            if (info == null) {
2368                info = dcTreeCache.get(element); // local cache
2369            }
2370            if (info == null) {
2371                info = getDocCommentInfo0(element); // get the real mccoy
2372            }
2373        }
2374
2375        return info;
2376    }
2377
2378    private DocCommentInfo getDocCommentInfo0(Element element) {
2379        // prevent nasty things downstream with overview element
2380        if (!isOverviewElement(element)) {
2381            TreePath path = getTreePath(element);
2382            if (path != null) {
2383                DocCommentTree docCommentTree
2384                    = docTrees.getDocCommentTree(path);
2385                return new DocCommentInfo(path, docCommentTree);
2386            }
2387        }
2388        return null;
2389    }
2390
2391    public void checkJavaScriptInOption(String name, String value) {
2392        if (!configuration.isAllowScriptInComments()) {
2393            DocCommentTree dct = configuration.cmtUtils.parse(
2394                URI.create("option://" + name.replace("-", "")),
2395                "<body>" + value + "</body>");
2396
2397            if (dct == null)
2398                return;
2399
2400            try {
2401                javaScriptScanner.scan(dct, null, p -> {
2402                    throw new JavaScriptScanner.Fault();
2403                });
2404            } catch (JavaScriptScanner.Fault jsf) {
2405                String text
2406                    = resources.getText("doclet.JavaScript_in_option", name);
2407                throw new UncheckedDocletException(
2408                    new SimpleDocletException(text, jsf));
2409            }
2410        }
2411    }
2412
2413    public DocCommentTree getDocCommentTree(Element element) {
2414        CommentHelper ch = commentHelperCache.get(element);
2415        if (ch != null) {
2416            return ch.dcTree;
2417        }
2418        DocCommentTree dcTree = getDocCommentTree0(element);
2419        if (dcTree != null) {
2420            commentHelperCache.put(element, new CommentHelper(configuration,
2421                element, getTreePath(element), dcTree));
2422        }
2423        return dcTree;
2424    }
2425
2426    public List<? extends DocTree> getPreamble(Element element) {
2427        DocCommentTree docCommentTree = getDocCommentTree(element);
2428        return docCommentTree == null
2429            ? List.of()
2430            : docCommentTree.getPreamble();
2431    }
2432
2433    public List<? extends DocTree> getFullBody(Element element) {
2434        DocCommentTree docCommentTree = getDocCommentTree(element);
2435        return (docCommentTree == null)
2436            ? List.of()
2437            : docCommentTree.getFullBody();
2438    }
2439
2440    public List<? extends DocTree> getBody(Element element) {
2441        DocCommentTree docCommentTree = getDocCommentTree(element);
2442        return (docCommentTree == null)
2443            ? List.of()
2444            : docCommentTree.getFullBody();
2445    }
2446
2447    public List<? extends DeprecatedTree> getDeprecatedTrees(Element element) {
2448        return getBlockTags(element, DEPRECATED, DeprecatedTree.class);
2449    }
2450
2451    public List<? extends ProvidesTree> getProvidesTrees(Element element) {
2452        return getBlockTags(element, PROVIDES, ProvidesTree.class);
2453    }
2454
2455    public List<? extends SeeTree> getSeeTrees(Element element) {
2456        return getBlockTags(element, SEE, SeeTree.class);
2457    }
2458
2459    public List<? extends SerialTree> getSerialTrees(Element element) {
2460        return getBlockTags(element, SERIAL, SerialTree.class);
2461    }
2462
2463    public List<? extends SerialFieldTree>
2464            getSerialFieldTrees(VariableElement field) {
2465        return getBlockTags(field, DocTree.Kind.SERIAL_FIELD,
2466            SerialFieldTree.class);
2467    }
2468
2469    public List<? extends SpecTree> getSpecTrees(Element element) {
2470        return getBlockTags(element, SPEC, SpecTree.class);
2471    }
2472
2473    public List<ThrowsTree> getThrowsTrees(Element element) {
2474        return getBlockTags(element,
2475            t -> switch (t.getKind()) {
2476            case EXCEPTION, THROWS -> true;
2477            default -> false;
2478            },
2479            ThrowsTree.class);
2480    }
2481
2482    public List<ParamTree> getTypeParamTrees(Element element) {
2483        return getParamTrees(element, true);
2484    }
2485
2486    public List<ParamTree> getParamTrees(Element element) {
2487        return getParamTrees(element, false);
2488    }
2489
2490    private List<ParamTree> getParamTrees(Element element,
2491            boolean isTypeParameters) {
2492        return getBlockTags(element,
2493            t -> t.getKind() == PARAM
2494                && ((ParamTree) t).isTypeParameter() == isTypeParameters,
2495            ParamTree.class);
2496    }
2497
2498    public List<? extends ReturnTree> getReturnTrees(Element element) {
2499        return getBlockTags(element, RETURN, ReturnTree.class);
2500    }
2501
2502    public List<? extends UsesTree> getUsesTrees(Element element) {
2503        return getBlockTags(element, USES, UsesTree.class);
2504    }
2505
2506    public List<? extends DocTree> getFirstSentenceTrees(Element element) {
2507        DocCommentTree dcTree = getDocCommentTree(element);
2508        if (dcTree == null) {
2509            return List.of();
2510        }
2511        return new ArrayList<>(dcTree.getFirstSentence());
2512    }
2513
2514    public ModuleElement containingModule(Element e) {
2515        // TODO: remove this short-circuit after JDK-8302545 has been fixed
2516        // or --ignore-source-errors has been removed
2517        if (e.getKind() == ElementKind.PACKAGE
2518            && e.getEnclosingElement() == null) {
2519            return null;
2520        }
2521        return elementUtils.getModuleOf(e);
2522    }
2523
2524    public PackageElement containingPackage(Element e) {
2525        // TODO: remove this short-circuit after JDK-8302545 has been fixed
2526        // or --ignore-source-errors has been removed
2527        if (e.getKind() == ElementKind.PACKAGE) {
2528            return (PackageElement) e;
2529        }
2530        return elementUtils.getPackageOf(e);
2531    }
2532
2533    /**
2534     * A memory-sensitive cache for {@link CommentHelper} objects,
2535     * which are expensive to compute.
2536     */
2537    private static class CommentHelperCache {
2538
2539        private final Map<Element, SoftReference<CommentHelper>> map;
2540        private final Utils utils;
2541
2542        public CommentHelperCache(Utils utils) {
2543            map = new HashMap<>();
2544            this.utils = utils;
2545        }
2546
2547        public CommentHelper remove(Element key) {
2548            SoftReference<CommentHelper> value = map.remove(key);
2549            return value == null ? null : value.get();
2550        }
2551
2552        public CommentHelper put(Element key, CommentHelper value) {
2553            SoftReference<CommentHelper> prev
2554                = map.put(key, new SoftReference<>(value));
2555            return prev == null ? null : prev.get();
2556        }
2557
2558        public CommentHelper get(Element key) {
2559            SoftReference<CommentHelper> value = map.get(key);
2560            return value == null ? null : value.get();
2561        }
2562
2563        public CommentHelper computeIfAbsent(Element key) {
2564            SoftReference<CommentHelper> refValue = map.get(key);
2565            if (refValue != null) {
2566                CommentHelper value = refValue.get();
2567                if (value != null) {
2568                    return value;
2569                }
2570            }
2571            CommentHelper newValue = new CommentHelper(utils.configuration, key,
2572                utils.getTreePath(key),
2573                utils.getDocCommentTree(key));
2574            map.put(key, new SoftReference<>(newValue));
2575            return newValue;
2576        }
2577    }
2578
2579    /**
2580     * A container holding a pair of values (tuple).
2581     *
2582     * @param <K> the type of the first value
2583     * @param <L> the type of the second value
2584     */
2585    public static class Pair<K, L> {
2586        public final K first;
2587        public final L second;
2588
2589        public Pair(K first, L second) {
2590            this.first = first;
2591            this.second = second;
2592        }
2593
2594        @Override
2595        public String toString() {
2596            return first + ":" + second;
2597        }
2598    }
2599
2600    /**
2601     * Return the set of preview language features used to declare the given element.
2602     *
2603     * @param e the Element to check.
2604     * @return the set of preview language features used to declare the given element
2605     */
2606    public Set<DeclarationPreviewLanguageFeatures>
2607            previewLanguageFeaturesUsed(Element e) {
2608        return new HashSet<>();
2609    }
2610
2611    public enum DeclarationPreviewLanguageFeatures {
2612        NONE(List.of(""));
2613
2614        public final List<String> features;
2615
2616        DeclarationPreviewLanguageFeatures(List<String> features) {
2617            this.features = features;
2618        }
2619    }
2620
2621    public PreviewSummary declaredUsingPreviewAPIs(Element el) {
2622        if (el.asType().getKind() == ERROR) {
2623            // Can happen with undocumented --ignore-source-errors option
2624            return new PreviewSummary(Set.of(), Set.of(), Set.of());
2625        }
2626        List<TypeElement> usedInDeclaration
2627            = new ArrayList<>(annotations2Classes(el));
2628        switch (el.getKind()) {
2629        case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, RECORD -> {
2630            TypeElement te = (TypeElement) el;
2631            for (TypeParameterElement tpe : te.getTypeParameters()) {
2632                usedInDeclaration.addAll(types2Classes(tpe.getBounds()));
2633            }
2634            usedInDeclaration
2635                .addAll(types2Classes(List.of(te.getSuperclass())));
2636            usedInDeclaration.addAll(types2Classes(te.getInterfaces()));
2637            usedInDeclaration
2638                .addAll(types2Classes(te.getPermittedSubclasses()));
2639            usedInDeclaration.addAll(types2Classes(te.getRecordComponents()
2640                .stream().map(Element::asType).toList())); // TODO: annotations
2641                                                           // on record
2642                                                           // components???
2643        }
2644        case CONSTRUCTOR, METHOD -> {
2645            ExecutableElement ee = (ExecutableElement) el;
2646            for (TypeParameterElement tpe : ee.getTypeParameters()) {
2647                usedInDeclaration.addAll(types2Classes(tpe.getBounds()));
2648            }
2649            usedInDeclaration
2650                .addAll(types2Classes(List.of(ee.getReturnType())));
2651            usedInDeclaration
2652                .addAll(types2Classes(List.of(ee.getReceiverType())));
2653            usedInDeclaration.addAll(types2Classes(ee.getThrownTypes()));
2654            usedInDeclaration.addAll(types2Classes(ee.getParameters().stream()
2655                .map(VariableElement::asType).toList()));
2656            usedInDeclaration
2657                .addAll(annotationValue2Classes(ee.getDefaultValue()));
2658        }
2659        case FIELD, ENUM_CONSTANT, RECORD_COMPONENT -> {
2660            VariableElement ve = (VariableElement) el;
2661            usedInDeclaration.addAll(types2Classes(List.of(ve.asType())));
2662        }
2663        case MODULE, PACKAGE -> {
2664        }
2665        default -> throw new IllegalArgumentException(
2666            "Unexpected: " + el.getKind());
2667        }
2668
2669        Set<TypeElement> previewAPI = new HashSet<>();
2670        Set<TypeElement> reflectivePreviewAPI = new HashSet<>();
2671        Set<TypeElement> declaredUsingPreviewFeature = new HashSet<>();
2672
2673        for (TypeElement type : usedInDeclaration) {
2674            if (!isIncluded(type) && !configuration.extern.isExternal(type)) {
2675                continue;
2676            }
2677            if (isPreviewAPI(type)) {
2678                if (isReflectivePreviewAPI(type)) {
2679                    reflectivePreviewAPI.add(type);
2680                } else {
2681                    previewAPI.add(type);
2682                }
2683            }
2684            if (!previewLanguageFeaturesUsed(type).isEmpty()) {
2685                declaredUsingPreviewFeature.add(type);
2686            }
2687        }
2688
2689        return new PreviewSummary(previewAPI, reflectivePreviewAPI,
2690            declaredUsingPreviewFeature);
2691    }
2692
2693    private Collection<TypeElement>
2694            types2Classes(List<? extends TypeMirror> types) {
2695        List<TypeElement> result = new ArrayList<>();
2696        List<TypeMirror> todo = new ArrayList<>(types);
2697
2698        while (!todo.isEmpty()) {
2699            TypeMirror type = todo.remove(todo.size() - 1);
2700
2701            result.addAll(annotations2Classes(type));
2702
2703            if (type.getKind() == DECLARED) {
2704                DeclaredType dt = (DeclaredType) type;
2705                result.add((TypeElement) dt.asElement());
2706                todo.addAll(dt.getTypeArguments());
2707            }
2708        }
2709
2710        return result;
2711    }
2712
2713    private Collection<TypeElement>
2714            annotations2Classes(AnnotatedConstruct annotated) {
2715        List<TypeElement> result = new ArrayList<>();
2716
2717        for (AnnotationMirror am : annotated.getAnnotationMirrors()) {
2718            result.addAll(annotation2Classes(am));
2719        }
2720
2721        return result;
2722    }
2723
2724    private Collection<TypeElement> annotation2Classes(AnnotationMirror am) {
2725        List<TypeElement> result = new ArrayList<>();
2726
2727        result.addAll(types2Classes(List.of(am.getAnnotationType())));
2728        am.getElementValues()
2729            .values()
2730            .stream()
2731            .flatMap(av -> annotationValue2Classes(av).stream())
2732            .forEach(result::add);
2733
2734        return result;
2735    }
2736
2737    private Collection<TypeElement>
2738            annotationValue2Classes(AnnotationValue value) {
2739        if (value == null) {
2740            return List.of();
2741        }
2742
2743        List<TypeElement> result = new ArrayList<>();
2744
2745        value.accept(new SimpleAnnotationValueVisitor14<>() {
2746            @Override
2747            public Object visitArray(List<? extends AnnotationValue> vals,
2748                    Object p) {
2749                vals.stream()
2750                    .forEach(v -> v.accept(this, null));
2751                return super.visitArray(vals, p);
2752            }
2753
2754            @Override
2755            public Object visitAnnotation(AnnotationMirror a, Object p) {
2756                result.addAll(annotation2Classes(a));
2757                return super.visitAnnotation(a, p);
2758            }
2759
2760            @Override
2761            public Object visitType(TypeMirror t, Object p) {
2762                result.addAll(types2Classes(List.of(t)));
2763                return super.visitType(t, p);
2764            }
2765
2766        }, null);
2767
2768        return result;
2769    }
2770
2771    public static final class PreviewSummary {
2772        public final Set<TypeElement> previewAPI;
2773        public final Set<TypeElement> reflectivePreviewAPI;
2774        public final Set<TypeElement> declaredUsingPreviewFeature;
2775
2776        public PreviewSummary(Set<TypeElement> previewAPI,
2777                Set<TypeElement> reflectivePreviewAPI,
2778                Set<TypeElement> declaredUsingPreviewFeature) {
2779            this.previewAPI = previewAPI;
2780            this.reflectivePreviewAPI = reflectivePreviewAPI;
2781            this.declaredUsingPreviewFeature = declaredUsingPreviewFeature;
2782        }
2783
2784        @Override
2785        public String toString() {
2786            return "PreviewSummary{" + "previewAPI=" + previewAPI
2787                + ", reflectivePreviewAPI=" + reflectivePreviewAPI
2788                + ", declaredUsingPreviewFeature=" + declaredUsingPreviewFeature
2789                + '}';
2790        }
2791
2792    }
2793
2794    /**
2795     * Checks whether the given Element should be marked as a preview API.
2796     *
2797     * Note that if a type is marked as a preview, its members are not.
2798     *
2799     * @param el the element to check
2800     * @return true if and only if the given element should be marked as a preview API
2801     */
2802    public boolean isPreviewAPI(Element el) {
2803        boolean parentPreviewAPI = false;
2804        if (!isClassOrInterface(el)) {
2805            Element enclosing = el.getEnclosingElement();
2806            if (isClassOrInterface(enclosing)) {
2807                parentPreviewAPI
2808                    = configuration.workArounds.isPreviewAPI(enclosing);
2809            }
2810        }
2811        boolean previewAPI = configuration.workArounds.isPreviewAPI(el);
2812        return !parentPreviewAPI && previewAPI;
2813    }
2814
2815    /**
2816     * Checks whether the given Element should be marked as a reflective preview API.
2817     *
2818     * Note that if a type is marked as a preview, its members are not.
2819     *
2820     * @param el the element to check
2821     * @return true if and only if the given element should be marked
2822     *              as a reflective preview API
2823     */
2824    public boolean isReflectivePreviewAPI(Element el) {
2825        return isPreviewAPI(el)
2826            && configuration.workArounds.isReflectivePreviewAPI(el);
2827    }
2828
2829    /**
2830     * Return all flags for the given Element.
2831     *
2832     * @param el the element to test
2833     * @return the set of all the element's flags.
2834     */
2835    public Set<ElementFlag> elementFlags(Element el) {
2836        Set<ElementFlag> flags = EnumSet.noneOf(ElementFlag.class);
2837
2838        if (isDeprecated(el)) {
2839            flags.add(ElementFlag.DEPRECATED);
2840        }
2841
2842        if (previewFlagProvider.isPreview(el)) {
2843            flags.add(ElementFlag.PREVIEW);
2844        }
2845
2846        return flags;
2847    }
2848
2849    /**
2850     * An element can have flags that place it into some subcategories, like
2851     * being a preview or a deprecated element.
2852     */
2853    public enum ElementFlag {
2854        DEPRECATED,
2855        PREVIEW
2856    }
2857
2858    private boolean isClassOrInterface(Element el) {
2859        return el != null
2860            && (el.getKind().isClass() || el.getKind().isInterface());
2861    }
2862
2863    private boolean hasNoPreviewAnnotation(Element el) {
2864        return el.getAnnotationMirrors()
2865            .stream()
2866            .anyMatch(am -> "jdk.internal.javac.NoPreview"
2867                .equals(getQualifiedTypeName(am.getAnnotationType())));
2868    }
2869
2870    private PreviewFlagProvider previewFlagProvider
2871        = new PreviewFlagProvider() {
2872            @Override
2873            public boolean isPreview(Element el) {
2874                PreviewSummary previewAPIs = declaredUsingPreviewAPIs(el);
2875                Element enclosing = el.getEnclosingElement();
2876
2877                return (!previewLanguageFeaturesUsed(el).isEmpty()
2878                    || configuration.workArounds.isPreviewAPI(el)
2879                    || (!isClassOrInterface(el) && isClassOrInterface(enclosing)
2880                        && configuration.workArounds.isPreviewAPI(enclosing))
2881                    || !previewAPIs.previewAPI.isEmpty()
2882                    || !previewAPIs.reflectivePreviewAPI.isEmpty()
2883                    || !previewAPIs.declaredUsingPreviewFeature.isEmpty())
2884                    && !hasNoPreviewAnnotation(el);
2885            }
2886        };
2887
2888    public PreviewFlagProvider
2889            setPreviewFlagProvider(PreviewFlagProvider provider) {
2890        Objects.requireNonNull(provider);
2891        PreviewFlagProvider old = previewFlagProvider;
2892        previewFlagProvider = provider;
2893        return old;
2894    }
2895
2896    public interface PreviewFlagProvider {
2897        boolean isPreview(Element el);
2898    }
2899
2900    public DocFinder docFinder() {
2901        return docFinder;
2902    }
2903
2904    private DocFinder newDocFinder() {
2905        return new DocFinder(e -> {
2906            var i = overriddenMethod(e);
2907            return i == null ? null : i.overriddenMethod();
2908        }, this::implementedMethods);
2909    }
2910
2911    private Iterable<ExecutableElement> implementedMethods(
2912            ExecutableElement originalMethod, ExecutableElement m) {
2913        var type = configuration.utils.getEnclosingTypeElement(m);
2914        return configuration.getVisibleMemberTable(type)
2915            .getImplementedMethods(originalMethod);
2916    }
2917}