|
3 | 3 | import java.lang.annotation.Annotation; |
4 | 4 | import java.util.ArrayList; |
5 | 5 | import java.util.Arrays; |
| 6 | +import java.util.Collections; |
| 7 | +import java.util.LinkedHashMap; |
6 | 8 | import java.util.LinkedHashSet; |
7 | 9 | import java.util.List; |
| 10 | +import java.util.Map; |
8 | 11 | import java.util.Optional; |
9 | 12 | import java.util.Set; |
10 | 13 |
|
|
49 | 52 | import spoon.reflect.code.CtNewArray; |
50 | 53 | import spoon.reflect.code.CtNewClass; |
51 | 54 | import spoon.reflect.code.CtOperatorAssignment; |
| 55 | +import spoon.reflect.code.CtResource; |
52 | 56 | import spoon.reflect.code.CtReturn; |
53 | 57 | import spoon.reflect.code.CtStatement; |
54 | 58 | import spoon.reflect.code.CtThisAccess; |
55 | 59 | import spoon.reflect.code.CtThrow; |
| 60 | +import spoon.reflect.code.CtTryWithResource; |
56 | 61 | import spoon.reflect.code.CtUnaryOperator; |
57 | 62 | import spoon.reflect.code.CtVariableAccess; |
58 | 63 | import spoon.reflect.code.CtVariableRead; |
59 | 64 | import spoon.reflect.code.CtVariableWrite; |
60 | 65 | import spoon.reflect.code.CtWhile; |
| 66 | +import spoon.reflect.cu.CompilationUnit; |
| 67 | +import spoon.reflect.cu.SourcePosition; |
61 | 68 | import spoon.reflect.declaration.*; |
62 | 69 | import spoon.reflect.factory.Factory; |
| 70 | +import spoon.reflect.reference.CtExecutableReference; |
63 | 71 | import spoon.reflect.reference.CtFieldReference; |
64 | 72 | import spoon.reflect.reference.CtTypeReference; |
65 | 73 | import spoon.reflect.reference.CtVariableReference; |
@@ -515,6 +523,56 @@ private boolean canCompleteNormally(CtStatement statement) { |
515 | 523 | return true; |
516 | 524 | } |
517 | 525 |
|
| 526 | + @Override |
| 527 | + public void visitCtTryWithResource(CtTryWithResource tryWithResource) { |
| 528 | + // A resource reference (Java 9 `try (r)`) is modelled by Spoon as an implicit copy of r's declaration, |
| 529 | + // initializer included, and repeated once per earlier local with the same name, so it is not scanned (that |
| 530 | + // would re-run the initializer) and is only closed once |
| 531 | + Map<String, CtResource<?>> resources = new LinkedHashMap<>(); |
| 532 | + for (CtResource<?> resource : tryWithResource.getResources()) { |
| 533 | + if (!resource.isImplicit()) |
| 534 | + scan(resource); |
| 535 | + resources.put(resource.getSimpleName(), resource); |
| 536 | + } |
| 537 | + scan(tryWithResource.getBody()); |
| 538 | + |
| 539 | + // the resources are closed when the body ends, in reverse order, before any catch or finally block runs |
| 540 | + List<CtResource<?>> toClose = new ArrayList<>(resources.values()); |
| 541 | + Collections.reverse(toClose); |
| 542 | + for (CtResource<?> resource : toClose) { |
| 543 | + SourcePosition position = resource.isImplicit() ? getHeaderPosition(tryWithResource) |
| 544 | + : resource.getPosition(); |
| 545 | + scan(createImplicitClose(resource, tryWithResource, position)); |
| 546 | + } |
| 547 | + scan(tryWithResource.getCatchers()); |
| 548 | + scan(tryWithResource.getFinalizer()); |
| 549 | + } |
| 550 | + |
| 551 | + /** Position of {@code try (...)}, without the blocks */ |
| 552 | + private SourcePosition getHeaderPosition(CtTryWithResource tryWithResource) { |
| 553 | + SourcePosition position = tryWithResource.getPosition(); |
| 554 | + CompilationUnit cu = position.getCompilationUnit(); |
| 555 | + int end = cu.getOriginalSourceCode().lastIndexOf(')', tryWithResource.getBody().getPosition().getSourceStart()); |
| 556 | + if (end < position.getSourceStart()) |
| 557 | + return position; |
| 558 | + return factory.Core().createSourcePosition(cu, position.getSourceStart(), end, cu.getLineSeparatorPositions()); |
| 559 | + } |
| 560 | + |
| 561 | + /** Builds the {@code resource.close()} that Java inserts at the end of a try-with-resources block */ |
| 562 | + private CtInvocation<?> createImplicitClose(CtResource<?> resource, CtTryWithResource tryWithResource, |
| 563 | + SourcePosition position) { |
| 564 | + CtTypeReference<?> type = resource.getType(); |
| 565 | + CtExecutableReference<?> close = type.getAllExecutables().stream() |
| 566 | + .filter(e -> e.getSimpleName().equals("close") && e.getParameters().isEmpty()).findFirst() |
| 567 | + .orElseGet(() -> factory.Executable().createReference(type, factory.Type().VOID_PRIMITIVE, "close")); |
| 568 | + CtExpression<?> target = factory.Code().createVariableRead(resource.getReference(), false); |
| 569 | + CtInvocation<?> invocation = factory.Code().createInvocation(target, close); |
| 570 | + invocation.setParent(tryWithResource); |
| 571 | + invocation.setPosition(position); |
| 572 | + target.setPosition(position); |
| 573 | + return invocation; |
| 574 | + } |
| 575 | + |
518 | 576 | @Override |
519 | 577 | public void visitCtWhile(CtWhile whileLoop) { |
520 | 578 | visitLoop(whileLoop, () -> { |
|
0 commit comments