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