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