1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
package lombok.eclipse.handlers;
import lombok.Cleanup;
import lombok.core.AnnotationValues;
import lombok.core.AST.Kind;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseAST.Node;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.CaseStatement;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.SwitchStatement;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.mangosdk.spi.ProviderFor;
@ProviderFor(EclipseAnnotationHandler.class)
public class HandleCleanup implements EclipseAnnotationHandler<Cleanup> {
public boolean handle(AnnotationValues<Cleanup> annotation, Annotation ast, Node annotationNode) {
String cleanupName = annotation.getInstance().cleanupMethod();
if ( cleanupName.length() == 0 ) {
annotationNode.addError("cleanupName cannot be the empty string.");
return true;
}
if ( annotationNode.up().getKind() != Kind.LOCAL ) {
annotationNode.addError("@Cleanup is legal only on local variable declarations.");
return true;
}
LocalDeclaration decl = (LocalDeclaration)annotationNode.up().get();
Node ancestor = annotationNode.up().directUp();
ASTNode blockNode = annotationNode.up().directUp().get();
final boolean isSwitch;
final Statement[] statements;
if ( blockNode instanceof AbstractMethodDeclaration ) {
isSwitch = false;
statements = ((AbstractMethodDeclaration)blockNode).statements;
} else if ( blockNode instanceof Block ) {
isSwitch = false;
statements = ((Block)blockNode).statements;
} else if ( blockNode instanceof SwitchStatement ) {
isSwitch = true;
statements = ((SwitchStatement)blockNode).statements;
} else {
annotationNode.addError("@Cleanup is legal only on a local variable declaration inside a block.");
return true;
}
if ( statements == null ) {
annotationNode.addError("LOMBOK BUG: Parent block does not contain any statements.");
return true;
}
int start = 0;
for ( ; start < statements.length ; start++ ) {
if ( statements[start] == decl ) break;
}
if ( start == statements.length ) {
annotationNode.addError("LOMBOK BUG: Can't find this local variable declaration inside its parent.");
return true;
}
start++;
int end;
if ( isSwitch ) {
end = start + 1;
for ( ; end < statements.length ; end++ ) {
if ( statements[end] instanceof CaseStatement ) {
break;
}
}
} else end = statements.length;
//At this point:
// start-1 = Local Declaration marked with @Cleanup
// start = first instruction that needs to be wrapped into a try block
// end = last intruction of the scope -OR- last instruction before the next case label in switch statements.
// hence:
// [start, end) = statements for the try block.
Statement[] tryBlock = new Statement[end - start];
System.arraycopy(statements, start, tryBlock, 0, end-start);
//Remove the stuff we just dumped into the tryBlock, and then leave room for the try node.
int newStatementsLength = statements.length - (end-start); //Remove room for every statement moved into try block...
newStatementsLength += 1; //But add room for the TryStatement node itself.
Statement[] newStatements = new Statement[newStatementsLength];
System.arraycopy(statements, 0, newStatements, 0, start); //copy all statements before the try block verbatim.
System.arraycopy(statements, end, newStatements, start+1, statements.length - end); //For switch statements.
TryStatement tryStatement = new TryStatement();
tryStatement.tryBlock = new Block(0);
tryStatement.tryBlock.statements = tryBlock;
newStatements[start] = tryStatement;
Statement[] finallyBlock = new Statement[1];
MessageSend unsafeClose = new MessageSend();
unsafeClose.receiver = new SingleNameReference(decl.name, 0);
unsafeClose.selector = cleanupName.toCharArray();
finallyBlock[0] = unsafeClose;
tryStatement.finallyBlock = new Block(0);
tryStatement.finallyBlock.statements = finallyBlock;
tryStatement.catchArguments = null;
tryStatement.catchBlocks = null;
if ( blockNode instanceof AbstractMethodDeclaration ) {
((AbstractMethodDeclaration)blockNode).statements = newStatements;
} else if ( blockNode instanceof Block ) {
((Block)blockNode).statements = newStatements;
} else if ( blockNode instanceof SwitchStatement ) {
((SwitchStatement)blockNode).statements = newStatements;
}
ancestor.rebuild();
return true;
}
}
|