1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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   * @noinspection ThisEscapedInObjectConstruction
81   */
82  @FileStatefulCheck
83  public class IndentationCheck extends AbstractCheck {
84  
85      /**
86       * A key is pointing to the warning message text in "messages.properties"
87       * file.
88       */
89      public static final String MSG_ERROR = "indentation.error";
90  
91      /**
92       * A key is pointing to the warning message text in "messages.properties"
93       * file.
94       */
95      public static final String MSG_ERROR_MULTI = "indentation.error.multi";
96  
97      /**
98       * A key is pointing to the warning message text in "messages.properties"
99       * 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 }