View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks;
21  
22  import java.util.Optional;
23  import java.util.regex.Pattern;
24  
25  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.FullIdent;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  
31  /**
32   * Detects uncommented main methods. Basically detects
33   * any main method, since if it is detectable
34   * that means it is uncommented.
35   *
36   * <pre class="body">
37   * &lt;module name=&quot;UncommentedMain&quot;/&gt;
38   * </pre>
39   *
40   * @author Michael Yui
41   * @author o_sukhodolsky
42   */
43  @FileStatefulCheck
44  public class UncommentedMainCheck
45      extends AbstractCheck {
46  
47      /**
48       * A key is pointing to the warning message text in "messages.properties"
49       * file.
50       */
51      public static final String MSG_KEY = "uncommented.main";
52  
53      /** Compiled regexp to exclude classes from check. */
54      private Pattern excludedClasses = Pattern.compile("^$");
55      /** Current class name. */
56      private String currentClass;
57      /** Current package. */
58      private FullIdent packageName;
59      /** Class definition depth. */
60      private int classDepth;
61  
62      /**
63       * Set the excluded classes pattern.
64       * @param excludedClasses a pattern
65       */
66      public void setExcludedClasses(Pattern excludedClasses) {
67          this.excludedClasses = excludedClasses;
68      }
69  
70      @Override
71      public int[] getAcceptableTokens() {
72          return getRequiredTokens();
73      }
74  
75      @Override
76      public int[] getDefaultTokens() {
77          return getRequiredTokens();
78      }
79  
80      @Override
81      public int[] getRequiredTokens() {
82          return new int[] {
83              TokenTypes.METHOD_DEF,
84              TokenTypes.CLASS_DEF,
85              TokenTypes.PACKAGE_DEF,
86          };
87      }
88  
89      @Override
90      public void beginTree(DetailAST rootAST) {
91          packageName = FullIdent.createFullIdent(null);
92          currentClass = null;
93          classDepth = 0;
94      }
95  
96      @Override
97      public void leaveToken(DetailAST ast) {
98          if (ast.getType() == TokenTypes.CLASS_DEF) {
99              classDepth--;
100         }
101     }
102 
103     @Override
104     public void visitToken(DetailAST ast) {
105 
106         switch (ast.getType()) {
107             case TokenTypes.PACKAGE_DEF:
108                 visitPackageDef(ast);
109                 break;
110             case TokenTypes.CLASS_DEF:
111                 visitClassDef(ast);
112                 break;
113             case TokenTypes.METHOD_DEF:
114                 visitMethodDef(ast);
115                 break;
116             default:
117                 throw new IllegalStateException(ast.toString());
118         }
119     }
120 
121     /**
122      * Sets current package.
123      * @param packageDef node for package definition
124      */
125     private void visitPackageDef(DetailAST packageDef) {
126         packageName = FullIdent.createFullIdent(packageDef.getLastChild()
127                 .getPreviousSibling());
128     }
129 
130     /**
131      * If not inner class then change current class name.
132      * @param classDef node for class definition
133      */
134     private void visitClassDef(DetailAST classDef) {
135         // we are not use inner classes because they can not
136         // have static methods
137         if (classDepth == 0) {
138             final DetailAST ident = classDef.findFirstToken(TokenTypes.IDENT);
139             currentClass = packageName.getText() + "." + ident.getText();
140             classDepth++;
141         }
142     }
143 
144     /**
145      * Checks method definition if this is
146      * {@code public static void main(String[])}.
147      * @param method method definition node
148      */
149     private void visitMethodDef(DetailAST method) {
150         if (classDepth == 1
151                 // method not in inner class or in interface definition
152                 && checkClassName()
153                 && checkName(method)
154                 && checkModifiers(method)
155                 && checkType(method)
156                 && checkParams(method)) {
157             log(method.getLineNo(), MSG_KEY);
158         }
159     }
160 
161     /**
162      * Checks that current class is not excluded.
163      * @return true if check passed, false otherwise
164      */
165     private boolean checkClassName() {
166         return !excludedClasses.matcher(currentClass).find();
167     }
168 
169     /**
170      * Checks that method name is @quot;main@quot;.
171      * @param method the METHOD_DEF node
172      * @return true if check passed, false otherwise
173      */
174     private static boolean checkName(DetailAST method) {
175         final DetailAST ident = method.findFirstToken(TokenTypes.IDENT);
176         return "main".equals(ident.getText());
177     }
178 
179     /**
180      * Checks that method has final and static modifiers.
181      * @param method the METHOD_DEF node
182      * @return true if check passed, false otherwise
183      */
184     private static boolean checkModifiers(DetailAST method) {
185         final DetailAST modifiers =
186             method.findFirstToken(TokenTypes.MODIFIERS);
187 
188         return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null
189             && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
190     }
191 
192     /**
193      * Checks that return type is {@code void}.
194      * @param method the METHOD_DEF node
195      * @return true if check passed, false otherwise
196      */
197     private static boolean checkType(DetailAST method) {
198         final DetailAST type =
199             method.findFirstToken(TokenTypes.TYPE).getFirstChild();
200         return type.getType() == TokenTypes.LITERAL_VOID;
201     }
202 
203     /**
204      * Checks that method has only {@code String[]} or only {@code String...} param.
205      * @param method the METHOD_DEF node
206      * @return true if check passed, false otherwise
207      */
208     private static boolean checkParams(DetailAST method) {
209         boolean checkPassed = false;
210         final DetailAST params = method.findFirstToken(TokenTypes.PARAMETERS);
211 
212         if (params.getChildCount() == 1) {
213             final DetailAST parameterType = params.getFirstChild().findFirstToken(TokenTypes.TYPE);
214             final Optional<DetailAST> arrayDecl = Optional.ofNullable(
215                 parameterType.findFirstToken(TokenTypes.ARRAY_DECLARATOR));
216             final Optional<DetailAST> varargs = Optional.ofNullable(
217                 params.getFirstChild().findFirstToken(TokenTypes.ELLIPSIS));
218 
219             if (arrayDecl.isPresent()) {
220                 checkPassed = isStringType(arrayDecl.get().getFirstChild());
221             }
222             else if (varargs.isPresent()) {
223                 checkPassed = isStringType(parameterType.getFirstChild());
224             }
225         }
226         return checkPassed;
227     }
228 
229     /**
230      * Whether the type is java.lang.String.
231      * @param typeAst the type to check.
232      * @return true, if the type is java.lang.String.
233      */
234     private static boolean isStringType(DetailAST typeAst) {
235         final FullIdent type = FullIdent.createFullIdent(typeAst);
236         return "String".equals(type.getText())
237             || "java.lang.String".equals(type.getText());
238     }
239 }