do cleanup before calling listeners on transactionCompleted()
[fedora-idea.git] / lang-impl / src / com / intellij / psi / impl / source / tree / ChangeUtil.java
blobc4fb2fdccb94f6ed7f9d0932d7da3b35f35f9be6
1 package com.intellij.psi.impl.source.tree;
3 import com.intellij.lang.ASTNode;
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.pom.PomManager;
6 import com.intellij.pom.PomModel;
7 import com.intellij.pom.event.PomModelEvent;
8 import com.intellij.pom.impl.PomTransactionBase;
9 import com.intellij.pom.tree.TreeAspect;
10 import com.intellij.pom.tree.events.ChangeInfo;
11 import com.intellij.pom.tree.events.TreeChangeEvent;
12 import com.intellij.pom.tree.events.impl.ChangeInfoImpl;
13 import com.intellij.pom.tree.events.impl.ReplaceChangeInfoImpl;
14 import com.intellij.pom.tree.events.impl.TreeChangeEventImpl;
15 import com.intellij.psi.PsiElement;
16 import com.intellij.psi.PsiFile;
17 import com.intellij.psi.PsiManager;
18 import com.intellij.psi.impl.PsiManagerEx;
19 import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl;
20 import com.intellij.psi.impl.source.DummyHolder;
21 import com.intellij.psi.impl.source.DummyHolderFactory;
22 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
23 import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
24 import com.intellij.psi.impl.source.parsing.ChameleonTransforming;
25 import com.intellij.util.CharTable;
26 import com.intellij.util.IncorrectOperationException;
27 import com.intellij.util.containers.HashMap;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.concurrent.CopyOnWriteArrayList;
35 public class ChangeUtil {
36 private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.ChangeUtil");
37 private static final List<TreeCopyHandler> ourCopyHandlers = new CopyOnWriteArrayList<TreeCopyHandler>();
38 private static final List<TreeGenerator> ourTreeGenerators = new CopyOnWriteArrayList<TreeGenerator>();
40 private ChangeUtil() { }
42 public static void registerCopyHandler(TreeCopyHandler handler) {
43 ourCopyHandlers.add(handler);
46 public static void registerTreeGenerator(TreeGenerator generator) {
47 ourTreeGenerators.add(generator);
50 public static void addChild(final CompositeElement parent, TreeElement child, final TreeElement anchorBefore) {
51 LOG.assertTrue(anchorBefore == null || anchorBefore.getTreeParent() == parent, "anchorBefore == null || anchorBefore.getTreeParent() == parent");
52 transformAll(parent.getFirstChildNode());
53 final TreeElement last = child.getTreeNext();
54 final TreeElement first = transformAll(child);
56 final CharTable newCharTab = SharedImplUtil.findCharTableByTree(parent);
57 final CharTable oldCharTab = SharedImplUtil.findCharTableByTree(child);
59 removeChildrenInner(first, last, oldCharTab);
61 if (newCharTab != oldCharTab) registerLeafsInCharTab(newCharTab, child, oldCharTab);
63 prepareAndRunChangeAction(new ChangeAction(){
64 public void makeChange(TreeChangeEvent destinationTreeChange) {
65 if (anchorBefore != null) {
66 insertBefore(destinationTreeChange, anchorBefore, first);
68 else {
69 add(destinationTreeChange, parent, first);
72 }, parent);
75 public static void removeChild(final CompositeElement parent, final TreeElement child) {
76 final CharTable charTableByTree = SharedImplUtil.findCharTableByTree(parent);
77 removeChildInner(child, charTableByTree);
80 public static void removeChildren(final CompositeElement parent, final TreeElement first, final TreeElement last) {
81 if(first == null) return;
82 final CharTable charTableByTree = SharedImplUtil.findCharTableByTree(parent);
83 removeChildrenInner(first, last, charTableByTree);
86 public static void replaceChild(final CompositeElement parent, @NotNull final TreeElement old, @NotNull final TreeElement newC) {
87 LOG.assertTrue(old.getTreeParent() == parent);
88 final TreeElement oldChild = transformAll(old);
89 final TreeElement newChildNext = newC.getTreeNext();
90 final TreeElement newChild = transformAll(newC);
92 if(oldChild == newChild) return;
93 final CharTable newCharTable = SharedImplUtil.findCharTableByTree(parent);
94 final CharTable oldCharTable = SharedImplUtil.findCharTableByTree(newChild);
96 removeChildrenInner(newChild, newChildNext, oldCharTable);
98 if (oldCharTable != newCharTable) registerLeafsInCharTab(newCharTable, newChild, oldCharTable);
100 prepareAndRunChangeAction(new ChangeAction(){
101 public void makeChange(TreeChangeEvent destinationTreeChange) {
102 replace(destinationTreeChange, oldChild, newChild);
103 repairRemovedElement(parent, newCharTable, oldChild);
105 }, parent);
108 public static void replaceAllChildren(final CompositeElement parent, final ASTNode newChildrenParent) {
109 transformAll(parent.getFirstChildNode());
110 transformAll((TreeElement)newChildrenParent.getFirstChildNode());
112 final CharTable newCharTab = SharedImplUtil.findCharTableByTree(parent);
113 final CharTable oldCharTab = SharedImplUtil.findCharTableByTree(newChildrenParent);
115 final ASTNode firstChild = newChildrenParent.getFirstChildNode();
116 prepareAndRunChangeAction(new ChangeAction(){
117 public void makeChange(TreeChangeEvent destinationTreeChange) {
118 destinationTreeChange.addElementaryChange(newChildrenParent, ChangeInfoImpl.create(ChangeInfo.CONTENTS_CHANGED, newChildrenParent));
119 TreeUtil.removeRange((TreeElement)newChildrenParent.getFirstChildNode(), null);
121 }, (TreeElement)newChildrenParent);
123 if (firstChild != null) {
124 registerLeafsInCharTab(newCharTab, firstChild, oldCharTab);
125 prepareAndRunChangeAction(new ChangeAction(){
126 public void makeChange(TreeChangeEvent destinationTreeChange) {
127 if(parent.getTreeParent() != null){
128 final ChangeInfoImpl changeInfo = ChangeInfoImpl.create(ChangeInfo.CONTENTS_CHANGED, parent);
129 changeInfo.setOldLength(parent.getTextLength());
130 destinationTreeChange.addElementaryChange(parent, changeInfo);
131 TreeUtil.removeRange(parent.getFirstChildNode(), null);
132 TreeUtil.addChildren(parent, (TreeElement)firstChild);
134 else{
135 final TreeElement first = parent.getFirstChildNode();
136 remove(destinationTreeChange, first, null);
137 add(destinationTreeChange, parent, (TreeElement)firstChild);
138 repairRemovedElement(parent, newCharTab, first);
141 }, parent);
143 else {
144 removeChildren(parent, parent.getFirstChildNode(), null);
148 private static TreeElement transformAll(TreeElement first){
149 ASTNode newFirst = null;
150 ASTNode child = first;
151 while (child != null) {
152 if (child instanceof ChameleonElement) {
153 ASTNode next = child.getTreeNext();
154 child = ChameleonTransforming.transform((ChameleonElement)child);
155 if (child == null) {
156 child = next;
158 continue;
160 if(newFirst == null) newFirst = child;
161 child = child.getTreeNext();
163 return (TreeElement)newFirst;
166 private static void repairRemovedElement(final CompositeElement oldParent, final CharTable newCharTable, final TreeElement oldChild) {
167 if(oldChild == null) return;
168 final FileElement treeElement = DummyHolderFactory.createHolder(oldParent.getManager(), newCharTable, false).getTreeElement();
169 TreeUtil.addChildren(treeElement, oldChild);
172 private static void add(final TreeChangeEvent destinationTreeChange,
173 final CompositeElement parent,
174 final TreeElement first) {
175 TreeUtil.addChildren(parent, first);
176 TreeElement child = first;
177 while(child != null){
178 destinationTreeChange.addElementaryChange(child, ChangeInfoImpl.create(ChangeInfo.ADD, child));
179 child = child.getTreeNext();
183 private static void remove(final TreeChangeEvent destinationTreeChange,
184 final TreeElement first,
185 final TreeElement last) {
186 TreeElement child = first;
187 while(child != last && child != null){
188 destinationTreeChange.addElementaryChange(child, ChangeInfoImpl.create(ChangeInfo.REMOVED, child));
189 child = child.getTreeNext();
191 TreeUtil.removeRange(first, last);
194 private static void insertBefore(final TreeChangeEvent destinationTreeChange,
195 final TreeElement anchorBefore,
196 final TreeElement first) {
197 TreeUtil.insertBefore(anchorBefore, first);
198 TreeElement child = first;
199 while(child != anchorBefore){
200 destinationTreeChange.addElementaryChange(child, ChangeInfoImpl.create(ChangeInfo.ADD, child));
201 child = child.getTreeNext();
205 private static void replace(final TreeChangeEvent sourceTreeChange,
206 final TreeElement oldChild,
207 final TreeElement newChild) {
208 TreeUtil.replaceWithList(oldChild, newChild);
209 final ReplaceChangeInfoImpl change = (ReplaceChangeInfoImpl)ChangeInfoImpl.create(ChangeInfo.REPLACE, newChild);
210 sourceTreeChange.addElementaryChange(newChild, change);
211 change.setReplaced(oldChild);
214 private static void registerLeafsInCharTab(CharTable newCharTab, ASTNode child, CharTable oldCharTab) {
215 if (newCharTab == oldCharTab) return;
216 while (child != null) {
217 CharTable charTable = child.getUserData(CharTable.CHAR_TABLE_KEY);
218 if (child instanceof LeafElement) {
219 ((LeafElement)child).registerInCharTable(newCharTab);
220 ((LeafElement)child).registerInCharTable(newCharTab);
221 ((LeafElement)child).registerInCharTable(newCharTab);
223 else {
224 registerLeafsInCharTab(newCharTab, child.getFirstChildNode(), charTable != null ? charTable : oldCharTab);
226 if (charTable != null) {
227 child.putUserData(CharTable.CHAR_TABLE_KEY, null);
229 child = child.getTreeNext();
233 private static void removeChildInner(final TreeElement child, final CharTable oldCharTab) {
234 removeChildrenInner(child, child.getTreeNext(), oldCharTab);
237 private static void removeChildrenInner(final TreeElement first, final TreeElement last, final CharTable oldCharTab) {
238 final FileElement fileElement = TreeUtil.getFileElement(first);
239 if (fileElement != null) {
240 prepareAndRunChangeAction(new ChangeAction() {
241 public void makeChange(TreeChangeEvent destinationTreeChange) {
242 remove(destinationTreeChange, first, last);
243 repairRemovedElement(fileElement, oldCharTab, first);
245 }, first.getTreeParent());
247 else {
248 TreeUtil.removeRange(first, last);
252 public static void changeElementInPlace(final ASTNode element, final ChangeAction action){
253 prepareAndRunChangeAction(new ChangeAction() {
254 public void makeChange(TreeChangeEvent destinationTreeChange) {
255 destinationTreeChange.addElementaryChange(element, ChangeInfoImpl.create(ChangeInfo.CONTENTS_CHANGED, element));
256 action.makeChange(destinationTreeChange);
257 ASTNode node = element;
258 while (node != null) {
259 ASTNode parent = node.getTreeParent();
260 ((TreeElement) node).clearCaches();
261 node = parent;
264 }, (TreeElement) element);
267 public interface ChangeAction{
268 void makeChange(TreeChangeEvent destinationTreeChange);
271 private static void prepareAndRunChangeAction(final ChangeAction action, final TreeElement changedElement){
272 final FileElement changedFile = TreeUtil.getFileElement(changedElement);
273 final PsiManager manager = changedFile.getManager();
274 final PomModel model = PomManager.getModel(manager.getProject());
275 try{
276 final TreeAspect treeAspect = model.getModelAspect(TreeAspect.class);
277 model.runTransaction(new PomTransactionBase(changedElement.getPsi(), treeAspect) {
278 public PomModelEvent runInner() {
279 final PomModelEvent event = new PomModelEvent(model);
280 final TreeChangeEvent destinationTreeChange = new TreeChangeEventImpl(treeAspect, changedFile);
281 event.registerChangeSet(treeAspect, destinationTreeChange);
282 final PsiManagerEx psiManager = (PsiManagerEx) manager;
283 final PsiFile file = (PsiFile)changedFile.getPsi();
285 if (file.isPhysical()) {
286 SmartPointerManagerImpl.fastenBelts(file);
289 action.makeChange(destinationTreeChange);
291 psiManager.invalidateFile(file);
292 TreeUtil.clearCaches(changedElement);
293 if (changedElement instanceof CompositeElement) {
294 ((CompositeElement) changedElement).subtreeChanged();
296 return event;
300 catch(IncorrectOperationException ioe){
301 LOG.error(ioe);
305 public static void encodeInformation(TreeElement element) {
306 encodeInformation(element, element);
309 private static void encodeInformation(TreeElement element, ASTNode original) {
310 encodeInformation(element, original, new HashMap<Object, Object>());
313 private static void encodeInformation(TreeElement element, ASTNode original, Map<Object, Object> state) {
314 for (TreeCopyHandler handler : ourCopyHandlers) {
315 handler.encodeInformation(element, original, state);
318 if (original instanceof CompositeElement) {
319 ChameleonTransforming.transformChildren(element);
320 ChameleonTransforming.transformChildren(original);
321 TreeElement child = element.getFirstChildNode();
322 ASTNode child1 = original.getFirstChildNode();
323 while (child != null) {
324 encodeInformation(child, child1, state);
325 child = child.getTreeNext();
326 child1 = child1.getTreeNext();
331 public static TreeElement decodeInformation(TreeElement element) {
332 return decodeInformation(element, new HashMap<Object, Object>());
335 private static TreeElement decodeInformation(TreeElement element, Map<Object, Object> state) {
336 if (element instanceof CompositeElement) {
337 ChameleonTransforming.transformChildren(element);
338 TreeElement child = element.getFirstChildNode();
339 while (child != null) {
340 child = decodeInformation(child, state);
341 child = child.getTreeNext();
346 for (TreeCopyHandler handler : ourCopyHandlers) {
347 final TreeElement handled = handler.decodeInformation(element, state);
348 if (handled != null) return handled;
351 return element;
354 public static TreeElement copyElement(TreeElement original, CharTable table) {
355 final TreeElement element = (TreeElement)original.clone();
356 final PsiManager manager = original.getManager();
357 final CharTable charTableByTree = SharedImplUtil.findCharTableByTree(original);
358 registerLeafsInCharTab(table, element, charTableByTree);
359 CompositeElement treeParent = original.getTreeParent();
360 DummyHolderFactory.createHolder(manager, element, treeParent == null ? null : treeParent.getPsi(), table).getTreeElement();
361 encodeInformation(element, original);
362 TreeUtil.clearCaches(element);
363 saveIndentationToCopy(original, element);
364 return element;
367 private static void saveIndentationToCopy(final TreeElement original, final TreeElement element) {
368 if(original == null || element == null || CodeEditUtil.isNodeGenerated(original)) return;
369 final int indentation = CodeEditUtil.getOldIndentation(original);
370 if(indentation < 0) CodeEditUtil.saveWhitespacesInfo(original);
371 CodeEditUtil.setOldIndentation(element, CodeEditUtil.getOldIndentation(original));
372 if(indentation < 0) CodeEditUtil.setOldIndentation(original, -1);
375 public static TreeElement copyToElement(PsiElement original) {
376 final DummyHolder holder = DummyHolderFactory.createHolder(original.getManager(), null, original.getLanguage());
377 final FileElement holderElement = holder.getTreeElement();
378 final TreeElement treeElement = generateTreeElement(original, holderElement.getCharTable(), original.getManager());
379 // TreeElement treePrev = treeElement.getTreePrev(); // This is hack to support bug used in formater
380 TreeUtil.addChildren(holderElement, treeElement);
381 TreeUtil.clearCaches(holderElement);
382 // treeElement.setTreePrev(treePrev);
383 saveIndentationToCopy((TreeElement)original.getNode(), treeElement);
384 return treeElement;
387 @Nullable
388 public static TreeElement generateTreeElement(PsiElement original, CharTable table, final PsiManager manager) {
389 LOG.assertTrue(original.isValid());
390 if (SourceTreeToPsiMap.hasTreeElement(original)) {
391 return copyElement((TreeElement)SourceTreeToPsiMap.psiElementToTree(original), table);
393 else {
394 for (TreeGenerator generator : ourTreeGenerators) {
395 final TreeElement element = generator.generateTreeFor(original, table, manager);
396 if (element != null) return element;
398 return null;
402 public static void addChildren(final ASTNode parent,
403 ASTNode firstChild,
404 final ASTNode lastChild,
405 final ASTNode anchorBefore) {
406 while (firstChild != lastChild) {
407 final ASTNode next = firstChild.getTreeNext();
408 parent.addChild(firstChild, anchorBefore);
409 firstChild = next;