-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Christoph Knoedlseder
committed
Oct 4, 2024
1 parent
a3dddea
commit 28c9d57
Showing
6 changed files
with
189 additions
and
16 deletions.
There are no files selected for viewing
85 changes: 85 additions & 0 deletions
85
...main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ThreadSafeSwingDetector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package de.jsilbereisen.perfumator.engine.detector.perfume; | ||
|
||
import com.github.javaparser.ast.CompilationUnit; | ||
import com.github.javaparser.ast.ImportDeclaration; | ||
import com.github.javaparser.ast.expr.MethodCallExpr; | ||
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; | ||
import de.jsilbereisen.perfumator.engine.detector.Detector; | ||
import de.jsilbereisen.perfumator.engine.visitor.MethodCallByNameVisitor; | ||
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.*; | ||
|
||
@EqualsAndHashCode | ||
public class ThreadSafeSwingDetector implements Detector<Perfume> { | ||
|
||
private Perfume perfume; | ||
|
||
private JavaParserFacade analysisContext; | ||
|
||
private final static String SWING_UTILITIES = "SwingUtilities"; | ||
private final static String INVOKE_LATER = "invokeLater"; | ||
private final static String INVOKE_AND_WAIT = "invokeAndWait"; | ||
private final static Set<String> RELEVANT_METHOD_NAMES = Set.of(INVOKE_LATER, INVOKE_AND_WAIT); | ||
private final static Map<String, String> RELEVANT_IMPORTS = | ||
Map.of(INVOKE_LATER, "javax.swing.SwingUtilities.invokeLater", | ||
INVOKE_AND_WAIT, "javax.swing.SwingUtilities.invokeAndWait"); | ||
|
||
@Override | ||
public @NotNull List<DetectedInstance<Perfume>> detect(@NotNull CompilationUnit astRoot) { | ||
List<DetectedInstance<Perfume>> detectedInstances = new ArrayList<>(); | ||
Set<String> staticImports = getStaticImports(astRoot); | ||
List<MethodCallExpr> methodCalls = getInvokeLaterInvokeAndWaitMethodCalls(astRoot, staticImports); | ||
methodCalls.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 Set<String> getStaticImports(@NotNull CompilationUnit astRoot) { | ||
Set<String> importedAnnotations = new HashSet<>(); | ||
|
||
for (ImportDeclaration importDeclaration : astRoot.getImports()) { | ||
for (Map.Entry<String, String> annotationToImport : RELEVANT_IMPORTS.entrySet()) { | ||
if (importDeclaration.getNameAsString().equals(annotationToImport.getValue())) { | ||
importedAnnotations.add(annotationToImport.getKey()); | ||
} | ||
} | ||
} | ||
return importedAnnotations; | ||
} | ||
|
||
private List<MethodCallExpr> getInvokeLaterInvokeAndWaitMethodCalls(@NotNull CompilationUnit astRoot, | ||
Set<String> imports) { | ||
MethodCallByNameVisitor methodCallByNameVisitor = new MethodCallByNameVisitor(); | ||
astRoot.accept(methodCallByNameVisitor, null); | ||
List<MethodCallExpr> relevantMethodCallExpressions = new ArrayList<>(); | ||
for (MethodCallExpr methodCallExpr : methodCallByNameVisitor.getMethodCalls()) { | ||
if (RELEVANT_METHOD_NAMES.contains(methodCallExpr.getNameAsString())) { | ||
if (isPartOfSwingUtilities(methodCallExpr)) { | ||
relevantMethodCallExpressions.add(methodCallExpr); | ||
} else if (imports.contains(methodCallExpr.getNameAsString())) { | ||
relevantMethodCallExpressions.add(methodCallExpr); | ||
} | ||
} | ||
} | ||
return relevantMethodCallExpressions; | ||
} | ||
|
||
private boolean isPartOfSwingUtilities(MethodCallExpr methodCallExpr) { | ||
var scope = methodCallExpr.getScope(); | ||
return scope.map(expression -> expression.toString().equals(SWING_UTILITIES)).orElse(false); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Thread_safe_swing.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"name": "Thread safe Swing", | ||
"description": "Java Swing application make use of the event dispatch thread. This thread must handle the initialization of and update operations on the GUI components, as well as event handling. Event handlers We as programmers are responsible that GUI components are created and updated in the event dispatch thread. We can achieve this by placing the related code inside a Runnable that we pass as an argument to either the \"SwingUtilities.invokeLater\" or the \"SwingUtilities.invokeAndWait\" method.", | ||
"detectorClassSimpleName": "ThreadSafeSwingDetector", | ||
"i18nBaseBundleName": "threadSafeSwing", | ||
"sources": ["Programmierung II at Uni Passau", "https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html"], | ||
"relatedPattern": "BUG", | ||
"additionalInformation": null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package detectors; | ||
|
||
import com.github.javaparser.ast.CompilationUnit; | ||
import de.jsilbereisen.perfumator.engine.detector.Detector; | ||
import de.jsilbereisen.perfumator.engine.detector.perfume.ThreadSafeSwingDetector; | ||
import de.jsilbereisen.perfumator.model.CodeRange; | ||
import de.jsilbereisen.perfumator.model.DetectedInstance; | ||
import de.jsilbereisen.perfumator.model.perfume.Perfume; | ||
import org.junit.jupiter.api.BeforeAll; | ||
import org.junit.jupiter.api.Test; | ||
import test.AbstractDetectorTest; | ||
|
||
import java.nio.file.Path; | ||
import java.util.List; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class ThreadSafeSwingDetectorTest extends AbstractDetectorTest { | ||
|
||
private static final Path TEST_FILE = | ||
DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("swing").resolve("InvokeLaterInvokeAndWait.java"); | ||
|
||
private static Perfume perfume; | ||
|
||
private static Detector<Perfume> detector; | ||
|
||
private static CompilationUnit ast; | ||
|
||
@BeforeAll | ||
static void init() { | ||
perfume = new Perfume(); | ||
perfume.setName("Thread safe Swing"); | ||
|
||
detector = new ThreadSafeSwingDetector(); | ||
detector.setConcreteDetectable(perfume); | ||
|
||
ast = parseAstForFile(TEST_FILE); | ||
} | ||
|
||
@Test | ||
void detect() { | ||
List<DetectedInstance<Perfume>> detections = detector.detect(ast); | ||
|
||
assertThat(detections).hasSize(2); | ||
|
||
DetectedInstance<Perfume> detection = detections.get(0); | ||
assertThat(detection.getDetectable()).isEqualTo(perfume); | ||
assertThat(detection.getTypeName()).isEqualTo("InvokeLaterInvokeAndWait"); | ||
assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(9, 9, 11, 10)); | ||
|
||
detection = detections.get(1); | ||
assertThat(detection.getDetectable()).isEqualTo(perfume); | ||
assertThat(detection.getTypeName()).isEqualTo("InvokeLaterInvokeAndWait"); | ||
assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(13, 9, 15, 10)); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
src/test/resources/detectors/swing/InvokeLaterInvokeAndWait.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package de.jsilbereisen.test; | ||
|
||
import javax.swing.SwingUtilities; | ||
import static javax.swing.SwingUtilities.invokeAndWait; | ||
|
||
public class InvokeLaterInvokeAndWait { | ||
|
||
public static void main(String[] args) { | ||
SwingUtilities.invokeLater(new Runnable() { | ||
// perfume | ||
}); | ||
|
||
invokeAndWait(() -> { | ||
// perfume | ||
}); | ||
|
||
// no perfume | ||
invokeLater(); | ||
} | ||
|
||
public static void invokeLater() { | ||
// no perfume, not the library method we are looking for | ||
} | ||
} |