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
;
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
);
69 add(destinationTreeChange
, parent
, first
);
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
);
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
);
135 final TreeElement first
= parent
.getFirstChildNode();
136 remove(destinationTreeChange
, first
, null);
137 add(destinationTreeChange
, parent
, (TreeElement
)firstChild
);
138 repairRemovedElement(parent
, newCharTab
, first
);
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
);
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
);
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());
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();
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());
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();
300 catch(IncorrectOperationException 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
;
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
);
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
);
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
);
394 for (TreeGenerator generator
: ourTreeGenerators
) {
395 final TreeElement element
= generator
.generateTreeFor(original
, table
, manager
);
396 if (element
!= null) return element
;
402 public static void addChildren(final ASTNode parent
,
404 final ASTNode lastChild
,
405 final ASTNode anchorBefore
) {
406 while (firstChild
!= lastChild
) {
407 final ASTNode next
= firstChild
.getTreeNext();
408 parent
.addChild(firstChild
, anchorBefore
);