2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com
.intellij
.psi
.impl
.source
.tree
;
19 import com
.intellij
.extapi
.psi
.ASTDelegatePsiElement
;
20 import com
.intellij
.lang
.*;
21 import com
.intellij
.openapi
.application
.ApplicationManager
;
22 import com
.intellij
.openapi
.diagnostic
.Logger
;
23 import com
.intellij
.pom
.tree
.events
.ChangeInfo
;
24 import com
.intellij
.pom
.tree
.events
.TreeChangeEvent
;
25 import com
.intellij
.pom
.tree
.events
.impl
.ChangeInfoImpl
;
26 import com
.intellij
.pom
.tree
.events
.impl
.ReplaceChangeInfoImpl
;
27 import com
.intellij
.psi
.PsiElement
;
28 import com
.intellij
.psi
.PsiFile
;
29 import com
.intellij
.psi
.PsiLock
;
30 import com
.intellij
.psi
.impl
.DebugUtil
;
31 import com
.intellij
.psi
.impl
.source
.DummyHolder
;
32 import com
.intellij
.psi
.impl
.source
.DummyHolderFactory
;
33 import com
.intellij
.psi
.impl
.source
.PsiElementArrayConstructor
;
34 import com
.intellij
.psi
.impl
.source
.SourceTreeToPsiMap
;
35 import com
.intellij
.psi
.impl
.source
.codeStyle
.CodeEditUtil
;
36 import com
.intellij
.psi
.tree
.IElementType
;
37 import com
.intellij
.psi
.tree
.TokenSet
;
38 import com
.intellij
.util
.text
.CharArrayCharSequence
;
39 import org
.jetbrains
.annotations
.NotNull
;
40 import org
.jetbrains
.annotations
.Nullable
;
42 public class CompositeElement
extends TreeElement
{
43 private static final Logger LOG
= Logger
.getInstance("#com.intellij.psi.impl.source.tree.CompositeElement");
45 private TreeElement firstChild
= null;
46 private TreeElement lastChild
= null;
48 private volatile int myModificationsCount
= 0;
49 private static final int NOT_CACHED
= -239;
50 private volatile int myCachedLength
= NOT_CACHED
;
51 private volatile int myHC
= -1;
52 private volatile PsiElement myWrapper
= null;
54 public CompositeElement(@NotNull IElementType type
) {
58 public int getModificationCount() {
59 return myModificationsCount
;
62 public Object
clone() {
63 CompositeElement clone
= (CompositeElement
)super.clone();
66 clone
.firstChild
= null;
67 clone
.lastChild
= null;
68 clone
.myModificationsCount
= 0;
69 clone
.myWrapper
= null;
70 for (ASTNode child
= rawFirstChild(); child
!= null; child
= child
.getTreeNext()) {
71 clone
.rawAddChildren((TreeElement
)child
.clone());
76 public void subtreeChanged() {
78 if (!(this instanceof PsiElement
)) {
79 final PsiElement psi
= getPsi();
80 if (psi
instanceof ASTDelegatePsiElement
) {
81 ((ASTDelegatePsiElement
)psi
).subtreeChanged();
83 else if (psi
instanceof PsiFile
) {
84 ((PsiFile
)psi
).subtreeChanged();
88 CompositeElement treeParent
= getTreeParent();
89 if (treeParent
!= null) treeParent
.subtreeChanged();
92 public void clearCaches() {
94 myCachedLength
= NOT_CACHED
;
96 myModificationsCount
++;
99 clearRelativeOffsets(rawFirstChild());
102 public void acceptTree(TreeElementVisitor visitor
) {
103 visitor
.visitComposite(this);
106 public LeafElement
findLeafElementAt(int offset
) {
107 TreeElement child
= getFirstChildNode();
108 while (child
!= null) {
109 final int textLength
= child
.getTextLength();
110 if (textLength
> offset
) {
111 if (child
instanceof ForeignLeafPsiElement
) {
114 return child
.findLeafElementAt(offset
);
116 offset
-= textLength
;
117 child
= child
.getTreeNext();
122 public ASTNode
findChildByType(IElementType type
) {
123 if (DebugUtil
.CHECK_INSIDE_ATOMIC_ACTION_ENABLED
){
124 ApplicationManager
.getApplication().assertReadAccessAllowed();
127 for(ASTNode element
= getFirstChildNode(); element
!= null; element
= element
.getTreeNext()){
128 if (element
.getElementType() == type
) return element
;
134 public ASTNode
findChildByType(@NotNull TokenSet types
) {
135 if (DebugUtil
.CHECK_INSIDE_ATOMIC_ACTION_ENABLED
){
136 ApplicationManager
.getApplication().assertReadAccessAllowed();
138 for(ASTNode element
= getFirstChildNode(); element
!= null; element
= element
.getTreeNext()){
139 if (types
.contains(element
.getElementType())) return element
;
145 public ASTNode
findChildByType(@NotNull TokenSet typesSet
, ASTNode anchor
) {
146 ASTNode child
= anchor
;
148 if (child
== null) return null;
149 if (typesSet
.contains(child
.getElementType())) return child
;
150 child
= child
.getTreeNext();
155 public String
getText() {
156 char[] buffer
= new char[getTextLength()];
157 AstBufferUtil
.toBuffer(this, buffer
, 0);
158 return new String(buffer
);
161 public CharSequence
getChars() {
162 char[] buffer
= new char[getTextLength()];
163 AstBufferUtil
.toBuffer(this, buffer
, 0);
164 return new CharArrayCharSequence(buffer
);
167 public int getNotCachedLength() {
169 TreeElement child
= getFirstChildNode();
170 while(child
!= null){
171 length
+= child
.getNotCachedLength();
172 child
= child
.getTreeNext();
178 public char[] textToCharArray() {
179 char[] buffer
= new char[getTextLength()];
180 AstBufferUtil
.toBuffer(this, buffer
, 0);
184 public boolean textContains(char c
) {
185 for (ASTNode child
= getFirstChildNode(); child
!= null; child
= child
.getTreeNext()) {
186 if (child
.textContains(c
)) return true;
191 protected int textMatches(CharSequence buffer
, int start
) {
192 int curOffset
= start
;
193 for (TreeElement child
= getFirstChildNode(); child
!= null; child
= child
.getTreeNext()) {
194 curOffset
= child
.textMatches(buffer
, curOffset
);
195 if (curOffset
== -1) return -1;
201 public final PsiElement
findChildByRoleAsPsiElement(int role
) {
202 ASTNode element
= findChildByRole(role
);
203 if (element
== null) return null;
204 return SourceTreeToPsiMap
.treeElementToPsi(element
);
208 public ASTNode
findChildByRole(int role
) {
209 // assert ChildRole.isUnique(role);
210 for (ASTNode child
= getFirstChildNode(); child
!= null; child
= child
.getTreeNext()) {
211 if (getChildRole(child
) == role
) return child
;
216 public int getChildRole(ASTNode child
) {
217 assert child
.getTreeParent() == this;
218 return 0; //ChildRole.NONE;
221 protected final int getChildRole(ASTNode child
, int roleCandidate
) {
222 if (findChildByRole(roleCandidate
) == child
) {
223 return roleCandidate
;
226 return 0; //ChildRole.NONE;
230 public ASTNode
[] getChildren(TokenSet filter
) {
231 int count
= countChildren(filter
);
235 final ASTNode
[] result
= new ASTNode
[count
];
237 for (ASTNode child
= getFirstChildNode(); child
!= null; child
= child
.getTreeNext()) {
238 if (filter
== null || filter
.contains(child
.getElementType())) {
239 result
[count
++] = child
;
247 public <T
extends PsiElement
> T
[] getChildrenAsPsiElements(TokenSet filter
, PsiElementArrayConstructor
<T
> constructor
) {
248 ApplicationManager
.getApplication().assertReadAccessAllowed();
249 int count
= countChildren(filter
);
250 T
[] result
= constructor
.newPsiElementArray(count
);
255 for (ASTNode child
= getFirstChildNode(); child
!= null && idx
< count
; child
= child
.getTreeNext()) {
256 if (filter
== null || filter
.contains(child
.getElementType())) {
257 T element
= (T
)child
.getPsi();
258 assert element
!= null;
259 result
[idx
++] = element
;
265 public int countChildren(TokenSet filter
) {
266 // no lock is needed because all chameleons are expanded already
268 for (ASTNode child
= getFirstChildNode(); child
!= null; child
= child
.getTreeNext()) {
269 if (filter
== null || filter
.contains(child
.getElementType())) {
278 * @return First element that was appended (for example whitespaces could be skipped)
280 public TreeElement
addInternal(TreeElement first
, ASTNode last
, ASTNode anchor
, Boolean before
) {
281 ASTNode anchorBefore
;
282 if (anchor
!= null) {
283 anchorBefore
= before
.booleanValue() ? anchor
: anchor
.getTreeNext();
286 anchorBefore
= before
== null || before
.booleanValue() ?
null : getFirstChildNode();
288 return (TreeElement
)CodeEditUtil
.addChildren(this, first
, last
, anchorBefore
);
291 public void deleteChildInternal(@NotNull ASTNode child
) {
292 CodeEditUtil
.removeChild(this, child
);
295 public void replaceChildInternal(@NotNull ASTNode child
, @NotNull TreeElement newElement
) {
296 CodeEditUtil
.replaceChild(this, child
, newElement
);
299 public int getTextLength() {
300 int cachedLength
= myCachedLength
;
301 if (cachedLength
>= 0) return cachedLength
;
303 synchronized (START_OFFSET_LOCK
) {
305 return myCachedLength
;
313 TreeElement child
= firstChild
;
314 while (child
!= null) {
316 child
= child
.getTreeNext();
323 public int getCachedLength() {
324 return myCachedLength
;
327 private void walkCachingLength() {
328 TreeElement cur
= this;
330 while (cur
!= null) {
331 cur
= next(cur
, cur
.getCachedLength() == NOT_CACHED
);
334 assert myCachedLength
>= 0;
338 private TreeElement
next(TreeElement cur
, boolean down
) {
340 CompositeElement composite
= (CompositeElement
)cur
; // It's a composite or we won't be going down
341 TreeElement child
= composite
.firstChild
;
343 LOG
.assertTrue(child
.getTreeParent() == composite
, cur
);
347 composite
.myCachedLength
= 0;
351 while (cur
!= this) {
352 CompositeElement parent
= cur
.getTreeParent();
353 parent
.myCachedLength
-= cur
.getCachedLength();
355 TreeElement next
= cur
.getTreeNext();
357 LOG
.assertTrue(next
.getTreePrev() == cur
, cur
);
361 parent
.myCachedLength
= -parent
.myCachedLength
+ NOT_CACHED
;
370 public TreeElement
getFirstChildNode() {
374 public TreeElement
getLastChildNode() {
378 public void setFirstChildNode(TreeElement firstChild
) {
379 this.firstChild
= firstChild
;
382 public void setLastChildNode(TreeElement lastChild
) {
383 this.lastChild
= lastChild
;
386 public void addChild(@NotNull ASTNode child
, final ASTNode anchorBefore
) {
387 LOG
.assertTrue(anchorBefore
== null || ((TreeElement
)anchorBefore
).getTreeParent() == this, "anchorBefore == null || anchorBefore.getTreeParent() == parent");
388 TreeUtil
.ensureParsed(getFirstChildNode());
389 TreeUtil
.ensureParsed(child
);
390 final TreeElement last
= ((TreeElement
)child
).getTreeNext();
391 final TreeElement first
= (TreeElement
)child
;
393 removeChildrenInner(first
, last
);
395 ChangeUtil
.prepareAndRunChangeAction(new ChangeUtil
.ChangeAction(){
396 public void makeChange(TreeChangeEvent destinationTreeChange
) {
397 if (anchorBefore
!= null) {
398 insertBefore(destinationTreeChange
, (TreeElement
)anchorBefore
, first
);
401 add(destinationTreeChange
, CompositeElement
.this, first
);
407 public void addLeaf(@NotNull final IElementType leafType
, final CharSequence leafText
, final ASTNode anchorBefore
) {
408 FileElement holder
= new DummyHolder(getManager(), null).getTreeElement();
409 final LeafElement leaf
= ASTFactory
.leaf(leafType
, holder
.getCharTable().intern(leafText
));
410 CodeEditUtil
.setNodeGenerated(leaf
, true);
411 holder
.rawAddChildren(leaf
);
413 addChild(leaf
, anchorBefore
);
416 public void addChild(@NotNull ASTNode child
) {
417 addChild(child
, null);
420 public void removeChild(@NotNull ASTNode child
) {
421 removeChildInner((TreeElement
)child
);
424 public void removeRange(@NotNull ASTNode first
, ASTNode firstWhichStayInTree
) {
425 removeChildrenInner((TreeElement
)first
, (TreeElement
)firstWhichStayInTree
);
428 public void replaceChild(@NotNull ASTNode oldChild
, @NotNull ASTNode newChild
) {
429 LOG
.assertTrue(((TreeElement
)oldChild
).getTreeParent() == this);
430 final TreeElement oldChild1
= (TreeElement
)oldChild
;
431 final TreeElement newChildNext
= ((TreeElement
)newChild
).getTreeNext();
432 final TreeElement newChild1
= (TreeElement
)newChild
;
434 if(oldChild1
== newChild1
) return;
436 removeChildrenInner(newChild1
, newChildNext
);
438 ChangeUtil
.prepareAndRunChangeAction(new ChangeUtil
.ChangeAction(){
439 public void makeChange(TreeChangeEvent destinationTreeChange
) {
440 replace(destinationTreeChange
, oldChild1
, newChild1
);
441 repairRemovedElement(CompositeElement
.this, oldChild1
);
446 public void replaceAllChildrenToChildrenOf(final ASTNode anotherParent
) {
447 TreeUtil
.ensureParsed(getFirstChildNode());
448 TreeUtil
.ensureParsed(anotherParent
.getFirstChildNode());
449 final ASTNode firstChild1
= anotherParent
.getFirstChildNode();
450 ChangeUtil
.prepareAndRunChangeAction(new ChangeUtil
.ChangeAction(){
451 public void makeChange(TreeChangeEvent destinationTreeChange
) {
452 destinationTreeChange
.addElementaryChange(anotherParent
, ChangeInfoImpl
.create(ChangeInfo
.CONTENTS_CHANGED
, anotherParent
));
453 ((CompositeElement
)anotherParent
).rawRemoveAllChildren();
455 }, (TreeElement
)anotherParent
);
457 if (firstChild1
!= null) {
458 ChangeUtil
.prepareAndRunChangeAction(new ChangeUtil
.ChangeAction(){
459 public void makeChange(TreeChangeEvent destinationTreeChange
) {
460 if(getTreeParent() != null){
461 final ChangeInfoImpl changeInfo
= ChangeInfoImpl
.create(ChangeInfo
.CONTENTS_CHANGED
, CompositeElement
.this);
462 changeInfo
.setOldLength(getTextLength());
463 destinationTreeChange
.addElementaryChange(CompositeElement
.this, changeInfo
);
464 rawRemoveAllChildren();
465 rawAddChildren((TreeElement
)firstChild1
);
468 final TreeElement first
= getFirstChildNode();
469 remove(destinationTreeChange
, first
, null);
470 add(destinationTreeChange
, CompositeElement
.this, (TreeElement
)firstChild1
);
471 repairRemovedElement(CompositeElement
.this, first
);
481 public void removeAllChildren() {
482 final TreeElement child
= getFirstChildNode();
484 removeRange(child
, null);
488 public void addChildren(ASTNode firstChild
, ASTNode lastChild
, ASTNode anchorBefore
) {
489 while (firstChild
!= lastChild
) {
490 final ASTNode next1
= firstChild
.getTreeNext();
491 addChild(firstChild
, anchorBefore
);
496 public PsiElement
getPsi() {
497 PsiElement wrapper
= myWrapper
;
498 if (wrapper
!= null) return wrapper
;
500 synchronized (PsiLock
.LOCK
) {
502 if (wrapper
!= null) return wrapper
;
504 final Language lang
= getElementType().getLanguage();
505 final ParserDefinition parserDefinition
= LanguageParserDefinitions
.INSTANCE
.forLanguage(lang
);
506 if (parserDefinition
!= null) {
507 myWrapper
= wrapper
= parserDefinition
.createElement(this);
508 //noinspection ConstantConditions
509 LOG
.assertTrue(wrapper
!= null, "ParserDefinition.createElement() may not return null");
516 public void setPsi(@NotNull PsiElement psi
) {
520 public void rawAddChildren(@NotNull TreeElement first
) {
521 final TreeElement last
= getLastChildNode();
523 setFirstChildNode(first
);
524 first
.setTreePrev(null);
526 final TreeElement treeNext
= first
.getTreeNext();
527 first
.setTreeParent(this);
528 if(treeNext
== null) break;
531 setLastChildNode(first
);
532 first
.setTreeParent(this);
535 last
.rawInsertAfterMe(first
);
538 if (DebugUtil
.CHECK
) DebugUtil
.checkTreeStructure(this);
541 public void rawRemoveAllChildren() {
542 TreeElement first
= getFirstChildNode();
544 first
.rawRemoveUpToLast();
548 private static void repairRemovedElement(final CompositeElement oldParent
, final TreeElement oldChild
) {
549 if(oldChild
== null) return;
550 final FileElement treeElement
= DummyHolderFactory
.createHolder(oldParent
.getManager(), null, false).getTreeElement();
551 treeElement
.rawAddChildren(oldChild
);
554 private static void add(final TreeChangeEvent destinationTreeChange
,
555 final CompositeElement parent
,
556 final TreeElement first
) {
557 parent
.rawAddChildren(first
);
558 TreeElement child
= first
;
559 while(child
!= null){
560 destinationTreeChange
.addElementaryChange(child
, ChangeInfoImpl
.create(ChangeInfo
.ADD
, child
));
561 child
= child
.getTreeNext();
565 private static void remove(final TreeChangeEvent destinationTreeChange
,
566 final TreeElement first
,
567 final TreeElement last
) {
569 TreeElement child
= first
;
570 while(child
!= last
&& child
!= null){
571 destinationTreeChange
.addElementaryChange(child
, ChangeInfoImpl
.create(ChangeInfo
.REMOVED
, child
));
572 child
= child
.getTreeNext();
575 first
.rawRemoveUpTo(last
);
579 private static void insertBefore(final TreeChangeEvent destinationTreeChange
,
580 final TreeElement anchorBefore
,
581 final TreeElement first
) {
582 anchorBefore
.rawInsertBeforeMe(first
);
583 TreeElement child
= first
;
584 while(child
!= anchorBefore
){
585 destinationTreeChange
.addElementaryChange(child
, ChangeInfoImpl
.create(ChangeInfo
.ADD
, child
));
586 child
= child
.getTreeNext();
590 private static void replace(final TreeChangeEvent sourceTreeChange
,
591 final TreeElement oldChild
,
592 final TreeElement newChild
) {
593 oldChild
.rawReplaceWithList(newChild
);
594 final ReplaceChangeInfoImpl change
= (ReplaceChangeInfoImpl
)ChangeInfoImpl
.create(ChangeInfo
.REPLACE
, newChild
);
595 sourceTreeChange
.addElementaryChange(newChild
, change
);
596 change
.setReplaced(oldChild
);
599 private static void removeChildInner(final TreeElement child
) {
600 removeChildrenInner(child
, child
.getTreeNext());
603 private static void removeChildrenInner(final TreeElement first
, final TreeElement last
) {
604 final FileElement fileElement
= TreeUtil
.getFileElement(first
);
605 if (fileElement
!= null) {
606 ChangeUtil
.prepareAndRunChangeAction(new ChangeUtil
.ChangeAction() {
607 public void makeChange(TreeChangeEvent destinationTreeChange
) {
608 remove(destinationTreeChange
, first
, last
);
609 repairRemovedElement(fileElement
, first
);
611 }, first
.getTreeParent());
614 first
.rawRemoveUpTo(last
);
619 public TreeElement
rawFirstChild() {
623 public TreeElement
rawLastChild() {