diff options
-rw-r--r-- | src/lombok/core/AST.java | 103 | ||||
-rw-r--r-- | src/lombok/eclipse/EclipseAST.java | 18 | ||||
-rw-r--r-- | src/lombok/eclipse/handlers/HandleCleanup.java | 133 | ||||
-rw-r--r-- | src/lombok/javac/JavacAST.java | 33 |
4 files changed, 166 insertions, 121 deletions
diff --git a/src/lombok/core/AST.java b/src/lombok/core/AST.java index 871f24be..f4d4d628 100644 --- a/src/lombok/core/AST.java +++ b/src/lombok/core/AST.java @@ -2,6 +2,7 @@ package lombok.core; import static lombok.Lombok.sneakyThrow; +import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; @@ -93,12 +94,12 @@ public abstract class AST<N> { public abstract class Node { protected final Kind kind; protected final N node; - protected final Collection<? extends Node> children; + protected final List<? extends Node> children; protected Node parent; protected boolean handled; protected boolean isStructurallySignificant; - protected Node(N node, Collection<? extends Node> children, Kind kind) { + protected Node(N node, List<? extends Node> children, Kind kind) { this.kind = kind; this.node = node; this.children = children == null ? Collections.<Node>emptyList() : children; @@ -129,6 +130,22 @@ public abstract class AST<N> { return node; } + @SuppressWarnings("unchecked") + public Node replaceWith(N newN, Kind kind) { + Node newNode = buildTree(newN, kind); + newNode.parent = parent; + for ( int i = 0 ; i < parent.children.size() ; i++ ) { + if ( parent.children.get(i) == this ) ((List)parent.children).set(i, newNode); + } + + parent.replaceChildNode(get(), newN); + return newNode; + } + + public void replaceChildNode(N oldN, N newN) { + replaceStatementInNode(get(), oldN, newN); + } + public Kind getKind() { return kind; } @@ -204,7 +221,6 @@ public abstract class AST<N> { private void gatherAndRemoveChildren(Map<N, Node> map) { for ( Node child : children ) child.gatherAndRemoveChildren(map); map.put(get(), this); - children.clear(); identityDetector.remove(get()); nodeMap.remove(get()); } @@ -301,6 +317,87 @@ public abstract class AST<N> { return list; } + protected boolean replaceStatementInNode(N statement, N oldN, N newN) { + for ( FieldAccess fa : fieldsOf(statement.getClass()) ) { + if ( replaceStatementInField(fa, statement, oldN, newN) ) return true; + } + + return false; + } + + private boolean replaceStatementInField(FieldAccess fa, N statement, N oldN, N newN) { + try { + Object o = fa.field.get(statement); + if ( o == null ) return false; + + if ( o == oldN ) { + fa.field.set(statement, newN); + return true; + } + + if ( fa.dim > 0 ) { + if ( o.getClass().isArray() ) { + return replaceStatementInArray(o, oldN, newN); + } else if ( Collection.class.isInstance(o) ) { + return replaceStatementInCollection(fa.field, statement, new ArrayList<Collection<?>>(), (Collection<?>)o, oldN, newN); + } + } + + return false; + } catch ( IllegalAccessException e ) { + throw sneakyThrow(e); + } + + } + + private boolean replaceStatementInCollection(Field field, Object fieldRef, List<Collection<?>> chain, Collection<?> collection, N oldN, N newN) throws IllegalAccessException { + if ( collection == null ) return false; + + int idx = -1; + for ( Object o : collection ) { + idx++; + if ( o == null ) continue; + if ( Collection.class.isInstance(o) ) { + Collection<?> newC = (Collection<?>)o; + List<Collection<?>> newChain = new ArrayList<Collection<?>>(chain); + newChain.add(newC); + if ( replaceStatementInCollection(field, fieldRef, newChain, newC, oldN, newN) ) return true; + } + if ( o == oldN ) { + setElementInASTCollection(field, fieldRef, chain, collection, idx, newN); + return true; + } + } + + return false; + } + + /** Override if your AST collection does not support the set method. Javac's for example, does not. */ + @SuppressWarnings("unchecked") + protected void setElementInASTCollection(Field field, Object fieldRef, List<Collection<?>> chain, Collection<?> collection, int idx, N newN) throws IllegalAccessException { + if ( collection instanceof List<?> ) { + ((List)collection).set(idx, newN); + } + } + + private boolean replaceStatementInArray(Object array, N oldN, N newN) { + if ( array == null ) return false; + + int len = Array.getLength(array); + for ( int i = 0 ; i < len ; i++ ) { + Object o = Array.get(array, i); + if ( o == null ) continue; + if ( o.getClass().isArray() ) { + if ( replaceStatementInArray(o, oldN, newN) ) return true; + } else if ( o == oldN ) { + Array.set(array, i, newN); + return true; + } + } + + return false; + } + @SuppressWarnings("unchecked") private <T extends Node> void buildWithField0(Class<T> nodeType, N child, FieldAccess fa, Collection<T> list) { try { diff --git a/src/lombok/eclipse/EclipseAST.java b/src/lombok/eclipse/EclipseAST.java index c5bdd2ff..5b2b153d 100644 --- a/src/lombok/eclipse/EclipseAST.java +++ b/src/lombok/eclipse/EclipseAST.java @@ -143,7 +143,7 @@ public class EclipseAST extends AST<ASTNode> { } public final class Node extends AST<ASTNode>.Node { - Node(ASTNode node, Collection<Node> children, Kind kind) { + Node(ASTNode node, List<Node> children, Kind kind) { super(node, children, kind); } @@ -300,7 +300,7 @@ public class EclipseAST extends AST<ASTNode> { private static String toFileName(CompilationUnitDeclaration ast) { return ast.compilationResult.fileName == null ? null : new String(ast.compilationResult.fileName); } - + public void reparse() { propagateProblems(); if ( completeParse ) return; @@ -349,7 +349,7 @@ public class EclipseAST extends AST<ASTNode> { } private Node buildCompilationUnit(CompilationUnitDeclaration top) { - Collection<Node> children = buildTypes(top.types); + List<Node> children = buildTypes(top.types); return putInMap(new Node(top, children, Kind.COMPILATION_UNIT)); } @@ -357,7 +357,7 @@ public class EclipseAST extends AST<ASTNode> { if ( n != null ) collection.add(n); } - private Collection<Node> buildTypes(TypeDeclaration[] children) { + private List<Node> buildTypes(TypeDeclaration[] children) { if ( children == null ) return Collections.emptyList(); List<Node> childNodes = new ArrayList<Node>(); for ( TypeDeclaration type : children ) addIfNotNull(childNodes, buildType(type)); @@ -381,9 +381,9 @@ public class EclipseAST extends AST<ASTNode> { return childNodes; } - private static <T> Collection<T> singleton(T item) { + private static <T> List<T> singleton(T item) { if ( item == null ) return Collections.emptyList(); - else return Collections.singleton(item); + else return Collections.singletonList(item); } private Node buildField(FieldDeclaration field) { @@ -454,21 +454,17 @@ public class EclipseAST extends AST<ASTNode> { return childNodes; } - //Almost anything is a statement, so this method has a different name to avoid overloading confusion private Node buildStatement(Statement child) { if ( child == null || alreadyHandled(child) ) return null; if ( child instanceof TypeDeclaration ) return buildType((TypeDeclaration)child); if ( child instanceof LocalDeclaration ) return buildLocal((LocalDeclaration)child, Kind.LOCAL); - //We drill down because LocalDeclarations and TypeDeclarations can occur anywhere, even in, say, - //an if block, or even the expression on an assert statement! - setAsHandled(child); return drill(child); } - protected Node drill(Statement statement) { + private Node drill(Statement statement) { List<Node> childNodes = new ArrayList<Node>(); for ( FieldAccess fa : fieldsOf(statement.getClass()) ) childNodes.addAll(buildWithField(Node.class, statement, fa)); return putInMap(new Node(statement, childNodes, Kind.STATEMENT)); diff --git a/src/lombok/eclipse/handlers/HandleCleanup.java b/src/lombok/eclipse/handlers/HandleCleanup.java index f381f25e..867bd0e5 100644 --- a/src/lombok/eclipse/handlers/HandleCleanup.java +++ b/src/lombok/eclipse/handlers/HandleCleanup.java @@ -1,9 +1,5 @@ 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; @@ -13,24 +9,14 @@ 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) @@ -69,7 +55,7 @@ public class HandleCleanup implements EclipseAnnotationHandler<Cleanup> { } if ( statements == null ) { - annotationNode.addError("Parent block does not contain any statements. This is a lombok bug."); + annotationNode.addError("LOMBOK BUG: Parent block does not contain any statements."); return true; } @@ -79,119 +65,53 @@ public class HandleCleanup implements EclipseAnnotationHandler<Cleanup> { } if ( start == statements.length ) { - annotationNode.addError("Can't find this local variable declaration inside its parent. This is a lombok bug."); + annotationNode.addError("LOMBOK BUG: Can't find this local variable declaration inside its parent."); 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; + int end; + if ( isSwitch ) { + end = start + 1; + for ( ; end < statements.length ; end++ ) { + if ( statements[end] instanceof CaseStatement ) { + break; } - break; } - } + } else end = statements.length; - 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. + //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 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); - System.arraycopy(statements, end+1, newStatements, start+2, statements.length - end -1); + //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(); - 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; + newStatements[start] = tryStatement; - 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); + finallyBlock[0] = unsafeClose; 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; - } + tryStatement.catchArguments = null; + tryStatement.catchBlocks = null; if ( blockNode instanceof AbstractMethodDeclaration ) { ((AbstractMethodDeclaration)blockNode).statements = newStatements; @@ -203,6 +123,7 @@ public class HandleCleanup implements EclipseAnnotationHandler<Cleanup> { ancestor.rebuild(); + return true; } } diff --git a/src/lombok/javac/JavacAST.java b/src/lombok/javac/JavacAST.java index a253f8cd..9e10f320 100644 --- a/src/lombok/javac/JavacAST.java +++ b/src/lombok/javac/JavacAST.java @@ -228,7 +228,7 @@ public class JavacAST extends AST<JCTree> { } public class Node extends AST<JCTree>.Node { - public Node(JCTree node, Collection<Node> children, Kind kind) { + public Node(JCTree node, List<Node> children, Kind kind) { super(node, children, kind); } @@ -411,6 +411,37 @@ public class JavacAST extends AST<JCTree> { } } + @SuppressWarnings("unchecked") + @Override protected void setElementInASTCollection(Field field, Object refField, List<Collection<?>> chain, Collection<?> collection, int idx, JCTree newN) throws IllegalAccessException { + com.sun.tools.javac.util.List<?> list = setElementInConsList(chain, collection, ((List)collection).get(idx), newN); + field.set(refField, list); + } + + private com.sun.tools.javac.util.List<?> setElementInConsList(List<Collection<?>> chain, Collection<?> current, Object oldO, Object newO) { + com.sun.tools.javac.util.List<?> oldL = (com.sun.tools.javac.util.List<?>) current; + com.sun.tools.javac.util.List<?> newL = replaceInConsList(oldL, oldO, newO); + if ( chain.isEmpty() ) return newL; + else { + List<Collection<?>> reducedChain = new ArrayList<Collection<?>>(chain); + Collection<?> newCurrent = reducedChain.remove(reducedChain.size() -1); + return setElementInConsList(reducedChain, newCurrent, oldL, newL); + } + } + + private com.sun.tools.javac.util.List<?> replaceInConsList(com.sun.tools.javac.util.List<?> oldL, Object oldO, Object newO) { + boolean repl = false; + Object[] a = oldL.toArray(); + for ( int i = 0 ; i < a.length ; i++ ) { + if ( a[i] == oldO ) { + a[i] = newO; + repl = true; + } + } + + if ( repl ) return com.sun.tools.javac.util.List.<Object>from(a); + else return oldL; + } + private void increaseErrorCount(Messager messager) { try { Field f = messager.getClass().getDeclaredField("errorCount"); |