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