001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.checks.whitespace;
021
022import java.util.Arrays;
023import java.util.Locale;
024
025import com.puppycrawl.tools.checkstyle.StatelessCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import com.puppycrawl.tools.checkstyle.utils.CodePointUtil;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
031
032/**
033 * <div>
034 * Checks line wrapping with separators.
035 * </div>
036 * <ul>
037 * <li>
038 * Property {@code option} - Specify policy on how to wrap lines.
039 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.WrapOption}.
040 * Default value is {@code eol}.
041 * </li>
042 * <li>
043 * Property {@code tokens} - tokens to check
044 * Type is {@code java.lang.String[]}.
045 * Validation type is {@code tokenSet}.
046 * Default value is:
047 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DOT">
048 * DOT</a>,
049 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMMA">
050 * COMMA</a>.
051 * </li>
052 * </ul>
053 *
054 * <p>
055 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
056 * </p>
057 *
058 * <p>
059 * Violation Message Keys:
060 * </p>
061 * <ul>
062 * <li>
063 * {@code line.new}
064 * </li>
065 * <li>
066 * {@code line.previous}
067 * </li>
068 * </ul>
069 *
070 * @since 5.8
071 */
072@StatelessCheck
073public class SeparatorWrapCheck
074    extends AbstractCheck {
075
076    /**
077     * A key is pointing to the warning message text in "messages.properties"
078     * file.
079     */
080    public static final String MSG_LINE_PREVIOUS = "line.previous";
081
082    /**
083     * A key is pointing to the warning message text in "messages.properties"
084     * file.
085     */
086    public static final String MSG_LINE_NEW = "line.new";
087
088    /** Specify policy on how to wrap lines. */
089    private WrapOption option = WrapOption.EOL;
090
091    /**
092     * Setter to specify policy on how to wrap lines.
093     *
094     * @param optionStr string to decode option from
095     * @throws IllegalArgumentException if unable to decode
096     * @since 5.8
097     */
098    public void setOption(String optionStr) {
099        option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
100    }
101
102    @Override
103    public int[] getDefaultTokens() {
104        return new int[] {
105            TokenTypes.DOT,
106            TokenTypes.COMMA,
107        };
108    }
109
110    @Override
111    public int[] getAcceptableTokens() {
112        return new int[] {
113            TokenTypes.DOT,
114            TokenTypes.COMMA,
115            TokenTypes.SEMI,
116            TokenTypes.ELLIPSIS,
117            TokenTypes.AT,
118            TokenTypes.LPAREN,
119            TokenTypes.RPAREN,
120            TokenTypes.ARRAY_DECLARATOR,
121            TokenTypes.RBRACK,
122            TokenTypes.METHOD_REF,
123        };
124    }
125
126    @Override
127    public int[] getRequiredTokens() {
128        return CommonUtil.EMPTY_INT_ARRAY;
129    }
130
131    @Override
132    public void visitToken(DetailAST ast) {
133        final String text = ast.getText();
134        final int colNo = ast.getColumnNo();
135        final int lineNo = ast.getLineNo();
136        final int[] currentLine = getLineCodePoints(lineNo - 1);
137        final boolean isLineEmptyAfterToken = CodePointUtil.isBlank(
138                Arrays.copyOfRange(currentLine, colNo + text.length(), currentLine.length)
139        );
140        final boolean isLineEmptyBeforeToken = CodePointUtil.isBlank(
141                Arrays.copyOfRange(currentLine, 0, colNo)
142        );
143
144        if (option == WrapOption.NL
145                 && isLineEmptyAfterToken) {
146            log(ast, MSG_LINE_NEW, text);
147        }
148        else if (option == WrapOption.EOL
149                && isLineEmptyBeforeToken) {
150            log(ast, MSG_LINE_PREVIOUS, text);
151        }
152    }
153
154}