001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.FullIdent; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 030 031/** 032 * Detects uncommented main methods. Basically detects 033 * any main method, since if it is detectable 034 * that means it is uncommented. 035 * 036 * <pre class="body"> 037 * <module name="UncommentedMain"/> 038 * </pre> 039 * 040 * @author Michael Yui 041 * @author o_sukhodolsky 042 */ 043public class UncommentedMainCheck 044 extends AbstractCheck { 045 046 /** 047 * A key is pointing to the warning message text in "messages.properties" 048 * file. 049 */ 050 public static final String MSG_KEY = "uncommented.main"; 051 052 /** The pattern to exclude classes from the check. */ 053 private String excludedClasses = "^$"; 054 /** Compiled regexp to exclude classes from check. */ 055 private Pattern excludedClassesPattern = 056 CommonUtils.createPattern(excludedClasses); 057 /** Current class name. */ 058 private String currentClass; 059 /** Current package. */ 060 private FullIdent packageName; 061 /** Class definition depth. */ 062 private int classDepth; 063 064 /** 065 * Set the excluded classes pattern. 066 * @param excludedClasses a {@code String} value 067 */ 068 public void setExcludedClasses(String excludedClasses) { 069 this.excludedClasses = excludedClasses; 070 excludedClassesPattern = CommonUtils.createPattern(excludedClasses); 071 } 072 073 @Override 074 public int[] getAcceptableTokens() { 075 return new int[] { 076 TokenTypes.METHOD_DEF, 077 TokenTypes.CLASS_DEF, 078 TokenTypes.PACKAGE_DEF, 079 }; 080 } 081 082 @Override 083 public int[] getDefaultTokens() { 084 return getAcceptableTokens(); 085 } 086 087 @Override 088 public int[] getRequiredTokens() { 089 return getAcceptableTokens(); 090 } 091 092 @Override 093 public void beginTree(DetailAST rootAST) { 094 packageName = FullIdent.createFullIdent(null); 095 currentClass = null; 096 classDepth = 0; 097 } 098 099 @Override 100 public void leaveToken(DetailAST ast) { 101 if (ast.getType() == TokenTypes.CLASS_DEF) { 102 if (classDepth == 1) { 103 currentClass = null; 104 } 105 classDepth--; 106 } 107 } 108 109 @Override 110 public void visitToken(DetailAST ast) { 111 112 switch (ast.getType()) { 113 case TokenTypes.PACKAGE_DEF: 114 visitPackageDef(ast); 115 break; 116 case TokenTypes.CLASS_DEF: 117 visitClassDef(ast); 118 break; 119 case TokenTypes.METHOD_DEF: 120 visitMethodDef(ast); 121 break; 122 default: 123 throw new IllegalStateException(ast.toString()); 124 } 125 } 126 127 /** 128 * Sets current package. 129 * @param packageDef node for package definition 130 */ 131 private void visitPackageDef(DetailAST packageDef) { 132 packageName = FullIdent.createFullIdent(packageDef.getLastChild() 133 .getPreviousSibling()); 134 } 135 136 /** 137 * If not inner class then change current class name. 138 * @param classDef node for class definition 139 */ 140 private void visitClassDef(DetailAST classDef) { 141 // we are not use inner classes because they can not 142 // have static methods 143 if (classDepth == 0) { 144 final DetailAST ident = classDef.findFirstToken(TokenTypes.IDENT); 145 currentClass = packageName.getText() + "." + ident.getText(); 146 classDepth++; 147 } 148 } 149 150 /** 151 * Checks method definition if this is 152 * {@code public static void main(String[])}. 153 * @param method method definition node 154 */ 155 private void visitMethodDef(DetailAST method) { 156 if (classDepth == 1 157 // method not in inner class or in interface definition 158 && checkClassName() 159 && checkName(method) 160 && checkModifiers(method) 161 && checkType(method) 162 && checkParams(method)) { 163 log(method.getLineNo(), MSG_KEY); 164 } 165 } 166 167 /** 168 * Checks that current class is not excluded. 169 * @return true if check passed, false otherwise 170 */ 171 private boolean checkClassName() { 172 return !excludedClassesPattern.matcher(currentClass).find(); 173 } 174 175 /** 176 * Checks that method name is @quot;main@quot;. 177 * @param method the METHOD_DEF node 178 * @return true if check passed, false otherwise 179 */ 180 private static boolean checkName(DetailAST method) { 181 final DetailAST ident = method.findFirstToken(TokenTypes.IDENT); 182 return "main".equals(ident.getText()); 183 } 184 185 /** 186 * Checks that method has final and static modifiers. 187 * @param method the METHOD_DEF node 188 * @return true if check passed, false otherwise 189 */ 190 private static boolean checkModifiers(DetailAST method) { 191 final DetailAST modifiers = 192 method.findFirstToken(TokenTypes.MODIFIERS); 193 194 return modifiers.branchContains(TokenTypes.LITERAL_PUBLIC) 195 && modifiers.branchContains(TokenTypes.LITERAL_STATIC); 196 } 197 198 /** 199 * Checks that return type is {@code void}. 200 * @param method the METHOD_DEF node 201 * @return true if check passed, false otherwise 202 */ 203 private static boolean checkType(DetailAST method) { 204 final DetailAST type = 205 method.findFirstToken(TokenTypes.TYPE).getFirstChild(); 206 return type.getType() == TokenTypes.LITERAL_VOID; 207 } 208 209 /** 210 * Checks that method has only {@code String[]} or only {@code String...} param. 211 * @param method the METHOD_DEF node 212 * @return true if check passed, false otherwise 213 */ 214 private static boolean checkParams(DetailAST method) { 215 boolean checkPassed = false; 216 final DetailAST params = method.findFirstToken(TokenTypes.PARAMETERS); 217 218 if (params.getChildCount() == 1) { 219 final DetailAST parameterType = params.getFirstChild().findFirstToken(TokenTypes.TYPE); 220 final Optional<DetailAST> arrayDecl = Optional.ofNullable( 221 parameterType.findFirstToken(TokenTypes.ARRAY_DECLARATOR)); 222 final Optional<DetailAST> varargs = Optional.ofNullable( 223 params.getFirstChild().findFirstToken(TokenTypes.ELLIPSIS)); 224 225 if (arrayDecl.isPresent()) { 226 checkPassed = isStringType(arrayDecl.get().getFirstChild()); 227 } 228 else if (varargs.isPresent()) { 229 checkPassed = isStringType(parameterType.getFirstChild()); 230 } 231 } 232 return checkPassed; 233 } 234 235 /** 236 * Whether the type is java.lang.String. 237 * @param typeAst the type to check. 238 * @return true, if the type is java.lang.String. 239 */ 240 private static boolean isStringType(DetailAST typeAst) { 241 final FullIdent type = FullIdent.createFullIdent(typeAst); 242 return "String".equals(type.getText()) 243 || "java.lang.String".equals(type.getText()); 244 } 245}