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