ForbidThrowAnonymousExceptionsCheck.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.coding;

  20. import java.util.ArrayList;
  21. import java.util.List;
  22. import java.util.regex.Pattern;

  23. import com.github.sevntu.checkstyle.SevntuUtil;
  24. import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
  25. import com.puppycrawl.tools.checkstyle.api.DetailAST;
  26. import com.puppycrawl.tools.checkstyle.api.TokenTypes;

  27. /**
  28.  * <p>
  29.  * This Check warns on throwing anonymous exception.
  30.  * </p>
  31.  * Examples:
  32.  * <pre>
  33.  * catch (Exception e) {
  34.  *        throw new RuntimeException()  { //WARNING
  35.  *          //some code
  36.  *     };
  37.  * }
  38.  * <br>
  39.  * catch (Exception e) {
  40.  *     RuntimeException run = new RuntimeException()  {
  41.  *          //some code
  42.  *     };
  43.  *     throw run;  //WARNING
  44.  * }
  45.  * </pre> The distinguishing of <b>exception</b> types occurs by
  46.  * analyzing variable's class's name.<br>
  47.  * Check has an option which contains the regular expression for exception class name matching<br>
  48.  * Default value is "^.*Exception" because usually exception type ends with suffix "Exception".<br>
  49.  * Then, if we have an ObjBlock (distinguished by curly braces), it's anonymous<br>
  50.  * exception definition. It could be defined in <b>throw</b> statement
  51.  * immediately.<br>
  52.  * In that case, after literal new, there would be an expression type finishing
  53.  * with and ObjBlock.<br>
  54.  * <br>
  55.  * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
  56.  * @author <a href="mailto:maxvetrenko2241@gmail.com">Max Vetrenko</a>
  57.  * @since 1.11.0
  58.  */
  59. public class ForbidThrowAnonymousExceptionsCheck extends AbstractCheck {

  60.     /**
  61.      * Warning message key.
  62.      */
  63.     public static final String MSG_KEY = "forbid.throw.anonymous.exception";

  64.     /** Regular expression of exception naming. */
  65.     private static final String DEFAULT_EXCEPTION_CLASS_NAME_REGEX = "^.*Exception";

  66.     /** List of anonymous exceptions to ignore. */
  67.     private final List<String> anonymousExceptions = new ArrayList<>();

  68.     /** User set expression for exception names. */
  69.     private Pattern pattern = Pattern.compile(DEFAULT_EXCEPTION_CLASS_NAME_REGEX);

  70.     /**
  71.      * Setter for pattern.
  72.      * @param exceptionClassNameRegex The regular expression to set.
  73.      */
  74.     public void setExceptionClassNameRegex(String exceptionClassNameRegex) {
  75.         this.pattern = Pattern.compile(exceptionClassNameRegex);
  76.     }

  77.     @Override
  78.     public int[] getDefaultTokens() {
  79.         return new int[] {
  80.             TokenTypes.LITERAL_THROW,
  81.             TokenTypes.VARIABLE_DEF,
  82.         };
  83.     }

  84.     @Override
  85.     public int[] getAcceptableTokens() {
  86.         return getDefaultTokens();
  87.     }

  88.     @Override
  89.     public int[] getRequiredTokens() {
  90.         return getDefaultTokens();
  91.     }

  92.     @Override
  93.     public void visitToken(DetailAST literalThrowOrVariableDefAst) {
  94.         switch (literalThrowOrVariableDefAst.getType()) {
  95.             case TokenTypes.LITERAL_THROW:
  96.                 identifyThrowingAnonymousException(literalThrowOrVariableDefAst);
  97.                 break;
  98.             case TokenTypes.VARIABLE_DEF:
  99.                 lookForAnonymousExceptionDefinition(literalThrowOrVariableDefAst);
  100.                 break;
  101.             default:
  102.                 SevntuUtil.reportInvalidToken(literalThrowOrVariableDefAst.getType());
  103.                 break;
  104.         }
  105.     }

  106.     /**
  107.      * Warns on throwing anonymous exception.
  108.      * @param throwDefAst The token to examine.
  109.      */
  110.     private void identifyThrowingAnonymousException(DetailAST throwDefAst) {
  111.         final DetailAST throwingLiteralNewAst = getLiteralNew(throwDefAst);

  112.         if (throwingLiteralNewAst != null
  113.                 && hasObjectBlock(throwingLiteralNewAst)) {
  114.             log(throwDefAst, MSG_KEY);
  115.         }
  116.         else if (throwingLiteralNewAst == null) {
  117.             final DetailAST throwingExceptionNameAst = getThrowingExceptionNameAst(throwDefAst
  118.                     .getFirstChild());
  119.             if (throwingExceptionNameAst != null
  120.                     && anonymousExceptions.contains(throwingExceptionNameAst
  121.                             .getText())) {
  122.                 log(throwDefAst, MSG_KEY);
  123.             }
  124.         }
  125.     }

  126.     /**
  127.      * Analyzes variable definition for anonymous exception definition. if found
  128.      * - adds it to list of anonymous exceptions
  129.      * @param variableDefAst The token to examine.
  130.      */
  131.     private void
  132.             lookForAnonymousExceptionDefinition(DetailAST variableDefAst) {
  133.         DetailAST variableLiteralNewAst = null;
  134.         final DetailAST variableAssignment = variableDefAst.findFirstToken(TokenTypes.ASSIGN);
  135.         if (variableAssignment != null && variableAssignment.getFirstChild() != null) {
  136.             variableLiteralNewAst = getLiteralNew(variableAssignment);
  137.         }

  138.         final DetailAST variableNameAst = variableDefAst
  139.                 .findFirstToken(TokenTypes.TYPE).getNextSibling();
  140.         if (isExceptionName(variableNameAst)) {
  141.             final String exceptionName = variableNameAst.getText();

  142.             if (anonymousExceptions.contains(exceptionName)) {
  143.                 anonymousExceptions.remove(exceptionName);
  144.             }

  145.             if (variableLiteralNewAst != null
  146.                     && hasObjectBlock(variableLiteralNewAst)) {
  147.                 anonymousExceptions.add(exceptionName);
  148.             }
  149.         }
  150.     }

  151.     /**
  152.      * Gets the literal new node from variable definition node or throw node.
  153.      * @param literalThrowOrVariableDefAst The token to examine.
  154.      * @return the specified node.
  155.      */
  156.     private static DetailAST
  157.             getLiteralNew(DetailAST literalThrowOrVariableDefAst) {
  158.         return literalThrowOrVariableDefAst.getFirstChild().findFirstToken(
  159.                 TokenTypes.LITERAL_NEW);
  160.     }

  161.     /**
  162.      * Retrieves the AST node which contains the name of throwing exception.
  163.      * @param expressionAst The token to examine.
  164.      * @return the specified node.
  165.      */
  166.     private static DetailAST
  167.             getThrowingExceptionNameAst(DetailAST expressionAst) {
  168.         return expressionAst.findFirstToken(TokenTypes.IDENT);
  169.     }

  170.     /**
  171.      * Checks if definition with a literal new has an ObjBlock.
  172.      * @param literalNewAst The token to examine.
  173.      * @return true if the new has an object block.
  174.      */
  175.     private static boolean hasObjectBlock(DetailAST literalNewAst) {
  176.         return literalNewAst.getLastChild().getType() == TokenTypes.OBJBLOCK;
  177.     }

  178.     /**
  179.      * Checks if variable name is definitely an exception name. It is so if
  180.      * variable type ends with "Exception" suffix
  181.      * @param variableNameAst The token to examine.
  182.      * @return true if the name is an exception.
  183.      */
  184.     private boolean isExceptionName(DetailAST variableNameAst) {
  185.         final DetailAST typeAst = variableNameAst.getPreviousSibling();
  186.         final String typeName = typeAst.getFirstChild().getText();
  187.         return pattern.matcher(typeName).matches();
  188.     }

  189. }