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