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 */
020 package org.sonar.java.ast.visitor;
021
022 import java.util.Arrays;
023 import java.util.List;
024
025 import org.sonar.squid.api.SourceCode;
026 import org.sonar.squid.api.SourceMethod;
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 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 = (DetailAST) 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 }