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 the padding between the identifier of a method definition,
033 * constructor definition, method call, or constructor invocation;
034 * and the left parenthesis of the parameter list.
035 * That is, if the identifier and left parenthesis are on the same line,
036 * checks whether a space is required immediately after the identifier or
037 * such a space is forbidden.
038 * If they are not on the same line, reports a violation, unless configured to
039 * allow line breaks. To allow linebreaks after the identifier, set property
040 * {@code allowLineBreaks} to {@code true}.
041 * </p>
042 * <ul>
043 * <li>
044 * Property {@code allowLineBreaks} - Allow a line break between the identifier
045 * and left parenthesis.
046 * Type is {@code boolean}.
047 * Default value is {@code false}.
048 * </li>
049 * <li>
050 * Property {@code option} - Specify policy on how to pad method parameter.
051 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.PadOption}.
052 * Default value is {@code nospace}.
053 * </li>
054 * <li>
055 * Property {@code tokens} - tokens to check
056 * Type is {@code int[]}.
057 * Default value is:
058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
059 * CTOR_DEF</a>,
060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW">
061 * LITERAL_NEW</a>,
062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL">
063 * METHOD_CALL</a>,
064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
065 * METHOD_DEF</a>,
066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SUPER_CTOR_CALL">
067 * SUPER_CTOR_CALL</a>,
068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
069 * ENUM_CONSTANT_DEF</a>.
070 * </li>
071 * </ul>
072 * <p>
073 * To configure the check:
074 * </p>
075 * <pre>
076 * &lt;module name="MethodParamPad"/&gt;
077 * </pre>
078 * <p>
079 * To configure the check to require a space
080 * after the identifier of a method definition, except if the left
081 * parenthesis occurs on a new line:
082 * </p>
083 * <pre>
084 * &lt;module name="MethodParamPad"&gt;
085 *   &lt;property name="tokens" value="METHOD_DEF"/&gt;
086 *   &lt;property name="option" value="space"/&gt;
087 *   &lt;property name="allowLineBreaks" value="true"/&gt;
088 * &lt;/module&gt;
089 * </pre>
090 * <p>
091 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
092 * </p>
093 * <p>
094 * Violation Message Keys:
095 * </p>
096 * <ul>
097 * <li>
098 * {@code line.previous}
099 * </li>
100 * <li>
101 * {@code ws.notPreceded}
102 * </li>
103 * <li>
104 * {@code ws.preceded}
105 * </li>
106 * </ul>
107 *
108 * @since 3.4
109 */
110
111@StatelessCheck
112public class MethodParamPadCheck
113    extends AbstractCheck {
114
115    /**
116     * A key is pointing to the warning message text in "messages.properties"
117     * file.
118     */
119    public static final String MSG_LINE_PREVIOUS = "line.previous";
120
121    /**
122     * A key is pointing to the warning message text in "messages.properties"
123     * file.
124     */
125    public static final String MSG_WS_PRECEDED = "ws.preceded";
126
127    /**
128     * A key is pointing to the warning message text in "messages.properties"
129     * file.
130     */
131    public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
132
133    /**
134     * Allow a line break between the identifier and left parenthesis.
135     */
136    private boolean allowLineBreaks;
137
138    /** Specify policy on how to pad method parameter. */
139    private PadOption option = PadOption.NOSPACE;
140
141    @Override
142    public int[] getDefaultTokens() {
143        return getAcceptableTokens();
144    }
145
146    @Override
147    public int[] getAcceptableTokens() {
148        return new int[] {
149            TokenTypes.CTOR_DEF,
150            TokenTypes.LITERAL_NEW,
151            TokenTypes.METHOD_CALL,
152            TokenTypes.METHOD_DEF,
153            TokenTypes.SUPER_CTOR_CALL,
154            TokenTypes.ENUM_CONSTANT_DEF,
155        };
156    }
157
158    @Override
159    public int[] getRequiredTokens() {
160        return CommonUtil.EMPTY_INT_ARRAY;
161    }
162
163    @Override
164    public void visitToken(DetailAST ast) {
165        final DetailAST parenAST;
166        if (ast.getType() == TokenTypes.METHOD_CALL) {
167            parenAST = ast;
168        }
169        else {
170            parenAST = ast.findFirstToken(TokenTypes.LPAREN);
171            // array construction => parenAST == null
172        }
173
174        if (parenAST != null) {
175            final String line = getLines()[parenAST.getLineNo() - 1];
176            if (CommonUtil.hasWhitespaceBefore(parenAST.getColumnNo(), line)) {
177                if (!allowLineBreaks) {
178                    log(parenAST, MSG_LINE_PREVIOUS, parenAST.getText());
179                }
180            }
181            else {
182                final int before = parenAST.getColumnNo() - 1;
183                if (option == PadOption.NOSPACE
184                    && Character.isWhitespace(line.charAt(before))) {
185                    log(parenAST, MSG_WS_PRECEDED, parenAST.getText());
186                }
187                else if (option == PadOption.SPACE
188                         && !Character.isWhitespace(line.charAt(before))) {
189                    log(parenAST, MSG_WS_NOT_PRECEDED, parenAST.getText());
190                }
191            }
192        }
193    }
194
195    /**
196     * Setter to allow a line break between the identifier and left parenthesis.
197     *
198     * @param allowLineBreaks whether whitespace should be
199     *     flagged at line breaks.
200     */
201    public void setAllowLineBreaks(boolean allowLineBreaks) {
202        this.allowLineBreaks = allowLineBreaks;
203    }
204
205    /**
206     * Setter to specify policy on how to pad method parameter.
207     *
208     * @param optionStr string to decode option from
209     * @throws IllegalArgumentException if unable to decode
210     */
211    public void setOption(String optionStr) {
212        option = PadOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
213    }
214
215}