NoMainMethodInAbstractClassCheck.java

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // checkstyle: Checks Java source code for adherence to a set of rules.
  3. // Copyright (C) 2001-2019 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. package com.github.sevntu.checkstyle.checks.design;

  20. import java.util.Deque;
  21. import java.util.LinkedList;

  22. import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
  23. import com.puppycrawl.tools.checkstyle.api.DetailAST;
  24. import com.puppycrawl.tools.checkstyle.api.TokenTypes;

  25. /**
  26.  * Forbids main methods in abstract classes. Existence of 'main' method can
  27.  * mislead a developer to consider this class as a ready-to-use implementation.
  28.  * @author Baratali Izmailov <a href="mailto:barataliba@gmail.com">email</a>
  29.  * @since 1.9.0
  30.  */
  31. public class NoMainMethodInAbstractClassCheck extends AbstractCheck {

  32.     /**
  33.      * Key for error message.
  34.      */
  35.     public static final String MSG_KEY = "avoid.main.method.in.abstract.class";

  36.     /** String representation of string class. */
  37.     private static final String STRING_CLASS = "String";

  38.     /**
  39.      * Keep OBJBLOCKs of classes that are under validation.
  40.      */
  41.     private final Deque<DetailAST> objBlockTokensStack =
  42.             new LinkedList<>();

  43.     @Override
  44.     public final int[] getDefaultTokens() {
  45.         return new int[] {
  46.             TokenTypes.CLASS_DEF,
  47.             TokenTypes.METHOD_DEF,
  48.         };
  49.     }

  50.     @Override
  51.     public int[] getAcceptableTokens() {
  52.         return getDefaultTokens();
  53.     }

  54.     @Override
  55.     public int[] getRequiredTokens() {
  56.         return getDefaultTokens();
  57.     }

  58.     @Override
  59.     public final void visitToken(final DetailAST ast) {
  60.         if (ast.getType() == TokenTypes.CLASS_DEF) {
  61.             if (isNotInnerClass(ast)) {
  62.                 // remove all tokens from stack
  63.                 objBlockTokensStack.clear();
  64.             }
  65.             if (hasAbstractModifier(ast)) {
  66.                 objBlockTokensStack.push(
  67.                         ast.findFirstToken(TokenTypes.OBJBLOCK));
  68.             }
  69.         }
  70.         // type of token is METHOD_DEF
  71.         else if (isChildOfCurrentObjBlockToken(ast) && isMainMethod(ast)) {
  72.             log(ast, MSG_KEY);
  73.             // remove current objblock
  74.             objBlockTokensStack.pop();
  75.         }
  76.     }

  77.     /**
  78.      * Verify that class is not inner.
  79.      * @param classDefAST
  80.      *        DetailAST of class definition.
  81.      * @return true if class is not inner, false otherwise.
  82.      */
  83.     private boolean isNotInnerClass(final DetailAST classDefAST) {
  84.         boolean result = true;
  85.         final DetailAST objBlockAST = classDefAST.getParent();
  86.         for (DetailAST currentObjBlock : objBlockTokensStack) {
  87.             if (objBlockAST == currentObjBlock) {
  88.                 result = false;
  89.                 break;
  90.             }
  91.         }
  92.         return result;
  93.     }

  94.     /**
  95.      * Verify that aMethodDefAST is child token of considered objblock.
  96.      * @param methodDefAST DetailAST of method definition.
  97.      * @return true if aMethodDefAST is child of of considered objblock.
  98.      */
  99.     private boolean isChildOfCurrentObjBlockToken(final DetailAST methodDefAST) {
  100.         final DetailAST objBlockAST = objBlockTokensStack.peek();
  101.         return objBlockAST != null
  102.                 && methodDefAST.getParent() == objBlockAST;
  103.     }

  104.     /**
  105.      * Return true if AST has abstract modifier.
  106.      * @param classDefAST
  107.      *        AST which has modifier
  108.      * @return true if AST has abstract modifier, false otherwise.
  109.      */
  110.     private static boolean hasAbstractModifier(final DetailAST classDefAST) {
  111.         final DetailAST modifiers =
  112.                 classDefAST.findFirstToken(TokenTypes.MODIFIERS);
  113.         return hasChildToken(modifiers, TokenTypes.ABSTRACT);
  114.     }

  115.     /**
  116.      * Verifies that the given DetailAST is a main method.
  117.      * @param methodAST
  118.      *        DetailAST instance.
  119.      * @return true if aMethodAST is a main method, false otherwise.
  120.      */
  121.     private static boolean isMainMethod(final DetailAST methodAST) {
  122.         boolean result = true;
  123.         final String methodName = getIdentifier(methodAST);
  124.         if ("main".equals(methodName)) {
  125.             result = isVoidType(methodAST)
  126.                     && isMainMethodModifiers(methodAST)
  127.                     && isMainMethodParameters(methodAST);
  128.         }
  129.         else {
  130.             result = false;
  131.         }
  132.         return result;
  133.     }

  134.     /**
  135.      * Get identifier of AST. These can be names of types, subpackages,
  136.      * fields, methods, parameters, and local variables.
  137.      * @param ast
  138.      *        DetailAST instance
  139.      * @return identifier of AST, null if AST does not have name.
  140.      */
  141.     private static String getIdentifier(final DetailAST ast) {
  142.         final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
  143.         return ident.getText();
  144.     }

  145.     /**
  146.      * Verifies that given AST has appropriate modifiers for main method.
  147.      * @param methodAST
  148.      *        DetailAST instance.
  149.      * @return true if aMethodAST has (public & static & !abstract) modifiers,
  150.      *         false otherwise.
  151.      */
  152.     private static boolean isMainMethodModifiers(final DetailAST methodAST) {
  153.         final DetailAST modifiers =
  154.                 methodAST.findFirstToken(TokenTypes.MODIFIERS);
  155.         return hasChildToken(modifiers, TokenTypes.LITERAL_PUBLIC)
  156.                 && hasChildToken(modifiers, TokenTypes.LITERAL_STATIC);
  157.     }

  158.     /**
  159.      * Verifies that given AST has type and this type is void.
  160.      * @param methodAST
  161.      *        DetailAST instance.
  162.      * @return true if AST's type void, false otherwise.
  163.      */
  164.     private static boolean isVoidType(final DetailAST methodAST) {
  165.         final DetailAST methodTypeAST = methodAST.findFirstToken(TokenTypes.TYPE);
  166.         return hasChildToken(methodTypeAST, TokenTypes.LITERAL_VOID);
  167.     }

  168.     /**
  169.      * Verifies that given AST has appropriate for main method parameters.
  170.      * @param methodAST
  171.      *        instance of a method
  172.      * @return true if parameters of aMethodAST are appropriate for main method,
  173.      *         false otherwise.
  174.      */
  175.     private static boolean isMainMethodParameters(final DetailAST methodAST) {
  176.         final DetailAST params =
  177.                 methodAST.findFirstToken(TokenTypes.PARAMETERS);
  178.         return hasOnlyStringArrayParameter(params)
  179.                 || hasOnlyStringEllipsisParameter(params);
  180.     }

  181.     /**
  182.      * Return true if AST of method parameters has String[] parameter child
  183.      * token.
  184.      * @param parametersAST
  185.      *        DetailAST of method parameters.
  186.      * @return true if AST has String[] parameter child token, false otherwise.
  187.      */
  188.     private static boolean hasOnlyStringArrayParameter(final DetailAST parametersAST) {
  189.         boolean result = true;
  190.         if (parametersAST.getChildCount(TokenTypes.PARAMETER_DEF) == 1) {
  191.             final DetailAST parameterDefinitionAST =
  192.                     parametersAST.findFirstToken(TokenTypes.PARAMETER_DEF);
  193.             final DetailAST parameterTypeAST = parameterDefinitionAST
  194.                     .findFirstToken(TokenTypes.TYPE);
  195.             if (hasChildToken(parameterTypeAST, TokenTypes.ARRAY_DECLARATOR)) {
  196.                 final DetailAST arrayDeclaratorAST = parameterTypeAST
  197.                         .findFirstToken(TokenTypes.ARRAY_DECLARATOR);
  198.                 final String parameterName =
  199.                         getIdentifier(arrayDeclaratorAST);
  200.                 result = STRING_CLASS.equals(parameterName);
  201.             }
  202.             else {
  203.                 result = false;
  204.             }
  205.         }
  206.         else {
  207.             // there is none or multiple parameters
  208.             result = false;
  209.         }
  210.         return result;
  211.     }

  212.     /**
  213.      * Return true if AST of method parameters has String... parameter child
  214.      * token.
  215.      * @param parametersAST
  216.      *        DetailAST of method parameters.
  217.      * @return true if aParametersAST has String... parameter child token, false
  218.      *         otherwise.
  219.      */
  220.     private static boolean hasOnlyStringEllipsisParameter(final DetailAST parametersAST) {
  221.         boolean result = true;
  222.         if (parametersAST.getChildCount(TokenTypes.PARAMETER_DEF) == 1) {
  223.             final DetailAST parameterDefinitionAST =
  224.                     parametersAST.findFirstToken(TokenTypes.PARAMETER_DEF);
  225.             if (hasChildToken(parameterDefinitionAST, TokenTypes.ELLIPSIS)) {
  226.                 final DetailAST parameterTypeAST =
  227.                         parameterDefinitionAST.findFirstToken(TokenTypes.TYPE);
  228.                 final String parameterName =
  229.                         getIdentifier(parameterTypeAST);
  230.                 result = STRING_CLASS.equals(parameterName);
  231.             }
  232.             else {
  233.                 result = false;
  234.             }
  235.         }
  236.         else {
  237.             // there is none or multiple parameters
  238.             result = false;
  239.         }
  240.         return result;
  241.     }

  242.     /**
  243.      * Return true if aAST has token of aTokenType type.
  244.      * @param ast
  245.      *        DetailAST instance.
  246.      * @param tokenType
  247.      *        one of TokenTypes
  248.      * @return true if aAST has token of given type, or false otherwise.
  249.      */
  250.     private static boolean hasChildToken(DetailAST ast, int tokenType) {
  251.         return ast.findFirstToken(tokenType) != null;
  252.     }

  253. }