1 | /******************************************************************************* |
---|---|
2 | * Copyright (c) 2004, 2013 IBM Corporation and others. |
3 | * |
4 | * This program and the accompanying materials |
5 | * are made available under the terms of the Eclipse Public License 2.0 |
6 | * which accompanies this distribution, and is available at |
7 | * https://www.eclipse.org/legal/epl-2.0/ |
8 | * |
9 | * SPDX-License-Identifier: EPL-2.0 |
10 | * |
11 | * Contributors: |
12 | * IBM Corporation - initial API and implementation |
13 | *******************************************************************************/ |
14 | package org.eclipse.jdt.core.dom; |
15 | import java.util.Hashtable; |
16 | import java.util.List; |
17 | import java.util.Map; |
18 | |
19 | import org.eclipse.text.edits.MultiTextEdit; |
20 | import org.eclipse.text.edits.TextEdit; |
21 | |
22 | import org.eclipse.jface.text.IDocument; |
23 | import org.eclipse.jface.text.TextUtilities; |
24 | |
25 | import org.eclipse.jdt.core.JavaCore; |
26 | import org.eclipse.jdt.core.dom.rewrite.TargetSourceRangeComputer; |
27 | import org.eclipse.jdt.internal.compiler.parser.RecoveryScannerData; |
28 | import org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer; |
29 | import org.eclipse.jdt.internal.core.dom.rewrite.LineInformation; |
30 | import org.eclipse.jdt.internal.core.dom.rewrite.ListRewriteEvent; |
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.RewriteEventStore.CopySourceInfo; |
35 | import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEventStore.PropertyLocation; |
36 | |
37 | /** |
38 | * Internal class: not intended to be used by client. |
39 | * When AST modifications recording is enabled, all changes are recorded by this class. |
40 | */ |
41 | @SuppressWarnings({"rawtypes", "unchecked"}) |
42 | class InternalASTRewrite extends NodeEventHandler { |
43 | |
44 | /** root node for the rewrite: Only nodes under this root are accepted */ |
45 | private CompilationUnit root; |
46 | |
47 | protected final RewriteEventStore eventStore; |
48 | protected final NodeInfoStore nodeStore; |
49 | /** ASTNode clone -> ASTNode original */ |
50 | protected final Hashtable clonedNodes; |
51 | |
52 | int cloneDepth = 0; |
53 | |
54 | /** |
55 | * Constructor |
56 | * @param root root node of the recorded ast. |
57 | */ |
58 | public InternalASTRewrite(CompilationUnit root) { |
59 | this.root = root; |
60 | this.eventStore = new RewriteEventStore(); |
61 | this.nodeStore = new NodeInfoStore(root.getAST()); |
62 | this.clonedNodes = new Hashtable(); |
63 | } |
64 | |
65 | /** |
66 | * Performs the rewrite: The rewrite events are translated to the corresponding in text changes. |
67 | * The given options can be null in which case the global options {@link JavaCore#getOptions() JavaCore.getOptions()} |
68 | * will be used. |
69 | * |
70 | * @param document Document which describes the code of the AST that is passed in in the |
71 | * constructor. This document is accessed read-only. |
72 | * @param options the given options |
73 | * @throws IllegalArgumentException if the rewrite fails |
74 | * @return Returns the edit describing the text changes. |
75 | */ |
76 | public TextEdit rewriteAST(IDocument document, Map options) { |
77 | TextEdit result = new MultiTextEdit(); |
78 | |
79 | final CompilationUnit rootNode = getRootNode(); |
80 | if (rootNode != null) { |
81 | TargetSourceRangeComputer xsrComputer = new TargetSourceRangeComputer() { |
82 | /** |
83 | * This implementation of |
84 | * {@link TargetSourceRangeComputer#computeSourceRange(ASTNode)} |
85 | * is specialized to work in the case of internal AST rewriting, where the |
86 | * original AST has been modified from its original form. This means that |
87 | * one cannot trust that the root of the given node is the compilation unit. |
88 | */ |
89 | @Override |
90 | public SourceRange computeSourceRange(ASTNode node) { |
91 | int extendedStartPosition = rootNode.getExtendedStartPosition(node); |
92 | int extendedLength = rootNode.getExtendedLength(node); |
93 | return new SourceRange(extendedStartPosition, extendedLength); |
94 | } |
95 | }; |
96 | char[] content= document.get().toCharArray(); |
97 | LineInformation lineInfo= LineInformation.create(document); |
98 | String lineDelim= TextUtilities.getDefaultLineDelimiter(document); |
99 | List comments= rootNode.getCommentList(); |
100 | |
101 | Map currentOptions = options == null ? JavaCore.getOptions() : options; |
102 | ASTRewriteAnalyzer visitor = new ASTRewriteAnalyzer(content, lineInfo, lineDelim, result, this.eventStore, this.nodeStore, comments, currentOptions, xsrComputer, (RecoveryScannerData)rootNode.getStatementsRecoveryData()); |
103 | rootNode.accept(visitor); |
104 | } |
105 | return result; |
106 | } |
107 | |
108 | private void markAsMoveOrCopyTarget(ASTNode node, ASTNode newChild) { |
109 | if (this.cloneDepth == 0) { |
110 | while (node != null && this.clonedNodes.containsKey(node)) { |
111 | /* |
112 | * A modified node cannot be considered as cloned any more. |
113 | * we can't copy the original formatting/comments and at the same time modify the node. |
114 | * |
115 | * Workaround for https://bugs.eclipse.org/405699 is to remove such nodes from clonedNodes |
116 | * and instead mark all children as cloned (or skip them if they are not in clonedNodes). |
117 | */ |
118 | ASTNode orig = (ASTNode) this.clonedNodes.remove(node); |
119 | if (orig != null) { |
120 | List properties = node.structuralPropertiesForType(); |
121 | for (int i= 0; i < properties.size(); i++) { |
122 | StructuralPropertyDescriptor property = (StructuralPropertyDescriptor) properties.get(i); |
123 | Object child = node.getStructuralProperty(property); |
124 | if (child instanceof ASTNode) { |
125 | markAsMoveOrCopyTarget(node, (ASTNode) child); |
126 | } else if (child instanceof List) { |
127 | List children = (List) child; |
128 | for (int j= 0; j < children.size(); j++) { |
129 | ASTNode clonedChild = (ASTNode) children.get(j); |
130 | markAsMoveOrCopyTarget(node, clonedChild); |
131 | } |
132 | } |
133 | } |
134 | } |
135 | |
136 | node = node.getParent(); |
137 | } |
138 | } |
139 | |
140 | ASTNode source = (ASTNode)this.clonedNodes.get(newChild); |
141 | if(source != null) { |
142 | if(this.cloneDepth == 0) { |
143 | PropertyLocation propertyLocation = this.eventStore.getPropertyLocation(source, RewriteEventStore.ORIGINAL); |
144 | CopySourceInfo sourceInfo = |
145 | this.eventStore.markAsCopySource( |
146 | propertyLocation.getParent(), |
147 | propertyLocation.getProperty(), |
148 | source, |
149 | false); |
150 | this.nodeStore.markAsCopyTarget(newChild, sourceInfo); |
151 | } |
152 | } else if((newChild.getFlags() & ASTNode.ORIGINAL) != 0) { |
153 | PropertyLocation propertyLocation = this.eventStore.getPropertyLocation(newChild, RewriteEventStore.ORIGINAL); |
154 | CopySourceInfo sourceInfo = |
155 | this.eventStore.markAsCopySource( |
156 | propertyLocation.getParent(), |
157 | propertyLocation.getProperty(), |
158 | newChild, |
159 | true); |
160 | this.nodeStore.markAsCopyTarget(newChild, sourceInfo); |
161 | } |
162 | } |
163 | |
164 | private CompilationUnit getRootNode() { |
165 | return this.root; |
166 | } |
167 | |
168 | @Override |
169 | public String toString() { |
170 | StringBuilder buf = new StringBuilder(); |
171 | buf.append("Events:\n"); //$NON-NLS-1$ |
172 | buf.append(this.eventStore.toString()); |
173 | return buf.toString(); |
174 | } |
175 | |
176 | @Override |
177 | void preValueChangeEvent(ASTNode node, SimplePropertyDescriptor property) { |
178 | // force event creation |
179 | getNodeEvent(node, property); |
180 | } |
181 | |
182 | @Override |
183 | void postValueChangeEvent(ASTNode node, SimplePropertyDescriptor property) { |
184 | NodeRewriteEvent event = getNodeEvent(node, property); |
185 | event.setNewValue(node.getStructuralProperty(property)); |
186 | } |
187 | |
188 | @Override |
189 | void preAddChildEvent(ASTNode node, ASTNode child, StructuralPropertyDescriptor property) { |
190 | if(property.isChildProperty()) { |
191 | NodeRewriteEvent event = getNodeEvent(node, property); |
192 | event.setNewValue(child); |
193 | if(child != null) { |
194 | markAsMoveOrCopyTarget(node, child); |
195 | } |
196 | } else if(property.isChildListProperty()) { |
197 | // force event creation |
198 | getListEvent(node, property); |
199 | } |
200 | } |
201 | |
202 | @Override |
203 | void postAddChildEvent(ASTNode node, ASTNode child, StructuralPropertyDescriptor property) { |
204 | if(property.isChildListProperty()) { |
205 | |
206 | ListRewriteEvent event = getListEvent(node, property); |
207 | List list = (List)node.getStructuralProperty(property); |
208 | int i = list.indexOf(child); |
209 | int s = list.size(); |
210 | int index; |
211 | if(i + 1 < s) { |
212 | ASTNode nextNode = (ASTNode)list.get(i + 1); |
213 | index = event.getIndex(nextNode, ListRewriteEvent.NEW); |
214 | } else { |
215 | index = -1; |
216 | } |
217 | event.insert(child, index); |
218 | if(child != null) { |
219 | markAsMoveOrCopyTarget(node, child); |
220 | } |
221 | } |
222 | } |
223 | |
224 | @Override |
225 | void preRemoveChildEvent(ASTNode node, ASTNode child, StructuralPropertyDescriptor property) { |
226 | if(property.isChildProperty()) { |
227 | NodeRewriteEvent event = getNodeEvent(node, property); |
228 | event.setNewValue(null); |
229 | } else if(property.isChildListProperty()) { |
230 | ListRewriteEvent event = getListEvent(node, property); |
231 | int i = event.getIndex(child, ListRewriteEvent.NEW); |
232 | NodeRewriteEvent nodeEvent = (NodeRewriteEvent)event.getChildren()[i]; |
233 | if(nodeEvent.getOriginalValue() == null) { |
234 | event.revertChange(nodeEvent); |
235 | } else { |
236 | nodeEvent.setNewValue(null); |
237 | } |
238 | } |
239 | } |
240 | |
241 | @Override |
242 | void preReplaceChildEvent(ASTNode node, ASTNode child, ASTNode newChild, StructuralPropertyDescriptor property) { |
243 | if(property.isChildProperty()) { |
244 | NodeRewriteEvent event = getNodeEvent(node, property); |
245 | event.setNewValue(newChild); |
246 | if(newChild != null) { |
247 | markAsMoveOrCopyTarget(node, newChild); |
248 | } |
249 | } else if(property.isChildListProperty()) { |
250 | ListRewriteEvent event = getListEvent(node, property); |
251 | int i = event.getIndex(child, ListRewriteEvent.NEW); |
252 | NodeRewriteEvent nodeEvent = (NodeRewriteEvent)event.getChildren()[i]; |
253 | nodeEvent.setNewValue(newChild); |
254 | if(newChild != null) { |
255 | markAsMoveOrCopyTarget(node, newChild); |
256 | } |
257 | } |
258 | } |
259 | |
260 | |
261 | @Override |
262 | void preCloneNodeEvent(ASTNode node) { |
263 | this.cloneDepth++; |
264 | } |
265 | |
266 | |
267 | @Override |
268 | void postCloneNodeEvent(ASTNode node, ASTNode clone) { |
269 | if(node.ast == this.root.ast && clone.ast == this.root.ast) { |
270 | if((node.getFlags() & ASTNode.ORIGINAL) != 0) { |
271 | this.clonedNodes.put(clone, node); |
272 | } else { |
273 | // node can be a cloned node |
274 | Object original = this.clonedNodes.get(node); |
275 | if(original != null) { |
276 | this.clonedNodes.put(clone, original); |
277 | } |
278 | } |
279 | } |
280 | this.cloneDepth--; |
281 | } |
282 | |
283 | private NodeRewriteEvent getNodeEvent(ASTNode node, StructuralPropertyDescriptor property) { |
284 | return this.eventStore.getNodeEvent(node, property, true); |
285 | } |
286 | |
287 | private ListRewriteEvent getListEvent(ASTNode node, StructuralPropertyDescriptor property) { |
288 | return this.eventStore.getListEvent(node, property, true); |
289 | } |
290 | } |
291 |
Members