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