001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.blocks; 021 022import java.util.Locale; 023 024import org.apache.commons.beanutils.ConversionException; 025import org.apache.commons.lang3.ArrayUtils; 026import org.apache.commons.lang3.StringUtils; 027 028import com.puppycrawl.tools.checkstyle.api.Check; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031 032/** 033 * Checks for empty blocks. The policy to verify is specified using the {@link 034 * BlockOption} class and defaults to {@link BlockOption#STMT}. 035 * 036 * <p> By default the check will check the following blocks: 037 * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}, 038 * {@link TokenTypes#LITERAL_TRY LITERAL_TRY}, 039 * {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, 040 * {@link TokenTypes#LITERAL_DO LITERAL_DO}, 041 * {@link TokenTypes#LITERAL_IF LITERAL_IF}, 042 * {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}, 043 * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}, 044 * {@link TokenTypes#STATIC_INIT STATIC_INIT}, 045 * {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}. 046 * {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}. 047 * </p> 048 * 049 * <p> An example of how to configure the check is: 050 * </p> 051 * <pre> 052 * <module name="EmptyBlock"/> 053 * </pre> 054 * 055 * <p> An example of how to configure the check for the {@link 056 * BlockOption#TEXT} policy and only try blocks is: 057 * </p> 058 * 059 * <pre> 060 * <module name="EmptyBlock"> 061 * <property name="tokens" value="LITERAL_TRY"/> 062 * <property name="option" value="text"/> 063 * </module> 064 * </pre> 065 * 066 * @author Lars Kühne 067 */ 068public class EmptyBlockCheck 069 extends Check { 070 /** 071 * A key is pointing to the warning message text in "messages.properties" 072 * file. 073 */ 074 public static final String MSG_KEY_BLOCK_NO_STMT = "block.noStmt"; 075 076 /** 077 * A key is pointing to the warning message text in "messages.properties" 078 * file. 079 */ 080 public static final String MSG_KEY_BLOCK_EMPTY = "block.empty"; 081 082 /** The policy to enforce. */ 083 private BlockOption option = BlockOption.STMT; 084 085 /** 086 * Set the option to enforce. 087 * @param optionStr string to decode option from 088 * @throws ConversionException if unable to decode 089 */ 090 public void setOption(String optionStr) { 091 try { 092 option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 093 } 094 catch (IllegalArgumentException iae) { 095 throw new ConversionException("unable to parse " + optionStr, iae); 096 } 097 } 098 099 @Override 100 public int[] getDefaultTokens() { 101 return new int[] { 102 TokenTypes.LITERAL_WHILE, 103 TokenTypes.LITERAL_TRY, 104 TokenTypes.LITERAL_FINALLY, 105 TokenTypes.LITERAL_DO, 106 TokenTypes.LITERAL_IF, 107 TokenTypes.LITERAL_ELSE, 108 TokenTypes.LITERAL_FOR, 109 TokenTypes.INSTANCE_INIT, 110 TokenTypes.STATIC_INIT, 111 TokenTypes.LITERAL_SWITCH, 112 TokenTypes.LITERAL_SYNCHRONIZED, 113 }; 114 } 115 116 @Override 117 public int[] getAcceptableTokens() { 118 return new int[] { 119 TokenTypes.LITERAL_WHILE, 120 TokenTypes.LITERAL_TRY, 121 TokenTypes.LITERAL_CATCH, 122 TokenTypes.LITERAL_FINALLY, 123 TokenTypes.LITERAL_DO, 124 TokenTypes.LITERAL_IF, 125 TokenTypes.LITERAL_ELSE, 126 TokenTypes.LITERAL_FOR, 127 TokenTypes.INSTANCE_INIT, 128 TokenTypes.STATIC_INIT, 129 TokenTypes.LITERAL_SWITCH, 130 TokenTypes.LITERAL_SYNCHRONIZED, 131 TokenTypes.LITERAL_CASE, 132 TokenTypes.LITERAL_DEFAULT, 133 TokenTypes.ARRAY_INIT, 134 }; 135 } 136 137 @Override 138 public int[] getRequiredTokens() { 139 return ArrayUtils.EMPTY_INT_ARRAY; 140 } 141 142 @Override 143 public void visitToken(DetailAST ast) { 144 final DetailAST slistToken = ast.findFirstToken(TokenTypes.SLIST); 145 final DetailAST leftCurly; 146 147 if (slistToken == null) { 148 leftCurly = ast.findFirstToken(TokenTypes.LCURLY); 149 } 150 else { 151 leftCurly = slistToken; 152 } 153 154 if (leftCurly != null) { 155 if (option == BlockOption.STMT) { 156 boolean emptyBlock; 157 if (leftCurly.getType() == TokenTypes.LCURLY) { 158 emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP; 159 } 160 else { 161 emptyBlock = leftCurly.getChildCount() <= 1; 162 } 163 if (emptyBlock) { 164 log(leftCurly.getLineNo(), 165 leftCurly.getColumnNo(), 166 MSG_KEY_BLOCK_NO_STMT, 167 ast.getText()); 168 } 169 } 170 else if (!hasText(leftCurly)) { 171 log(leftCurly.getLineNo(), 172 leftCurly.getColumnNo(), 173 MSG_KEY_BLOCK_EMPTY, 174 ast.getText()); 175 } 176 } 177 } 178 179 /** 180 * @param slistAST a {@code DetailAST} value 181 * @return whether the SLIST token contains any text. 182 */ 183 protected boolean hasText(final DetailAST slistAST) { 184 final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY); 185 final DetailAST rcurlyAST; 186 187 if (rightCurly == null) { 188 rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY); 189 } 190 else { 191 rcurlyAST = rightCurly; 192 } 193 final int slistLineNo = slistAST.getLineNo(); 194 final int slistColNo = slistAST.getColumnNo(); 195 final int rcurlyLineNo = rcurlyAST.getLineNo(); 196 final int rcurlyColNo = rcurlyAST.getColumnNo(); 197 final String[] lines = getLines(); 198 boolean returnValue = false; 199 if (slistLineNo == rcurlyLineNo) { 200 // Handle braces on the same line 201 final String txt = lines[slistLineNo - 1] 202 .substring(slistColNo + 1, rcurlyColNo); 203 if (StringUtils.isNotBlank(txt)) { 204 returnValue = true; 205 } 206 } 207 else { 208 // check only whitespace of first & last lines 209 if (lines[slistLineNo - 1].substring(slistColNo + 1).trim().isEmpty() 210 && lines[rcurlyLineNo - 1].substring(0, rcurlyColNo).trim().isEmpty()) { 211 // check if all lines are also only whitespace 212 returnValue = !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo); 213 } 214 else { 215 returnValue = true; 216 } 217 } 218 return returnValue; 219 } 220 221 /** 222 * Checks is all lines in array contain whitespaces only. 223 * 224 * @param lines 225 * array of lines 226 * @param lineFrom 227 * check from this line number 228 * @param lineTo 229 * check to this line numbers 230 * @return true if lines contain only whitespaces 231 */ 232 private static boolean checkIsAllLinesAreWhitespace(String[] lines, int lineFrom, int lineTo) { 233 boolean result = true; 234 for (int i = lineFrom; i < lineTo - 1; i++) { 235 if (!lines[i].trim().isEmpty()) { 236 result = false; 237 break; 238 } 239 } 240 return result; 241 } 242}