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.regex.Pattern; 023 024import com.google.common.base.Optional; 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 in inner class or in interface definition 158 return; 159 } 160 161 if (checkClassName() 162 && checkName(method) 163 && checkModifiers(method) 164 && checkType(method) 165 && checkParams(method)) { 166 log(method.getLineNo(), MSG_KEY); 167 } 168 } 169 170 /** 171 * Checks that current class is not excluded. 172 * @return true if check passed, false otherwise 173 */ 174 private boolean checkClassName() { 175 return !excludedClassesPattern.matcher(currentClass).find(); 176 } 177 178 /** 179 * Checks that method name is @quot;main@quot;. 180 * @param method the METHOD_DEF node 181 * @return true if check passed, false otherwise 182 */ 183 private static boolean checkName(DetailAST method) { 184 final DetailAST ident = method.findFirstToken(TokenTypes.IDENT); 185 return "main".equals(ident.getText()); 186 } 187 188 /** 189 * Checks that method has final and static modifiers. 190 * @param method the METHOD_DEF node 191 * @return true if check passed, false otherwise 192 */ 193 private static boolean checkModifiers(DetailAST method) { 194 final DetailAST modifiers = 195 method.findFirstToken(TokenTypes.MODIFIERS); 196 197 return modifiers.branchContains(TokenTypes.LITERAL_PUBLIC) 198 && modifiers.branchContains(TokenTypes.LITERAL_STATIC); 199 } 200 201 /** 202 * Checks that return type is {@code void}. 203 * @param method the METHOD_DEF node 204 * @return true if check passed, false otherwise 205 */ 206 private static boolean checkType(DetailAST method) { 207 final DetailAST type = 208 method.findFirstToken(TokenTypes.TYPE).getFirstChild(); 209 return type.getType() == TokenTypes.LITERAL_VOID; 210 } 211 212 /** 213 * Checks that method has only {@code String[]} or only {@code String...} param. 214 * @param method the METHOD_DEF node 215 * @return true if check passed, false otherwise 216 */ 217 private static boolean checkParams(DetailAST method) { 218 boolean checkPassed = false; 219 final DetailAST params = method.findFirstToken(TokenTypes.PARAMETERS); 220 221 if (params.getChildCount() == 1) { 222 final DetailAST parameterType = params.getFirstChild().findFirstToken(TokenTypes.TYPE); 223 final Optional<DetailAST> arrayDecl = Optional.fromNullable( 224 parameterType.findFirstToken(TokenTypes.ARRAY_DECLARATOR)); 225 final Optional<DetailAST> varargs = Optional.fromNullable( 226 params.getFirstChild().findFirstToken(TokenTypes.ELLIPSIS)); 227 228 if (arrayDecl.isPresent()) { 229 checkPassed = isStringType(arrayDecl.get().getFirstChild()); 230 } 231 else if (varargs.isPresent()) { 232 checkPassed = isStringType(parameterType.getFirstChild()); 233 } 234 } 235 return checkPassed; 236 } 237 238 /** 239 * Whether the type is java.lang.String. 240 * @param typeAst the type to check. 241 * @return true, if the type is java.lang.String. 242 */ 243 private static boolean isStringType(DetailAST typeAst) { 244 final FullIdent type = FullIdent.createFullIdent(typeAst); 245 return "String".equals(type.getText()) 246 || "java.lang.String".equals(type.getText()); 247 } 248}