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