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