/* * Copyright 2000-2009 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * @author max */ package com.intellij.extapi.psi; import com.intellij.lang.ASTNode; import com.intellij.lang.Language; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiInvalidElementAccessException; import com.intellij.psi.impl.CheckUtil; import com.intellij.psi.impl.PsiElementBase; import com.intellij.psi.impl.PsiManagerEx; import com.intellij.psi.impl.source.SourceTreeToPsiMap; import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; import com.intellij.psi.impl.source.tree.*; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; public abstract class ASTDelegatePsiElement extends PsiElementBase { private static final Logger LOG = Logger.getInstance("#com.intellij.extapi.psi.ASTDelegatePsiElement"); private static final List EMPTY = Collections.emptyList(); @Override public PsiManagerEx getManager() { PsiElement parent = this; while (parent instanceof ASTDelegatePsiElement) { parent = parent.getParent(); } if (parent == null) { throw new PsiInvalidElementAccessException(this); } return (PsiManagerEx)parent.getManager(); } @Override @NotNull public PsiElement[] getChildren() { PsiElement psiChild = getFirstChild(); if (psiChild == null) return PsiElement.EMPTY_ARRAY; List<PsiElement> result = new ArrayList<PsiElement>(); while (psiChild != null) { if (psiChild.getNode() instanceof CompositeElement) { result.add(psiChild); } psiChild = psiChild.getNextSibling(); } return PsiUtilCore.toPsiElementArray(result); } @Override public PsiElement getFirstChild() { return SharedImplUtil.getFirstChild(getNode()); } @Override public PsiElement getLastChild() { return SharedImplUtil.getLastChild(getNode()); } @Override public PsiElement getNextSibling() { return SharedImplUtil.getNextSibling(getNode()); } @Override public PsiElement getPrevSibling() { return SharedImplUtil.getPrevSibling(getNode()); } @Override public TextRange getTextRange() { return getNode().getTextRange(); } @Override public int getStartOffsetInParent() { return getNode().getStartOffset() - getNode().getTreeParent().getStartOffset(); } @Override public int getTextLength() { return getNode().getTextLength(); } @Override public PsiElement findElementAt(int offset) { ASTNode treeElement = getNode().findLeafElementAt(offset); return SourceTreeToPsiMap.treeElementToPsi(treeElement); } @Override public int getTextOffset() { return getNode().getStartOffset(); } @Override public String getText() { return getNode().getText(); } @Override @NotNull public char[] textToCharArray() { return getNode().getText().toCharArray(); } @Override public boolean textContains(char c) { return getNode().textContains(c); } @Override public <T> T getCopyableUserData(Key<T> key) { return getNode().getCopyableUserData(key); } @Override public <T> void putCopyableUserData(Key<T> key, T value) { getNode().putCopyableUserData(key, value); } @Override @NotNull public abstract ASTNode getNode(); public void subtreeChanged() { } @Override @NotNull public Language getLanguage() { return getNode().getElementType().getLanguage(); } @Nullable protected PsiElement findChildByType(IElementType type) { ASTNode node = getNode().findChildByType(type); return node == null ? null : node.getPsi(); } @Nullable protected PsiElement findLastChildByType(IElementType type) { PsiElement child = getLastChild(); while (child != null) { final ASTNode node = child.getNode(); if (node != null && node.getElementType() == type) return child; child = child.getPrevSibling(); } return null; } @NotNull protected PsiElement findNotNullChildByType(IElementType type) { return notNullChild(findChildByType(type)); } @Nullable protected PsiElement findChildByType(TokenSet type) { ASTNode node = getNode().findChildByType(type); return node == null ? null : node.getPsi(); } @NotNull protected PsiElement findNotNullChildByType(TokenSet type) { return notNullChild(findChildByType(type)); } @Nullable protected PsiElement findChildByFilter(TokenSet tokenSet) { ASTNode[] nodes = getNode().getChildren(tokenSet); return nodes == null || nodes.length == 0 ? null : nodes[0].getPsi(); } @NotNull protected PsiElement findNotNullChildByFilter(TokenSet tokenSet) { return notNullChild(findChildByFilter(tokenSet)); } protected <T extends PsiElement> T[] findChildrenByType(IElementType elementType, Class<T> arrayClass) { return ContainerUtil.map2Array(SharedImplUtil.getChildrenOfType(getNode(), elementType), arrayClass, new Function<ASTNode, T>() { @Override public T fun(final ASTNode s) { return (T)s.getPsi(); } }); } protected <T extends PsiElement> List<T> findChildrenByType(TokenSet elementType) { List<T> result = EMPTY; ASTNode child = getNode().getFirstChildNode(); while (child != null) { final IElementType tt = child.getElementType(); if (elementType.contains(tt)) { if (result == EMPTY) { result = new ArrayList<T>(); } result.add((T)child.getPsi()); } child = child.getTreeNext(); } return result; } protected <T extends PsiElement> List<T> findChildrenByType(IElementType elementType) { List<T> result = EMPTY; ASTNode child = getNode().getFirstChildNode(); while (child != null) { if (elementType == child.getElementType()) { if (result == EMPTY) { result = new ArrayList<T>(); } result.add((T)child.getPsi()); } child = child.getTreeNext(); } return result; } protected <T extends PsiElement> T[] findChildrenByType(TokenSet elementType, Class<T> arrayClass) { return (T[])ContainerUtil.map2Array(getNode().getChildren(elementType), arrayClass, new Function<ASTNode, PsiElement>() { @Override public PsiElement fun(final ASTNode s) { return s.getPsi(); } }); } @Override public PsiElement copy() { return getNode().copyElement().getPsi(); } @Override public PsiElement add(@NotNull PsiElement element) throws IncorrectOperationException { return addInnerBefore(element, null); } @Override public PsiElement addBefore(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException { return addInnerBefore(element, anchor); } private PsiElement addInnerBefore(final PsiElement element, final PsiElement anchor) throws IncorrectOperationException { CheckUtil.checkWritable(this); TreeElement elementCopy = ChangeUtil.copyToElement(element); ASTNode treeElement = addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.TRUE); if (treeElement != null) { if (treeElement instanceof TreeElement) { return ChangeUtil.decodeInformation((TreeElement) treeElement).getPsi(); } return treeElement.getPsi(); } throw new IncorrectOperationException("Element cannot be added"); } @Override public PsiElement addAfter(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException { CheckUtil.checkWritable(this); TreeElement elementCopy = ChangeUtil.copyToElement(element); ASTNode treeElement = addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE); if (treeElement instanceof TreeElement) { return ChangeUtil.decodeInformation((TreeElement) treeElement).getPsi(); } return treeElement.getPsi(); } @Override public void checkAdd(@NotNull final PsiElement element) throws IncorrectOperationException { CheckUtil.checkWritable(this); } public ASTNode addInternal(ASTNode first, ASTNode last, ASTNode anchor, Boolean before) { return CodeEditUtil.addChildren(getNode(), first, last, getAnchorNode(anchor, before)); } @Override public PsiElement addRange(final PsiElement first, final PsiElement last) throws IncorrectOperationException { return SharedImplUtil.addRange(this, first, last, null, null); } @Override public PsiElement addRangeBefore(@NotNull final PsiElement first, @NotNull final PsiElement last, final PsiElement anchor) throws IncorrectOperationException { return SharedImplUtil.addRange(this, first, last, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.TRUE); } @Override public PsiElement addRangeAfter(final PsiElement first, final PsiElement last, final PsiElement anchor) throws IncorrectOperationException { return SharedImplUtil.addRange(this, first, last, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE); } @Override public void delete() throws IncorrectOperationException { PsiElement parent = getParent(); if (parent instanceof ASTDelegatePsiElement) { CheckUtil.checkWritable(this); ((ASTDelegatePsiElement)parent).deleteChildInternal(getNode()); } else if (parent instanceof CompositePsiElement) { CheckUtil.checkWritable(this); ((CompositePsiElement)parent).deleteChildInternal(getNode()); } else if (parent instanceof PsiFile) { CheckUtil.checkWritable(this); parent.deleteChildRange(this, this); } else { throw new UnsupportedOperationException(getClass().getName() + " under " + (parent == null ? "null" : parent.getClass().getName())); } } public void deleteChildInternal(@NotNull ASTNode child) { CodeEditUtil.removeChild(getNode(), child); } @Override public void checkDelete() throws IncorrectOperationException { CheckUtil.checkWritable(this); } @Override public void deleteChildRange(final PsiElement first, final PsiElement last) throws IncorrectOperationException { CheckUtil.checkWritable(this); ASTNode firstElement = SourceTreeToPsiMap.psiElementToTree(first); ASTNode lastElement = SourceTreeToPsiMap.psiElementToTree(last); LOG.assertTrue(firstElement.getTreeParent() == getNode()); LOG.assertTrue(lastElement.getTreeParent() == getNode()); CodeEditUtil.removeChildren(getNode(), firstElement, lastElement); } @Override public PsiElement replace(@NotNull final PsiElement newElement) throws IncorrectOperationException { CheckUtil.checkWritable(this); TreeElement elementCopy = ChangeUtil.copyToElement(newElement); if (getParent() instanceof ASTDelegatePsiElement) { final ASTDelegatePsiElement parentElement = (ASTDelegatePsiElement)getParent(); parentElement.replaceChildInternal(this, elementCopy); } else { CodeEditUtil.replaceChild(getParent().getNode(), getNode(), elementCopy); } elementCopy = ChangeUtil.decodeInformation(elementCopy); return SourceTreeToPsiMap.treeElementToPsi(elementCopy); } public void replaceChildInternal(final PsiElement child, final TreeElement newElement) { CodeEditUtil.replaceChild(getNode(), child.getNode(), newElement); } private ASTNode getAnchorNode(final ASTNode anchor, final Boolean before) { ASTNode anchorBefore; if (anchor != null) { anchorBefore = before.booleanValue() ? anchor : anchor.getTreeNext(); } else { if (before != null && !before.booleanValue()) { anchorBefore = getNode().getFirstChildNode(); } else { anchorBefore = null; } } return anchorBefore; } }