blob: fbcb7afc0731b7476741a16b3286a75751533480 [file] [log] [blame]
/*
* 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;
}
}