diff --git a/src/main/java/spoon/reflect/code/CtResource.java b/src/main/java/spoon/reflect/code/CtResource.java index 29e7fdc508b..eebf9048d0a 100644 --- a/src/main/java/spoon/reflect/code/CtResource.java +++ b/src/main/java/spoon/reflect/code/CtResource.java @@ -7,12 +7,12 @@ */ package spoon.reflect.code; -import spoon.reflect.declaration.CtVariable; +import spoon.reflect.declaration.CtElement; /** * This code element defines a resource used in the try-with-resource statement. * @param * The type of the resource. */ -public interface CtResource extends CtVariable { +public interface CtResource extends CtElement { } diff --git a/src/main/java/spoon/reflect/code/CtVariableRead.java b/src/main/java/spoon/reflect/code/CtVariableRead.java index 7d097c0d149..84a6a6d4547 100644 --- a/src/main/java/spoon/reflect/code/CtVariableRead.java +++ b/src/main/java/spoon/reflect/code/CtVariableRead.java @@ -21,7 +21,7 @@ * @param * type of the variable */ -public interface CtVariableRead extends CtVariableAccess { +public interface CtVariableRead extends CtVariableAccess, CtResource { @Override CtVariableRead clone(); } diff --git a/src/main/java/spoon/reflect/declaration/CtField.java b/src/main/java/spoon/reflect/declaration/CtField.java index bb6d70f23c9..2d2767f630e 100644 --- a/src/main/java/spoon/reflect/declaration/CtField.java +++ b/src/main/java/spoon/reflect/declaration/CtField.java @@ -9,7 +9,6 @@ import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtRHSReceiver; -import spoon.reflect.code.CtResource; import spoon.reflect.reference.CtFieldReference; import spoon.support.DerivedProperty; import spoon.support.UnsettableProperty; @@ -17,7 +16,7 @@ /** * This element defines a field declaration. */ -public interface CtField extends CtVariable, CtResource, CtTypeMember, CtRHSReceiver, CtShadowable { +public interface CtField extends CtVariable, CtTypeMember, CtRHSReceiver, CtShadowable { /** * The separator for a string representation of a field. diff --git a/src/main/java/spoon/support/compiler/jdt/ParentExiter.java b/src/main/java/spoon/support/compiler/jdt/ParentExiter.java index aeadd6ad802..c95ab05962d 100644 --- a/src/main/java/spoon/support/compiler/jdt/ParentExiter.java +++ b/src/main/java/spoon/support/compiler/jdt/ParentExiter.java @@ -61,7 +61,6 @@ import spoon.reflect.code.CtNewClass; import spoon.reflect.code.CtPattern; import spoon.reflect.code.CtRecordPattern; -import spoon.reflect.code.CtResource; import spoon.reflect.code.CtReturn; import spoon.reflect.code.CtStatement; import spoon.reflect.code.CtSuperAccess; @@ -107,11 +106,9 @@ import spoon.reflect.reference.CtIntersectionTypeReference; import spoon.reflect.reference.CtTypeParameterReference; import spoon.reflect.reference.CtTypeReference; -import spoon.reflect.reference.CtVariableReference; import spoon.reflect.reference.CtWildcardReference; import spoon.reflect.visitor.CtInheritanceScanner; import spoon.reflect.visitor.CtScanner; -import spoon.reflect.visitor.filter.TypeFilter; import java.util.ArrayList; import java.util.Collections; @@ -1083,31 +1080,11 @@ public void visitCtTry(CtTry tryBlock) { @Override public void visitCtTryWithResource(CtTryWithResource tryWithResource) { - if (child instanceof CtLocalVariable) { + if (child instanceof CtLocalVariable var) { // normal, happy path of declaring a new variable - tryWithResource.addResource((CtLocalVariable) child); - } else if (child instanceof CtVariableRead) { - // special case of the resource being declared before - final CtVariableReference variableRef = ((CtVariableRead) child).getVariable(); - if (variableRef.getDeclaration() != null) { - // getDeclaration works - tryWithResource.addResource((CtResource) variableRef.getDeclaration().clone().setImplicit(true)); - } else { - // we have to find it manually - for (ASTPair pair: this.jdtTreeBuilder.getContextBuilder().getAllContexts()) { - final List variables = pair.element().getElements(new TypeFilter<>(CtLocalVariable.class)); - for (CtLocalVariable v: variables) { - if (v.getSimpleName().equals(variableRef.getSimpleName())) { - // we found the resource - // we clone it in order to comply with the contract of being a tree - final CtLocalVariable clone = v.clone(); - clone.setImplicit(true); - tryWithResource.addResource(clone); - break; - } - } - } - } + tryWithResource.addResource(var); + } else if (child instanceof CtVariableRead read) { + tryWithResource.addResource(read); } super.visitCtTryWithResource(tryWithResource); } diff --git a/src/test/java/spoon/test/trycatch/TryCatchTest.java b/src/test/java/spoon/test/trycatch/TryCatchTest.java index 78197c1fe76..af638022990 100644 --- a/src/test/java/spoon/test/trycatch/TryCatchTest.java +++ b/src/test/java/spoon/test/trycatch/TryCatchTest.java @@ -34,19 +34,26 @@ import spoon.reflect.CtModel; import spoon.reflect.code.CtCatch; import spoon.reflect.code.CtCatchVariable; +import spoon.reflect.code.CtFieldRead; +import spoon.reflect.code.CtLocalVariable; import spoon.reflect.code.CtResource; import spoon.reflect.code.CtTry; import spoon.reflect.code.CtTryWithResource; +import spoon.reflect.code.CtVariableRead; import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtParameter; import spoon.reflect.declaration.CtVariable; import spoon.reflect.declaration.ModifierKind; import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtCatchVariableReference; import spoon.reflect.reference.CtLocalVariableReference; import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.reference.CtVariableReference; import spoon.reflect.visitor.filter.AbstractFilter; import spoon.reflect.visitor.filter.TypeFilter; +import spoon.support.compiler.VirtualFile; import spoon.support.reflect.CtExtendedModifier; import spoon.test.trycatch.testclasses.Foo; import spoon.test.trycatch.testclasses.Main; @@ -54,9 +61,7 @@ import spoon.testing.utils.LineSeparatorExtension; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -236,6 +241,7 @@ public void testCompileMultiTryCatchWithCustomExceptions() { fail(e.getMessage()); } } + @Test public void testTryCatchVariableGetType() { Factory factory = createFactory(); @@ -376,7 +382,7 @@ public void testCatchUnqualifiedReferenceMarkedSimplyQualifiedWhenMultipleTypesA CtCatch targetCatch = catches.get(0); List> paramTypes = targetCatch.getParameter().getMultiTypes(); - assertThat(paramTypes.size(), equalTo(2)); + assertThat(paramTypes).hasSize(2); assertTrue(paramTypes.get(0).isSimplyQualified(), "first type reference is fully qualified"); assertTrue(paramTypes.get(1).isSimplyQualified(), "second type reference is fully qualified"); } @@ -393,7 +399,7 @@ public void testCatchWithQualifiedAndUnqualifiedTypeReferencesInSameCatcher() th CtCatch targetCatch = catches.get(0); List> paramTypes = targetCatch.getParameter().getMultiTypes(); - assertThat(paramTypes.size(), equalTo(2)); + assertThat(paramTypes).hasSize(2); assertTrue(paramTypes.get(0).isSimplyQualified(), "first type reference should be unqualified"); assertFalse(paramTypes.get(1).isSimplyQualified(), "second type reference should be qualified"); } @@ -411,10 +417,10 @@ public void testNonCloseableGenericTypeInTryWithResources(CtModel model) { CtLocalVariableReference varRef = model.filterChildren(CtLocalVariableReference.class::isInstance).first(); - assertThat(varRef.getType().getQualifiedName(), equalTo("NonClosableGenericInTryWithResources.GenericType")); + assertThat(varRef.getType().getQualifiedName()).isEqualTo("NonClosableGenericInTryWithResources.GenericType"); // We don't extract the type arguments - assertThat(varRef.getType().getActualTypeArguments().size(), equalTo(0)); + assertThat(varRef.getType().getActualTypeArguments()).isEmpty(); } @ExtendWith(LineSeparatorExtension.class) @@ -466,7 +472,7 @@ void addsCatcherAtTheSpecifiedPosition() { .addCatcherAt(1, second); // assert - assertThat(tryStatement.getCatchers(), contains(first, second, third)); + assertThat(tryStatement.getCatchers()).containsExactlyInAnyOrder(first, second, third); } @Test @@ -492,4 +498,80 @@ private CtCatch createCatch(Factory factory, Class type ); } } + + @Test + void testFieldAsCtResource() { + // contract: Since java 9 normal variables, fields, parameters and catch variables are allowed + // in try-with-resources + CtModel model = createModelFromString(""" + class ClosableException extends RuntimeException implements AutoCloseable { + @Override + public void close() throws Exception {} + } + class Foo { + final AutoCloseable field = null; + public void bar(AutoCloseable param) { + try { } + catch (ClosableException e) { + AutoCloseable localVar = param; + try (e; field; param; localVar; AutoCloseable inside = () -> {}) { + } catch (Exception ignored) {} + } + } + } + """); + CtClass foo = (CtClass) model.getAllTypes() + .stream() + .filter(it -> it.getSimpleName().equals("Foo")) + .findAny() + .orElseThrow(); + CtMethod bar = foo.getMethodsByName("bar").get(0); + + CtCatchVariable e = bar.getElements(new TypeFilter<>(CtCatchVariable.class)).get(0); + CtLocalVariable localVar = bar.getElements(new TypeFilter<>(CtLocalVariable.class)) + .stream() + .filter(it -> it.getSimpleName().equals("localVar")) + .findAny() + .orElseThrow(); + CtField field = foo.getField("field"); + CtParameter param = bar.getParameters().get(0); + CtLocalVariable inside = bar.getElements(new TypeFilter<>(CtLocalVariable.class)) + .stream() + .filter(it -> it.getSimpleName().equals("inside")) + .findAny() + .orElseThrow(); + + List> resources = bar.getElements(new TypeFilter<>(CtTryWithResource.class)) + .get(0) + .getResources(); + assertThat(resources).containsExactlyInAnyOrder( + getRead(field), + getRead(param.getReference()), + getRead(e.getReference()), + getRead(localVar.getReference()), + inside + ); + } + + private CtVariableRead getRead(CtVariableReference ref) { + return ref.getFactory().createVariableRead().>setVariable(ref); + } + + private CtVariableRead getRead(CtField field) { + Factory factory = field.getFactory(); + CtFieldRead read = factory.createFieldRead(); + read.setVariable(field.getReference()); + read.setTarget(factory.createThisAccess(field.getDeclaringType().getReference())); + + return read; + } + + private static CtModel createModelFromString(String code) { + Launcher launcher = new Launcher(); + launcher.getEnvironment().setComplianceLevel(21); + launcher.getEnvironment().setShouldCompile(true); + launcher.addInputResource(new VirtualFile(code)); + return launcher.buildModel(); + } + } diff --git a/src/test/java/spoon/testing/assertions/CtResourceAssertInterface.java b/src/test/java/spoon/testing/assertions/CtResourceAssertInterface.java index 94ee959efa7..986656c8e23 100644 --- a/src/test/java/spoon/testing/assertions/CtResourceAssertInterface.java +++ b/src/test/java/spoon/testing/assertions/CtResourceAssertInterface.java @@ -1,4 +1,4 @@ package spoon.testing.assertions; import org.assertj.core.api.AbstractObjectAssert; import spoon.reflect.code.CtResource; -public interface CtResourceAssertInterface, W extends CtResource> extends CtVariableAssertInterface , SpoonAssert {} +public interface CtResourceAssertInterface, W extends CtResource> extends CtElementAssertInterface , SpoonAssert {} diff --git a/src/test/java/spoon/testing/assertions/CtVariableReadAssertInterface.java b/src/test/java/spoon/testing/assertions/CtVariableReadAssertInterface.java index 02c919faf99..b3b9a4692ec 100644 --- a/src/test/java/spoon/testing/assertions/CtVariableReadAssertInterface.java +++ b/src/test/java/spoon/testing/assertions/CtVariableReadAssertInterface.java @@ -1,4 +1,4 @@ package spoon.testing.assertions; import org.assertj.core.api.AbstractObjectAssert; import spoon.reflect.code.CtVariableRead; -public interface CtVariableReadAssertInterface, W extends CtVariableRead> extends CtVariableAccessAssertInterface , SpoonAssert {} +public interface CtVariableReadAssertInterface, W extends CtVariableRead> extends CtVariableAccessAssertInterface , SpoonAssert , CtResourceAssertInterface {}