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 * &lt;module name=&quot;UncommentedMain&quot;/&gt;
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}