Skip to content

Commit

Permalink
Merge pull request #2 from se2p/more-perfumes
Browse files Browse the repository at this point in the history
Add Perfumes for Testing and the Java Swing library
  • Loading branch information
chrisknedl authored Oct 10, 2024
2 parents d2d81b0 + 5e174b8 commit 62461b2
Show file tree
Hide file tree
Showing 48 changed files with 1,297 additions and 10 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>de.jsilbereisen</groupId>
<artifactId>perfumator-java</artifactId>
<version>0.3.1</version>
<version>0.4.0</version>
<packaging>jar</packaging>

<developers>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.Problem;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import com.github.javaparser.symbolsolver.utils.SymbolSolverCollectionStrategy;
import de.jsilbereisen.perfumator.model.AnalysisResult;
import de.jsilbereisen.perfumator.model.StatisticsSummary;
Expand Down Expand Up @@ -118,7 +120,8 @@ public static Builder builder(@NotNull Locale locale) {
public static JavaParser getConfiguredJavaParser() {
ParserConfiguration config = new ParserConfiguration();

config.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17);
config.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21);
config.setSymbolResolver(new JavaSymbolSolver(new ReflectionTypeSolver(false)));
config.setDoNotAssignCommentsPrecedingEmptyLines(false);

return new JavaParser(config);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package de.jsilbereisen.perfumator.engine.detector.perfume;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
import com.github.javaparser.resolution.model.typesystem.ReferenceTypeImpl;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import de.jsilbereisen.perfumator.engine.detector.Detector;
import de.jsilbereisen.perfumator.model.DetectedInstance;
import de.jsilbereisen.perfumator.model.perfume.Perfume;
import lombok.EqualsAndHashCode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

/**
* {@link Detector} for the "Assert all" {@link Perfume}.
* Detects the perfume only if the method is part of the {@link org.junit.jupiter.api.Assertions} class.
*/
@EqualsAndHashCode
public class AssertAllDetector implements Detector<Perfume> {

private Perfume perfume;

private JavaParserFacade analysisContext;

private static final String QUALIFIED_ASSERT_ALL_METHOD_NAME = "org.junit.jupiter.api.Assertions.assertAll";
private static final String ASSERTIONS_IMPORT_NAME = "org.junit.jupiter.api.Assertions";
private static final String ASSERT_ALL = "assertAll";

@Override
public @NotNull List<DetectedInstance<Perfume>> detect(@NotNull CompilationUnit astRoot) {
List<DetectedInstance<Perfume>> detectedInstances = new ArrayList<>();
List<MethodCallExpr> assertAllMethodCallExpressions = getAssertAllMethodCalls(astRoot);
assertAllMethodCallExpressions
.forEach(callExpr -> detectedInstances.add(DetectedInstance.from(callExpr, perfume, astRoot)));
return detectedInstances;
}

@Override
public void setConcreteDetectable(@NotNull Perfume concreteDetectable) {
this.perfume = concreteDetectable;
}

@Override
public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) {
this.analysisContext = analysisContext;
}

private List<MethodCallExpr> getAssertAllMethodCalls(@NotNull CompilationUnit astRoot) {
return astRoot.findAll(MethodCallExpr.class, expr -> {
// contains instead of equals because of possible 'Assertions.assertAll' calls
if (!expr.getNameAsString().contains(ASSERT_ALL)) {
return false;
}
if (expr.getScope().isPresent()) {
// for non-static imports
ResolvedType resolvedType = expr.getScope().get().calculateResolvedType();
return resolvedType instanceof ReferenceTypeImpl referenceType
&& referenceType.getQualifiedName().equals(ASSERTIONS_IMPORT_NAME);
} else {
// for static imports
ResolvedMethodDeclaration resolvedMethodDeclaration = expr.resolve();
String qualifiedName = resolvedMethodDeclaration.getQualifiedName();
return qualifiedName.equals(QUALIFIED_ASSERT_ALL_METHOD_NAME);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package de.jsilbereisen.perfumator.engine.detector.perfume;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.resolution.model.typesystem.ReferenceTypeImpl;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import de.jsilbereisen.perfumator.engine.detector.Detector;
import de.jsilbereisen.perfumator.model.DetectedInstance;
import de.jsilbereisen.perfumator.model.perfume.Perfume;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

/**
* {@link Detector} for the "JFrame dispose" {@link Perfume}.
* Detects the perfume only if the method is part of the {@link javax.swing.JFrame} class.
*/
public class JFrameDisposeDetector implements Detector<Perfume> {

private Perfume perfume;

private JavaParserFacade analysisContext;

private static final String DISPOSE_METHOD_NAME = "dispose";
private static final String QUALIFIED_JFRAME_CLASS_NAME = "javax.swing.JFrame";

@Override
public @NotNull List<DetectedInstance<Perfume>> detect(@NotNull CompilationUnit astRoot) {
List<DetectedInstance<Perfume>> detectedInstances = new ArrayList<>();
List<MethodCallExpr> disposeMethodCallExpressions = getJFrameDisposeMethodCalls(astRoot);
disposeMethodCallExpressions
.forEach(expr -> detectedInstances.add(DetectedInstance.from(expr, perfume, astRoot)));
return detectedInstances;
}

@Override
public void setConcreteDetectable(@NotNull Perfume concreteDetectable) {
this.perfume = concreteDetectable;
}

@Override
public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) {
this.analysisContext = analysisContext;
}

private List<MethodCallExpr> getJFrameDisposeMethodCalls(@NotNull CompilationUnit astRoot) {
return astRoot.findAll(MethodCallExpr.class, expr -> {
if (!expr.getNameAsString().equals(DISPOSE_METHOD_NAME)) {
return false;
}
var scope = expr.getScope();
if (scope.isPresent()) {
var resolvedType = scope.get().calculateResolvedType();
if (resolvedType instanceof ReferenceTypeImpl referenceType) {
return referenceType.getQualifiedName().equals(QUALIFIED_JFRAME_CLASS_NAME);
}
}
return false;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package de.jsilbereisen.perfumator.engine.detector.perfume;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import de.jsilbereisen.perfumator.engine.detector.Detector;
import de.jsilbereisen.perfumator.model.DetectedInstance;
import de.jsilbereisen.perfumator.model.perfume.Perfume;
import lombok.EqualsAndHashCode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

/**
* {@link Detector} for the "Parameterized Test" {@link Perfume}.
* Detects the perfume only if the annotation is part of JUnit 5 ({@link org.junit.jupiter.params.ParameterizedTest}).
*/
@EqualsAndHashCode
public class ParameterizedTestDetector implements Detector<Perfume> {

private Perfume perfume;

private JavaParserFacade analysisContext;

private static final String PARAMETERIZED_TEST_IDENTIFIER = "org.junit.jupiter.params.ParameterizedTest";

@Override
public @NotNull List<DetectedInstance<Perfume>> detect(@NotNull CompilationUnit astRoot) {
List<DetectedInstance<Perfume>> detectedInstances = new ArrayList<>();
List<MethodDeclaration> parameterizedTestMethodDeclarations = getParameterizedTestMethodDeclarations(astRoot);
parameterizedTestMethodDeclarations
.forEach(declaration -> detectedInstances.add(DetectedInstance.from(declaration, perfume, astRoot)));
return detectedInstances;
}

@Override
public void setConcreteDetectable(@NotNull Perfume concreteDetectable) {
perfume = concreteDetectable;
}

@Override
public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) {
this.analysisContext = analysisContext;
}

private List<MethodDeclaration> getParameterizedTestMethodDeclarations(@NotNull CompilationUnit astRoot) {
return astRoot.findAll(MethodDeclaration.class, methodDeclaration -> methodDeclaration.getAnnotations().stream()
.map(AnnotationExpr::resolve)
.map(ResolvedTypeDeclaration::getQualifiedName)
.anyMatch(name -> name.equals(PARAMETERIZED_TEST_IDENTIFIER)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package de.jsilbereisen.perfumator.engine.detector.perfume;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import de.jsilbereisen.perfumator.engine.detector.Detector;
import de.jsilbereisen.perfumator.model.DetectedInstance;
import de.jsilbereisen.perfumator.model.perfume.Perfume;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
* {@link Detector} for the "Setup or teardown method" {@link Perfume}.
* Detects the perfume only if the annotation is part of the JUnit 5 package {@link org.junit.jupiter.api}.
*/
public class SetupAndTeardownMethodDetector implements Detector<Perfume> {

private Perfume perfume;

private JavaParserFacade analysisContext;

public static final String IMPORT_QUALIFIER = "org.junit.jupiter.api.";
public static final Set<String> TEST_ANNOTATIONS = Set.of("BeforeAll", "BeforeEach", "AfterAll", "AfterEach");

@Override
public @NotNull List<DetectedInstance<Perfume>> detect(@NotNull CompilationUnit astRoot) {
List<DetectedInstance<Perfume>> detectedInstances = new ArrayList<>();
List<MethodDeclaration> setupAndTeardownMethods = getSetupAndTeardownMethodDeclarations(astRoot);
setupAndTeardownMethods
.forEach(declaration -> detectedInstances.add(DetectedInstance.from(declaration, perfume, astRoot)));
return detectedInstances;
}

@Override
public void setConcreteDetectable(@NotNull Perfume concreteDetectable) {
this.perfume = concreteDetectable;
}

@Override
public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) {
this.analysisContext = analysisContext;
}

private Set<String> getQualifiedAnnotations() {
return TEST_ANNOTATIONS.stream().map(annotation -> IMPORT_QUALIFIER + annotation).collect(Collectors.toSet());
}

private List<MethodDeclaration> getSetupAndTeardownMethodDeclarations(@NotNull CompilationUnit astRoot) {
return astRoot.findAll(MethodDeclaration.class, methodDeclaration -> methodDeclaration.getAnnotations().stream()
.map(AnnotationExpr::resolve)
.map(ResolvedTypeDeclaration::getQualifiedName)
.anyMatch(name -> getQualifiedAnnotations().contains(name)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package de.jsilbereisen.perfumator.engine.detector.perfume;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.model.typesystem.ReferenceTypeImpl;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import de.jsilbereisen.perfumator.engine.detector.Detector;
import de.jsilbereisen.perfumator.model.DetectedInstance;
import de.jsilbereisen.perfumator.model.perfume.Perfume;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

/**
* {@link Detector} for the "Swing timer" {@link Perfume}.
* Detects the perfume only if an object of type {@link javax.swing.Timer} is created.
*/
public class SwingTimerDetector implements Detector<Perfume> {

private Perfume perfume;

private JavaParserFacade analysisContext;

private static final String QUALIFIED_TIMER_NAME = "javax.swing.Timer";

@Override
public @NotNull List<DetectedInstance<Perfume>> detect(@NotNull CompilationUnit astRoot) {
List<DetectedInstance<Perfume>> detectedInstances = new ArrayList<>();
List<ObjectCreationExpr> newTimerExpressions = getNewTimerExpressions(astRoot);
newTimerExpressions
.forEach(callExpr -> detectedInstances.add(DetectedInstance.from(callExpr, perfume, astRoot)));
return detectedInstances;
}

@Override
public void setConcreteDetectable(@NotNull Perfume concreteDetectable) {
this.perfume = concreteDetectable;
}

@Override
public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) {
this.analysisContext = analysisContext;
}

private List<ObjectCreationExpr> getNewTimerExpressions(@NotNull CompilationUnit astRoot) {
return astRoot.findAll(ObjectCreationExpr.class, expr -> {
ResolvedType resolvedType;
// when classes from the same package are instantiated, the javaparser cannot resolve the type, therefore
// we simply skip such occurrences of object creation expressions
try {
resolvedType = expr.calculateResolvedType();
} catch (UnsolvedSymbolException e) {
return false;
}
return resolvedType instanceof ReferenceTypeImpl referenceType
&& referenceType.getQualifiedName().equals(QUALIFIED_TIMER_NAME);
});
}
}
Loading

0 comments on commit 62461b2

Please sign in to comment.