View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.indentation;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  import java.util.HashSet;
25  import java.util.Set;
26  
27  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
28  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  
31  /**
32   * Checks correct indentation of Java Code.
33   *
34   * <p>
35   * The basic idea behind this is that while
36   * pretty printers are sometimes convenient for reformatting of
37   * legacy code, they often either aren't configurable enough or
38   * just can't anticipate how format should be done.  Sometimes this is
39   * personal preference, other times it is practical experience.  In any
40   * case, this check should just ensure that a minimal set of indentation
41   * rules are followed.
42   * </p>
43   *
44   * <p>
45   * Implementation --
46   *  Basically, this check requests visitation for all handled token
47   *  types (those tokens registered in the HandlerFactory).  When visitToken
48   *  is called, a new ExpressionHandler is created for the AST and pushed
49   *  onto the handlers stack.  The new handler then checks the indentation
50   *  for the currently visiting AST.  When leaveToken is called, the
51   *  ExpressionHandler is popped from the stack.
52   * </p>
53   *
54   * <p>
55   *  While on the stack the ExpressionHandler can be queried for the
56   *  indentation level it suggests for children as well as for other
57   *  values.
58   * </p>
59   *
60   * <p>
61   *  While an ExpressionHandler checks the indentation level of its own
62   *  AST, it typically also checks surrounding ASTs.  For instance, a
63   *  while loop handler checks the while loop as well as the braces
64   *  and immediate children.
65   * </p>
66   * <pre>
67   *   - handler class -to-&gt; ID mapping kept in Map
68   *   - parent passed in during construction
69   *   - suggest child indent level
70   *   - allows for some tokens to be on same line (ie inner classes OBJBLOCK)
71   *     and not increase indentation level
72   *   - looked at using double dispatch for getSuggestedChildIndent(), but it
73   *     doesn't seem worthwhile, at least now
74   *   - both tabs and spaces are considered whitespace in front of the line...
75   *     tabs are converted to spaces
76   *   - block parents with parens -- for, while, if, etc... -- are checked that
77   *     they match the level of the parent
78   * </pre>
79   *
80   * @author jrichard
81   * @author o_sukhodolsky
82   * @author Maikel Steneker
83   * @author maxvetrenko
84   * @noinspection ThisEscapedInObjectConstruction
85   */
86  @FileStatefulCheck
87  public class IndentationCheck extends AbstractCheck {
88      /**
89       * A key is pointing to the warning message text in "messages.properties"
90       * file.
91       */
92      public static final String MSG_ERROR = "indentation.error";
93  
94      /**
95       * A key is pointing to the warning message text in "messages.properties"
96       * file.
97       */
98      public static final String MSG_ERROR_MULTI = "indentation.error.multi";
99  
100     /**
101      * A key is pointing to the warning message text in "messages.properties"
102      * file.
103      */
104     public static final String MSG_CHILD_ERROR = "indentation.child.error";
105 
106     /**
107      * A key is pointing to the warning message text in "messages.properties"
108      * file.
109      */
110     public static final String MSG_CHILD_ERROR_MULTI = "indentation.child.error.multi";
111 
112     /** Default indentation amount - based on Sun. */
113     private static final int DEFAULT_INDENTATION = 4;
114 
115     /** Handlers currently in use. */
116     private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>();
117 
118     /** Instance of line wrapping handler to use. */
119     private final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(this);
120 
121     /** Factory from which handlers are distributed. */
122     private final HandlerFactory handlerFactory = new HandlerFactory();
123 
124     /** Lines logged as having incorrect indentation. */
125     private Set<Integer> incorrectIndentationLines;
126 
127     /** How many tabs or spaces to use. */
128     private int basicOffset = DEFAULT_INDENTATION;
129 
130     /** How much to indent a case label. */
131     private int caseIndent = DEFAULT_INDENTATION;
132 
133     /** How far brace should be indented when on next line. */
134     private int braceAdjustment;
135 
136     /** How far throws should be indented when on next line. */
137     private int throwsIndent = DEFAULT_INDENTATION;
138 
139     /** How much to indent an array initialization when on next line. */
140     private int arrayInitIndent = DEFAULT_INDENTATION;
141 
142     /** How far continuation line should be indented when line-wrapping is present. */
143     private int lineWrappingIndentation = DEFAULT_INDENTATION;
144 
145     /**
146      * Force strict condition in line wrapping case. If value is true, line wrap indent
147      * have to be same as lineWrappingIndentation parameter, if value is false, line wrap indent
148      * have to be not less than lineWrappingIndentation parameter.
149      */
150     private boolean forceStrictCondition;
151 
152     /**
153      * Get forcing strict condition.
154      * @return forceStrictCondition value.
155      */
156     public boolean isForceStrictCondition() {
157         return forceStrictCondition;
158     }
159 
160     /**
161      * Set forcing strict condition.
162      * @param value user's value of forceStrictCondition.
163      */
164     public void setForceStrictCondition(boolean value) {
165         forceStrictCondition = value;
166     }
167 
168     /**
169      * Set the basic offset.
170      *
171      * @param basicOffset   the number of tabs or spaces to indent
172      */
173     public void setBasicOffset(int basicOffset) {
174         this.basicOffset = basicOffset;
175     }
176 
177     /**
178      * Get the basic offset.
179      *
180      * @return the number of tabs or spaces to indent
181      */
182     public int getBasicOffset() {
183         return basicOffset;
184     }
185 
186     /**
187      * Adjusts brace indentation (positive offset).
188      *
189      * @param adjustmentAmount   the brace offset
190      */
191     public void setBraceAdjustment(int adjustmentAmount) {
192         braceAdjustment = adjustmentAmount;
193     }
194 
195     /**
196      * Get the brace adjustment amount.
197      *
198      * @return the positive offset to adjust braces
199      */
200     public int getBraceAdjustment() {
201         return braceAdjustment;
202     }
203 
204     /**
205      * Set the case indentation level.
206      *
207      * @param amount   the case indentation level
208      */
209     public void setCaseIndent(int amount) {
210         caseIndent = amount;
211     }
212 
213     /**
214      * Get the case indentation level.
215      *
216      * @return the case indentation level
217      */
218     public int getCaseIndent() {
219         return caseIndent;
220     }
221 
222     /**
223      * Set the throws indentation level.
224      *
225      * @param throwsIndent the throws indentation level
226      */
227     public void setThrowsIndent(int throwsIndent) {
228         this.throwsIndent = throwsIndent;
229     }
230 
231     /**
232      * Get the throws indentation level.
233      *
234      * @return the throws indentation level
235      */
236     public int getThrowsIndent() {
237         return throwsIndent;
238     }
239 
240     /**
241      * Set the array initialisation indentation level.
242      *
243      * @param arrayInitIndent the array initialisation indentation level
244      */
245     public void setArrayInitIndent(int arrayInitIndent) {
246         this.arrayInitIndent = arrayInitIndent;
247     }
248 
249     /**
250      * Get the line-wrapping indentation level.
251      *
252      * @return the initialisation indentation level
253      */
254     public int getArrayInitIndent() {
255         return arrayInitIndent;
256     }
257 
258     /**
259      * Get the array line-wrapping indentation level.
260      *
261      * @return the line-wrapping indentation level
262      */
263     public int getLineWrappingIndentation() {
264         return lineWrappingIndentation;
265     }
266 
267     /**
268      * Set the line-wrapping indentation level.
269      *
270      * @param lineWrappingIndentation the line-wrapping indentation level
271      */
272     public void setLineWrappingIndentation(int lineWrappingIndentation) {
273         this.lineWrappingIndentation = lineWrappingIndentation;
274     }
275 
276     /**
277      * Log an error message.
278      *
279      * @param line the line number where the error was found
280      * @param key the message that describes the error
281      * @param args the details of the message
282      *
283      * @see java.text.MessageFormat
284      */
285     public void indentationLog(int line, String key, Object... args) {
286         if (!incorrectIndentationLines.contains(line)) {
287             incorrectIndentationLines.add(line);
288             log(line, key, args);
289         }
290     }
291 
292     /**
293      * Get the width of a tab.
294      *
295      * @return the width of a tab
296      */
297     public int getIndentationTabWidth() {
298         return getTabWidth();
299     }
300 
301     @Override
302     public int[] getDefaultTokens() {
303         return getRequiredTokens();
304     }
305 
306     @Override
307     public int[] getAcceptableTokens() {
308         return getRequiredTokens();
309     }
310 
311     @Override
312     public int[] getRequiredTokens() {
313         return handlerFactory.getHandledTypes();
314     }
315 
316     @Override
317     public void beginTree(DetailAST ast) {
318         handlerFactory.clearCreatedHandlers();
319         handlers.clear();
320         final PrimordialHandler primordialHandler = new PrimordialHandler(this);
321         handlers.push(primordialHandler);
322         primordialHandler.checkIndentation();
323         incorrectIndentationLines = new HashSet<>();
324     }
325 
326     @Override
327     public void visitToken(DetailAST ast) {
328         final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast,
329             handlers.peek());
330         handlers.push(handler);
331         handler.checkIndentation();
332     }
333 
334     @Override
335     public void leaveToken(DetailAST ast) {
336         handlers.pop();
337     }
338 
339     /**
340      * Accessor for the line wrapping handler.
341      *
342      * @return the line wrapping handler
343      */
344     public LineWrappingHandler getLineWrappingHandler() {
345         return lineWrappingHandler;
346     }
347 
348     /**
349      * Accessor for the handler factory.
350      *
351      * @return the handler factory
352      */
353     public final HandlerFactory getHandlerFactory() {
354         return handlerFactory;
355     }
356 }