/* * SonarQube Java * Copyright (C) 2012-2016 SonarSource SA * mailto:contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.java.matcher; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import java.util.List; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Symbol.MethodSymbol; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.NewClassTree; import org.sonar.plugins.java.api.tree.Tree; import javax.annotation.Nullable; public class MethodMatcher { private TypeCriteria typeDefinition; private TypeCriteria callSite; private NameCriteria methodName; private ParametersCriteria parameters; private List<TypeCriteria> parameterTypes; public static MethodMatcher create() { return new MethodMatcher(); } public MethodMatcher name(String methodName) { Preconditions.checkState(this.methodName == null); this.methodName = NameCriteria.is(methodName); return this; } public MethodMatcher name(NameCriteria methodName) { Preconditions.checkState(this.methodName == null); this.methodName = methodName; return this; } public MethodMatcher typeDefinition(TypeCriteria typeDefinition) { Preconditions.checkState(this.typeDefinition == null); this.typeDefinition = typeDefinition; return this; } public MethodMatcher typeDefinition(String fullyQualifiedTypeName) { Preconditions.checkState(typeDefinition == null); this.typeDefinition = TypeCriteria.is(fullyQualifiedTypeName); return this; } public MethodMatcher callSite(TypeCriteria callSite) { this.callSite = callSite; return this; } public MethodMatcher addParameter(String fullyQualifiedTypeParameterName) { return addParameter(TypeCriteria.is(fullyQualifiedTypeParameterName)); } public MethodMatcher addParameter(TypeCriteria parameterTypeCriteria) { if (parameters == null) { parameterTypes = Lists.newArrayList(); parameters = ParametersCriteria.of(parameterTypes); } else { Preconditions.checkState(parameterTypes != null, "parameters is already initialized and doesn't support addParameter."); } parameterTypes.add(parameterTypeCriteria); return this; } public MethodMatcher parameters(String... parameterTypes) { if (parameterTypes.length == 0) { return withoutParameter(); } for (String type : parameterTypes) { addParameter(type); } return this; } public MethodMatcher parameters(TypeCriteria... parameterTypes) { if (parameterTypes.length == 0) { return withoutParameter(); } for (TypeCriteria type : parameterTypes) { addParameter(type); } return this; } public MethodMatcher withAnyParameters() { Preconditions.checkState(parameters == null); parameters = ParametersCriteria.any(); return this; } public MethodMatcher withoutParameter() { Preconditions.checkState(parameters == null); parameters = ParametersCriteria.none(); return this; } public boolean matches(NewClassTree newClassTree) { return matches(newClassTree.constructorSymbol(), null); } public boolean matches(MethodInvocationTree mit) { IdentifierTree id = getIdentifier(mit); return matches(id.symbol(), getCallSiteType(mit)); } public boolean matches(MethodTree methodTree) { MethodSymbol symbol = methodTree.symbol(); Symbol.TypeSymbol enclosingClass = symbol.enclosingClass(); return enclosingClass != null && matches(symbol, enclosingClass.type()); } private boolean matches(Symbol symbol, Type callSiteType) { return symbol.isMethodSymbol() && isSearchedMethod((MethodSymbol) symbol, callSiteType); } private static Type getCallSiteType(MethodInvocationTree mit) { ExpressionTree methodSelect = mit.methodSelect(); // methodSelect can only be Tree.Kind.IDENTIFIER or Tree.Kind.MEMBER_SELECT if (methodSelect.is(Tree.Kind.IDENTIFIER)) { Symbol.TypeSymbol enclosingClassSymbol = ((IdentifierTree) methodSelect).symbol().enclosingClass(); return enclosingClassSymbol != null ? enclosingClassSymbol.type() : null; } else { return ((MemberSelectExpressionTree) methodSelect).expression().symbolType(); } } private boolean isSearchedMethod(MethodSymbol symbol, @Nullable Type callSiteType) { boolean result = nameAcceptable(symbol) && parametersAcceptable(symbol); if (typeDefinition != null) { result &= typeDefinition.test(symbol.owner().type()); } if (callSite != null) { result &= callSiteType != null && callSite.test(callSiteType); } return result; } private boolean nameAcceptable(MethodSymbol symbol) { Preconditions.checkState(methodName != null); return methodName.test(symbol.name()); } private boolean parametersAcceptable(MethodSymbol methodSymbol) { Preconditions.checkState(parameters != null); return parameters.test(methodSymbol.parameterTypes()); } private static IdentifierTree getIdentifier(MethodInvocationTree mit) { // methodSelect can only be Tree.Kind.IDENTIFIER or Tree.Kind.MEMBER_SELECT if (mit.methodSelect().is(Tree.Kind.IDENTIFIER)) { return (IdentifierTree) mit.methodSelect(); } return ((MemberSelectExpressionTree) mit.methodSelect()).identifier(); } }