001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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.utils;
021
022import java.util.AbstractMap;
023import java.util.Map;
024
025import org.antlr.v4.runtime.CommonToken;
026
027import com.puppycrawl.tools.checkstyle.DetailAstImpl;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030
031/**
032 * Contains utility methods for parser to use while creating ast.
033 */
034public final class ParserUtil {
035
036    /** Symbols with which javadoc starts. */
037    private static final String JAVADOC_START = "/**";
038    /** Symbols with which multiple comment starts. */
039    private static final String BLOCK_MULTIPLE_COMMENT_BEGIN = "/*";
040    /** Symbols with which multiple comment ends. */
041    private static final String BLOCK_MULTIPLE_COMMENT_END = "*/";
042
043    /** Stop instances being created. **/
044    private ParserUtil() {
045    }
046
047    /**
048     * Create block comment from string content.
049     *
050     * @param content comment content.
051     * @return DetailAST block comment
052     */
053    public static DetailAST createBlockCommentNode(String content) {
054        final DetailAstImpl blockCommentBegin = new DetailAstImpl();
055        blockCommentBegin.setType(TokenTypes.BLOCK_COMMENT_BEGIN);
056        blockCommentBegin.setText(BLOCK_MULTIPLE_COMMENT_BEGIN);
057        blockCommentBegin.setLineNo(0);
058        blockCommentBegin.setColumnNo(-JAVADOC_START.length());
059
060        final DetailAstImpl commentContent = new DetailAstImpl();
061        commentContent.setType(TokenTypes.COMMENT_CONTENT);
062        commentContent.setText("*" + content);
063        commentContent.setLineNo(0);
064        // javadoc should starts at 0 column, so COMMENT_CONTENT node
065        // that contains javadoc identifier has -1 column
066        commentContent.setColumnNo(-1);
067
068        final DetailAstImpl blockCommentEnd = new DetailAstImpl();
069        blockCommentEnd.setType(TokenTypes.BLOCK_COMMENT_END);
070        blockCommentEnd.setText(BLOCK_MULTIPLE_COMMENT_END);
071
072        blockCommentBegin.setFirstChild(commentContent);
073        commentContent.setNextSibling(blockCommentEnd);
074        return blockCommentBegin;
075    }
076
077    /**
078     * Create block comment from token.
079     *
080     * @param token Token object.
081     * @return DetailAST with BLOCK_COMMENT type.
082     */
083    public static DetailAST createBlockCommentNode(CommonToken token) {
084        final DetailAstImpl blockComment = new DetailAstImpl();
085        blockComment.initialize(TokenTypes.BLOCK_COMMENT_BEGIN, BLOCK_MULTIPLE_COMMENT_BEGIN);
086
087        final int tokenCharPositionInLine = token.getCharPositionInLine();
088        final int tokenLine = token.getLine();
089        final String tokenText = token.getText();
090
091        blockComment.setColumnNo(tokenCharPositionInLine);
092        blockComment.setLineNo(tokenLine);
093
094        final DetailAstImpl blockCommentContent = new DetailAstImpl();
095        blockCommentContent.setType(TokenTypes.COMMENT_CONTENT);
096
097        // Add length of '/*'
098        blockCommentContent.setColumnNo(tokenCharPositionInLine + 2);
099        blockCommentContent.setLineNo(tokenLine);
100        blockCommentContent.setText(tokenText);
101
102        final DetailAstImpl blockCommentClose = new DetailAstImpl();
103        blockCommentClose.initialize(TokenTypes.BLOCK_COMMENT_END, BLOCK_MULTIPLE_COMMENT_END);
104
105        final Map.Entry<Integer, Integer> linesColumns = countLinesColumns(
106                tokenText, tokenLine, tokenCharPositionInLine + 1);
107        blockCommentClose.setLineNo(linesColumns.getKey());
108        blockCommentClose.setColumnNo(linesColumns.getValue());
109
110        blockComment.addChild(blockCommentContent);
111        blockComment.addChild(blockCommentClose);
112        return blockComment;
113    }
114
115    /**
116     * Count lines and columns (in last line) in text.
117     *
118     * @param text              String.
119     * @param initialLinesCnt   initial value of lines counter.
120     * @param initialColumnsCnt initial value of columns counter.
121     * @return entry(pair), key is line counter, value is column counter.
122     */
123    private static Map.Entry<Integer, Integer> countLinesColumns(
124        String text, int initialLinesCnt, int initialColumnsCnt) {
125        int lines = initialLinesCnt;
126        int columns = initialColumnsCnt;
127        boolean foundCr = false;
128        for (char c : text.toCharArray()) {
129            if (c == '\n') {
130                foundCr = false;
131                lines++;
132                columns = 0;
133            }
134            else {
135                if (foundCr) {
136                    foundCr = false;
137                    lines++;
138                    columns = 0;
139                }
140                if (c == '\r') {
141                    foundCr = true;
142                }
143                columns++;
144            }
145        }
146        if (foundCr) {
147            lines++;
148            columns = 0;
149        }
150        return new AbstractMap.SimpleEntry<>(lines, columns);
151    }
152}