001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.Locale;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
029
030/**
031 * <p>
032 * Checks line wrapping with separators.
033 * </p>
034 * <ul>
035 * <li>
036 * Property {@code option} - Specify policy on how to wrap lines.
037 * Default value is {@code eol}.
038 * </li>
039 * <li>
040 * Property {@code tokens} - tokens to check
041 * Default value is:
042 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DOT">
043 * DOT</a>,
044 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMMA">
045 * COMMA</a>.
046 * </li>
047 * </ul>
048 *  <p>
049 * To configure the check:
050 * </p>
051 * <pre>
052 * &lt;module name=&quot;SeparatorWrap&quot;/&gt;
053 * </pre>
054 * <p>
055 * Example:
056 * </p>
057 * <pre>
058 * import java.io.
059 *          IOException; // OK
060 *
061 * class Test {
062 *
063 *   String s;
064 *
065 *   public void foo(int a,
066 *                     int b) { // OK
067 *   }
068 *
069 *   public void bar(int p
070 *                     , int q) { // violation, separator comma on new line
071 *     if (s
072 *           .isEmpty()) { // violation, separator dot on new line
073 *     }
074 *   }
075 *
076 * }
077 * </pre>
078 * <p>
079 * To configure the check for
080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_REF">
081 * METHOD_REF</a> at new line:
082 * </p>
083 * <pre>
084 * &lt;module name=&quot;SeparatorWrap&quot;&gt;
085 *   &lt;property name=&quot;tokens&quot; value=&quot;METHOD_REF&quot;/&gt;
086 *   &lt;property name=&quot;option&quot; value=&quot;nl&quot;/&gt;
087 * &lt;/module&gt;
088 * </pre>
089 * <p>
090 * Example:
091 * </p>
092 * <pre>
093 * import java.util.Arrays;
094 *
095 * class Test2 {
096 *
097 *   String[] stringArray = {&quot;foo&quot;, &quot;bar&quot;};
098 *
099 *   void fun() {
100 *     Arrays.sort(stringArray, String::
101 *       compareToIgnoreCase);  // violation, separator method reference on same line
102 *     Arrays.sort(stringArray, String
103 *       ::compareTo);  // OK
104 *   }
105 *
106 * }
107 * </pre>
108 * <p>
109 * To configure the check for comma at the new line:
110 * </p>
111 * <pre>
112 * &lt;module name=&quot;SeparatorWrap&quot;&gt;
113 *   &lt;property name=&quot;tokens&quot; value=&quot;COMMA&quot;/&gt;
114 *   &lt;property name=&quot;option&quot; value=&quot;nl&quot;/&gt;
115 * &lt;/module&gt;
116 * </pre>
117 * <p>
118 * Example:
119 * </p>
120 * <pre>
121 * class Test3 {
122 *
123 *   String s;
124 *
125 *   int a,
126 *     b;  // violation, separator comma on same line
127 *
128 *   public void foo(int a,
129 *                      int b) {  // violation, separator comma on the same line
130 *     int r
131 *       , t; // OK
132 *   }
133 *
134 *   public void bar(int p
135 *                     , int q) {  // OK
136 *   }
137 *
138 * }
139 * </pre>
140 *
141 * @since 5.8
142 */
143@StatelessCheck
144public class SeparatorWrapCheck
145    extends AbstractCheck {
146
147    /**
148     * A key is pointing to the warning message text in "messages.properties"
149     * file.
150     */
151    public static final String MSG_LINE_PREVIOUS = "line.previous";
152
153    /**
154     * A key is pointing to the warning message text in "messages.properties"
155     * file.
156     */
157    public static final String MSG_LINE_NEW = "line.new";
158
159    /** Specify policy on how to wrap lines. */
160    private WrapOption option = WrapOption.EOL;
161
162    /**
163     * Setter to specify policy on how to wrap lines.
164     *
165     * @param optionStr string to decode option from
166     * @throws IllegalArgumentException if unable to decode
167     */
168    public void setOption(String optionStr) {
169        option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
170    }
171
172    @Override
173    public int[] getDefaultTokens() {
174        return new int[] {
175            TokenTypes.DOT,
176            TokenTypes.COMMA,
177        };
178    }
179
180    @Override
181    public int[] getAcceptableTokens() {
182        return new int[] {
183            TokenTypes.DOT,
184            TokenTypes.COMMA,
185            TokenTypes.SEMI,
186            TokenTypes.ELLIPSIS,
187            TokenTypes.AT,
188            TokenTypes.LPAREN,
189            TokenTypes.RPAREN,
190            TokenTypes.ARRAY_DECLARATOR,
191            TokenTypes.RBRACK,
192            TokenTypes.METHOD_REF,
193        };
194    }
195
196    @Override
197    public int[] getRequiredTokens() {
198        return CommonUtil.EMPTY_INT_ARRAY;
199    }
200
201    @Override
202    public void visitToken(DetailAST ast) {
203        final String text = ast.getText();
204        final int colNo = ast.getColumnNo();
205        final int lineNo = ast.getLineNo();
206        final String currentLine = getLines()[lineNo - 1];
207        final String substringAfterToken =
208                currentLine.substring(colNo + text.length()).trim();
209        final String substringBeforeToken =
210                currentLine.substring(0, colNo).trim();
211
212        if (option == WrapOption.EOL
213                && substringBeforeToken.isEmpty()) {
214            log(ast, MSG_LINE_PREVIOUS, text);
215        }
216        else if (option == WrapOption.NL
217                 && substringAfterToken.isEmpty()) {
218            log(ast, MSG_LINE_NEW, text);
219        }
220    }
221
222}