KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > core > dom > rewrite > ASTRewrite


1 /*******************************************************************************
2  * Copyright (c) 2004, 2005 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jdt.core.dom.rewrite;
12
13 import java.util.Iterator JavaDoc;
14 import java.util.Map JavaDoc;
15
16 import org.eclipse.text.edits.MultiTextEdit;
17 import org.eclipse.text.edits.TextEdit;
18 import org.eclipse.text.edits.TextEditGroup;
19
20 import org.eclipse.jface.text.IDocument;
21
22 import org.eclipse.jdt.core.JavaCore;
23
24 import org.eclipse.jdt.core.dom.AST;
25 import org.eclipse.jdt.core.dom.ASTNode;
26 import org.eclipse.jdt.core.dom.Block;
27 import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
28 import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
29
30 import org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer;
31 import org.eclipse.jdt.internal.core.dom.rewrite.NodeInfoStore;
32 import org.eclipse.jdt.internal.core.dom.rewrite.NodeRewriteEvent;
33 import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEventStore;
34 import org.eclipse.jdt.internal.core.dom.rewrite.TrackedNodePosition;
35 import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEventStore.CopySourceInfo;
36
37 /**
38  * Infrastucture for modifying code by describing changes to AST nodes.
39  * The AST rewriter collects descriptions of modifications to nodes and
40  * translates these descriptions into text edits that can then be applied to
41  * the original source. The key thing is that this is all done without actually
42  * modifying the original AST, which has the virtue of allowing one to entertain
43  * several alternate sets of changes on the same AST (e.g., for calculating
44  * quick fix proposals). The rewrite infrastructure tries to generate minimal
45  * text changes, preserve existing comments and indentation, and follow code
46  * formatter settings. If the freedom to explore multiple alternate changes is
47  * not required, consider using the AST's built-in rewriter
48  * (see {@link org.eclipse.jdt.core.dom.CompilationUnit#rewrite(IDocument, Map)}).
49  * <p>
50  * The following code snippet illustrated usage of this class:
51  * </p>
52  * <pre>
53  * Document doc = new Document("import java.util.List;\nclass X {}\n");
54  * ASTParser parser = ASTParser.newParser(AST.JLS3);
55  * parser.setSource(doc.get().toCharArray());
56  * CompilationUnit cu = (CompilationUnit) parser.createAST(null);
57  * AST ast = cu.getAST();
58  * ImportDeclaration id = ast.newImportDeclaration();
59  * id.setName(ast.newName(new String[] {"java", "util", "Set"});
60  * ASTRewrite rewriter = ASTRewrite.create(ast);
61  * TypeDeclaration td = (TypeDeclaration) cu.types().get(0);
62  * ITrackedNodePosition tdLocation = rewriter.track(td);
63  * ListRewriter lrw = rewriter.getListRewrite(cu,
64  * CompilationUnit.IMPORTS_PROPERTY);
65  * lrw.insertLast(id, null);
66  * TextEdit edits = rewriter.rewriteAST(document, null);
67  * UndoEdit undo = edits.apply(document);
68  * assert "import java.util.List;\nimport java.util.Set;\nclass X {}"
69  * .equals(doc.get().toCharArray());
70  * // tdLocation.getStartPosition() and tdLocation.getLength()
71  * // are new source range for "class X {}" in doc.get()
72  * </pre>
73  * <p>
74  * This class is not intended to be subclassed.
75  * </p>
76  * @since 3.0
77  */

78 public class ASTRewrite {
79
80     /** root node for the rewrite: Only nodes under this root are accepted */
81     private final AST ast;
82
83     private final RewriteEventStore eventStore;
84     private final NodeInfoStore nodeStore;
85     
86     /**
87      * Target source range computer; null means uninitialized;
88      * lazy initialized to <code>new TargetSourceRangeComputer()</code>.
89      * @since 3.1
90      */

91     private TargetSourceRangeComputer targetSourceRangeComputer = null;
92     
93     /**
94      * Creates a new instance for describing manipulations of
95      * the given AST.
96      *
97      * @param ast the AST whose nodes will be rewritten
98      * @return the new rewriter instance
99      */

100     public static ASTRewrite create(AST ast) {
101         return new ASTRewrite(ast);
102     }
103
104     /**
105      * Internal constructor. Creates a new instance for the given AST.
106      * Clients should use {@link #create(AST)} to create instances.
107      *
108      * @param ast the AST being rewritten
109      */

110     protected ASTRewrite(AST ast) {
111         this.ast= ast;
112         this.eventStore= new RewriteEventStore();
113         this.nodeStore= new NodeInfoStore(ast);
114     }
115     
116     /**
117      * Returns the AST the rewrite was set up on.
118      *
119      * @return the AST the rewrite was set up on
120      */

121     public final AST getAST() {
122         return this.ast;
123     }
124             
125     /**
126      * Internal method. Returns the internal event store.
127      * Clients should not use.
128      * @return Returns the internal event store. Clients should not use.
129      */

130     protected final RewriteEventStore getRewriteEventStore() {
131         return this.eventStore;
132     }
133     
134     /**
135      * Internal method. Returns the internal node info store.
136      * Clients should not use.
137      * @return Returns the internal info store. Clients should not use.
138      */

139     protected final NodeInfoStore getNodeStore() {
140         return this.nodeStore;
141     }
142     
143     /**
144      * Converts all modifications recorded by this rewriter
145      * into an object representing the corresponding text
146      * edits to the given document containing the original source
147      * code. The document itself is not modified.
148      * <p>
149      * For nodes in the original that are being replaced or deleted,
150      * this rewriter computes the adjusted source ranges
151      * by calling <code>getTargetSourceRangeComputer().computeSourceRange(node)</code>.
152      * </p>
153      * <p>
154      * Calling this methods does not discard the modifications
155      * on record. Subsequence modifications are added to the ones
156      * already on record. If this method is called again later,
157      * the resulting text edit object will accurately reflect
158      * the net cumulative affect of all those changes.
159      * </p>
160      *
161      * @param document original document containing source code
162      * @param options the table of formatter options
163      * (key type: <code>String</code>; value type: <code>String</code>);
164      * or <code>null</code> to use the standard global options
165      * {@link JavaCore#getOptions() JavaCore.getOptions()}
166      * @return text edit object describing the changes to the
167      * document corresponding to the changes recorded by this rewriter
168      * @throws IllegalArgumentException An <code>IllegalArgumentException</code>
169      * is thrown if the document passed does not correspond to the AST that is rewritten.
170      */

171     public TextEdit rewriteAST(IDocument document, Map JavaDoc options) throws IllegalArgumentException JavaDoc {
172         if (document == null) {
173             throw new IllegalArgumentException JavaDoc();
174         }
175         TextEdit result= new MultiTextEdit();
176         
177         ASTNode rootNode= getRootNode();
178         if (rootNode != null) {
179             //validateASTNotModified(rootNode);
180

181             TargetSourceRangeComputer sourceRangeComputer= getExtendedSourceRangeComputer();
182             
183             this.eventStore.prepareMovedNodes(sourceRangeComputer);
184
185             ASTRewriteAnalyzer visitor= new ASTRewriteAnalyzer(document, result, this.eventStore, this.nodeStore, options, sourceRangeComputer);
186             rootNode.accept(visitor); // throws IllegalArgumentException
187

188             this.eventStore.revertMovedNodes();
189         }
190         return result;
191     }
192     
193     private ASTNode getRootNode() {
194         ASTNode node= null;
195         int start= -1;
196         int end= -1;
197         
198         for (Iterator JavaDoc iter= getRewriteEventStore().getChangeRootIterator(); iter.hasNext();) {
199             ASTNode curr= (ASTNode) iter.next();
200             if (!RewriteEventStore.isNewNode(curr)) {
201                 int currStart= curr.getStartPosition();
202                 int currEnd= currStart + curr.getLength();
203                 if (node == null || currStart < start && currEnd > end) {
204                     start= currStart;
205                     end= currEnd;
206                     node= curr;
207                 } else if (currStart < start) {
208                     start= currStart;
209                 } else if (currEnd > end) {
210                     end= currEnd;
211                 }
212             }
213         }
214         if (node != null) {
215             int currStart= node.getStartPosition();
216             int currEnd= currStart + node.getLength();
217             while (start < currStart || end > currEnd) { // go up until a node covers all
218
node= node.getParent();
219                 currStart= node.getStartPosition();
220                 currEnd= currStart + node.getLength();
221             }
222             ASTNode parent= node.getParent(); // go up until a parent has different range
223
while (parent != null && parent.getStartPosition() == node.getStartPosition() && parent.getLength() == node.getLength()) {
224                 node= parent;
225                 parent= node.getParent();
226             }
227         }
228         return node;
229     }
230     
231     /*
232     private void validateASTNotModified(ASTNode root) throws IllegalArgumentException {
233         GenericVisitor isModifiedVisitor= new GenericVisitor() {
234             protected boolean visitNode(ASTNode node) {
235                 if ((node.getFlags() & ASTNode.ORIGINAL) == 0) {
236                     throw new IllegalArgumentException("The AST that is rewritten must not be modified."); //$NON-NLS-1$
237                 }
238                 return true;
239             }
240         };
241         root.accept(isModifiedVisitor);
242     }
243     */

244         
245     /**
246      * Removes the given node from its parent in this rewriter. The AST itself
247      * is not actually modified in any way; rather, the rewriter just records
248      * a note that this node should not be there.
249      *
250      * @param node the node being removed
251      * @param editGroup the edit group in which to collect the corresponding
252      * text edits, or <code>null</code> if ungrouped
253      * @throws IllegalArgumentException if the node is null, or if the node is not
254      * part of this rewriter's AST, or if the described modification is invalid
255      * (such as removing a required node)
256      */

257     public final void remove(ASTNode node, TextEditGroup editGroup) {
258         if (node == null) {
259             throw new IllegalArgumentException JavaDoc();
260         }
261         StructuralPropertyDescriptor property= node.getLocationInParent();
262         if (property.isChildListProperty()) {
263             getListRewrite(node.getParent(), (ChildListPropertyDescriptor) property).remove(node, editGroup);
264         } else {
265             set(node.getParent(), property, null, editGroup);
266         }
267     }
268     
269     /**
270      * Replaces the given node in this rewriter. The replacement node
271      * must either be brand new (not part of the original AST) or a placeholder
272      * node (for example, one created by {@link #createCopyTarget(ASTNode)}
273      * or {@link #createStringPlaceholder(String, int)}). The AST itself
274      * is not actually modified in any way; rather, the rewriter just records
275      * a note that this node has been replaced.
276      *
277      * @param node the node being replaced
278      * @param replacement the replacement node, or <code>null</code> if no
279      * replacement
280      * @param editGroup the edit group in which to collect the corresponding
281      * text edits, or <code>null</code> if ungrouped
282      * @throws IllegalArgumentException if the node is null, or if the node is not part
283      * of this rewriter's AST, or if the replacement node is not a new node (or
284      * placeholder), or if the described modification is otherwise invalid
285      */

286     public final void replace(ASTNode node, ASTNode replacement, TextEditGroup editGroup) {
287         if (node == null) {
288             throw new IllegalArgumentException JavaDoc();
289         }
290         StructuralPropertyDescriptor property= node.getLocationInParent();
291         if (property.isChildListProperty()) {
292             getListRewrite(node.getParent(), (ChildListPropertyDescriptor) property).replace(node, replacement, editGroup);
293         } else {
294             set(node.getParent(), property, replacement, editGroup);
295         }
296     }
297
298     /**
299      * Sets the given property of the given node. If the given property is a child
300      * property, the value must be a replacement node that is either be brand new
301      * (not part of the original AST) or a placeholder node (for example, one
302      * created by {@link #createCopyTarget(ASTNode)}
303      * or {@link #createStringPlaceholder(String, int)}); or it must be
304      * </code>null</code>, indicating that the child should be deleted.
305      * If the given property is a simple property, the value must be the new
306      * value (primitive types must be boxed) or </code>null</code>.
307      * The AST itself is not actually modified in any way; rather, the rewriter
308      * just records a note that this node has been changed in the specified way.
309      *
310      * @param node the node
311      * @param property the node's property; either a simple property or a child property
312      * @param value the replacement child or new value, or <code>null</code> if none
313      * @param editGroup the edit group in which to collect the corresponding
314      * text edits, or <code>null</code> if ungrouped
315      * @throws IllegalArgumentException if the node or property is null, or if the node
316      * is not part of this rewriter's AST, or if the property is not a node property,
317      * or if the described modification is invalid
318      */

319     public final void set(ASTNode node, StructuralPropertyDescriptor property, Object JavaDoc value, TextEditGroup editGroup) {
320         if (node == null || property == null) {
321             throw new IllegalArgumentException JavaDoc();
322         }
323         validateIsInsideAST(node);
324         validatePropertyType(property, value);
325
326         NodeRewriteEvent nodeEvent= this.eventStore.getNodeEvent(node, property, true);
327         nodeEvent.setNewValue(value);
328         if (editGroup != null) {
329             this.eventStore.setEventEditGroup(nodeEvent, editGroup);
330         }
331     }
332
333     /**
334      * Creates and returns a new rewriter for describing modifications to the
335      * given list property of the given node.
336      *
337      * @param node the node
338      * @param property the node's property; the child list property
339      * @return a new list rewriter object
340      * @throws IllegalArgumentException if the node or property is null, or if the node
341      * is not part of this rewriter's AST, or if the property is not a node property,
342      * or if the described modification is invalid
343      */

344     public final ListRewrite getListRewrite(ASTNode node, ChildListPropertyDescriptor property) {
345         if (node == null || property == null) {
346             throw new IllegalArgumentException JavaDoc();
347         }
348         validateIsListProperty(property);
349         
350         return new ListRewrite(this, node, property);
351     }
352         
353     /**
354      * Returns an object that tracks the source range of the given node
355      * across the rewrite to its AST. Upon return, the result object reflects
356      * the given node's current source range in the AST. After
357      * <code>rewrite</code> is called, the result object is updated to
358      * reflect the given node's source range in the rewritten AST.
359      *
360      * @param node the node to track
361      * @return an object that tracks the source range of <code>node</code>
362      * @throws IllegalArgumentException if the node is null, or if the node
363      * is not part of this rewriter's AST, or if the node is already being
364      * tracked
365      */

366     public final ITrackedNodePosition track(ASTNode node) {
367         if (node == null) {
368             throw new IllegalArgumentException JavaDoc();
369         }
370         TextEditGroup group= this.eventStore.getTrackedNodeData(node);
371         if (group == null) {
372             group= new TextEditGroup("internal"); //$NON-NLS-1$
373
this.eventStore.setTrackedNodeData(node, group);
374         }
375         return new TrackedNodePosition(group, node);
376     }
377             
378     private void validateIsInsideAST(ASTNode node) {
379         if (node.getStartPosition() == -1) {
380             throw new IllegalArgumentException JavaDoc("Node is not an existing node"); //$NON-NLS-1$
381
}
382     
383         if (node.getAST() != getAST()) {
384             throw new IllegalArgumentException JavaDoc("Node is not inside the AST"); //$NON-NLS-1$
385
}
386     }
387     
388     private void validateIsListProperty(StructuralPropertyDescriptor property) {
389         if (!property.isChildListProperty()) {
390             String JavaDoc message= property.getId() + " is not a list property"; //$NON-NLS-1$
391
throw new IllegalArgumentException JavaDoc(message);
392         }
393     }
394     
395     private void validatePropertyType(StructuralPropertyDescriptor prop, Object JavaDoc node) {
396         if (prop.isChildListProperty()) {
397             String JavaDoc message= "Can not modify a list property, use a list rewriter"; //$NON-NLS-1$
398
throw new IllegalArgumentException JavaDoc(message);
399         }
400 // if (node == null) {
401
// if (prop.isSimpleProperty() || (prop.isChildProperty() && ((ChildPropertyDescriptor) prop).isMandatory())) {
402
// String message= "Can not remove property " + prop.getId(); //$NON-NLS-1$
403
// throw new IllegalArgumentException(message);
404
// }
405
// } else {
406
// if (!prop.getNodeClass().isInstance(node)) {
407
// String message= node.getClass().getName() + " is not a valid type for property " + prop.getId(); //$NON-NLS-1$
408
// throw new IllegalArgumentException(message);
409
// }
410
// }
411
}
412         
413     /**
414      * Creates and returns a placeholder node for a source string that is to be inserted into
415      * the output document at the position corresponding to the placeholder.
416      * The string will be inserted without being reformatted beyond correcting
417      * the indentation level. The placeholder node can either be inserted as new or
418      * used to replace an existing node.
419      *
420      * @param code the string to be inserted; lines should should not have extra indentation
421      * @param nodeType the ASTNode type that corresponds to the passed code.
422      * @return the new placeholder node
423      * @throws IllegalArgumentException if the code is null, or if the node
424      * type is invalid
425      */

426     public final ASTNode createStringPlaceholder(String JavaDoc code, int nodeType) {
427         if (code == null) {
428             throw new IllegalArgumentException JavaDoc();
429         }
430         ASTNode placeholder= getNodeStore().newPlaceholderNode(nodeType);
431         if (placeholder == null) {
432             throw new IllegalArgumentException JavaDoc("String placeholder is not supported for type" + nodeType); //$NON-NLS-1$
433
}
434         
435         getNodeStore().markAsStringPlaceholder(placeholder, code);
436         return placeholder;
437     }
438     
439     /**
440      * Creates and returns a node that represents a sequence of nodes.
441      * Each of the given nodes must be either be brand new (not part of the original AST), or
442      * a placeholder node (for example, one created by {@link #createCopyTarget(ASTNode)}
443      * or {@link #createStringPlaceholder(String, int)}), or another group node.
444      * The type of the returned node is unspecified. The returned node can be used
445      * to replace an existing node (or as an element of another group node).
446      * When the document is rewritten, the source code for each of the given nodes is
447      * inserted, in order, into the output document at the position corresponding to the
448      * group (indentation is adjusted).
449      *
450      * @param targetNodes the nodes to go in the group
451      * @return the new group node
452      * @throws IllegalArgumentException if the targetNodes is <code>null</code> or empty
453      * @since 3.1
454      */

455     public final ASTNode createGroupNode(ASTNode[] targetNodes) {
456         if (targetNodes == null || targetNodes.length == 0) {
457             throw new IllegalArgumentException JavaDoc();
458         }
459         Block res= getNodeStore().createCollapsePlaceholder();
460         ListRewrite listRewrite= getListRewrite(res, Block.STATEMENTS_PROPERTY);
461         for (int i= 0; i < targetNodes.length; i++) {
462             listRewrite.insertLast(targetNodes[i], null);
463         }
464         return res;
465     }
466     
467     
468     private ASTNode createTargetNode(ASTNode node, boolean isMove) {
469         if (node == null) {
470             throw new IllegalArgumentException JavaDoc();
471         }
472         validateIsInsideAST(node);
473         CopySourceInfo info= getRewriteEventStore().markAsCopySource(node.getParent(), node.getLocationInParent(), node, isMove);
474     
475         ASTNode placeholder= getNodeStore().newPlaceholderNode(node.getNodeType());
476         if (placeholder == null) {
477             throw new IllegalArgumentException JavaDoc("Creating a target node is not supported for nodes of type" + node.getClass().getName()); //$NON-NLS-1$
478
}
479         getNodeStore().markAsCopyTarget(placeholder, info);
480         
481         return placeholder;
482     }
483
484     /**
485      * Creates and returns a placeholder node for a true copy of the given node.
486      * The placeholder node can either be inserted as new or used to replace an
487      * existing node. When the document is rewritten, a copy of the source code
488      * for the given node is inserted into the output document at the position
489      * corresponding to the placeholder (indentation is adjusted).
490      *
491      * @param node the node to create a copy placeholder for
492      * @return the new placeholder node
493      * @throws IllegalArgumentException if the node is null, or if the node
494      * is not part of this rewriter's AST
495      */

496     public final ASTNode createCopyTarget(ASTNode node) {
497         return createTargetNode(node, false);
498     }
499     
500     /**
501      * Creates and returns a placeholder node for the new locations of the given node.
502      * After obtaining a placeholder, the node should then to be removed or replaced.
503      * The placeholder node can either be inserted as new or used to replace an
504      * existing node. When the document is rewritten, the source code for the given
505      * node is inserted into the output document at the position corresponding to the
506      * placeholder (indentation is adjusted).
507      *
508      * @param node the node to create a move placeholder for
509      * @return the new placeholder node
510      * @throws IllegalArgumentException if the node is null, or if the node
511      * is not part of this rewriter's AST
512      */

513     public final ASTNode createMoveTarget(ASTNode node) {
514         return createTargetNode(node, true);
515     }
516
517     /**
518      * Returns the extended source range computer for this AST rewriter.
519      * The default value is a <code>new ExtendedSourceRangeComputer()</code>.
520      *
521      * @return an extended source range computer
522      * @since 3.1
523      */

524     public final TargetSourceRangeComputer getExtendedSourceRangeComputer() {
525         if (this.targetSourceRangeComputer == null) {
526             // lazy initialize
527
this.targetSourceRangeComputer = new TargetSourceRangeComputer();
528         }
529         return this.targetSourceRangeComputer;
530     }
531     
532     /**
533      * Sets a custom target source range computer for this AST rewriter. This is advanced feature to modify how
534      * comments are assotiated with nodes, which should be done only in special cases.
535      *
536      * @param computer a target source range computer,
537      * or <code>null</code> to restore the default value of
538      * <code>new TargetSourceRangeComputer()</code>
539      * @since 3.1
540      */

541     public final void setTargetSourceRangeComputer(TargetSourceRangeComputer computer) {
542         // if computer==null, rely on lazy init code in getTargetSourceRangeComputer()
543
this.targetSourceRangeComputer = computer;
544     }
545     
546     /**
547      * Returns a string suitable for debugging purposes (only).
548      *
549      * @return a debug string
550      */

551     public String JavaDoc toString() {
552         StringBuffer JavaDoc buf= new StringBuffer JavaDoc();
553         buf.append("Events:\n"); //$NON-NLS-1$
554
// be extra careful of uninitialized or mangled instances
555
if (this.eventStore != null) {
556             buf.append(this.eventStore.toString());
557         }
558         return buf.toString();
559     }
560 }
561
Popular Tags