View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.Set;
24  import java.util.regex.Pattern;
25  
26  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.FullIdent;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  
32  /**
33   * <div>
34   * Detects uncommented {@code main} methods.
35   * </div>
36   *
37   * <p>
38   * Rationale: A {@code main} method is often used for debugging purposes.
39   * When debugging is finished, developers often forget to remove the method,
40   * which changes the API and increases the size of the resulting class or JAR file.
41   * Except for the real program entry points, all {@code main} methods
42   * should be removed or commented out of the sources.
43   * </p>
44   * <ul>
45   * <li>
46   * Property {@code excludedClasses} - Specify pattern for qualified names of
47   * classes which are allowed to have a {@code main} method.
48   * Type is {@code java.util.regex.Pattern}.
49   * Default value is {@code "^$"}.
50   * </li>
51   * </ul>
52   *
53   * <p>
54   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
55   * </p>
56   *
57   * <p>
58   * Violation Message Keys:
59   * </p>
60   * <ul>
61   * <li>
62   * {@code uncommented.main}
63   * </li>
64   * </ul>
65   *
66   * @since 3.2
67   */
68  @FileStatefulCheck
69  public class UncommentedMainCheck
70      extends AbstractCheck {
71  
72      /**
73       * A key is pointing to the warning message text in "messages.properties"
74       * file.
75       */
76      public static final String MSG_KEY = "uncommented.main";
77  
78      /** Set of possible String array types. */
79      private static final Set<String> STRING_PARAMETER_NAMES = Set.of(
80          String[].class.getCanonicalName(),
81          String.class.getCanonicalName(),
82          String[].class.getSimpleName(),
83          String.class.getSimpleName()
84      );
85  
86      /**
87       * Specify pattern for qualified names of classes which are allowed to
88       * have a {@code main} method.
89       */
90      private Pattern excludedClasses = Pattern.compile("^$");
91      /** Current class name. */
92      private String currentClass;
93      /** Current package. */
94      private FullIdent packageName;
95      /** Class definition depth. */
96      private int classDepth;
97  
98      /**
99       * Setter to specify pattern for qualified names of classes which are allowed
100      * to have a {@code main} method.
101      *
102      * @param excludedClasses a pattern
103      * @since 3.2
104      */
105     public void setExcludedClasses(Pattern excludedClasses) {
106         this.excludedClasses = excludedClasses;
107     }
108 
109     @Override
110     public int[] getAcceptableTokens() {
111         return getRequiredTokens();
112     }
113 
114     @Override
115     public int[] getDefaultTokens() {
116         return getRequiredTokens();
117     }
118 
119     @Override
120     public int[] getRequiredTokens() {
121         return new int[] {
122             TokenTypes.METHOD_DEF,
123             TokenTypes.CLASS_DEF,
124             TokenTypes.PACKAGE_DEF,
125             TokenTypes.RECORD_DEF,
126         };
127     }
128 
129     @Override
130     public void beginTree(DetailAST rootAST) {
131         packageName = FullIdent.createFullIdent(null);
132         classDepth = 0;
133     }
134 
135     @Override
136     public void leaveToken(DetailAST ast) {
137         if (ast.getType() == TokenTypes.CLASS_DEF) {
138             classDepth--;
139         }
140     }
141 
142     @Override
143     public void visitToken(DetailAST ast) {
144         switch (ast.getType()) {
145             case TokenTypes.PACKAGE_DEF:
146                 visitPackageDef(ast);
147                 break;
148             case TokenTypes.RECORD_DEF:
149             case TokenTypes.CLASS_DEF:
150                 visitClassOrRecordDef(ast);
151                 break;
152             case TokenTypes.METHOD_DEF:
153                 visitMethodDef(ast);
154                 break;
155             default:
156                 throw new IllegalStateException(ast.toString());
157         }
158     }
159 
160     /**
161      * Sets current package.
162      *
163      * @param packageDef node for package definition
164      */
165     private void visitPackageDef(DetailAST packageDef) {
166         packageName = FullIdent.createFullIdent(packageDef.getLastChild()
167                 .getPreviousSibling());
168     }
169 
170     /**
171      * If not inner class then change current class name.
172      *
173      * @param classOrRecordDef node for class or record definition
174      */
175     private void visitClassOrRecordDef(DetailAST classOrRecordDef) {
176         // we are not use inner classes because they can not
177         // have static methods
178         if (classDepth == 0) {
179             final DetailAST ident = classOrRecordDef.findFirstToken(TokenTypes.IDENT);
180             currentClass = packageName.getText() + "." + ident.getText();
181             classDepth++;
182         }
183     }
184 
185     /**
186      * Checks method definition if this is
187      * {@code public static void main(String[])}.
188      *
189      * @param method method definition node
190      */
191     private void visitMethodDef(DetailAST method) {
192         if (classDepth == 1
193                 // method not in inner class or in interface definition
194                 && checkClassName()
195                 && checkName(method)
196                 && checkModifiers(method)
197                 && checkType(method)
198                 && checkParams(method)) {
199             log(method, MSG_KEY);
200         }
201     }
202 
203     /**
204      * Checks that current class is not excluded.
205      *
206      * @return true if check passed, false otherwise
207      */
208     private boolean checkClassName() {
209         return !excludedClasses.matcher(currentClass).find();
210     }
211 
212     /**
213      * Checks that method name is @quot;main@quot;.
214      *
215      * @param method the METHOD_DEF node
216      * @return true if check passed, false otherwise
217      */
218     private static boolean checkName(DetailAST method) {
219         final DetailAST ident = method.findFirstToken(TokenTypes.IDENT);
220         return "main".equals(ident.getText());
221     }
222 
223     /**
224      * Checks that method has final and static modifiers.
225      *
226      * @param method the METHOD_DEF node
227      * @return true if check passed, false otherwise
228      */
229     private static boolean checkModifiers(DetailAST method) {
230         final DetailAST modifiers =
231             method.findFirstToken(TokenTypes.MODIFIERS);
232 
233         return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null
234             && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
235     }
236 
237     /**
238      * Checks that return type is {@code void}.
239      *
240      * @param method the METHOD_DEF node
241      * @return true if check passed, false otherwise
242      */
243     private static boolean checkType(DetailAST method) {
244         final DetailAST type =
245             method.findFirstToken(TokenTypes.TYPE).getFirstChild();
246         return type.getType() == TokenTypes.LITERAL_VOID;
247     }
248 
249     /**
250      * Checks that method has only {@code String[]} or only {@code String...} param.
251      *
252      * @param method the METHOD_DEF node
253      * @return true if check passed, false otherwise
254      */
255     private static boolean checkParams(DetailAST method) {
256         boolean checkPassed = false;
257         final DetailAST params = method.findFirstToken(TokenTypes.PARAMETERS);
258 
259         if (params.getChildCount() == 1) {
260             final DetailAST parameterType = params.getFirstChild().findFirstToken(TokenTypes.TYPE);
261             final boolean isArrayDeclaration =
262                 parameterType.findFirstToken(TokenTypes.ARRAY_DECLARATOR) != null;
263             final Optional<DetailAST> varargs = Optional.ofNullable(
264                 params.getFirstChild().findFirstToken(TokenTypes.ELLIPSIS));
265 
266             if (isArrayDeclaration || varargs.isPresent()) {
267                 checkPassed = isStringType(parameterType.getFirstChild());
268             }
269         }
270         return checkPassed;
271     }
272 
273     /**
274      * Whether the type is java.lang.String.
275      *
276      * @param typeAst the type to check.
277      * @return true, if the type is java.lang.String.
278      */
279     private static boolean isStringType(DetailAST typeAst) {
280         final FullIdent type = FullIdent.createFullIdent(typeAst);
281         return STRING_PARAMETER_NAMES.contains(type.getText());
282     }
283 
284 }