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-> 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}