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