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