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