001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.grammar;
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024import java.util.Set;
025import java.util.stream.Collectors;
026
027import org.antlr.v4.runtime.Token;
028
029import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
030
031/**
032 * Utility class for Javadoc comments lexer operations.
033 */
034public final class JavadocCommentsLexerUtil {
035
036    /**
037     * Private constructor to prevent instantiation of this utility class.
038     *
039     * @throws IllegalStateException if this constructor is called, indicating that
040     */
041    private JavadocCommentsLexerUtil() {
042        throw new IllegalStateException("Utility class");
043    }
044
045    /**
046     * Finds unclosed tag name tokens by comparing open and close tag name tokens.
047     *
048     * <p>
049     * This method attempts to match each closing tag with the most recent
050     * unmatched opening tag of the same name, considering only tags that appear
051     * before it in the token stream. Any remaining unmatched opening tags are
052     * considered unclosed and returned.
053     * </p>
054     *
055     * <p>
056     * <b>Note:</b> This method must be called after lexing is complete to ensure
057     * that all tokens have their index values set.
058     * </p>
059     *
060     * @param openTagNameTokens  a deque of {@link Token} instances representing open tag names
061     * @param closeTagNameTokens a deque of {@link Token} instances representing close tag names
062     * @return a set of {@link SimpleToken} instances representing unclosed tag names
063     */
064    public static Set<SimpleToken> getUnclosedTagNameTokens(
065            Deque<Token> openTagNameTokens, Deque<Token> closeTagNameTokens) {
066
067        final Deque<Token> unmatchedOpen = new ArrayDeque<>(openTagNameTokens);
068
069        for (Token closingTag : closeTagNameTokens) {
070            final Deque<Token> tempStack = new ArrayDeque<>();
071            while (!unmatchedOpen.isEmpty()) {
072                final Token openingTag = unmatchedOpen.pop();
073                if (openingTag.getText().equalsIgnoreCase(closingTag.getText())
074                        && openingTag.getTokenIndex() < closingTag.getTokenIndex()) {
075                    break;
076                }
077                tempStack.push(openingTag);
078            }
079
080            // Put unmatched tags back
081            while (!tempStack.isEmpty()) {
082                unmatchedOpen.push(tempStack.pop());
083            }
084
085        }
086
087        // We cannot map to SimpleToken until lexing has completed, otherwise
088        // the token index will not be set.
089        return unmatchedOpen.stream()
090                .map(SimpleToken::from)
091                .collect(Collectors.toUnmodifiableSet());
092    }
093
094    /**
095     * Checks if the previous token is an open tag name.
096     *
097     * @param previousToken the previous token to check
098     * @return true if the previous token is null or not a closing tag, false otherwise
099     */
100    public static boolean isOpenTagName(Token previousToken) {
101        return previousToken == null
102                || previousToken.getType() != JavadocCommentsTokenTypes.TAG_SLASH;
103    }
104
105}