Skip to content

Commit

Permalink
feat(matcher): Add new matchers for spoon elements (#804)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinWitt authored Jul 8, 2023
1 parent e92cdb3 commit 43a8483
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 1 deletion.
4 changes: 4 additions & 0 deletions matcher/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

plugins {
id 'xyz.keksdose.spoon.code_solver.java-common-conventions'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.github.martinwitt.laughing_train.spoonutils;

import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import spoon.reflect.code.CtConstructorCall;
import spoon.reflect.code.CtExpression;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.Filter;

/**
* A filter for matching constructor calls with a specific target type and argument types.
*/
public class ConstructorMatcher implements Filter<CtConstructorCall<?>> {

private final String fqTargetType;
private final String[] argsFQN;

/**
* Creates a new constructor matcher with the given target type and argument types.
*
* @param fqTargetType the fully-qualified name of the target type
* @param argsFQN the fully-qualified names of the argument types
*/
public ConstructorMatcher(String fqTargetType, String... argsFQN) {
this.fqTargetType = fqTargetType;
this.argsFQN = argsFQN;
}

/**
* Determines whether the given constructor call matches the target type and argument types.
*
* @param element the constructor call to match
* @return true if the constructor call matches the target type and argument types, false otherwise
*/
@Override
public boolean matches(CtConstructorCall<?> element) {
if (element == null) {
return false;
}

if (!element.getType().getQualifiedName().equals(fqTargetType)) {
return false;
}
if (argsFQN == null || argsFQN.length == 0) {
return true;
}
if (element.getArguments().size() != argsFQN.length) {
return false;
}

List<Pair<CtTypeReference<?>, CtExpression<?>>> zipped = new ArrayList<>();
for (int i = 0; i < argsFQN.length && i < element.getArguments().size(); i++) {
zipped.add(Pair.of(
element.getFactory().createReference(argsFQN[i]),
element.getArguments().get(i)));
}
return zipped.stream().allMatch(pair -> pair.getRight().getType().isSubtypeOf(pair.getLeft()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.github.martinwitt.laughing_train.spoonutils;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.tuple.Pair;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtTypeAccess;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.Filter;

/**
* A matcher that checks if a given {@link CtInvocation} object matches a specified target type, method name, and argument types.
*/
public class InvocationMatcher implements Filter<CtInvocation<?>> {
private final String fqTargetType;
private final String methodName;
private final String[] argsFQN;

/**
* Creates a new {@link InvocationMatcher} object with the specified target type, method name, and argument types.
* @param fqTargetType the fully qualified name of the target type
* @param methodName the name of the method
* @param argsFQN the fully qualified names of the argument types
*/
public InvocationMatcher(String fqTargetType, String methodName, String... argsFQN) {
this.fqTargetType = fqTargetType;
this.methodName = methodName;
this.argsFQN = argsFQN;
}

/**
* Checks if the specified {@link CtInvocation} object matches the target type, method name, and argument types of this {@link InvocationMatcher}.
* @param element the {@link CtInvocation} object to check
* @return true if the invocation matches, false otherwise
*/
public boolean matches(CtInvocation<?> element) {
if (element == null) {
return false;
}

// Check if the target type matches
CtExpression<?> target = element.getTarget();
if (target == null || target.getType() == null) {
return false;
}
if (target instanceof CtTypeAccess access) {
if (!access.getAccessedType().getQualifiedName().equals(fqTargetType)) {
return false;
}
} else {
if (!target.getType().getQualifiedName().equals(fqTargetType)) {
return false;
}
}

// Check if the method name matches
if (Optional.ofNullable(element.getExecutable())
.map(v -> v.getExecutableDeclaration())
.filter(v -> v.getSimpleName().equals(methodName))
.isEmpty()) {
return false;
}

// Check if the argument types match
if (argsFQN == null || argsFQN.length == 0) {
return true;
}
if (element.getArguments().size() != argsFQN.length) {
return false;
}

List<Pair<CtTypeReference<?>, CtExpression<?>>> zipped = new ArrayList<>();
for (int i = 0; i < argsFQN.length && i < element.getArguments().size(); i++) {
zipped.add(Pair.of(
element.getFactory().createReference(argsFQN[i]),
element.getArguments().get(i)));
}
return zipped.stream().allMatch(pair -> pair.getRight().getType().isSubtypeOf(pair.getLeft()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.github.martinwitt.laughing_train.spoonutils.matcher;

/**
* A functional interface for matching elements of a certain type.
*
* @param <T> the type of elements to match
*/
@FunctionalInterface
public interface Matcher<T> {

/**
* Determines whether the given element matches a certain criteria.
*
* @param element the element to match
* @return true if the element matches the criteria, false otherwise
*/
boolean matches(T element);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.github.martinwitt.laughing_train.spoonutils.matcher;

import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtLiteral;
import spoon.reflect.declaration.CtModifiable;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.ModifierKind;

/**
* A utility class for creating matchers for Spoon elements.
*/
public final class Matchers {

/**
* Returns a matcher that matches elements that are public.
*
* @return a matcher that matches elements that are public
*/
public static Matcher<CtModifiable> isPublic() {
return v -> v.getModifiers().contains(ModifierKind.PUBLIC);
}

/**
* Returns a matcher that matches elements that are private.
*
* @return a matcher that matches elements that are private
*/
public static Matcher<CtModifiable> isPrivate() {
return v -> v.getModifiers().contains(ModifierKind.PRIVATE);
}

/**
* Returns a matcher that matches elements that are enums.
*
* @return a matcher that matches elements that are enums
*/
public static Matcher<CtType<?>> isEnum() {
return v -> v.isEnum();
}

/**
* Returns a matcher that matches elements that are integer literals with the given value.
*
* @param literal the value of the integer literal to match
* @return a matcher that matches elements that are integer literals with the given value
*/
public static Matcher<CtExpression<?>> isLiteral(int literal) {
return v -> v instanceof CtLiteral
&& ((CtLiteral<?>) v).getValue() instanceof Integer value
&& value.equals(literal);
}

/**
* Returns a matcher that matches elements that are final.
*
* @return a matcher that matches elements that are final
*/
public static Matcher<CtModifiable> isFinal() {
return v -> v.getModifiers().contains(ModifierKind.FINAL);
}

/**
* Returns a matcher that matches elements that match all of the given matchers.
*
* @param matchers the matchers to match
* @param <T> the type of elements to match
* @return a matcher that matches elements that match all of the given matchers
*/
@SafeVarargs
public static <T> Matcher<T> allOf(Matcher<T>... matchers) {
return v -> {
for (Matcher<T> matcher : matchers) {
if (!matcher.matches(v)) {
return false;
}
}
return true;
};
}

private Matchers() {
throw new AssertionError("Utility class should not be instantiated");
}
}
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
rootProject.name = 'laughing-train-project'
include(':code-transformation',":commons", ":github-bot", ":application")
include(':code-transformation',":commons", ":github-bot", ":application", ":matcher")

0 comments on commit 43a8483

Please sign in to comment.