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