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.api.SourceCode;
026import org.sonar.squid.api.SourceMethod;
027import org.sonar.squid.measures.Metric;
028
029import antlr.collections.AST;
030
031import com.puppycrawl.tools.checkstyle.api.DetailAST;
032import com.puppycrawl.tools.checkstyle.api.Scope;
033import com.puppycrawl.tools.checkstyle.api.TokenTypes;
034
035public class AccessorVisitor extends JavaAstVisitor {
036
037  private static final List<Integer> TOKENS = Arrays.asList(TokenTypes.METHOD_DEF);
038
039  @Override
040  public List<Integer> getWantedTokens() {
041    return TOKENS;
042  }
043
044  @Override
045  public void visitToken(DetailAST methodAst) {
046    SourceCode currentMethod = peekSourceCode();
047    AstUtils.ensureResourceType(currentMethod, SourceMethod.class);
048    Scope methodScope = AstUtils.getScope(methodAst);
049    if (AstUtils.isScope(methodScope, Scope.PUBLIC) && isAccessor(methodAst, currentMethod.getName())) {
050      currentMethod.setMeasure(Metric.ACCESSORS, 1);
051    }
052  }
053
054  @Override
055  public void leaveToken(DetailAST ast) {
056    SourceMethod currentMethod = (SourceMethod) peekSourceCode();
057    if (currentMethod.isAccessor()) {
058      currentMethod.setMeasure(Metric.PUBLIC_API, 0);
059      currentMethod.setMeasure(Metric.PUBLIC_DOC_API, 0);
060      currentMethod.setMeasure(Metric.METHODS, 0);
061      currentMethod.setMeasure(Metric.COMPLEXITY, 0);
062    }
063  }
064
065  private boolean isAccessor(DetailAST methodAst, String methodName) {
066    boolean methodWithoutParams = isMethodWithoutParameters(methodAst);
067    return isValidGetter(methodAst, methodName, methodWithoutParams) || isValidSetter(methodAst, methodName, methodWithoutParams)
068        || isValidBooleanGetter(methodAst, methodName, methodWithoutParams);
069  }
070
071  private boolean isMethodWithoutParameters(DetailAST methodAst) {
072    return methodAst.findFirstToken(TokenTypes.PARAMETERS).getChildCount() == 0;
073  }
074
075  private boolean isValidBooleanGetter(DetailAST method, String methodName, boolean methodWithoutParams) {
076    if (methodName.startsWith("is") && methodWithoutParams) {
077      AST type = AstUtils.findType(method);
078      if (isAstType(type, TokenTypes.LITERAL_BOOLEAN)) {
079        return isValidGetter(method, methodName.replace("is", "get"), methodWithoutParams);
080      }
081    }
082    return false;
083  }
084
085  private boolean isValidSetter(DetailAST method, String methodName, boolean methodWithoutParams) {
086    if (methodName.startsWith("set") && !methodWithoutParams) {
087      AST type = AstUtils.findType(method);
088      if (isVoidMethodReturn(type)) {
089        DetailAST methodParams = method.findFirstToken(TokenTypes.PARAMETERS);
090        if (methodParams.getChildCount(TokenTypes.PARAMETER_DEF) == 1) {
091          DetailAST methodBody = method.getLastChild();
092          if (isAstType(methodBody, TokenTypes.SLIST) && methodBody.getChildCount() == 3) {
093            return inspectSetterMethodBody(method, methodParams, methodBody);
094          }
095        }
096      }
097    }
098    return false;
099  }
100
101  private boolean isValidGetter(DetailAST method, String methodName, boolean methodWithoutParams) {
102    if (methodName.startsWith("get") && methodWithoutParams) {
103      AST type = AstUtils.findType(method);
104      if (!isVoidMethodReturn(type)) {
105        DetailAST methodBody = method.getLastChild();
106        if (isAstType(methodBody, TokenTypes.SLIST) && methodBody.getChildCount() == 2) {
107          return inspectGetterMethodBody(method, methodBody);
108        }
109      }
110    }
111    return false;
112  }
113
114  private boolean inspectGetterMethodBody(DetailAST method, DetailAST methodBody) {
115    AST returnAST = methodBody.getFirstChild();
116    if (isAstType(returnAST, TokenTypes.LITERAL_RETURN)) {
117      AST expr = returnAST.getFirstChild();
118      if (isAstType(expr, TokenTypes.EXPR) && isAstType(expr.getNextSibling(), TokenTypes.SEMI)) {
119        AST varReturned = expr.getFirstChild();
120        if (isAstType(varReturned, TokenTypes.IDENT)) {
121          return findPrivateClassVariable(method, varReturned.getText());
122        }
123      }
124    }
125    return false;
126  }
127
128  private boolean inspectSetterMethodBody(DetailAST method, DetailAST methodParams, DetailAST methodBody) {
129    DetailAST expr = methodBody.getFirstChild();
130    if (isAstType(expr, TokenTypes.EXPR)) {
131      DetailAST assignment = expr.findFirstToken(TokenTypes.ASSIGN);
132      if (assignment != null) {
133        DetailAST dotAst = assignment.findFirstToken(TokenTypes.DOT);
134        DetailAST varAssigned = assignment.getLastChild();
135        DetailAST varAssignedMethodParam = methodParams.findFirstToken(TokenTypes.PARAMETER_DEF).findFirstToken(TokenTypes.IDENT);
136        // check that the var assigned is the var from the method param
137        if (isAstType(varAssigned, TokenTypes.IDENT) && varAssigned.getText().equals(varAssignedMethodParam.getText())) {
138          DetailAST varToAssign = dotAst != null ? dotAst.findFirstToken(TokenTypes.IDENT) : assignment.findFirstToken(TokenTypes.IDENT);
139          return findPrivateClassVariable(method, varToAssign.getText());
140        }
141      }
142    }
143    return false;
144  }
145
146  private boolean findPrivateClassVariable(DetailAST method, String varName) {
147    DetailAST objBlock = AstUtils.findParent(method, TokenTypes.OBJBLOCK);
148    for (AST i = objBlock.getFirstChild(); i != null; i = i.getNextSibling()) {
149      if (isAstType(i, TokenTypes.VARIABLE_DEF)) {
150        DetailAST varDef = (DetailAST) i;
151        if (AstUtils.isScope(AstUtils.getScope(varDef), Scope.PRIVATE) 
152            && varDef.findFirstToken(TokenTypes.IDENT).getText().equals(varName)) {
153          return true;
154        }
155      }
156    }
157    return false;
158  }
159
160  private boolean isVoidMethodReturn(AST type) {
161    return isAstType(type, TokenTypes.LITERAL_VOID);
162  }
163
164  private boolean isAstType(AST ast, int type) {
165    return ast.getType() == type;
166  }
167
168}