Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ThreadLocalWithInitial #38

Merged
merged 1 commit into from
Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import xyz.keksdose.spoon.code_solver.transformations.qodana.ArraysToString;
import xyz.keksdose.spoon.code_solver.transformations.qodana.EmptyStringCheck;
import xyz.keksdose.spoon.code_solver.transformations.self.StringBuilderDirectUse;
import xyz.keksdose.spoon.code_solver.transformations.self.ThreadLocalWithInitial;

public class TransformationEngine {

Expand Down Expand Up @@ -80,6 +81,7 @@ protected void addProcessors(ProcessingManager pm, ChangeListener listener) {
pm.addProcessor(new ArraysToString(listener));
pm.addProcessor(new EmptyStringCheck(listener));
pm.addProcessor(new StringBuilderDirectUse(listener));
pm.addProcessor(new ThreadLocalWithInitial(listener));
// pm.addProcessor(new AssertTrueEqualsCheck(listener));
// pm.addProcessor(new AssertFalseEqualsCheck(listener));
// pm.addProcessor(new AssertNullTransformation(listener));
Expand Down Expand Up @@ -138,8 +140,9 @@ protected void printChangedTypes(PrettyPrinter prettyPrinter, ChangeListener lis
private static Environment setEnvironmentOptions(Launcher launcher) {
Environment environment = launcher.getEnvironment();
environment.setNoClasspath(true);
environment.setComplianceLevel(11);
environment.setIgnoreDuplicateDeclarations(true);
environment.setIgnoreSyntaxErrors(true);
environment.setPreserveLineNumbers(true);
environment.setPrettyPrinterCreator(() -> new ImportAwareSniperPrinter(environment));
return environment;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@

package xyz.keksdose.spoon.code_solver.transformations.self;

import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;

import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLambda;
import spoon.reflect.code.CtNewClass;
import spoon.reflect.code.CtReturn;
import spoon.reflect.code.CtStatement;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtType;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtTypeReference;
import xyz.keksdose.spoon.code_solver.history.Change;
import xyz.keksdose.spoon.code_solver.history.ChangeListener;
import xyz.keksdose.spoon.code_solver.history.Link;
import xyz.keksdose.spoon.code_solver.history.MarkdownString;
import xyz.keksdose.spoon.code_solver.transformations.BadSmell;
import xyz.keksdose.spoon.code_solver.transformations.TransformationProcessor;

public class ThreadLocalWithInitial extends TransformationProcessor<CtNewClass<?>> {

private static final BadSmell threadLocalWithInitalValue = new BadSmell() {
@Override
public MarkdownString getName() {
return MarkdownString.fromRaw("ThreadLocalWithInitialValue");
}

@Override
public MarkdownString getDescription() {
String rawText = "ThreadLocal with initialValue override shall be replaced by ThreadLocal.withInitialValue";
String markdown = "`ThreadLocal` with initialValue override shall be replaced by `ThreadLocal.withInitialValue`";
return MarkdownString.fromMarkdown(rawText, markdown);
}

@Override
public List<Link> getLinks() {
return List.of(new Link("https://rules.sonarsource.com/java/RSPEC-4065"));
}

};
public ThreadLocalWithInitial(ChangeListener listener) {
super(listener);
}

@Override
public void process(CtNewClass<?> threadLocal) {
if (threadLocal.getType() != null && threadLocal.getType().getQualifiedName().equals("java.lang.ThreadLocal")) {
CtClass<?> innerClass = threadLocal.getAnonymousClass();
if (hasNoFields(innerClass) && hasOnlyConstructorAndSingleMethod(innerClass)) {
Optional<CtExecutableReference<?>> initalValueMethod = findInitalValueMethod(innerClass);
if (initalValueMethod.isPresent()) {
CtLambda<?> lambda = createSupplier(initalValueMethod.get());
CtInvocation<?> invocation = createInitalMethod(threadLocal, lambda);
invocation.setArguments(List.of(lambda));
notifyChangeListener(threadLocal, lambda, invocation);
threadLocal.replace(invocation);
}
}
}
}

private void notifyChangeListener(CtNewClass<?> threadLocal, CtLambda<?> lambda, CtInvocation<?> invocation) {
CtElement element = lambda.getBody() == null ? lambda.getExpression() : lambda.getBody();
String rawText = String.format(
"ThreadLocal with initialValue %s was replaced by ThreadLocal.withInitialValue(%s)", element, invocation);
String markdown = String.format(
"`ThreadLocal` with initialValue `%s` was replaced by `ThreadLocal.withInitialValue(%s)`", element,
invocation);
setChanged(threadLocal.getParent(CtType.class).getTopLevelType(), new Change(threadLocalWithInitalValue,
MarkdownString.fromMarkdown(rawText, markdown), threadLocal.getParent(CtType.class).getTopLevelType()));
}

private CtInvocation<?> createInitalMethod(CtNewClass<?> threadLocal, CtLambda<?> lambda) {
return getFactory().createInvocation(getFactory().createTypeAccess(createThreadLocalRef()),
getFactory().Executable()
.createReference(threadLocal.getType(), true, threadLocal.getType(), "withInitial",
List.of(lambda.getType())));
}

private CtTypeReference<Object> createThreadLocalRef() {
return getFactory().createCtTypeReference(ThreadLocal.class);
}

private CtLambda<?> createSupplier(CtExecutableReference<?> initalValueMethod) {
CtLambda<?> lambda = getFactory().createLambda();
if (initalValueMethod.getDeclaration().getBody().getStatements().size() == 1) {
CtStatement statement = initalValueMethod.getDeclaration().getBody().getStatement(0);
if (statement instanceof CtReturn) {
lambda.setExpression(getReturnStatement(statement));
}
else {
lambda.setBody(initalValueMethod.getDeclaration().getBody());
}
}
else {
lambda.setBody(initalValueMethod.getDeclaration().getBody());
}
lambda.setType(getFactory().createCtTypeReference(Supplier.class));
return lambda;
}

private <T> CtExpression<T> getReturnStatement(CtStatement statement) {
return ((CtReturn<T>) statement).getReturnedExpression();
}

private Optional<CtExecutableReference<?>> findInitalValueMethod(CtClass<?> innerClass) {
return innerClass.getDeclaredExecutables()
.stream()
.filter(v -> v.getSimpleName().equals("initialValue"))
.findFirst();
}

private boolean hasOnlyConstructorAndSingleMethod(CtClass<?> innerClass) {
return innerClass.getDeclaredExecutables().size() == 2;
}

private boolean hasNoFields(CtClass<?> innerClass) {
return innerClass.getDeclaredFields().isEmpty();
}
}