Skip to content

Commit

Permalink
feature: add support for first class resource in try-with-resource (#…
Browse files Browse the repository at this point in the history
…4371)

Co-authored-by: Martin Monperrus <[email protected]>
  • Loading branch information
henryhchchc and monperrus authored Feb 14, 2022
1 parent ca9977e commit af5774e
Show file tree
Hide file tree
Showing 12 changed files with 88 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/main/java/spoon/metamodel/Metamodel.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public static Set<CtType<?>> getAllMetamodelInterfaces() {
result.add(factory.Type().get(spoon.reflect.code.CtOperatorAssignment.class));
result.add(factory.Type().get(spoon.reflect.code.CtPattern.class));
result.add(factory.Type().get(spoon.reflect.code.CtRHSReceiver.class));
result.add(factory.Type().get(spoon.reflect.code.CtResource.class));
result.add(factory.Type().get(spoon.reflect.code.CtReturn.class));
result.add(factory.Type().get(spoon.reflect.code.CtStatement.class));
result.add(factory.Type().get(spoon.reflect.code.CtStatementList.class));
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/spoon/reflect/code/CtLocalVariable.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
* type of the variable
* @see spoon.reflect.declaration.CtExecutable
*/
public interface CtLocalVariable<T> extends CtStatement, CtVariable<T>, CtRHSReceiver<T> {
public interface CtLocalVariable<T> extends CtStatement, CtVariable<T>, CtRHSReceiver<T>, CtResource<T> {
/*
* (non-Javadoc)
*
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/spoon/reflect/code/CtResource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* SPDX-License-Identifier: (MIT OR CECILL-C)
*
* Copyright (C) 2006-2019 INRIA and contributors
*
* Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) of the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon.
*/
package spoon.reflect.code;

import spoon.reflect.declaration.CtTypedElement;

/**
* This code element defines a resource used in the try-with-resource statement.
* @param <T>
* The type of the resource.
*/
public interface CtResource<T> extends CtTypedElement<T>, CtCodeElement {
}
10 changes: 6 additions & 4 deletions src/main/java/spoon/reflect/code/CtTryWithResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,30 @@ public interface CtTryWithResource extends CtTry {
/**
* Gets the auto-closeable resources of this <code>try</code>. Available
* from Java 7 with the <i>try-with-resource</i> statement.
*
* The returned list is immutable for sake of proper encapsulation.
*/
@PropertyGetter(role = TRY_RESOURCE)
List<CtLocalVariable<?>> getResources();
List<CtResource<?>> getResources();

/**
* Sets the auto-closeable resources of this <code>try</code>. Available
* from Java 7 with the <i>try-with-resource</i> statement.
*/
@PropertySetter(role = TRY_RESOURCE)
<T extends CtTryWithResource> T setResources(List<CtLocalVariable<?>> resources);
<T extends CtTryWithResource> T setResources(List<? extends CtResource<?>> resources);

/**
* Adds a resource.
*/
@PropertySetter(role = TRY_RESOURCE)
<T extends CtTryWithResource> T addResource(CtLocalVariable<?> resource);
<T extends CtTryWithResource> T addResource(CtResource<?> resource);

/**
* Removes a resource.
*/
@PropertySetter(role = TRY_RESOURCE)
boolean removeResource(CtLocalVariable<?> resource);
boolean removeResource(CtResource<?> resource);

@Override
CtTryWithResource clone();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/spoon/reflect/code/CtVariableRead.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* @param <T>
* type of the variable
*/
public interface CtVariableRead<T> extends CtVariableAccess<T> {
public interface CtVariableRead<T> extends CtVariableAccess<T>, CtResource<T> {
@Override
CtVariableRead<T> clone();
}
5 changes: 3 additions & 2 deletions src/main/java/spoon/reflect/meta/impl/ModelRoleHandlers.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import spoon.reflect.code.CtNewClass;
import spoon.reflect.code.CtOperatorAssignment;
import spoon.reflect.code.CtRHSReceiver;
import spoon.reflect.code.CtResource;
import spoon.reflect.code.CtReturn;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtStatementList;
Expand Down Expand Up @@ -355,9 +356,9 @@ public <T, U> void setValue(T element, U value) {
}
}

static class CtTryWithResource_TRY_RESOURCE_RoleHandler extends ListHandler<CtTryWithResource, CtLocalVariable<? extends Object>> {
static class CtTryWithResource_TRY_RESOURCE_RoleHandler extends ListHandler<CtTryWithResource, CtResource<? extends Object>> {
private CtTryWithResource_TRY_RESOURCE_RoleHandler() {
super(CtRole.TRY_RESOURCE, CtTryWithResource.class, CtLocalVariable.class);
super(CtRole.TRY_RESOURCE, CtTryWithResource.class, CtResource.class);
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import spoon.reflect.code.CtFor;
import spoon.reflect.code.CtForEach;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtResource;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtStatementList;
import spoon.reflect.code.CtTryWithResource;
Expand Down Expand Up @@ -99,7 +100,11 @@ public <T> void scanCtType(CtType<T> type) {

@Override
public void visitCtTryWithResource(CtTryWithResource e) {
variables.addAll(e.getResources());
for (CtResource<?> resource: e.getResources()) {
if (resource instanceof CtLocalVariable) {
variables.add((CtLocalVariable<?>) resource);
}
}
super.visitCtTryWithResource(e);
}

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/spoon/reflect/visitor/CtInheritanceScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import spoon.reflect.code.CtOperatorAssignment;
import spoon.reflect.code.CtPattern;
import spoon.reflect.code.CtRHSReceiver;
import spoon.reflect.code.CtResource;
import spoon.reflect.code.CtReturn;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtStatementList;
Expand Down Expand Up @@ -284,6 +285,14 @@ public void scanCtReference(CtReference reference) {

}

/**
* Scans an abstract resource in try-with-resource statement.
* @param resource The resource
*/
public void scanCtResource(CtResource<?> resource) {

}

/**
* Scans an abstract statement.
*/
Expand Down Expand Up @@ -687,6 +696,7 @@ public <T> void visitCtLocalVariable(CtLocalVariable<T> e) {
scanCtVariable(e);
scanCtCodeElement(e);
scanCtNamedElement(e);
scanCtResource(e);
scanCtTypedElement(e);
scanCtElement(e);
scanCtModifiable(e);
Expand Down Expand Up @@ -922,6 +932,7 @@ public <T> void visitCtUnaryOperator(CtUnaryOperator<T> e) {

@Override
public <T> void visitCtVariableRead(CtVariableRead<T> e) {
scanCtResource(e);
scanCtVariableAccess(e);
scanCtExpression(e);
scanCtCodeElement(e);
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/spoon/support/compiler/jdt/ParentExiter.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import spoon.reflect.code.CtLoop;
import spoon.reflect.code.CtNewArray;
import spoon.reflect.code.CtNewClass;
import spoon.reflect.code.CtResource;
import spoon.reflect.code.CtReturn;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtSuperAccess;
Expand Down Expand Up @@ -972,8 +973,8 @@ private CtCatch getLastCatcher(CtTry tryBlock) {

@Override
public void visitCtTryWithResource(CtTryWithResource tryWithResource) {
if (child instanceof CtLocalVariable) {
tryWithResource.addResource((CtLocalVariable<?>) child);
if (child instanceof CtResource) {
tryWithResource.addResource((CtResource<?>) child);
}
super.visitCtTryWithResource(tryWithResource);
}
Expand Down
21 changes: 11 additions & 10 deletions src/main/java/spoon/support/reflect/code/CtTryWithResourceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
package spoon.support.reflect.code;

import spoon.reflect.annotations.MetamodelPropertyField;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtResource;
import spoon.reflect.code.CtTryWithResource;
import spoon.reflect.visitor.CtVisitor;
import spoon.support.reflect.declaration.CtElementImpl;

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

import static spoon.reflect.ModelElementContainerDefaultCapacities.RESOURCES_CONTAINER_DEFAULT_CAPACITY;
Expand All @@ -23,38 +24,38 @@ public class CtTryWithResourceImpl extends CtTryImpl implements CtTryWithResourc
private static final long serialVersionUID = 1L;

@MetamodelPropertyField(role = TRY_RESOURCE)
List<CtLocalVariable<?>> resources = emptyList();
List<CtResource<?>> resources = emptyList();

@Override
public void accept(CtVisitor visitor) {
visitor.visitCtTryWithResource(this);
}

@Override
public List<CtLocalVariable<?>> getResources() {
return resources;
public List<CtResource<?>> getResources() {
return Collections.unmodifiableList(resources);
}

@Override
public <T extends CtTryWithResource> T setResources(List<CtLocalVariable<?>> resources) {
public <T extends CtTryWithResource> T setResources(List<? extends CtResource<?>> resources) {
if (resources == null || resources.isEmpty()) {
this.resources = CtElementImpl.emptyList();
return (T) this;
}
getFactory().getEnvironment().getModelChangeListener().onListDeleteAll(this, TRY_RESOURCE, this.resources, new ArrayList<>(this.resources));
this.resources.clear();
for (CtLocalVariable<?> l : resources) {
for (CtResource<?> l : resources) {
addResource(l);
}
return (T) this;
}

@Override
public <T extends CtTryWithResource> T addResource(CtLocalVariable<?> resource) {
public <T extends CtTryWithResource> T addResource(CtResource<?> resource) {
if (resource == null) {
return (T) this;
}
if (resources == CtElementImpl.<CtLocalVariable<?>>emptyList()) {
if (resources == CtElementImpl.<CtResource<?>>emptyList()) {
resources = new ArrayList<>(RESOURCES_CONTAINER_DEFAULT_CAPACITY);
}
resource.setParent(this);
Expand All @@ -64,8 +65,8 @@ public <T extends CtTryWithResource> T addResource(CtLocalVariable<?> resource)
}

@Override
public boolean removeResource(CtLocalVariable<?> resource) {
if (resources == CtElementImpl.<CtLocalVariable<?>>emptyList()) {
public boolean removeResource(CtResource<?> resource) {
if (resources.isEmpty()) {
return false;
}
getFactory().getEnvironment().getModelChangeListener().onListDelete(this, TRY_RESOURCE, resources, resources.indexOf(resource), resource);
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/spoon/generating/CloneVisitorGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ private CtInvocation createFactoryInvocation(CtVariableAccess<CtElement> element
"spoon.support.reflect.declaration.CtCodeSnippetImpl", "spoon.support.reflect.declaration.CtFormalTypeDeclarerImpl", //
"spoon.support.reflect.declaration.CtGenericElementImpl", "spoon.support.reflect.reference.CtGenericElementReferenceImpl", //
"spoon.support.reflect.declaration.CtModifiableImpl", "spoon.support.reflect.declaration.CtMultiTypedElementImpl", //
"spoon.support.reflect.declaration.CtTypeMemberImpl", "spoon.support.reflect.code.CtRHSReceiverImpl",
"spoon.support.reflect.declaration.CtTypeMemberImpl", "spoon.support.reflect.code.CtRHSReceiverImpl", "spoon.support.reflect.code.CtResourceImpl",
"spoon.support.reflect.declaration.CtShadowableImpl", "spoon.support.reflect.code.CtBodyHolderImpl", "spoon.support.reflect.declaration.CtModuleDirectiveImpl",
"spoon.support.reflect.code.CtPatternImpl");
private final List<String> excludesFields = Arrays.asList("factory", "elementValues", "target", "rootFragment", "originalSourceCode", "myPartialSourcePosition");
Expand Down
26 changes: 26 additions & 0 deletions src/test/java/spoon/test/trycatch/TryCatchTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
import spoon.reflect.CtModel;
import spoon.reflect.code.CtCatch;
import spoon.reflect.code.CtCatchVariable;
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.CtMethod;
import spoon.reflect.declaration.ModifierKind;
Expand Down Expand Up @@ -412,4 +414,28 @@ public void testNonCloseableGenericTypeInTryWithResources() {
// We don't extract the type arguments
assertThat(varRef.getType().getActualTypeArguments().size(), equalTo(0));
}

@Test
public void testTryWithVariableAsResource() {
Factory factory = createFactory();
factory.getEnvironment().setComplianceLevel(9);

// contract: a try with resource is modeled with CtTryWithResource
CtTryWithResource tryStmt = factory.Code().createCodeSnippetStatement("" +
"java.lang.AutoCloseable resource = null;" +
"try(resource){}"
).compile();
List<CtResource<?>> resources = tryStmt.getResources();
assertEquals(1, resources.size());
final CtResource<?> ctResource = resources.get(0);
assertTrue(ctResource instanceof CtVariableRead);
assertEquals("resource", ((CtVariableRead<?>) ctResource).getVariable().getSimpleName());

// contract: removeResource does remove the resource
tryStmt.removeResource(ctResource);
assertEquals(0, tryStmt.getResources().size());
// contract: removeResource of nothing is graceful and accepts it
tryStmt.removeResource(ctResource);

}
}

0 comments on commit af5774e

Please sign in to comment.