001/*
002 * Copyright (c) 2015, 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 com.sun.source.doctree.AuthorTree;
029import com.sun.source.doctree.BlockTagTree;
030import com.sun.source.doctree.CommentTree;
031import com.sun.source.doctree.DeprecatedTree;
032import com.sun.source.doctree.DocCommentTree;
033import com.sun.source.doctree.DocTree;
034import com.sun.source.doctree.EscapeTree;
035import com.sun.source.doctree.IdentifierTree;
036import com.sun.source.doctree.InlineTagTree;
037import com.sun.source.doctree.LinkTree;
038import com.sun.source.doctree.LiteralTree;
039import com.sun.source.doctree.ParamTree;
040import com.sun.source.doctree.ProvidesTree;
041import com.sun.source.doctree.ReferenceTree;
042import com.sun.source.doctree.ReturnTree;
043import com.sun.source.doctree.SeeTree;
044import com.sun.source.doctree.SerialDataTree;
045import com.sun.source.doctree.SerialFieldTree;
046import com.sun.source.doctree.SerialTree;
047import com.sun.source.doctree.SinceTree;
048import com.sun.source.doctree.SpecTree;
049import com.sun.source.doctree.TextTree;
050import com.sun.source.doctree.ThrowsTree;
051import com.sun.source.doctree.UnknownBlockTagTree;
052import com.sun.source.doctree.UsesTree;
053import com.sun.source.doctree.ValueTree;
054import com.sun.source.doctree.VersionTree;
055import com.sun.source.util.DocTreePath;
056import com.sun.source.util.DocTrees;
057import com.sun.source.util.SimpleDocTreeVisitor;
058import com.sun.source.util.TreePath;
059
060import javax.lang.model.element.Element;
061import javax.lang.model.element.ElementKind;
062import javax.lang.model.element.ExecutableElement;
063import javax.lang.model.element.ModuleElement;
064import javax.lang.model.element.PackageElement;
065import javax.lang.model.element.TypeElement;
066import javax.lang.model.type.TypeKind;
067import javax.lang.model.type.TypeMirror;
068
069import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseConfiguration;
070import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFinder.Result;
071
072import java.util.List;
073import java.util.Optional;
074
075import static com.sun.source.doctree.DocTree.Kind.SEE;
076import static com.sun.source.doctree.DocTree.Kind.SERIAL_FIELD;
077
078/**
079 * A utility class.
080 */
081public class CommentHelper {
082    private final BaseConfiguration configuration;
083    public final TreePath path;
084    public final DocCommentTree dcTree;
085    public final Element element;
086
087    /**
088     * Creates a utility class to encapsulate the contextual information for a doc comment tree.
089     *
090     * @param configuration the configuration
091     * @param element       the element for which this is a doc comment
092     * @param path          the path for the element
093     * @param dcTree        the doc comment
094     */
095    public CommentHelper(BaseConfiguration configuration, Element element,
096            TreePath path, DocCommentTree dcTree) {
097        this.configuration = configuration;
098        this.element = element;
099        this.path = path;
100        this.dcTree = dcTree;
101    }
102
103    public String getTagName(DocTree dtree) {
104        return switch (dtree.getKind()) {
105        case AUTHOR, DEPRECATED, PARAM, PROVIDES, RETURN, SEE, SERIAL_DATA, SERIAL_FIELD, THROWS, UNKNOWN_BLOCK_TAG, USES, VERSION -> ((BlockTagTree) dtree)
106            .getTagName();
107        case UNKNOWN_INLINE_TAG -> ((InlineTagTree) dtree).getTagName();
108        case ERRONEOUS -> "erroneous";
109        default -> dtree.getKind().tagName;
110        };
111    }
112
113    public String getParameterName(ParamTree p) {
114        return p.getName().getName().toString();
115    }
116
117    Element getElement(ReferenceTree rtree) {
118        // We need to lookup type variables and other types
119        Utils utils = configuration.utils;
120        // likely a synthesized tree
121        if (path == null) {
122            // NOTE: this code path only supports module/package/type signatures
123            // and not member signatures. For more complete support,
124            // set a suitable path and avoid this branch.
125            TypeMirror symbol = utils.getSymbol(rtree.getSignature());
126            if (symbol == null) {
127                return null;
128            }
129            return configuration.docEnv.getTypeUtils().asElement(symbol);
130        }
131        DocTreePath docTreePath = getDocTreePath(rtree);
132        if (docTreePath == null) {
133            return null;
134        }
135        DocTrees doctrees = configuration.docEnv.getDocTrees();
136        // Workaround for JDK-8284193
137        // DocTrees.getElement(DocTreePath) returns javac-internal Symbols
138        var e = doctrees.getElement(docTreePath);
139        return e == null || e.getKind() == ElementKind.CLASS
140            && e.asType().getKind() != TypeKind.DECLARED ? null : e;
141    }
142
143    public TypeMirror getType(ReferenceTree rtree) {
144        DocTreePath docTreePath = getDocTreePath(rtree);
145        if (docTreePath != null) {
146            DocTrees docTrees = configuration.docEnv.getDocTrees();
147            return docTrees.getType(docTreePath);
148        }
149        return null;
150    }
151
152    public Element getException(ThrowsTree tt) {
153        return getElement(tt.getExceptionName());
154    }
155
156    public List<? extends DocTree> getDescription(DocTree dtree) {
157        return getTags(dtree);
158    }
159
160    public TypeElement getReferencedClass(Element e) {
161        Utils utils = configuration.utils;
162        if (e == null) {
163            return null;
164        } else if (utils.isTypeElement(e)) {
165            return (TypeElement) e;
166        } else if (!utils.isPackage(e) && !utils.isModule(e)) {
167            return utils.getEnclosingTypeElement(e);
168        }
169        return null;
170    }
171
172    public String getReferencedModuleName(String signature) {
173        if (signature == null || signature.contains("#")
174            || signature.contains("(")) {
175            return null;
176        }
177        int n = signature.indexOf("/");
178        return (n == -1) ? signature : signature.substring(0, n);
179    }
180
181    public Element getReferencedMember(DocTree dtree) {
182        Element e = getReferencedElement(dtree);
183        return getReferencedMember(e);
184    }
185
186    public Element getReferencedMember(Element e) {
187        Utils utils = configuration.utils;
188        if (e == null) {
189            return null;
190        }
191        return (utils.isExecutableElement(e) || utils.isVariableElement(e)) ? e
192            : null;
193    }
194
195    public String getReferencedFragment(String signature) {
196        if (signature == null) {
197            return null;
198        }
199        int n = signature.indexOf("#");
200        return (n == -1) ? null : signature.substring(n + 1);
201    }
202
203    public PackageElement getReferencedPackage(Element e) {
204        if (e != null) {
205            Utils utils = configuration.utils;
206            return utils.containingPackage(e);
207        }
208        return null;
209    }
210
211    public ModuleElement getReferencedModule(Element e) {
212        if (e != null && configuration.utils.isModule(e)) {
213            return (ModuleElement) e;
214        }
215        return null;
216    }
217
218    public List<? extends DocTree>
219            getFirstSentenceTrees(List<? extends DocTree> body) {
220        return configuration.docEnv.getDocTrees().getFirstSentence(body);
221    }
222
223    public Element getReferencedElement(DocTree dtree) {
224        return new ReferenceDocTreeVisitor<Element>() {
225            @Override
226            public Element visitReference(ReferenceTree node, Void p) {
227                return getElement(node);
228            }
229        }.visit(dtree, null);
230    }
231
232    public TypeMirror getReferencedType(DocTree dtree) {
233        return new ReferenceDocTreeVisitor<TypeMirror>() {
234            @Override
235            public TypeMirror visitReference(ReferenceTree node, Void p) {
236                return getType(node);
237            }
238        }.visit(dtree, null);
239    }
240
241    public TypeElement getServiceType(DocTree dtree) {
242        Element e = getReferencedElement(dtree);
243        if (e != null) {
244            Utils utils = configuration.utils;
245            return utils.isTypeElement(e) ? (TypeElement) e : null;
246        }
247        return null;
248    }
249
250    /**
251     * {@return the normalized signature from a {@code ReferenceTree}}
252     */
253    public String getReferencedSignature(DocTree dtree) {
254        return new ReferenceDocTreeVisitor<String>() {
255            @Override
256            public String visitReference(ReferenceTree node, Void p) {
257                return normalizeSignature(node.getSignature());
258            }
259        }.visit(dtree, null);
260    }
261
262    @SuppressWarnings("fallthrough")
263    private static String normalizeSignature(String sig) {
264        if (sig == null
265            || (!sig.contains(" ") && !sig.contains("\n")
266                && !sig.contains("\r") && !sig.endsWith("/"))) {
267            return sig;
268        }
269        StringBuilder sb = new StringBuilder();
270        char lastChar = 0;
271        for (int i = 0; i < sig.length(); i++) {
272            char ch = sig.charAt(i);
273            switch (ch) {
274            case '\n':
275            case '\r':
276            case '\f':
277            case '\t':
278            case ' ':
279                // Add at most one space char, or none if it isn't needed
280                switch (lastChar) {
281                case 0:
282                case '(':
283                case '<':
284                case ' ':
285                case '.':
286                    break;
287                default:
288                    sb.append(' ');
289                    lastChar = ' ';
290                    break;
291                }
292                break;
293            case ',':
294            case '>':
295            case ')':
296            case '.':
297                // Remove preceding space character
298                if (lastChar == ' ') {
299                    sb.setLength(sb.length() - 1);
300                }
301                // fallthrough
302            default:
303                sb.append(ch);
304                lastChar = ch;
305            }
306        }
307        // Delete trailing slash
308        if (lastChar == '/') {
309            sb.setLength(sb.length() - 1);
310        }
311        return sb.toString();
312    }
313
314    private static class ReferenceDocTreeVisitor<R>
315            extends SimpleDocTreeVisitor<R, Void> {
316        @Override
317        public R visitSee(SeeTree node, Void p) {
318            for (DocTree dt : node.getReference()) {
319                return visit(dt, null);
320            }
321            return null;
322        }
323
324        @Override
325        public R visitLink(LinkTree node, Void p) {
326            return visit(node.getReference(), null);
327        }
328
329        @Override
330        public R visitProvides(ProvidesTree node, Void p) {
331            return visit(node.getServiceType(), null);
332        }
333
334        @Override
335        public R visitValue(ValueTree node, Void p) {
336            return visit(node.getReference(), null);
337        }
338
339        @Override
340        public R visitSerialField(SerialFieldTree node, Void p) {
341            return visit(node.getType(), null);
342        }
343
344        @Override
345        public R visitUses(UsesTree node, Void p) {
346            return visit(node.getServiceType(), null);
347        }
348
349        @Override
350        protected R defaultAction(DocTree node, Void p) {
351            return null;
352        }
353    }
354
355    public List<? extends DocTree> getReference(DocTree dtree) {
356        return dtree.getKind() == SEE ? ((SeeTree) dtree).getReference() : null;
357    }
358
359    public IdentifierTree getName(DocTree dtree) {
360        return switch (dtree.getKind()) {
361        case PARAM -> ((ParamTree) dtree).getName();
362        case SERIAL_FIELD -> ((SerialFieldTree) dtree).getName();
363        default -> null;
364        };
365    }
366
367    public List<? extends DocTree> getTags(DocTree dtree) {
368        return new SimpleDocTreeVisitor<List<? extends DocTree>, Void>() {
369
370            private List<DocTree> asList(String content) {
371                return List.of(configuration.cmtUtils.makeTextTree(content));
372            }
373
374            @Override
375            public List<? extends DocTree> visitAuthor(AuthorTree node,
376                    Void p) {
377                return node.getName();
378            }
379
380            @Override
381            public List<? extends DocTree> visitComment(CommentTree node,
382                    Void p) {
383                return asList(node.getBody());
384            }
385
386            @Override
387            public List<? extends DocTree> visitDeprecated(DeprecatedTree node,
388                    Void p) {
389                return node.getBody();
390            }
391
392            @Override
393            public List<? extends DocTree> visitDocComment(DocCommentTree node,
394                    Void p) {
395                return node.getBody();
396            }
397
398            @Override
399            public List<? extends DocTree> visitEscape(EscapeTree node,
400                    Void p) {
401                return asList(node.getBody());
402            }
403
404            @Override
405            public List<? extends DocTree> visitLiteral(LiteralTree node,
406                    Void p) {
407                return asList(node.getBody().getBody());
408            }
409
410            @Override
411            public List<? extends DocTree> visitProvides(ProvidesTree node,
412                    Void p) {
413                return node.getDescription();
414            }
415
416            @Override
417            public List<? extends DocTree> visitSince(SinceTree node, Void p) {
418                return node.getBody();
419            }
420
421            @Override
422            public List<? extends DocTree> visitText(TextTree node, Void p) {
423                return asList(node.getBody());
424            }
425
426            @Override
427            public List<? extends DocTree> visitVersion(VersionTree node,
428                    Void p) {
429                return node.getBody();
430            }
431
432            @Override
433            public List<? extends DocTree> visitParam(ParamTree node, Void p) {
434                return node.getDescription();
435            }
436
437            @Override
438            public List<? extends DocTree> visitReturn(ReturnTree node,
439                    Void p) {
440                return node.getDescription();
441            }
442
443            @Override
444            public List<? extends DocTree> visitSee(SeeTree node, Void p) {
445                return node.getReference();
446            }
447
448            @Override
449            public List<? extends DocTree> visitSerial(SerialTree node,
450                    Void p) {
451                return node.getDescription();
452            }
453
454            @Override
455            public List<? extends DocTree> visitSerialData(SerialDataTree node,
456                    Void p) {
457                return node.getDescription();
458            }
459
460            @Override
461            public List<? extends DocTree>
462                    visitSerialField(SerialFieldTree node, Void p) {
463                return node.getDescription();
464            }
465
466            @Override
467            public List<? extends DocTree> visitSpec(SpecTree node, Void p) {
468                return node.getTitle();
469            }
470
471            @Override
472            public List<? extends DocTree> visitThrows(ThrowsTree node,
473                    Void p) {
474                return node.getDescription();
475            }
476
477            @Override
478            public List<? extends DocTree>
479                    visitUnknownBlockTag(UnknownBlockTagTree node, Void p) {
480                return node.getContent();
481            }
482
483            @Override
484            public List<? extends DocTree> visitUses(UsesTree node, Void p) {
485                return node.getDescription();
486            }
487
488            @Override
489            protected List<? extends DocTree> defaultAction(DocTree node,
490                    Void p) {
491                return List.of();
492            }
493        }.visit(dtree, null);
494    }
495
496    public List<? extends DocTree> getBody(DocTree dtree) {
497        return getTags(dtree);
498    }
499
500    public ReferenceTree getType(DocTree dtree) {
501        if (dtree.getKind() == SERIAL_FIELD) {
502            return ((SerialFieldTree) dtree).getType();
503        } else {
504            return null;
505        }
506    }
507
508    public DocTreePath getDocTreePath(DocTree dtree) {
509        if (dcTree == null && element instanceof ExecutableElement ee) {
510            return getInheritedDocTreePath(dtree, ee);
511        }
512        if (path == null || dcTree == null || dtree == null) {
513            return null;
514        }
515        DocTreePath dtPath = DocTreePath.getPath(path, dcTree, dtree);
516        if (dtPath == null && element instanceof ExecutableElement ee) {
517            // The overriding element has a doc tree, but it doesn't contain
518            // what we're looking for.
519            return getInheritedDocTreePath(dtree, ee);
520        }
521        return dtPath;
522    }
523
524    private DocTreePath getInheritedDocTreePath(DocTree dtree,
525            ExecutableElement ee) {
526        Utils utils = configuration.utils;
527        var docFinder = utils.docFinder();
528        Optional<ExecutableElement> inheritedDoc = docFinder.search(ee,
529            (m -> {
530                Optional<ExecutableElement> optional
531                    = utils.getFullBody(m).isEmpty() ? Optional.empty()
532                        : Optional.of(m);
533                return Result.fromOptional(optional);
534            })).toOptional();
535        return inheritedDoc.isEmpty() || inheritedDoc.get().equals(ee)
536            ? null
537            : utils.getCommentHelper(inheritedDoc.get()).getDocTreePath(dtree);
538    }
539
540    /**
541     * For debugging purposes only. Do not rely on this for other things.
542     * @return a string representation.
543     */
544    @Override
545    public String toString() {
546        return "CommentHelper{"
547            + "path=" + path
548            + ", dcTree=" + dcTree
549            + ", element=" + element.getEnclosingElement() + "::" + element
550            + '}';
551    }
552}