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 * @noinspection ThisEscapedInObjectConstruction
081 */
082@FileStatefulCheck
083public class IndentationCheck extends AbstractCheck {
084
085    /**
086     * A key is pointing to the warning message text in "messages.properties"
087     * file.
088     */
089    public static final String MSG_ERROR = "indentation.error";
090
091    /**
092     * A key is pointing to the warning message text in "messages.properties"
093     * file.
094     */
095    public static final String MSG_ERROR_MULTI = "indentation.error.multi";
096
097    /**
098     * A key is pointing to the warning message text in "messages.properties"
099     * file.
100     */
101    public static final String MSG_CHILD_ERROR = "indentation.child.error";
102
103    /**
104     * A key is pointing to the warning message text in "messages.properties"
105     * file.
106     */
107    public static final String MSG_CHILD_ERROR_MULTI = "indentation.child.error.multi";
108
109    /** Default indentation amount - based on Sun. */
110    private static final int DEFAULT_INDENTATION = 4;
111
112    /** Handlers currently in use. */
113    private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>();
114
115    /** Instance of line wrapping handler to use. */
116    private final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(this);
117
118    /** Factory from which handlers are distributed. */
119    private final HandlerFactory handlerFactory = new HandlerFactory();
120
121    /** Lines logged as having incorrect indentation. */
122    private Set<Integer> incorrectIndentationLines;
123
124    /** How many tabs or spaces to use. */
125    private int basicOffset = DEFAULT_INDENTATION;
126
127    /** How much to indent a case label. */
128    private int caseIndent = DEFAULT_INDENTATION;
129
130    /** How far brace should be indented when on next line. */
131    private int braceAdjustment;
132
133    /** How far throws should be indented when on next line. */
134    private int throwsIndent = DEFAULT_INDENTATION;
135
136    /** How much to indent an array initialization when on next line. */
137    private int arrayInitIndent = DEFAULT_INDENTATION;
138
139    /** How far continuation line should be indented when line-wrapping is present. */
140    private int lineWrappingIndentation = DEFAULT_INDENTATION;
141
142    /**
143     * Force strict condition in line wrapping case. If value is true, line wrap indent
144     * have to be same as lineWrappingIndentation parameter, if value is false, line wrap indent
145     * have to be not less than lineWrappingIndentation parameter.
146     */
147    private boolean forceStrictCondition;
148
149    /**
150     * Get forcing strict condition.
151     * @return forceStrictCondition value.
152     */
153    public boolean isForceStrictCondition() {
154        return forceStrictCondition;
155    }
156
157    /**
158     * Set forcing strict condition.
159     * @param value user's value of forceStrictCondition.
160     */
161    public void setForceStrictCondition(boolean value) {
162        forceStrictCondition = value;
163    }
164
165    /**
166     * Set the basic offset.
167     *
168     * @param basicOffset   the number of tabs or spaces to indent
169     */
170    public void setBasicOffset(int basicOffset) {
171        this.basicOffset = basicOffset;
172    }
173
174    /**
175     * Get the basic offset.
176     *
177     * @return the number of tabs or spaces to indent
178     */
179    public int getBasicOffset() {
180        return basicOffset;
181    }
182
183    /**
184     * Adjusts brace indentation (positive offset).
185     *
186     * @param adjustmentAmount   the brace offset
187     */
188    public void setBraceAdjustment(int adjustmentAmount) {
189        braceAdjustment = adjustmentAmount;
190    }
191
192    /**
193     * Get the brace adjustment amount.
194     *
195     * @return the positive offset to adjust braces
196     */
197    public int getBraceAdjustment() {
198        return braceAdjustment;
199    }
200
201    /**
202     * Set the case indentation level.
203     *
204     * @param amount   the case indentation level
205     */
206    public void setCaseIndent(int amount) {
207        caseIndent = amount;
208    }
209
210    /**
211     * Get the case indentation level.
212     *
213     * @return the case indentation level
214     */
215    public int getCaseIndent() {
216        return caseIndent;
217    }
218
219    /**
220     * Set the throws indentation level.
221     *
222     * @param throwsIndent the throws indentation level
223     */
224    public void setThrowsIndent(int throwsIndent) {
225        this.throwsIndent = throwsIndent;
226    }
227
228    /**
229     * Get the throws indentation level.
230     *
231     * @return the throws indentation level
232     */
233    public int getThrowsIndent() {
234        return throwsIndent;
235    }
236
237    /**
238     * Set the array initialisation indentation level.
239     *
240     * @param arrayInitIndent the array initialisation indentation level
241     */
242    public void setArrayInitIndent(int arrayInitIndent) {
243        this.arrayInitIndent = arrayInitIndent;
244    }
245
246    /**
247     * Get the line-wrapping indentation level.
248     *
249     * @return the initialisation indentation level
250     */
251    public int getArrayInitIndent() {
252        return arrayInitIndent;
253    }
254
255    /**
256     * Get the array line-wrapping indentation level.
257     *
258     * @return the line-wrapping indentation level
259     */
260    public int getLineWrappingIndentation() {
261        return lineWrappingIndentation;
262    }
263
264    /**
265     * Set the line-wrapping indentation level.
266     *
267     * @param lineWrappingIndentation the line-wrapping indentation level
268     */
269    public void setLineWrappingIndentation(int lineWrappingIndentation) {
270        this.lineWrappingIndentation = lineWrappingIndentation;
271    }
272
273    /**
274     * Log an error message.
275     *
276     * @param line the line number where the error was found
277     * @param key the message that describes the error
278     * @param args the details of the message
279     *
280     * @see java.text.MessageFormat
281     */
282    public void indentationLog(int line, String key, Object... args) {
283        if (!incorrectIndentationLines.contains(line)) {
284            incorrectIndentationLines.add(line);
285            log(line, key, args);
286        }
287    }
288
289    /**
290     * Get the width of a tab.
291     *
292     * @return the width of a tab
293     */
294    public int getIndentationTabWidth() {
295        return getTabWidth();
296    }
297
298    @Override
299    public int[] getDefaultTokens() {
300        return getRequiredTokens();
301    }
302
303    @Override
304    public int[] getAcceptableTokens() {
305        return getRequiredTokens();
306    }
307
308    @Override
309    public int[] getRequiredTokens() {
310        return handlerFactory.getHandledTypes();
311    }
312
313    @Override
314    public void beginTree(DetailAST ast) {
315        handlerFactory.clearCreatedHandlers();
316        handlers.clear();
317        final PrimordialHandler primordialHandler = new PrimordialHandler(this);
318        handlers.push(primordialHandler);
319        primordialHandler.checkIndentation();
320        incorrectIndentationLines = new HashSet<>();
321    }
322
323    @Override
324    public void visitToken(DetailAST ast) {
325        final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast,
326            handlers.peek());
327        handlers.push(handler);
328        handler.checkIndentation();
329    }
330
331    @Override
332    public void leaveToken(DetailAST ast) {
333        handlers.pop();
334    }
335
336    /**
337     * Accessor for the line wrapping handler.
338     *
339     * @return the line wrapping handler
340     */
341    public LineWrappingHandler getLineWrappingHandler() {
342        return lineWrappingHandler;
343    }
344
345    /**
346     * Accessor for the handler factory.
347     *
348     * @return the handler factory
349     */
350    public final HandlerFactory getHandlerFactory() {
351        return handlerFactory;
352    }
353
354}