001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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; 021 022import java.util.Optional; 023import java.util.regex.Pattern; 024 025import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.FullIdent; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030 031/** 032 * <p> 033 * Detects uncommented {@code main} methods. 034 * </p> 035 * <p> 036 * Rationale: A {@code main} method is often used for debugging purposes. 037 * When debugging is finished, developers often forget to remove the method, 038 * which changes the API and increases the size of the resulting class or JAR file. 039 * With the exception of the real program entry points, all {@code main} methods 040 * should be removed or commented out of the sources. 041 * </p> 042 * <ul> 043 * <li> 044 * Property {@code excludedClasses} - Specify pattern for qualified names of 045 * classes which are allowed to have a {@code main} method. 046 * Default value is {@code "^$" (empty)}. 047 * </li> 048 * </ul> 049 * <p> 050 * To configure the check: 051 * </p> 052 * <pre> 053 * <module name="UncommentedMain"/> 054 * </pre> 055 * <p>Example:</p> 056 * <pre> 057 * public class Game { 058 * public static void main(String... args){} // violation 059 * } 060 * 061 * public class Main { 062 * public static void main(String[] args){} // violation 063 * } 064 * 065 * public class Launch { 066 * //public static void main(String[] args){} // OK 067 * } 068 * 069 * public class Start { 070 * public void main(){} // OK 071 * } 072 * </pre> 073 * <p> 074 * To configure the check to allow the {@code main} method for all classes with "Main" name: 075 * </p> 076 * <pre> 077 * <module name="UncommentedMain"> 078 * <property name="excludedClasses" value="\.Main$"/> 079 * </module> 080 * </pre> 081 * <p>Example:</p> 082 * <pre> 083 * public class Game { 084 * public static void main(String... args){} // violation 085 * } 086 * 087 * public class Main { 088 * public static void main(String[] args){} // OK 089 * } 090 * 091 * public class Launch { 092 * //public static void main(String[] args){} // OK 093 * } 094 * 095 * public class Start { 096 * public void main(){} // OK 097 * } 098 * </pre> 099 * 100 * @since 3.2 101 */ 102@FileStatefulCheck 103public class UncommentedMainCheck 104 extends AbstractCheck { 105 106 /** 107 * A key is pointing to the warning message text in "messages.properties" 108 * file. 109 */ 110 public static final String MSG_KEY = "uncommented.main"; 111 112 /** 113 * Specify pattern for qualified names of classes which are allowed to 114 * have a {@code main} method. 115 */ 116 private Pattern excludedClasses = Pattern.compile("^$"); 117 /** Current class name. */ 118 private String currentClass; 119 /** Current package. */ 120 private FullIdent packageName; 121 /** Class definition depth. */ 122 private int classDepth; 123 124 /** 125 * Setter to specify pattern for qualified names of classes which are allowed 126 * to have a {@code main} method. 127 * 128 * @param excludedClasses a pattern 129 */ 130 public void setExcludedClasses(Pattern excludedClasses) { 131 this.excludedClasses = excludedClasses; 132 } 133 134 @Override 135 public int[] getAcceptableTokens() { 136 return getRequiredTokens(); 137 } 138 139 @Override 140 public int[] getDefaultTokens() { 141 return getRequiredTokens(); 142 } 143 144 @Override 145 public int[] getRequiredTokens() { 146 return new int[] { 147 TokenTypes.METHOD_DEF, 148 TokenTypes.CLASS_DEF, 149 TokenTypes.PACKAGE_DEF, 150 }; 151 } 152 153 @Override 154 public void beginTree(DetailAST rootAST) { 155 packageName = FullIdent.createFullIdent(null); 156 currentClass = null; 157 classDepth = 0; 158 } 159 160 @Override 161 public void leaveToken(DetailAST ast) { 162 if (ast.getType() == TokenTypes.CLASS_DEF) { 163 classDepth--; 164 } 165 } 166 167 @Override 168 public void visitToken(DetailAST ast) { 169 switch (ast.getType()) { 170 case TokenTypes.PACKAGE_DEF: 171 visitPackageDef(ast); 172 break; 173 case TokenTypes.CLASS_DEF: 174 visitClassDef(ast); 175 break; 176 case TokenTypes.METHOD_DEF: 177 visitMethodDef(ast); 178 break; 179 default: 180 throw new IllegalStateException(ast.toString()); 181 } 182 } 183 184 /** 185 * Sets current package. 186 * 187 * @param packageDef node for package definition 188 */ 189 private void visitPackageDef(DetailAST packageDef) { 190 packageName = FullIdent.createFullIdent(packageDef.getLastChild() 191 .getPreviousSibling()); 192 } 193 194 /** 195 * If not inner class then change current class name. 196 * 197 * @param classDef node for class definition 198 */ 199 private void visitClassDef(DetailAST classDef) { 200 // we are not use inner classes because they can not 201 // have static methods 202 if (classDepth == 0) { 203 final DetailAST ident = classDef.findFirstToken(TokenTypes.IDENT); 204 currentClass = packageName.getText() + "." + ident.getText(); 205 classDepth++; 206 } 207 } 208 209 /** 210 * Checks method definition if this is 211 * {@code public static void main(String[])}. 212 * 213 * @param method method definition node 214 */ 215 private void visitMethodDef(DetailAST method) { 216 if (classDepth == 1 217 // method not in inner class or in interface definition 218 && checkClassName() 219 && checkName(method) 220 && checkModifiers(method) 221 && checkType(method) 222 && checkParams(method)) { 223 log(method, MSG_KEY); 224 } 225 } 226 227 /** 228 * Checks that current class is not excluded. 229 * 230 * @return true if check passed, false otherwise 231 */ 232 private boolean checkClassName() { 233 return !excludedClasses.matcher(currentClass).find(); 234 } 235 236 /** 237 * Checks that method name is @quot;main@quot;. 238 * 239 * @param method the METHOD_DEF node 240 * @return true if check passed, false otherwise 241 */ 242 private static boolean checkName(DetailAST method) { 243 final DetailAST ident = method.findFirstToken(TokenTypes.IDENT); 244 return "main".equals(ident.getText()); 245 } 246 247 /** 248 * Checks that method has final and static modifiers. 249 * 250 * @param method the METHOD_DEF node 251 * @return true if check passed, false otherwise 252 */ 253 private static boolean checkModifiers(DetailAST method) { 254 final DetailAST modifiers = 255 method.findFirstToken(TokenTypes.MODIFIERS); 256 257 return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null 258 && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 259 } 260 261 /** 262 * Checks that return type is {@code void}. 263 * 264 * @param method the METHOD_DEF node 265 * @return true if check passed, false otherwise 266 */ 267 private static boolean checkType(DetailAST method) { 268 final DetailAST type = 269 method.findFirstToken(TokenTypes.TYPE).getFirstChild(); 270 return type.getType() == TokenTypes.LITERAL_VOID; 271 } 272 273 /** 274 * Checks that method has only {@code String[]} or only {@code String...} param. 275 * 276 * @param method the METHOD_DEF node 277 * @return true if check passed, false otherwise 278 */ 279 private static boolean checkParams(DetailAST method) { 280 boolean checkPassed = false; 281 final DetailAST params = method.findFirstToken(TokenTypes.PARAMETERS); 282 283 if (params.getChildCount() == 1) { 284 final DetailAST parameterType = params.getFirstChild().findFirstToken(TokenTypes.TYPE); 285 final Optional<DetailAST> arrayDecl = Optional.ofNullable( 286 parameterType.findFirstToken(TokenTypes.ARRAY_DECLARATOR)); 287 final Optional<DetailAST> varargs = Optional.ofNullable( 288 params.getFirstChild().findFirstToken(TokenTypes.ELLIPSIS)); 289 290 if (arrayDecl.isPresent()) { 291 checkPassed = isStringType(arrayDecl.get().getFirstChild()); 292 } 293 else if (varargs.isPresent()) { 294 checkPassed = isStringType(parameterType.getFirstChild()); 295 } 296 } 297 return checkPassed; 298 } 299 300 /** 301 * Whether the type is java.lang.String. 302 * 303 * @param typeAst the type to check. 304 * @return true, if the type is java.lang.String. 305 */ 306 private static boolean isStringType(DetailAST typeAst) { 307 final FullIdent type = FullIdent.createFullIdent(typeAst); 308 return "String".equals(type.getText()) 309 || "java.lang.String".equals(type.getText()); 310 } 311 312}