001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.indentation;
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024import java.util.HashSet;
025import java.util.Set;
026
027import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
028import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030
031/**
032 * Checks correct indentation of Java Code.
033 *
034 * <p>
035 * The basic idea behind this is that while
036 * pretty printers are sometimes convenient for reformatting of
037 * legacy code, they often either aren't configurable enough or
038 * just can't anticipate how format should be done.  Sometimes this is
039 * personal preference, other times it is practical experience.  In any
040 * case, this check should just ensure that a minimal set of indentation
041 * rules are followed.
042 * </p>
043 *
044 * <p>
045 * Implementation --
046 *  Basically, this check requests visitation for all handled token
047 *  types (those tokens registered in the HandlerFactory).  When visitToken
048 *  is called, a new ExpressionHandler is created for the AST and pushed
049 *  onto the handlers stack.  The new handler then checks the indentation
050 *  for the currently visiting AST.  When leaveToken is called, the
051 *  ExpressionHandler is popped from the stack.
052 * </p>
053 *
054 * <p>
055 *  While on the stack the ExpressionHandler can be queried for the
056 *  indentation level it suggests for children as well as for other
057 *  values.
058 * </p>
059 *
060 * <p>
061 *  While an ExpressionHandler checks the indentation level of its own
062 *  AST, it typically also checks surrounding ASTs.  For instance, a
063 *  while loop handler checks the while loop as well as the braces
064 *  and immediate children.
065 * </p>
066 * <pre>
067 *   - handler class -to-&gt; ID mapping kept in Map
068 *   - parent passed in during construction
069 *   - suggest child indent level
070 *   - allows for some tokens to be on same line (ie inner classes OBJBLOCK)
071 *     and not increase indentation level
072 *   - looked at using double dispatch for getSuggestedChildIndent(), but it
073 *     doesn't seem worthwhile, at least now
074 *   - both tabs and spaces are considered whitespace in front of the line...
075 *     tabs are converted to spaces
076 *   - block parents with parens -- for, while, if, etc... -- are checked that
077 *     they match the level of the parent
078 * </pre>
079 *
080 * @author jrichard
081 * @author o_sukhodolsky
082 * @author Maikel Steneker
083 * @author maxvetrenko
084 * @noinspection ThisEscapedInObjectConstruction
085 */
086@FileStatefulCheck
087public class IndentationCheck extends AbstractCheck {
088
089    /**
090     * A key is pointing to the warning message text in "messages.properties"
091     * file.
092     */
093    public static final String MSG_ERROR = "indentation.error";
094
095    /**
096     * A key is pointing to the warning message text in "messages.properties"
097     * file.
098     */
099    public static final String MSG_ERROR_MULTI = "indentation.error.multi";
100
101    /**
102     * A key is pointing to the warning message text in "messages.properties"
103     * file.
104     */
105    public static final String MSG_CHILD_ERROR = "indentation.child.error";
106
107    /**
108     * A key is pointing to the warning message text in "messages.properties"
109     * file.
110     */
111    public static final String MSG_CHILD_ERROR_MULTI = "indentation.child.error.multi";
112
113    /** Default indentation amount - based on Sun. */
114    private static final int DEFAULT_INDENTATION = 4;
115
116    /** Handlers currently in use. */
117    private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>();
118
119    /** Instance of line wrapping handler to use. */
120    private final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(this);
121
122    /** Factory from which handlers are distributed. */
123    private final HandlerFactory handlerFactory = new HandlerFactory();
124
125    /** Lines logged as having incorrect indentation. */
126    private Set<Integer> incorrectIndentationLines;
127
128    /** How many tabs or spaces to use. */
129    private int basicOffset = DEFAULT_INDENTATION;
130
131    /** How much to indent a case label. */
132    private int caseIndent = DEFAULT_INDENTATION;
133
134    /** How far brace should be indented when on next line. */
135    private int braceAdjustment;
136
137    /** How far throws should be indented when on next line. */
138    private int throwsIndent = DEFAULT_INDENTATION;
139
140    /** How much to indent an array initialization when on next line. */
141    private int arrayInitIndent = DEFAULT_INDENTATION;
142
143    /** How far continuation line should be indented when line-wrapping is present. */
144    private int lineWrappingIndentation = DEFAULT_INDENTATION;
145
146    /**
147     * Force strict condition in line wrapping case. If value is true, line wrap indent
148     * have to be same as lineWrappingIndentation parameter, if value is false, line wrap indent
149     * have to be not less than lineWrappingIndentation parameter.
150     */
151    private boolean forceStrictCondition;
152
153    /**
154     * Get forcing strict condition.
155     * @return forceStrictCondition value.
156     */
157    public boolean isForceStrictCondition() {
158        return forceStrictCondition;
159    }
160
161    /**
162     * Set forcing strict condition.
163     * @param value user's value of forceStrictCondition.
164     */
165    public void setForceStrictCondition(boolean value) {
166        forceStrictCondition = value;
167    }
168
169    /**
170     * Set the basic offset.
171     *
172     * @param basicOffset   the number of tabs or spaces to indent
173     */
174    public void setBasicOffset(int basicOffset) {
175        this.basicOffset = basicOffset;
176    }
177
178    /**
179     * Get the basic offset.
180     *
181     * @return the number of tabs or spaces to indent
182     */
183    public int getBasicOffset() {
184        return basicOffset;
185    }
186
187    /**
188     * Adjusts brace indentation (positive offset).
189     *
190     * @param adjustmentAmount   the brace offset
191     */
192    public void setBraceAdjustment(int adjustmentAmount) {
193        braceAdjustment = adjustmentAmount;
194    }
195
196    /**
197     * Get the brace adjustment amount.
198     *
199     * @return the positive offset to adjust braces
200     */
201    public int getBraceAdjustment() {
202        return braceAdjustment;
203    }
204
205    /**
206     * Set the case indentation level.
207     *
208     * @param amount   the case indentation level
209     */
210    public void setCaseIndent(int amount) {
211        caseIndent = amount;
212    }
213
214    /**
215     * Get the case indentation level.
216     *
217     * @return the case indentation level
218     */
219    public int getCaseIndent() {
220        return caseIndent;
221    }
222
223    /**
224     * Set the throws indentation level.
225     *
226     * @param throwsIndent the throws indentation level
227     */
228    public void setThrowsIndent(int throwsIndent) {
229        this.throwsIndent = throwsIndent;
230    }
231
232    /**
233     * Get the throws indentation level.
234     *
235     * @return the throws indentation level
236     */
237    public int getThrowsIndent() {
238        return throwsIndent;
239    }
240
241    /**
242     * Set the array initialisation indentation level.
243     *
244     * @param arrayInitIndent the array initialisation indentation level
245     */
246    public void setArrayInitIndent(int arrayInitIndent) {
247        this.arrayInitIndent = arrayInitIndent;
248    }
249
250    /**
251     * Get the line-wrapping indentation level.
252     *
253     * @return the initialisation indentation level
254     */
255    public int getArrayInitIndent() {
256        return arrayInitIndent;
257    }
258
259    /**
260     * Get the array line-wrapping indentation level.
261     *
262     * @return the line-wrapping indentation level
263     */
264    public int getLineWrappingIndentation() {
265        return lineWrappingIndentation;
266    }
267
268    /**
269     * Set the line-wrapping indentation level.
270     *
271     * @param lineWrappingIndentation the line-wrapping indentation level
272     */
273    public void setLineWrappingIndentation(int lineWrappingIndentation) {
274        this.lineWrappingIndentation = lineWrappingIndentation;
275    }
276
277    /**
278     * Log an error message.
279     *
280     * @param line the line number where the error was found
281     * @param key the message that describes the error
282     * @param args the details of the message
283     *
284     * @see java.text.MessageFormat
285     */
286    public void indentationLog(int line, String key, Object... args) {
287        if (!incorrectIndentationLines.contains(line)) {
288            incorrectIndentationLines.add(line);
289            log(line, key, args);
290        }
291    }
292
293    /**
294     * Get the width of a tab.
295     *
296     * @return the width of a tab
297     */
298    public int getIndentationTabWidth() {
299        return getTabWidth();
300    }
301
302    @Override
303    public int[] getDefaultTokens() {
304        return getRequiredTokens();
305    }
306
307    @Override
308    public int[] getAcceptableTokens() {
309        return getRequiredTokens();
310    }
311
312    @Override
313    public int[] getRequiredTokens() {
314        return handlerFactory.getHandledTypes();
315    }
316
317    @Override
318    public void beginTree(DetailAST ast) {
319        handlerFactory.clearCreatedHandlers();
320        handlers.clear();
321        final PrimordialHandler primordialHandler = new PrimordialHandler(this);
322        handlers.push(primordialHandler);
323        primordialHandler.checkIndentation();
324        incorrectIndentationLines = new HashSet<>();
325    }
326
327    @Override
328    public void visitToken(DetailAST ast) {
329        final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast,
330            handlers.peek());
331        handlers.push(handler);
332        handler.checkIndentation();
333    }
334
335    @Override
336    public void leaveToken(DetailAST ast) {
337        handlers.pop();
338    }
339
340    /**
341     * Accessor for the line wrapping handler.
342     *
343     * @return the line wrapping handler
344     */
345    public LineWrappingHandler getLineWrappingHandler() {
346        return lineWrappingHandler;
347    }
348
349    /**
350     * Accessor for the handler factory.
351     *
352     * @return the handler factory
353     */
354    public final HandlerFactory getHandlerFactory() {
355        return handlerFactory;
356    }
357
358}