001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.java.ast.visitor;
021
022import java.util.Arrays;
023import java.util.List;
024
025import org.sonar.squid.measures.Metric;
026
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029
030public class StatementVisitor extends JavaAstVisitor {
031
032  private static final List<Integer> WANTED_TOKENS = Arrays.asList(TokenTypes.VARIABLE_DEF, TokenTypes.CTOR_CALL, TokenTypes.LITERAL_IF,
033                                                      TokenTypes.LITERAL_ELSE, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO,
034                                                      TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_SWITCH, TokenTypes.LITERAL_BREAK,
035                                                      TokenTypes.LITERAL_CONTINUE, TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_THROW,
036                                                      TokenTypes.LITERAL_SYNCHRONIZED, TokenTypes.LITERAL_CATCH,
037                                                      TokenTypes.LITERAL_FINALLY, TokenTypes.EXPR, TokenTypes.LABELED_STAT,
038                                                      TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT, TokenTypes.LITERAL_ASSERT);
039
040  @Override
041  public List<Integer> getWantedTokens() {
042    return WANTED_TOKENS;
043  }
044
045  @Override
046  public void visitToken(DetailAST ast) {
047    if (isCountable(ast)) {
048      peekSourceCode().add(Metric.STATEMENTS, 1);
049    }
050  }
051
052  /**
053   * Checks if a token is countable for the ncss metric
054   * 
055   * @param ast
056   *          the AST
057   * @return true if the token is countable
058   */
059  private boolean isCountable(DetailAST ast) {
060    boolean countable = true;
061    final int tokenType = ast.getType();
062    // check if an expression is countable
063    if (TokenTypes.EXPR == tokenType) {
064      countable = isExpressionCountable(ast);
065    }
066    // check if an variable definition is countable, only for non class
067    // variables
068    else if (TokenTypes.VARIABLE_DEF == tokenType) {
069      countable = isVariableDefCountable(ast);
070    }
071    return countable;
072  }
073
074  /**
075   * Checks if a variable definition is countable.
076   * 
077   * @param aAST
078   *          the AST
079   * @return true if the variable definition is countable, false otherwise
080   */
081  private boolean isVariableDefCountable(DetailAST aAST) {
082    boolean countable = false;
083    if (!AstUtils.isClassVariable(aAST) && !AstUtils.isInterfaceVariable(aAST)) {
084      // count variable defs only if they are direct child to a slist or
085      // object block
086      final int parentType = aAST.getParent().getType();
087      if ((TokenTypes.SLIST == parentType) || (TokenTypes.OBJBLOCK == parentType)) {
088        final DetailAST prevSibling = aAST.getPreviousSibling();
089        // is countable if no previous sibling is found or
090        // the sibling is no COMMA.
091        // This is done because multiple assignment on one line are
092        // countes as 1
093        countable = (prevSibling == null) || (TokenTypes.COMMA != prevSibling.getType());
094      }
095    }
096    return countable;
097  }
098
099  /**
100   * Checks if an expression is countable for the ncss metric.
101   * 
102   * @param aAST the AST
103   * @return true if the expression is countable, false otherwise
104   */
105  private boolean isExpressionCountable(DetailAST aAST) {
106    boolean countable;
107    // count expressions only if they are direct child to a slist (method
108    // body, for loop...)
109    // or direct child of label,if,else,do,while,for
110    final int parentType = aAST.getParent().getType();
111    switch (parentType) {
112      case TokenTypes.SLIST:
113      case TokenTypes.LABELED_STAT:
114      case TokenTypes.LITERAL_FOR:
115      case TokenTypes.LITERAL_DO:
116      case TokenTypes.LITERAL_WHILE:
117      case TokenTypes.LITERAL_IF:
118      case TokenTypes.LITERAL_ELSE:
119        // don't count if or loop conditions
120        final DetailAST prevSibling = aAST.getPreviousSibling();
121        countable = (prevSibling == null) || (TokenTypes.LPAREN != prevSibling.getType());
122        break;
123      default:
124        countable = false;
125        break;
126    }
127    return countable;
128  }
129}