aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorReinier Zwitserloot <reinier@tipit.to>2009-06-26 04:56:40 +0200
committerReinier Zwitserloot <reinier@tipit.to>2009-06-26 04:56:40 +0200
commitcea4c9bb042e6dceb6512f094a25870c7a084744 (patch)
tree30757acbeb84fb7be8d1a4277f4afb173de0feb2 /src
parent91cd698b1efc627dd4ddd46c8cb8f561dd7bf0a7 (diff)
downloadlombok-cea4c9bb042e6dceb6512f094a25870c7a084744.tar.gz
lombok-cea4c9bb042e6dceb6512f094a25870c7a084744.tar.bz2
lombok-cea4c9bb042e6dceb6512f094a25870c7a084744.zip
Cleanup implemented for eclipse!
There's one serious problem though: The cleanup routine modifies the eclipse internal AST, but doesn't update our bi-directional AST. Thus, or example, having a @Cleanup annotation inside the scope of another @Cleanup fails, because the application of the second one climbs up to the wrong block level (the original block level instead of newly built try block).
Diffstat (limited to 'src')
-rw-r--r--src/lombok/Cleanup.java12
-rw-r--r--src/lombok/eclipse/handlers/HandleCleanup.java205
2 files changed, 217 insertions, 0 deletions
diff --git a/src/lombok/Cleanup.java b/src/lombok/Cleanup.java
new file mode 100644
index 00000000..c847e942
--- /dev/null
+++ b/src/lombok/Cleanup.java
@@ -0,0 +1,12 @@
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.LOCAL_VARIABLE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Cleanup {
+ String cleanupMethod() default "close";
+}
diff --git a/src/lombok/eclipse/handlers/HandleCleanup.java b/src/lombok/eclipse/handlers/HandleCleanup.java
new file mode 100644
index 00000000..908a9b04
--- /dev/null
+++ b/src/lombok/eclipse/handlers/HandleCleanup.java
@@ -0,0 +1,205 @@
+package lombok.eclipse.handlers;
+
+import static lombok.eclipse.Eclipse.copyType;
+
+import java.util.Arrays;
+
+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.Argument;
+import org.eclipse.jdt.internal.compiler.ast.Assignment;
+import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.CaseStatement;
+import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
+import org.eclipse.jdt.internal.compiler.ast.IfStatement;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+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.ThrowStatement;
+import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TryStatement;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.mangosdk.spi.ProviderFor;
+
+@ProviderFor(EclipseAnnotationHandler.class)
+public class HandleCleanup implements EclipseAnnotationHandler<Cleanup> {
+ @Override public boolean handle(AnnotationValues<Cleanup> annotation, Annotation ast, Node annotationNode) {
+ String cleanupName = annotation.getInstance().cleanupMethod();
+ if ( cleanupName.isEmpty() ) {
+ 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();
+
+ 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("Parent block does not contain any statements. This is a lombok bug.");
+ return true;
+ }
+
+ int start = 0;
+ for ( ; start < statements.length ; start++ ) {
+ if ( statements[start] == decl ) break;
+ }
+
+ if ( start == statements.length ) {
+ annotationNode.addError("Can't find this local variable declaration inside its parent. This is a lombok bug.");
+ return true;
+ }
+
+ start++;
+
+ int end = start + 1;
+ for ( ; end < statements.length ; end++ ) {
+ if ( isSwitch && statements[end] instanceof CaseStatement ) {
+ annotationNode.addError("The cleanup method must be called before the next case/default statement.");
+ return true;
+ }
+ if ( statements[end] instanceof MessageSend ) {
+ MessageSend ms = (MessageSend)statements[end];
+ //The method name is the same as the 'cleanupName = ' field of the @Cleanup annotation...
+ if ( ms.selector == null || !cleanupName.equals(new String(ms.selector)) ) continue;
+ //The call is of the form 'foo.cleanup(anything)', where foo is a simple reference and not an expression...
+ if ( !(ms.receiver instanceof SingleNameReference) ) continue;
+ //And the reference is the same name as the local variable annotated with @Cleanup...
+ if ( !Arrays.equals(((SingleNameReference)ms.receiver).token, decl.name) ) continue;
+ //Then we found it!
+ if ( ms.arguments != null && ms.arguments.length > 0 ) {
+ //As we'll be moving the close() call around, any references to local vars may not be valid in the new scope.
+ //Technically we could throw scope markers around the whole shebang and split local var declarations into a separate
+ //declaration (in the newly created top scope) and an initialization, but, then there's 'final' and 'definite assignment'
+ //rules to worry about. So, let's make this easy on ourselves and allow no arguments, for now.
+ annotationNode.addError("The cleanup method cannot have any arguments.");
+ return true;
+ }
+ break;
+ }
+ }
+
+ if ( end == statements.length ) {
+ annotationNode.addError("You need to include a " + new String(decl.name) + "." + cleanupName + "() call at the same scope level.");
+ return true;
+ }
+
+ //At this point, at start-1, there's the local declaration, and at end, there's the close call.
+ //Thus, we need to move [start, end) into a try block, and move the close call to its own scope.
+
+ Statement[] tryBlock = new Statement[end - start];
+ System.arraycopy(statements, start, tryBlock, 0, end-start);
+ //Remove the stuff we just dumped into the tryBlock, AND the close() call, and then leave room for the try node and the unique name.
+ Statement[] newStatements = new Statement[statements.length - (end-start) +1];
+ System.arraycopy(statements, 0, newStatements, 0, start);
+ if ( statements.length - end > 0 ) System.arraycopy(statements, end+1, newStatements, start+2, statements.length - end -1);
+ TryStatement tryStatement = new TryStatement();
+ newStatements[start+1] = tryStatement;
+ LocalDeclaration tempVar = new LocalDeclaration(("$lombok$cleanup$" + new String(decl.name)).toCharArray(), 0, 0);
+ tempVar.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0);
+ tempVar.initialization = new FalseLiteral(0, 0);
+ newStatements[start] = tempVar;
+ tryStatement.tryBlock = new Block(0);
+ tryStatement.tryBlock.statements = tryBlock;
+
+ char[] exName = ("$lombok$cleanup$ex$" + new String(decl.name)).toCharArray();
+ Statement[] finallyBlock = new Statement[1];
+ TryStatement safeClose = new TryStatement();
+ safeClose.tryBlock = new Block(0);
+ safeClose.tryBlock.statements = new Statement[1];
+ MessageSend newCloseCall = new MessageSend();
+ newCloseCall.receiver = new SingleNameReference(decl.name, 0);
+ newCloseCall.selector = cleanupName.toCharArray();
+ safeClose.tryBlock.statements[0] = newCloseCall;
+ safeClose.catchArguments = new Argument[1];
+ safeClose.catchArguments[0] = new Argument(exName, 0,
+ new QualifiedTypeReference(TypeConstants.JAVA_LANG_THROWABLE, new long[] { 0, 0, 0}), 0);
+ safeClose.catchBlocks = new Block[1];
+ safeClose.catchBlocks[0] = new Block(0);
+ safeClose.catchBlocks[0].sourceEnd = safeClose.catchBlocks[0].sourceStart = -2;
+ MessageSend unsafeClose = new MessageSend();
+ unsafeClose.receiver = new SingleNameReference(decl.name, 0);
+ unsafeClose.selector = cleanupName.toCharArray();
+ finallyBlock[0] = new IfStatement(new SingleNameReference(tempVar.name, 0), safeClose, unsafeClose, 0, 0);
+ tryStatement.finallyBlock = new Block(0);
+ tryStatement.finallyBlock.statements = finallyBlock;
+
+ Node containingMethodNode = annotationNode;
+ TypeReference[] thrownExceptions = null;
+ findThrownExceptions:
+ while ( containingMethodNode != null ) {
+ switch ( containingMethodNode.getKind() ) {
+ case INITIALIZER:
+ break findThrownExceptions;
+ case METHOD:
+ thrownExceptions = ((AbstractMethodDeclaration)containingMethodNode.get()).thrownExceptions;
+ break findThrownExceptions;
+ default:
+ containingMethodNode = containingMethodNode.up();
+ }
+ }
+
+ if ( thrownExceptions == null ) thrownExceptions = new TypeReference[0];
+ tryStatement.catchArguments = new Argument[thrownExceptions.length + 2];
+ tryStatement.catchBlocks = new Block[thrownExceptions.length + 2];
+ int idx = 0;
+ tryStatement.catchArguments[idx++] = new Argument(exName, 0,
+ new QualifiedTypeReference(TypeConstants.JAVA_LANG_RUNTIMEEXCEPTION, new long[] { 0, 0, 0 }), 0);
+ tryStatement.catchArguments[idx++] = new Argument(exName, 0,
+ new QualifiedTypeReference(TypeConstants.JAVA_LANG_ERROR, new long[] { 0, 0, 0 }), 0);
+ for ( ; idx < tryStatement.catchArguments.length ; idx++ ) {
+ tryStatement.catchArguments[idx] = new Argument(exName, 0, copyType(thrownExceptions[idx-2]), 0);
+ }
+
+ for ( idx = 0 ; idx < tryStatement.catchBlocks.length ; idx++ ) {
+ Block b = new Block(0);
+ tryStatement.catchBlocks[idx] = b;
+ b.statements = new Statement[2];
+ b.statements[0] = new Assignment(new SingleNameReference(tempVar.name, 0), new TrueLiteral(0, 0), 0);
+ b.statements[1] = new ThrowStatement(new SingleNameReference(exName, 0), 0, 0);
+ b.sourceEnd = b.sourceStart = -2;
+ }
+
+ 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;
+ }
+
+ return true;
+ }
+}