org.sonar.java.matcher.MethodMatchersBuilder Maven / Gradle / Ivy
The newest version!
/*
* SonarQube Java
* Copyright (C) 2012-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.matcher;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonarsource.analyzer.commons.collections.SetUtils;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.semantic.Symbol;
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.MethodReferenceTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.NewClassTree;
import org.sonar.plugins.java.api.tree.Tree;
public class MethodMatchersBuilder implements MethodMatchers.TypeBuilder, MethodMatchers.NameBuilder, MethodMatchers.ParametersBuilder, MethodMatchers {
@Nullable
private final Predicate typePredicate;
@Nullable
private final Predicate namePredicate;
@Nullable
private final Predicate> parametersPredicate;
public MethodMatchersBuilder() {
this.typePredicate = null;
this.namePredicate = null;
this.parametersPredicate = null;
}
private MethodMatchersBuilder(@Nullable Predicate typePredicate, @Nullable Predicate namePredicate, @Nullable Predicate> parametersPredicate) {
this.typePredicate = typePredicate;
this.namePredicate = namePredicate;
this.parametersPredicate = parametersPredicate;
}
private static Predicate substituteAny(Predicate predicate, String... elements) {
if (SetUtils.immutableSetOf(elements).contains(ANY)) {
if (elements.length > 1) {
throw new IllegalStateException("Incompatible MethodMatchers.ANY with other predicates.");
}
return e -> true;
}
return predicate;
}
private static Predicate substituteAnyAndCreateEfficientPredicate(
String[] elements,
Function> singleElementPredicate,
Function, Predicate> multiElementsPredicate) {
if (elements.length == 0) {
throw new IllegalStateException("Method arguments can not be empty, otherwise the predicate would be always false.");
}
if (elements.length == 1) {
String singleElement = elements[0];
return substituteAny(singleElementPredicate.apply(singleElement), elements);
} else {
List multiElements = Arrays.asList(elements);
return substituteAny(multiElementsPredicate.apply(multiElements), elements);
}
}
@Override
public NameBuilder ofSubTypes(String... fullyQualifiedTypeNames) {
return ofType(substituteAnyAndCreateEfficientPredicate(
fullyQualifiedTypeNames,
name -> (type -> type.isSubtypeOf(name)),
names -> (type -> names.stream().anyMatch(type::isSubtypeOf))));
}
@Override
public NameBuilder ofAnyType() {
return ofTypes(ANY);
}
@Override
public NameBuilder ofTypes(String... fullyQualifiedTypeNames) {
return ofType(substituteAnyAndCreateEfficientPredicate(
fullyQualifiedTypeNames,
name -> (type -> type.is(name)),
names -> (type -> names.stream().anyMatch(type::is))));
}
@Override
public NameBuilder ofType(Predicate typePredicate) {
return new MethodMatchersBuilder(or(this.typePredicate, typePredicate), namePredicate, parametersPredicate);
}
@Override
public ParametersBuilder names(String... names) {
return name(substituteAnyAndCreateEfficientPredicate(
names,
name -> name::equals,
nameList -> nameList::contains));
}
@Override
public ParametersBuilder anyName() {
return names(ANY);
}
@Override
public ParametersBuilder constructor() {
return names(MethodMatchers.CONSTRUCTOR);
}
@Override
public ParametersBuilder name(Predicate namePredicate) {
return new MethodMatchersBuilder(typePredicate, or(this.namePredicate, namePredicate), parametersPredicate);
}
@Override
public ParametersBuilder addParametersMatcher(String... parametersType) {
return addParametersMatcher(Arrays.stream(parametersType)
.>map(parameterType -> substituteAny(type -> type.is(parameterType), parameterType))
.toList());
}
private ParametersBuilder addParametersMatcher(List> parametersType) {
return addParametersMatcher((List actualTypes) -> exactMatchesParameters(parametersType, actualTypes));
}
@Override
public ParametersBuilder addWithoutParametersMatcher() {
return addParametersMatcher(Collections.emptyList());
}
@Override
public ParametersBuilder withAnyParameters() {
if (parametersPredicate != null) {
throw new IllegalStateException("Incompatible 'any parameters' constraint added to existing parameters constraint.");
}
return addParametersMatcher((List actualParameters) -> true);
}
@Override
public ParametersBuilder addParametersMatcher(Predicate> parametersPredicate) {
return new MethodMatchersBuilder(typePredicate, namePredicate, or(this.parametersPredicate, parametersPredicate));
}
private static boolean exactMatchesParameters(List> expectedTypes, List actualTypes) {
return actualTypes.size() == expectedTypes.size() && matchesParameters(expectedTypes, actualTypes);
}
private static boolean matchesParameters(List> expectedTypes, List actualTypes) {
for (int i = 0; i < expectedTypes.size(); i++) {
if (!expectedTypes.get(i).test(actualTypes.get(i))) {
return false;
}
}
return true;
}
@Override
public boolean matches(NewClassTree newClassTree) {
return matches(newClassTree.methodSymbol(), null);
}
@Override
public boolean matches(MethodInvocationTree mit) {
IdentifierTree id = getIdentifier(mit);
return matches(id.symbol(), getCallSiteType(mit));
}
@Override
public boolean matches(MethodTree methodTree) {
Symbol.MethodSymbol symbol = methodTree.symbol();
Symbol.TypeSymbol enclosingClass = symbol.enclosingClass();
return enclosingClass != null && matches(symbol, enclosingClass.type());
}
@Override
public boolean matches(MethodReferenceTree methodReferenceTree) {
return matches(methodReferenceTree.method().symbol(), getCallSiteType(methodReferenceTree));
}
@Override
public boolean matches(Symbol symbol) {
return matches(symbol, null);
}
@Override
public MethodMatchers build() {
if (typePredicate == null || namePredicate == null || parametersPredicate == null) {
throw new IllegalStateException("MethodMatchers need to be fully initialized.");
}
return this;
}
private boolean matches(Symbol symbol, @Nullable Type callSiteType) {
return symbol.isMethodSymbol() && isSearchedMethod((Symbol.MethodSymbol) symbol, callSiteType);
}
@CheckForNull
private static Type getCallSiteType(MethodReferenceTree referenceTree) {
Tree expression = referenceTree.expression();
if (expression instanceof ExpressionTree expressionTree) {
return expressionTree.symbolType();
}
return null;
}
@CheckForNull
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(Symbol.MethodSymbol symbol, @Nullable Type callSiteType) {
Type type = callSiteType;
if (type == null) {
Symbol owner = symbol.owner();
if (owner != null) {
type = owner.type();
}
}
return type != null &&
namePredicate.test(symbol.name()) &&
parametersPredicate.test(symbol.parameterTypes()) &&
typePredicate.test(type);
}
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();
}
private static Predicate or(@Nullable Predicate accumulator, Predicate next) {
return accumulator != null ? accumulator.or(next) : next;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy