| Summary: | [ast rewrite] code lost applying changes after AST modification | ||
|---|---|---|---|
| Product: | [Eclipse Project] JDT | Reporter: | Randall Theobald <rstheo> |
| Component: | Core | Assignee: | Martin Aeschlimann <martinae> |
| Status: | CLOSED WORKSFORME | QA Contact: | |
| Severity: | normal | ||
| Priority: | P3 | ||
| Version: | 3.0.2 | ||
| Target Milestone: | --- | ||
| Hardware: | PC | ||
| OS: | Windows XP | ||
| Whiteboard: | |||
In 3.1, this is the output of your example:
original code: String myString = ((String)"myString");
Single removal of cast
[cast removal]
code: String myString = ("myString");
Piggyback removal of parens
[paren removal]
code: String myString = "myString";
Removal of both cast and parens
[cast removal]
[paren removal]
[code from AST: String myString="myString";
]
code after applying changes: String myString = "myString";
That's the intended result, no?
Yes, it looks like it works fine in 3.1. Looks like this is a duplicate of a previous defect. I didn't have the chance to investigate further, so thanks. closing No longer an issue. |
My eclipse build is 200503110845, as part of IBM Websphere Integration Developer 6.0. Basically, the problem is that if you apply the right combination of changes to the AST before applying the changes, things can get lost. If the same changes are made individually and applied in a serial fashion, there is no problem. So, it appears that the mechanism that takes the changes to the AST and writes them back to the document gets confused for certain change combinations. I wrote a Java file that illustrates this by removing a CastExpression and a ParenthesizedExpression from a line of code. The problem is that the deepest expression somehow gets lost. If you see a problem with the way I am doing things, please let me know. This problem has caused me days of wasted effort and I didn't want somebody else to have to go through it. Here is the output of running the code: ****************************************************** original code: String myString = ((String)"myString"); Single removal of cast [cast removal] code: String myString = ("myString"); Piggyback removal of parens [paren removal] code: String myString = "myString"; Removal of both cast and parens [cast removal] [paren removal] [code from AST: String myString="myString";] code after applying changes: String myString = ; ****************************************************** Here is the entire Java code: ****************************************************** import java.util.List; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.CastExpression; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.ParenthesizedExpression; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jface.text.Document; import org.eclipse.text.edits.TextEdit; import org.eclipse.text.edits.UndoEdit; /** * This file demonstrates a defect in the applying of changes made in the AST * back to the document. When making each change individually (applying in between), * there is no problem. However, when making both changes before applying, there * is a BIG problem (the expression disappears). * * Here, I use the example of removing a Cast and Parentheses from an expression. * * @author randallt */ public class ASTChangeTest { private static final String CU_START = "public class DUMMYCLASS { public void X(){"; //$NON-NLS-1$ private static final String CU_END = "\n}}//DUMMYCLASS"; //$NON-NLS-1$ private static String NL = System.getProperty("line.separator"); //$NON-NLS-1$ // instance variables private static CompilationUnit cu = null; private static Document doc = null; /** * Creates the parser and parses the snippet * @param snippet the snippet * @return the Block object representing the body of the snippet */ private static Block getBodyFromSnippet(String snippet) { ASTParser parser = ASTParser.newParser(AST.JLS2); StringBuffer buf = new StringBuffer(CU_START).append(snippet).append(CU_END); doc = new Document(buf.toString()); parser.setSource(doc.get().toCharArray()); cu = (CompilationUnit)parser.createAST(null); cu.recordModifications(); List cuTypes = cu.types(); TypeDeclaration type = (TypeDeclaration)cuTypes.get(0); MethodDeclaration method = type.getMethods()[0]; Block body = method.getBody(); return body; } /** * Applies the changes and returns the changed code */ private static String applyChanges() throws Exception { // Apply the rewrite, and get the output TextEdit edits = cu.rewrite(doc, null); UndoEdit undo = edits.apply(doc); String code = doc.get(); code = code.substring(CU_START.length(), code.length() - CU_END.length()); return code; } static String CODE = "String myString = ((String)\"myString\");"; public static void main(String[] args) throws Exception { System.out.println("original code: " + CODE); // remove cast System.out.println(NL + "Single removal of cast"); Block body = getBodyFromSnippet(CODE); ChangeVisitor visitor = new ChangeVisitor(); visitor.state = 1; body.accept(visitor); String newCode = applyChanges(); System.out.println("code: " + newCode); // remove paren from result of cast removal System.out.println(NL + "Piggyback removal of parens"); body = getBodyFromSnippet(newCode); visitor.state = 2; body.accept(visitor); newCode = applyChanges(); System.out.println("code: " + newCode); // start over and remove both before applying changes System.out.println(NL + "Removal of both cast and parens"); body = getBodyFromSnippet(CODE); visitor.state = 3; body.accept(visitor); newCode = applyChanges(); System.out.println("code after applying changes: " + newCode); } } class ChangeVisitor extends ASTVisitor { public int state = -1; public void endVisit(CastExpression cast) { if (state == 1 || state == 3) { System.out.println(" [cast removal]"); Expression expression = cast.getExpression(); Util.replaceNode(cast,expression); } super.endVisit(cast); } public void endVisit(ParenthesizedExpression paren) { if (state == 2 || state == 3) { ASTNode parent = paren.getParent().getParent(); System.out.println(" [paren removal]"); Expression expression = paren.getExpression(); Util.replaceNode(paren,expression); if (state == 3) { System.out.println(" [code from AST: " + parent + "]"); } } super.endVisit(paren); } } class Util { /** * Replaces the given oldNode with the newNode in the AST of the oldNode * @param oldNode the node to be replaced * @param newNode the node to replace with */ public static void replaceNode(ASTNode oldNode, ASTNode newNode) { ASTNode copiedNode = ASTNode.copySubtree(oldNode.getAST(),newNode); ASTNode parent = oldNode.getParent(); StructuralPropertyDescriptor location = oldNode.getLocationInParent(); if (location.isChildProperty()) { parent.setStructuralProperty(location,copiedNode); } else if (location.isChildListProperty()) { List list = (List)parent.getStructuralProperty(location); int index = list.indexOf(oldNode); list.set(index,copiedNode); } } } ****************************************************** import java.util.List; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.CastExpression; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.ParenthesizedExpression; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jface.text.Document; import org.eclipse.text.edits.TextEdit; import org.eclipse.text.edits.UndoEdit; /** * This file demonstrates a defect in the applying of changes made in the AST * back to the document. When making each change individually (applying in between), * there is no problem. However, when making both changes before applying, there * is a BIG problem (the expression disappears). * * Here, I use the example of removing a Cast and Parentheses from an expression. * * @author randallt */ public class ASTChangeTest { private static final String CU_START = "public class DUMMYCLASS { public void X(){"; //$NON-NLS-1$ private static final String CU_END = "\n}}//DUMMYCLASS"; //$NON-NLS-1$ private static String NL = System.getProperty("line.separator"); //$NON-NLS-1$ // instance variables private static CompilationUnit cu = null; private static Document doc = null; /** * Creates the parser and parses the snippet * @param snippet the snippet * @return the Block object representing the body of the snippet */ private static Block getBodyFromSnippet(String snippet) { ASTParser parser = ASTParser.newParser(AST.JLS2); StringBuffer buf = new StringBuffer(CU_START).append(snippet).append(CU_END); doc = new Document(buf.toString()); parser.setSource(doc.get().toCharArray()); cu = (CompilationUnit)parser.createAST(null); cu.recordModifications(); List cuTypes = cu.types(); TypeDeclaration type = (TypeDeclaration)cuTypes.get(0); MethodDeclaration method = type.getMethods()[0]; Block body = method.getBody(); return body; } /** * Applies the changes and returns the changed code */ private static String applyChanges() throws Exception { // Apply the rewrite, and get the output TextEdit edits = cu.rewrite(doc, null); UndoEdit undo = edits.apply(doc); String code = doc.get(); code = code.substring(CU_START.length(), code.length() - CU_END.length()); return code; } static String CODE = "String myString = ((String)\"myString\");"; public static void main(String[] args) throws Exception { System.out.println("original code: " + CODE); // remove cast System.out.println(NL + "Single removal of cast"); Block body = getBodyFromSnippet(CODE); ChangeVisitor visitor = new ChangeVisitor(); visitor.state = 1; body.accept(visitor); String newCode = applyChanges(); System.out.println("code: " + newCode); // remove paren from result of cast removal System.out.println(NL + "Piggyback removal of parens"); body = getBodyFromSnippet(newCode); visitor.state = 2; body.accept(visitor); newCode = applyChanges(); System.out.println("code: " + newCode); // start over and remove both before applying changes System.out.println(NL + "Removal of both cast and parens"); body = getBodyFromSnippet(CODE); visitor.state = 3; body.accept(visitor); newCode = applyChanges(); System.out.println("code after applying changes: " + newCode); } } class ChangeVisitor extends ASTVisitor { public int state = -1; public void endVisit(CastExpression cast) { if (state == 1 || state == 3) { System.out.println(" [cast removal]"); Expression expression = cast.getExpression(); Util.replaceNode(cast,expression); } super.endVisit(cast); } public void endVisit(ParenthesizedExpression paren) { if (state == 2 || state == 3) { ASTNode parent = paren.getParent().getParent(); System.out.println(" [paren removal]"); Expression expression = paren.getExpression(); Util.replaceNode(paren,expression); if (state == 3) { System.out.println(" [code from AST: " + parent + "]"); } } super.endVisit(paren); } } class Util { /** * Replaces the given oldNode with the newNode in the AST of the oldNode * @param oldNode the node to be replaced * @param newNode the node to replace with */ public static void replaceNode(ASTNode oldNode, ASTNode newNode) { ASTNode copiedNode = ASTNode.copySubtree(oldNode.getAST(),newNode); ASTNode parent = oldNode.getParent(); StructuralPropertyDescriptor location = oldNode.getLocationInParent(); if (location.isChildProperty()) { parent.setStructuralProperty(location,copiedNode); } else if (location.isChildListProperty()) { List list = (List)parent.getStructuralProperty(location); int index = list.indexOf(oldNode); list.set(index,copiedNode); } } }