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 * <p>
034 * Checks line wrapping with separators.
035 * </p>
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 * <p>
054 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
055 * </p>
056 * <p>
057 * Violation Message Keys:
058 * </p>
059 * <ul>
060 * <li>
061 * {@code line.new}
062 * </li>
063 * <li>
064 * {@code line.previous}
065 * </li>
066 * </ul>
067 *
068 * @since 5.8
069 */
070@StatelessCheck
071public class SeparatorWrapCheck
072    extends AbstractCheck {
073
074    /**
075     * A key is pointing to the warning message text in "messages.properties"
076     * file.
077     */
078    public static final String MSG_LINE_PREVIOUS = "line.previous";
079
080    /**
081     * A key is pointing to the warning message text in "messages.properties"
082     * file.
083     */
084    public static final String MSG_LINE_NEW = "line.new";
085
086    /** Specify policy on how to wrap lines. */
087    private WrapOption option = WrapOption.EOL;
088
089    /**
090     * Setter to specify policy on how to wrap lines.
091     *
092     * @param optionStr string to decode option from
093     * @throws IllegalArgumentException if unable to decode
094     * @since 5.8
095     */
096    public void setOption(String optionStr) {
097        option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
098    }
099
100    @Override
101    public int[] getDefaultTokens() {
102        return new int[] {
103            TokenTypes.DOT,
104            TokenTypes.COMMA,
105        };
106    }
107
108    @Override
109    public int[] getAcceptableTokens() {
110        return new int[] {
111            TokenTypes.DOT,
112            TokenTypes.COMMA,
113            TokenTypes.SEMI,
114            TokenTypes.ELLIPSIS,
115            TokenTypes.AT,
116            TokenTypes.LPAREN,
117            TokenTypes.RPAREN,
118            TokenTypes.ARRAY_DECLARATOR,
119            TokenTypes.RBRACK,
120            TokenTypes.METHOD_REF,
121        };
122    }
123
124    @Override
125    public int[] getRequiredTokens() {
126        return CommonUtil.EMPTY_INT_ARRAY;
127    }
128
129    @Override
130    public void visitToken(DetailAST ast) {
131        final String text = ast.getText();
132        final int colNo = ast.getColumnNo();
133        final int lineNo = ast.getLineNo();
134        final int[] currentLine = getLineCodePoints(lineNo - 1);
135        final boolean isLineEmptyAfterToken = CodePointUtil.isBlank(
136                Arrays.copyOfRange(currentLine, colNo + text.length(), currentLine.length)
137        );
138        final boolean isLineEmptyBeforeToken = CodePointUtil.isBlank(
139                Arrays.copyOfRange(currentLine, 0, colNo)
140        );
141
142        if (option == WrapOption.NL
143                 && isLineEmptyAfterToken) {
144            log(ast, MSG_LINE_NEW, text);
145        }
146        else if (option == WrapOption.EOL
147                && isLineEmptyBeforeToken) {
148            log(ast, MSG_LINE_PREVIOUS, text);
149        }
150    }
151
152}