001/*
002 * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package org.jdrupes.mdoclet.internal.doclets.toolkit;
027
028import java.net.URI;
029import java.util.ArrayList;
030import java.util.HashMap;
031import java.util.List;
032import java.util.regex.Matcher;
033import java.util.regex.Pattern;
034
035import javax.lang.model.element.Element;
036import javax.lang.model.element.ExecutableElement;
037import javax.lang.model.element.Name;
038import javax.lang.model.element.PackageElement;
039import javax.lang.model.element.RecordComponentElement;
040import javax.lang.model.element.TypeElement;
041import javax.lang.model.element.VariableElement;
042import javax.lang.model.util.Elements;
043import javax.tools.FileObject;
044import javax.tools.JavaFileObject;
045import javax.tools.SimpleJavaFileObject;
046
047import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils;
048import org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable;
049
050import com.sun.source.doctree.AttributeTree;
051import com.sun.source.doctree.DocCommentTree;
052import com.sun.source.doctree.DocTree;
053import com.sun.source.doctree.IdentifierTree;
054import com.sun.source.doctree.LiteralTree;
055import com.sun.source.doctree.ParamTree;
056import com.sun.source.doctree.ReferenceTree;
057import com.sun.source.doctree.ReturnTree;
058import com.sun.source.doctree.SinceTree;
059import com.sun.source.doctree.TextTree;
060import com.sun.source.doctree.UnknownBlockTagTree;
061import com.sun.source.util.DocTreeFactory;
062import com.sun.source.util.DocTreePath;
063import com.sun.source.util.DocTrees;
064import com.sun.source.util.TreePath;
065import com.sun.tools.javac.util.DefinedBy;
066import com.sun.tools.javac.util.DefinedBy.Api;
067
068/**
069 * A utility class for handling documentation comments.
070 */
071public class CommentUtils {
072
073    final BaseConfiguration configuration;
074    final Utils utils;
075    final Resources resources;
076    final DocTreeFactory treeFactory;
077    final DocTrees trees;
078    final Elements elementUtils;
079
080    /**
081     * A map for storing automatically generated comments for various
082     * elements, such as mandated elements (Enum.values, Enum.valueOf, etc)
083     * and JavaFX properties.
084     *
085     * @see Utils#dcTreeCache
086     */
087    final HashMap<Element, DocCommentInfo> dcInfoMap = new HashMap<>();
088
089    protected CommentUtils(BaseConfiguration configuration) {
090        this.configuration = configuration;
091        utils = configuration.utils;
092        resources = configuration.getDocResources();
093        trees = configuration.docEnv.getDocTrees();
094        treeFactory = trees.getDocTreeFactory();
095        elementUtils = configuration.docEnv.getElementUtils();
096    }
097
098    public List<? extends DocTree>
099            makePropertyDescriptionTree(List<? extends DocTree> content) {
100        Name name = elementUtils.getName("propertyDescription");
101        return List.of(treeFactory.newUnknownBlockTagTree(name, content));
102    }
103
104    public LiteralTree makeCodeTree(String text) {
105        return treeFactory.newCodeTree(makeTextTree(text));
106    }
107
108    public List<? extends DocTree> makeFirstSentenceTree(String content) {
109        return List.of(treeFactory.newTextTree(content));
110    }
111
112    public ParamTree makeParamTree(Name name,
113            List<? extends DocTree> description) {
114        return treeFactory.newParamTree(false,
115            treeFactory.newIdentifierTree(name), description);
116    }
117
118    public ReturnTree makeReturnTree(List<? extends DocTree> description) {
119        return treeFactory.newReturnTree(false, description);
120    }
121
122    public DocTree makeSeeTree(String sig, Element e) {
123        return treeFactory
124            .newSeeTree(List.of(treeFactory.newReferenceTree(sig)));
125    }
126
127    public TextTree makeTextTree(String content) {
128        return treeFactory.newTextTree(content);
129    }
130
131    public TextTree makeTextTreeForResource(String key) {
132        return treeFactory.newTextTree(resources.getText(key));
133    }
134
135    /**
136     * Parses a string, looking for simple embedded HTML.
137     * @param s the string
138     * @return the list of parsed {@code DocTree} nodes
139     */
140    private List<DocTree> parse(String s) {
141        List<DocTree> list = null;
142        Pattern p = Pattern.compile("(?i)<(/)?([a-z0-9]+)(/)?>");
143        Matcher m = p.matcher(s);
144        int start = 0;
145        while (m.find()) {
146            if (list == null) {
147                list = new ArrayList<>();
148            }
149            if (m.start() > 0) {
150                list.add(
151                    treeFactory.newTextTree(s.substring(start, m.start())));
152            }
153            Name name = elementUtils.getName(m.group(2));
154            list.add(m.group(1) == null
155                ? treeFactory.newStartElementTree(name, List.of(),
156                    m.group(3) != null)
157                : treeFactory.newEndElementTree(name));
158            start = m.end();
159        }
160        if (list == null) {
161            return List.of(treeFactory.newTextTree(s));
162        } else {
163            if (start < s.length()) {
164                list.add(
165                    treeFactory.newTextTree(s.substring(start, s.length())));
166            }
167            return list;
168        }
169    }
170
171    public void setEnumValuesTree(ExecutableElement ee) {
172        List<DocTree> fullBody = new ArrayList<>();
173        fullBody.add(treeFactory
174            .newTextTree(resources.getText("doclet.enum_values_doc.fullbody")));
175
176        List<DocTree> descriptions = new ArrayList<>();
177        descriptions.add(treeFactory
178            .newTextTree(resources.getText("doclet.enum_values_doc.return")));
179
180        List<DocTree> tags = new ArrayList<>();
181        tags.add(treeFactory.newReturnTree(descriptions));
182        DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags);
183        dcInfoMap.put(ee, new DocCommentInfo(null, docTree));
184    }
185
186    public void setEnumValueOfTree(ExecutableElement ee) {
187        List<DocTree> fullBody
188            = parse(resources.getText("doclet.enum_valueof_doc.fullbody"));
189
190        List<DocTree> tags = new ArrayList<>();
191
192        List<DocTree> paramDescs = new ArrayList<>();
193        paramDescs.add(treeFactory.newTextTree(
194            resources.getText("doclet.enum_valueof_doc.param_name")));
195        java.util.List<? extends VariableElement> parameters
196            = ee.getParameters();
197        VariableElement param = parameters.get(0);
198        IdentifierTree id = treeFactory.newIdentifierTree(
199            elementUtils.getName(param.getSimpleName().toString()));
200        tags.add(treeFactory.newParamTree(false, id, paramDescs));
201
202        List<DocTree> returnDescs = new ArrayList<>();
203        returnDescs.add(treeFactory
204            .newTextTree(resources.getText("doclet.enum_valueof_doc.return")));
205        tags.add(treeFactory.newReturnTree(returnDescs));
206
207        List<DocTree> throwsDescs = new ArrayList<>();
208        throwsDescs.add(treeFactory.newTextTree(
209            resources.getText("doclet.enum_valueof_doc.throws_ila")));
210
211        ReferenceTree ref = treeFactory
212            .newReferenceTree("java.lang.IllegalArgumentException");
213        tags.add(treeFactory.newThrowsTree(ref, throwsDescs));
214
215        throwsDescs = new ArrayList<>();
216        throwsDescs.add(treeFactory.newTextTree(
217            resources.getText("doclet.enum_valueof_doc.throws_npe")));
218
219        ref = treeFactory.newReferenceTree("java.lang.NullPointerException");
220        tags.add(treeFactory.newThrowsTree(ref, throwsDescs));
221
222        DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags);
223
224        dcInfoMap.put(ee, new DocCommentInfo(null, docTree));
225    }
226
227    /**
228     * Generates the description for the canonical constructor for a record.
229     * @param ee the constructor
230     */
231    public void setRecordConstructorTree(ExecutableElement ee) {
232        TypeElement te = utils.getEnclosingTypeElement(ee);
233
234        List<DocTree> fullBody = makeDescriptionWithName(
235            "doclet.record_constructor_doc.fullbody", te.getSimpleName());
236
237        List<DocTree> tags = new ArrayList<>();
238        for (VariableElement param : ee.getParameters()) {
239            Name name = param.getSimpleName();
240            IdentifierTree id = treeFactory.newIdentifierTree(name);
241            tags.add(treeFactory.newParamTree(false, id,
242                makeDescriptionWithComponent(
243                    "doclet.record_constructor_doc.param_name", te, name)));
244        }
245
246        DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags);
247        dcInfoMap.put(ee, new DocCommentInfo(null, docTree));
248    }
249
250    /**
251     * Generates the description for the standard {@code equals} method for a record.
252     * @param ee the {@code equals} method
253     */
254    public void setRecordEqualsTree(ExecutableElement ee) {
255        List<DocTree> fullBody = new ArrayList<>();
256        add(fullBody, "doclet.record_equals_doc.fullbody.head");
257        fullBody.add(treeFactory.newTextTree(" "));
258
259        List<? extends RecordComponentElement> comps
260            = ((TypeElement) ee.getEnclosingElement()).getRecordComponents();
261        boolean hasPrimitiveComponents
262            = comps.stream().anyMatch(e -> e.asType().getKind().isPrimitive());
263        boolean hasReferenceComponents
264            = comps.stream().anyMatch(e -> !e.asType().getKind().isPrimitive());
265        if (hasPrimitiveComponents && hasReferenceComponents) {
266            add(fullBody, "doclet.record_equals_doc.fullbody.tail.both");
267        } else if (hasPrimitiveComponents) {
268            add(fullBody, "doclet.record_equals_doc.fullbody.tail.primitive");
269        } else if (hasReferenceComponents) {
270            add(fullBody, "doclet.record_equals_doc.fullbody.tail.reference");
271        }
272        Name paramName = ee.getParameters().get(0).getSimpleName();
273        IdentifierTree id = treeFactory.newIdentifierTree(paramName);
274        List<DocTree> paramDesc = makeDescriptionWithName(
275            "doclet.record_equals_doc.param_name", paramName);
276        DocTree paramTree = treeFactory.newParamTree(false, id, paramDesc);
277
278        DocTree returnTree = treeFactory.newReturnTree(
279            makeDescriptionWithName("doclet.record_equals_doc.return",
280                paramName));
281
282        TreePath treePath = utils.getTreePath(ee.getEnclosingElement());
283        DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody,
284            List.of(paramTree, returnTree));
285        dcInfoMap.put(ee, new DocCommentInfo(treePath, docTree));
286    }
287
288    private void add(List<DocTree> contents, String resourceKey) {
289        // Special case to allow '{@link ...}' to appear in the string.
290        // A less general case would be to detect literal use of Object.equals
291        // A more general case would be to allow access to DocCommentParser
292        // somehow
293        String body = resources.getText(resourceKey);
294        Pattern p = Pattern.compile("\\{@link (\\S*)(.*)}");
295        Matcher m = p.matcher(body);
296        int start = 0;
297        while (m.find(start)) {
298            if (m.start() > start) {
299                contents.addAll(parse(body.substring(start, m.start())));
300            }
301            ReferenceTree refTree = treeFactory.newReferenceTree(m.group(1));
302            List<DocTree> descr = parse(m.group(2).trim());
303            contents.add(treeFactory.newLinkTree(refTree, descr));
304            start = m.end();
305        }
306        if (start < body.length()) {
307            contents.addAll(parse(body.substring(start)));
308        }
309    }
310
311    /**
312     * Generates the description for the standard {@code hashCode} method for a record.
313     * @param ee the {@code hashCode} method
314     */
315    public void setRecordHashCodeTree(ExecutableElement ee) {
316        List<DocTree> fullBody = List
317            .of(makeTextTreeForResource("doclet.record_hashCode_doc.fullbody"));
318
319        DocTree returnTree = treeFactory.newReturnTree(
320            List.of(
321                makeTextTreeForResource("doclet.record_hashCode_doc.return")));
322
323        DocCommentTree docTree
324            = treeFactory.newDocCommentTree(fullBody, List.of(returnTree));
325        dcInfoMap.put(ee, new DocCommentInfo(null, docTree));
326    }
327
328    /**
329     * Generates the description for the standard {@code toString} method for a record.
330     * @param ee the {@code toString} method
331     */
332    public void setRecordToStringTree(ExecutableElement ee) {
333        List<DocTree> fullBody = List.of(
334            treeFactory.newTextTree(
335                resources.getText("doclet.record_toString_doc.fullbody")));
336
337        DocTree returnTree = treeFactory.newReturnTree(List.of(
338            treeFactory.newTextTree(
339                resources.getText("doclet.record_toString_doc.return"))));
340
341        DocCommentTree docTree
342            = treeFactory.newDocCommentTree(fullBody, List.of(returnTree));
343        dcInfoMap.put(ee, new DocCommentInfo(null, docTree));
344    }
345
346    /**
347     * Generates the description for the accessor method for a state component of a record.
348     * @param ee the accessor method
349     */
350    public void setRecordAccessorTree(ExecutableElement ee) {
351        TypeElement te = utils.getEnclosingTypeElement(ee);
352
353        List<DocTree> fullBody = makeDescriptionWithComponent(
354            "doclet.record_accessor_doc.fullbody", te, ee.getSimpleName());
355
356        DocTree returnTree = treeFactory.newReturnTree(
357            makeDescriptionWithComponent("doclet.record_accessor_doc.return",
358                te, ee.getSimpleName()));
359
360        DocCommentTree docTree
361            = treeFactory.newDocCommentTree(fullBody, List.of(returnTree));
362        dcInfoMap.put(ee, new DocCommentInfo(null, docTree));
363    }
364
365    /**
366     * Generates the description for the field for a state component of a record.
367     * @param ve the field
368     */
369    public void setRecordFieldTree(VariableElement ve) {
370        TypeElement te = utils.getEnclosingTypeElement(ve);
371
372        List<DocTree> fullBody = makeDescriptionWithComponent(
373            "doclet.record_field_doc.fullbody", te, ve.getSimpleName());
374
375        DocCommentTree docTree
376            = treeFactory.newDocCommentTree(fullBody, List.of());
377        dcInfoMap.put(ve, new DocCommentInfo(null, docTree));
378    }
379
380    /**
381     * Update the property method, property setter and/or property getter
382     * comment text so that it contains the documentation from
383     * the preferred property description (field or property method).
384     * The method adds the leading sentence, copied documentation including
385     * the defaultValue tag and the {@code @see} tags if the appropriate methods are
386     * available.
387     *
388     * @param member the member which is to be augmented
389     * @param property the element containing the preferred property description
390     */
391    public void updatePropertyMethodComment(ExecutableElement member,
392            Element property) {
393        final String memberName = member.getSimpleName().toString();
394        final boolean isSetter = memberName.startsWith("set");
395        final boolean isGetter
396            = memberName.startsWith("get") || memberName.startsWith("is");
397
398        List<DocTree> fullBody = new ArrayList<>();
399        List<DocTree> blockTags = new ArrayList<>();
400
401        if (isGetter || isSetter) {
402            DocTree propName = makeCodeTree(utils.propertyName(member));
403
404            if (isGetter) {
405                // Set the body and @return
406                fullBody.addAll(
407                    getComment("doclet.PropertyGetterWithName", propName));
408                blockTags.add(makeReturnTree(
409                    getComment("doclet.PropertyGetterReturn", propName)));
410            }
411
412            if (isSetter) {
413                // Set the body and @param
414                fullBody.addAll(
415                    getComment("doclet.PropertySetterWithName", propName));
416                VariableElement arg0 = member.getParameters().get(0);
417                blockTags.add(makeParamTree(arg0.getSimpleName(),
418                    getComment("doclet.PropertySetterParam", propName)));
419            }
420
421            // Set the @propertyDescription
422            List<? extends DocTree> propertyTags = utils.getBlockTags(property,
423                t -> (t instanceof UnknownBlockTagTree tree)
424                    && (tree.getTagName().equals("propertyDescription")));
425            if (propertyTags.isEmpty()) {
426                List<? extends DocTree> comment = utils.getFullBody(property);
427                blockTags.addAll(makePropertyDescriptionTree(comment));
428            }
429        } else {
430            // property method
431            fullBody.addAll(utils.getFullBody(property));
432
433            // Set the @return
434            DocTree propName
435                = makeCodeTree(configuration.propertyUtils.getBaseName(member));
436            List<? extends DocTree> returnTags
437                = utils.getBlockTags(property, DocTree.Kind.RETURN);
438            if (returnTags.isEmpty()) {
439                blockTags.add(makeReturnTree(
440                    getComment("doclet.PropertyMethodReturn", propName)));
441            } else {
442                blockTags.addAll(returnTags);
443            }
444        }
445
446        // copy certain tags
447        List<? extends SinceTree> sinceTags
448            = utils.getBlockTags(property, DocTree.Kind.SINCE, SinceTree.class);
449        blockTags.addAll(sinceTags);
450
451        List<? extends DocTree> bTags = utils.getBlockTags(property,
452            t -> (t instanceof UnknownBlockTagTree tree)
453                && (tree.getTagName().equals("defaultValue")));
454        blockTags.addAll(bTags);
455
456        // add @see tags
457        TypeElement te = (TypeElement) member.getEnclosingElement();
458        VisibleMemberTable vmt = configuration.getVisibleMemberTable(te);
459        ExecutableElement getter = vmt.getPropertyGetter(member);
460        ExecutableElement setter = vmt.getPropertySetter(member);
461        ExecutableElement propMethod = vmt.getPropertyMethod(member);
462
463        if (getter != null && getter != member) {
464            String sig = "#" + getter.getSimpleName() + "()";
465            blockTags.add(makeSeeTree(sig, getter));
466        }
467
468        if (setter != null && setter != member) {
469            VariableElement param = setter.getParameters().get(0);
470            StringBuilder sb = new StringBuilder("#");
471            sb.append(setter.getSimpleName());
472            if (!utils.isTypeVariable(param.asType())) {
473                sb.append("(")
474                    .append(utils.getTypeSignature(param.asType(), false, true))
475                    .append(")");
476            }
477            blockTags.add(makeSeeTree(sb.toString(), setter));
478        }
479
480        if (propMethod != member) {
481            String sig = "#" + propMethod.getSimpleName() + "()";
482            blockTags.add(makeSeeTree(sig, propMethod));
483        }
484
485        setDocCommentTree(member, fullBody, blockTags);
486    }
487
488    /**
489     * Creates a description that contains a reference to a state component of a record.
490     * The description is looked up as a resource, and should contain {@code {0}} where the
491     * reference to the component is to be inserted. The reference will be a link if the
492     * doc comment for the record has a {@code @param} tag for the component.
493     * @param key the resource key for the description
494     * @param elem the record element
495     * @param component the name of the component
496     * @return the description
497     */
498    private List<DocTree> makeDescriptionWithComponent(String key,
499            TypeElement elem, Name component) {
500        List<DocTree> result = new ArrayList<>();
501        String text = resources.getText(key);
502        int index = text.indexOf("{0}");
503        result.add(treeFactory.newTextTree(text.substring(0, index)));
504        Name A = elementUtils.getName("a");
505        Name CODE = elementUtils.getName("code");
506        Name HREF = elementUtils.getName("href");
507        List<DocTree> code = List.of(
508            treeFactory.newStartElementTree(CODE, List.of(), false),
509            treeFactory.newTextTree(component.toString()),
510            treeFactory.newEndElementTree(CODE));
511        if (hasParamForComponent(elem, component)) {
512            DocTree href = treeFactory.newAttributeTree(HREF,
513                AttributeTree.ValueKind.DOUBLE,
514                List.of(treeFactory.newTextTree("#param-" + component)));
515            result
516                .add(treeFactory.newStartElementTree(A, List.of(href), false));
517            result.addAll(code);
518            result.add(treeFactory.newEndElementTree(A));
519        } else {
520            result.addAll(code);
521        }
522        result.add(treeFactory.newTextTree(text.substring(index + 3)));
523        return result;
524    }
525
526    /**
527     * Returns whether or not the doc comment for a record contains an {@code @param}}
528     * for a state component of the record.
529     * @param elem the record element
530     * @param component the name of the component
531     * @return whether or not there is a {@code @param}} for the component
532     */
533    private boolean hasParamForComponent(TypeElement elem, Name component) {
534        DocCommentTree elemComment = utils.getDocCommentTree(elem);
535        if (elemComment == null) {
536            return false;
537        }
538
539        for (DocTree t : elemComment.getBlockTags()) {
540            if (t instanceof ParamTree pt
541                && pt.getName().getName() == component) {
542                return true;
543            }
544        }
545
546        return false;
547    }
548
549    /**
550     * Creates a description that contains the simple name of a program element
551     * The description is looked up as a resource, and should contain {@code {0}} where the
552     * name is to be inserted.
553     * @param key the resource key for the description
554     * @param name the name
555     * @return the description
556     */
557    private List<DocTree> makeDescriptionWithName(String key, Name name) {
558        String text = resources.getText(key);
559        int index = text.indexOf("{0}");
560        if (index == -1) {
561            return parse(text);
562        } else {
563            Name CODE = elementUtils.getName("code");
564            var list = new ArrayList<DocTree>();
565            list.addAll(parse(text.substring(0, index)));
566            list.add(treeFactory.newStartElementTree(CODE, List.of(), false));
567            list.add(treeFactory.newTextTree(name.toString()));
568            list.add(treeFactory.newEndElementTree(CODE));
569            list.addAll(parse(text.substring(index + 3)));
570            return list;
571        }
572    }
573
574    /**
575     * {@return a list containing the string for a given key in the doclet's
576     * resources, formatted with given arguments}
577     *
578     * @param key the key for the desired string
579     * @param o0  string or DocTree argument to be formatted into the result
580     */
581    public List<? extends DocTree> getComment(String key, Object o0) {
582        return getComment(key, o0, null, null);
583    }
584
585    /**
586     * {@return a list containing the string for a given key in the doclet's
587     * resources, formatted with given arguments}
588     *
589     * @param key the key for the desired strings
590     * @param o0  string or a DocTree argument to be formatted into the result
591     * @param o1  string or a DocTree argument to be formatted into the result
592     * @param o2  string or a DocTree argument to be formatted into the result
593     */
594    public List<? extends DocTree> getComment(String key, Object o0, Object o1,
595            Object o2) {
596        List<DocTree> l = new ArrayList<>();
597        Pattern p = Pattern.compile("\\{([012])\\}");
598        String text = resources.getText(key);
599        Matcher m = p.matcher(text);
600        int start = 0;
601        while (m.find(start)) {
602            l.add(makeTextTree(text.substring(start, m.start())));
603
604            Object o = null;
605            switch (m.group(1).charAt(0)) {
606            case '0':
607                o = o0;
608                break;
609            case '1':
610                o = o1;
611                break;
612            case '2':
613                o = o2;
614                break;
615            }
616
617            if (o == null) {
618                l.add(makeTextTree("{" + m.group(1) + "}"));
619            } else if (o instanceof String str) {
620                l.add(makeTextTree(str));
621            } else if (o instanceof DocTree t) {
622                l.add(t);
623            }
624
625            start = m.end();
626        }
627
628        l.add(makeTextTree(text.substring(start)));
629        return l;
630    }
631
632    /*
633     * Returns the TreePath/DocCommentTree info that has been generated for an
634     * element.
635     * 
636     * @param e the element
637     * 
638     * @return the info object containing the tree path and doc comment
639     */
640    // "synthetic" is not the best word here, and should not be confused with
641    // synthetic elements
642    public DocCommentInfo getSyntheticCommentInfo(Element e) {
643        return dcInfoMap.get(e);
644    }
645
646    /*
647     * Returns the TreePath/DocCommentTree info for HTML sources.
648     */
649    public DocCommentInfo getHtmlCommentInfo(Element e) {
650        FileObject fo = null;
651        PackageElement pe = null;
652        switch (e.getKind()) {
653        case OTHER:
654            if (e instanceof DocletElement de) {
655                fo = de.getFileObject();
656                pe = de.getPackageElement();
657            }
658            break;
659        case PACKAGE:
660            pe = (PackageElement) e;
661            fo = configuration.workArounds.getJavaFileObject(pe);
662            break;
663        default:
664            return null;
665        }
666        if (fo == null) {
667            return null;
668        }
669
670        DocCommentTree dcTree = trees.getDocCommentTree(fo);
671        if (dcTree == null) {
672            return null;
673        }
674        DocTreePath treePath = trees.getDocTreePath(fo, pe);
675        return new DocCommentInfo(treePath.getTreePath(), dcTree);
676    }
677
678    public DocCommentTree parse(URI uri, String text) {
679        return trees.getDocCommentTree(new SimpleJavaFileObject(
680            uri, JavaFileObject.Kind.SOURCE) {
681            @Override
682            @DefinedBy(Api.COMPILER)
683            public CharSequence getCharContent(boolean ignoreEncoding) {
684                return text;
685            }
686        });
687    }
688
689    public DocCommentInfo setDocCommentTree(Element element,
690            List<? extends DocTree> fullBody,
691            List<? extends DocTree> blockTags) {
692        DocCommentTree docTree
693            = treeFactory.newDocCommentTree(fullBody, blockTags);
694        return setDocCommentInfo(element, new DocCommentInfo(null, docTree));
695    }
696
697    public DocCommentInfo setDocCommentInfo(Element element,
698            DocCommentInfo dci) {
699        DocCommentInfo prev = dcInfoMap.put(element, dci);
700        // A method having null comment (no comment) that might need to be
701        // replaced
702        // with a generated comment, remove such a comment from the cache.
703        utils.removeCommentHelper(element);
704        return prev;
705    }
706
707    /**
708     * Info about a doc comment:
709     *   the position in the enclosing AST, and
710     *   the parsed doc comment itself.
711     *
712     * The position in the AST is {@code null} for automatically generated comments,
713     * such as for {@code Enum.values}, {@code Enum.valuesOf}, and JavaFX properties.
714     */
715    public static class DocCommentInfo {
716        /**
717         * The position of the comment in the enclosing AST, or {@code null}
718         * for automatically generated comments.
719         */
720        public final TreePath treePath;
721
722        /**
723         * The doc comment tree that is the root node of a parsed doc comment,
724         * or {@code null} if there is no comment.
725         */
726        public final DocCommentTree dcTree;
727
728        public DocCommentInfo(TreePath treePath, DocCommentTree dcTree) {
729            this.treePath = treePath;
730            this.dcTree = dcTree;
731        }
732    }
733}