001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.blocks; 020 021import com.puppycrawl.tools.checkstyle.api.DetailAST; 022import com.puppycrawl.tools.checkstyle.api.TokenTypes; 023import com.puppycrawl.tools.checkstyle.api.Utils; 024import com.puppycrawl.tools.checkstyle.checks.AbstractOptionCheck; 025 026/** 027 * <p> 028 * Checks the placement of left curly braces on types, methods and 029 * other blocks: 030 * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, {@link 031 * TokenTypes#LITERAL_DO LITERAL_DO}, {@link TokenTypes#LITERAL_ELSE 032 * LITERAL_ELSE}, {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, {@link 033 * TokenTypes#LITERAL_FOR LITERAL_FOR}, {@link TokenTypes#LITERAL_IF 034 * LITERAL_IF}, {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}, {@link 035 * TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}, {@link 036 * TokenTypes#LITERAL_TRY LITERAL_TRY}, {@link TokenTypes#LITERAL_WHILE 037 * LITERAL_WHILE}. 038 * </p> 039 * 040 * <p> 041 * The policy to verify is specified using the {@link LeftCurlyOption} class and 042 * defaults to {@link LeftCurlyOption#EOL}. Policies {@link LeftCurlyOption#EOL} 043 * and {@link LeftCurlyOption#NLOW} take into account property maxLineLength. 044 * The default value for maxLineLength is 80. 045 * </p> 046 * <p> 047 * An example of how to configure the check is: 048 * </p> 049 * <pre> 050 * <module name="LeftCurly"/> 051 * </pre> 052 * <p> 053 * An example of how to configure the check with policy 054 * {@link LeftCurlyOption#NLOW} and maxLineLength 120 is: 055 * </p> 056 * <pre> 057 * <module name="LeftCurly"> 058 * <property name="option" 059 * value="nlow"/> <property name="maxLineLength" value="120"/> < 060 * /module> 061 * </pre> 062 * <p> 063 * An example of how to configure the check to validate enum definitions: 064 * </p> 065 * <pre> 066 * <module name="LeftCurly"> 067 * <property name="ignoreEnums" value="false"/> 068 * </module> 069 * </pre> 070 * 071 * @author Oliver Burn 072 * @author lkuehne 073 * @author maxvetrenko 074 * @version 1.0 075 */ 076public class LeftCurlyCheck 077 extends AbstractOptionCheck<LeftCurlyOption> 078{ 079 /** default maximum line length */ 080 private static final int DEFAULT_MAX_LINE_LENGTH = 80; 081 082 /** TODO: replace this ugly hack **/ 083 private int mMaxLineLength = DEFAULT_MAX_LINE_LENGTH; 084 085 /** If true, Check will ignore enums*/ 086 private boolean mIgnoreEnums = true; 087 088 /** 089 * Creates a default instance and sets the policy to EOL. 090 */ 091 public LeftCurlyCheck() 092 { 093 super(LeftCurlyOption.EOL, LeftCurlyOption.class); 094 } 095 096 /** 097 * Sets the maximum line length used in calculating the placement of the 098 * left curly brace. 099 * @param aMaxLineLength the max allowed line length 100 */ 101 public void setMaxLineLength(int aMaxLineLength) 102 { 103 mMaxLineLength = aMaxLineLength; 104 } 105 106 @Override 107 public int[] getDefaultTokens() 108 { 109 return new int[] { 110 TokenTypes.INTERFACE_DEF, 111 TokenTypes.CLASS_DEF, 112 TokenTypes.ANNOTATION_DEF, 113 TokenTypes.ENUM_DEF, 114 TokenTypes.CTOR_DEF, 115 TokenTypes.METHOD_DEF, 116 TokenTypes.ENUM_CONSTANT_DEF, 117 TokenTypes.LITERAL_WHILE, 118 TokenTypes.LITERAL_TRY, 119 TokenTypes.LITERAL_CATCH, 120 TokenTypes.LITERAL_FINALLY, 121 TokenTypes.LITERAL_SYNCHRONIZED, 122 TokenTypes.LITERAL_SWITCH, 123 TokenTypes.LITERAL_DO, 124 TokenTypes.LITERAL_IF, 125 TokenTypes.LITERAL_ELSE, 126 TokenTypes.LITERAL_FOR, 127 // TODO: need to handle.... 128 //TokenTypes.STATIC_INIT, 129 }; 130 } 131 132 @Override 133 public void visitToken(DetailAST aAST) 134 { 135 final DetailAST startToken; 136 final DetailAST brace; 137 138 switch (aAST.getType()) { 139 case TokenTypes.CTOR_DEF : 140 case TokenTypes.METHOD_DEF : 141 startToken = skipAnnotationOnlyLines(aAST); 142 brace = aAST.findFirstToken(TokenTypes.SLIST); 143 break; 144 145 case TokenTypes.INTERFACE_DEF : 146 case TokenTypes.CLASS_DEF : 147 case TokenTypes.ANNOTATION_DEF : 148 case TokenTypes.ENUM_DEF : 149 case TokenTypes.ENUM_CONSTANT_DEF : 150 startToken = skipAnnotationOnlyLines(aAST); 151 final DetailAST objBlock = aAST.findFirstToken(TokenTypes.OBJBLOCK); 152 brace = (objBlock == null) 153 ? null 154 : (DetailAST) objBlock.getFirstChild(); 155 break; 156 157 case TokenTypes.LITERAL_WHILE: 158 case TokenTypes.LITERAL_CATCH: 159 case TokenTypes.LITERAL_SYNCHRONIZED: 160 case TokenTypes.LITERAL_FOR: 161 case TokenTypes.LITERAL_TRY: 162 case TokenTypes.LITERAL_FINALLY: 163 case TokenTypes.LITERAL_DO: 164 case TokenTypes.LITERAL_IF : 165 startToken = aAST; 166 brace = aAST.findFirstToken(TokenTypes.SLIST); 167 break; 168 169 case TokenTypes.LITERAL_ELSE : 170 startToken = aAST; 171 final DetailAST candidate = aAST.getFirstChild(); 172 brace = 173 (candidate.getType() == TokenTypes.SLIST) 174 ? candidate 175 : null; // silently ignore 176 break; 177 178 case TokenTypes.LITERAL_SWITCH : 179 startToken = aAST; 180 brace = aAST.findFirstToken(TokenTypes.LCURLY); 181 break; 182 183 default : 184 startToken = null; 185 brace = null; 186 } 187 188 if ((brace != null) && (startToken != null)) { 189 verifyBrace(brace, startToken); 190 } 191 } 192 193 /** 194 * Skip lines that only contain <code>TokenTypes.ANNOTATION</code>s. 195 * If the received <code>DetailAST</code> 196 * has annotations within its modifiers then first token on the line 197 * of the first token afer all annotations is return. This might be 198 * an annotation. 199 * Otherwise, the received <code>DetailAST</code> is returned. 200 * @param aAST <code>DetailAST</code>. 201 * @return <code>DetailAST</code>. 202 */ 203 private DetailAST skipAnnotationOnlyLines(DetailAST aAST) 204 { 205 final DetailAST modifiers = aAST.findFirstToken(TokenTypes.MODIFIERS); 206 if (modifiers == null) { 207 return aAST; 208 } 209 DetailAST lastAnnot = findLastAnnotation(modifiers); 210 if (lastAnnot == null) { 211 // There are no annotations. 212 return aAST; 213 } 214 final DetailAST tokenAfterLast = lastAnnot.getNextSibling() != null 215 ? lastAnnot.getNextSibling() 216 : modifiers.getNextSibling(); 217 if (tokenAfterLast.getLineNo() > lastAnnot.getLineNo()) { 218 return tokenAfterLast; 219 } 220 final int lastAnnotLineNumber = lastAnnot.getLineNo(); 221 while (lastAnnot.getPreviousSibling() != null 222 && (lastAnnot.getPreviousSibling().getLineNo() 223 == lastAnnotLineNumber)) 224 { 225 lastAnnot = lastAnnot.getPreviousSibling(); 226 } 227 return lastAnnot; 228 } 229 230 /** 231 * Find the last token of type <code>TokenTypes.ANNOTATION</code> 232 * under the given set of modifiers. 233 * @param aModifiers <code>DetailAST</code>. 234 * @return <code>DetailAST</code> or null if there are no annotations. 235 */ 236 private DetailAST findLastAnnotation(DetailAST aModifiers) 237 { 238 DetailAST aAnnot = aModifiers.findFirstToken(TokenTypes.ANNOTATION); 239 while (aAnnot != null && aAnnot.getNextSibling() != null 240 && aAnnot.getNextSibling().getType() == TokenTypes.ANNOTATION) 241 { 242 aAnnot = aAnnot.getNextSibling(); 243 } 244 return aAnnot; 245 } 246 247 /** 248 * Verifies that a specified left curly brace is placed correctly 249 * according to policy. 250 * @param aBrace token for left curly brace 251 * @param aStartToken token for start of expression 252 */ 253 private void verifyBrace(final DetailAST aBrace, 254 final DetailAST aStartToken) 255 { 256 final String braceLine = getLines()[aBrace.getLineNo() - 1]; 257 258 // calculate the previous line length without trailing whitespace. Need 259 // to handle the case where there is no previous line, cause the line 260 // being check is the first line in the file. 261 final int prevLineLen = (aBrace.getLineNo() == 1) 262 ? mMaxLineLength 263 : Utils.lengthMinusTrailingWhitespace( 264 getLines()[aBrace.getLineNo() - 2]); 265 266 // Check for being told to ignore, or have '{}' which is a special case 267 if ((braceLine.length() > (aBrace.getColumnNo() + 1)) 268 && (braceLine.charAt(aBrace.getColumnNo() + 1) == '}')) 269 { 270 ; // ignore 271 } 272 else if (getAbstractOption() == LeftCurlyOption.NL) { 273 if (!Utils.whitespaceBefore(aBrace.getColumnNo(), braceLine)) { 274 log(aBrace.getLineNo(), aBrace.getColumnNo(), 275 "line.new", "{"); 276 } 277 } 278 else if (getAbstractOption() == LeftCurlyOption.EOL) { 279 if (Utils.whitespaceBefore(aBrace.getColumnNo(), braceLine) 280 && ((prevLineLen + 2) <= mMaxLineLength)) 281 { 282 log(aBrace.getLineNo(), aBrace.getColumnNo(), 283 "line.previous", "{"); 284 } 285 if (!hasLineBreakAfter(aBrace)) { 286 log(aBrace.getLineNo(), aBrace.getColumnNo(), "line.break.after"); 287 } 288 } 289 else if (getAbstractOption() == LeftCurlyOption.NLOW) { 290 if (aStartToken.getLineNo() == aBrace.getLineNo()) { 291 ; // all ok as on the same line 292 } 293 else if ((aStartToken.getLineNo() + 1) == aBrace.getLineNo()) { 294 if (!Utils.whitespaceBefore(aBrace.getColumnNo(), braceLine)) { 295 log(aBrace.getLineNo(), aBrace.getColumnNo(), 296 "line.new", "{"); 297 } 298 else if ((prevLineLen + 2) <= mMaxLineLength) { 299 log(aBrace.getLineNo(), aBrace.getColumnNo(), 300 "line.previous", "{"); 301 } 302 } 303 else if (!Utils.whitespaceBefore(aBrace.getColumnNo(), braceLine)) { 304 log(aBrace.getLineNo(), aBrace.getColumnNo(), 305 "line.new", "{"); 306 } 307 } 308 } 309 310 /** 311 * Checks if left curly has line break after. 312 * @param aLeftCurly 313 * Left curly token. 314 * @return 315 * True, left curly has line break after. 316 */ 317 private boolean hasLineBreakAfter(DetailAST aLeftCurly) 318 { 319 DetailAST nextToken = null; 320 if (aLeftCurly.getType() == TokenTypes.SLIST) { 321 nextToken = aLeftCurly.getFirstChild(); 322 } 323 else { 324 if (aLeftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) 325 { 326 if (!mIgnoreEnums) { 327 nextToken = aLeftCurly.getNextSibling(); 328 } 329 } 330 } 331 if (nextToken != null && nextToken.getType() != TokenTypes.RCURLY) { 332 if (aLeftCurly.getLineNo() == nextToken.getLineNo()) { 333 return false; 334 } 335 } 336 return true; 337 } 338}