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