/*
* Copyright 2003-2013 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.
*/
package jetbrains.mps.idea.core.psi.impl;
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.PsiManager;
import com.intellij.psi.PsiReference;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.SmartList;
import jetbrains.mps.fileTypes.MPSLanguage;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.idea.core.psi.impl.NodeList.Entry;
import jetbrains.mps.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.module.SRepository;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/**
* evgeny, 1/25/13
*/
public abstract class MPSPsiNodeBase extends LightElement {
private NodeList children;
private NodeList.Entry listEntry;
private int cachedTreePosition;
public MPSPsiNodeBase(PsiManager manager) {
super(manager, MPSLanguage.INSTANCE);
this.children = new NodeList(this);
}
@Override
public PsiManager getManager() {
if (myManager != null) {
return myManager;
}
return getContainingModel().getManager();
}
public MPSPsiModel getContainingModel() {
PsiElement parent = this;
while (parent != null && !(parent instanceof MPSPsiRootNode)) {
parent = parent.getParent();
}
if (parent == null) {
throw new PsiInvalidElementAccessException(this);
}
return ((MPSPsiRootNode) parent).getContainingModel();
}
protected SRepository getProjectRepository() {
Project p = ProjectHelper.fromIdeaProject(getProject());
assert p != null;
return p.getRepository();
}
@Override
public PsiFile getContainingFile() {
return null; // either that or a real file!
}
@Override
public TextRange getTextRange() {
int p = getTreePosition();
return new TextRange(p, p);
}
@Override
public int getTextOffset() {
return getTreePosition();
}
private int getTreePosition() {
MPSPsiModel model = getContainingModel();
Integer pos = model.getNodePosition(this);
if (pos == null) {
return cachedTreePosition;
}
cachedTreePosition = pos;
return pos;
}
@Override
public boolean isValid() {
return listEntry != null;
}
@Override
public MPSPsiNodeBase getFirstChild() {
return children.first();
}
@Override
public MPSPsiNodeBase getLastChild() {
return children.last();
}
@NotNull
@Override
public PsiElement[] getChildren() {
PsiElement[] result = new PsiElement[children.size()];
children.toArray(result);
return result;
}
@SuppressWarnings("unchecked")
public <T extends PsiElement> T[] getChildren(Class<T> aClass) {
T[] result = (T[]) Array.newInstance(aClass, children.size());
children.toArray(result);
return result;
}
public <T extends PsiElement> T[] getChildrenOfType(String role, @NotNull Class<T> aClass) {
if (role == null) return null;
List<T> result = null;
for (PsiElement child = getFirstChild(); child != null; child = child.getNextSibling()) {
if (child instanceof MPSPsiNode && role.equals(((MPSPsiNode) child).getContainingRole()) && aClass.isInstance(child)) {
if (result == null) result = new SmartList<T>();
//noinspection unchecked
result.add((T) child);
}
}
return result == null ? null : ArrayUtil.toObjectArray(result, aClass);
}
protected <T extends PsiElement> T getChildOfType(@NotNull Class<T> aClass) {
for (PsiElement child = getFirstChild(); child != null; child = child.getNextSibling()) {
if (child instanceof MPSPsiNodeBase && aClass.isInstance(child)) {
return aClass.cast(child);
}
}
return null;
}
/**
* Enables PSI nodes to have a different parent-child structure than that of MPS nodes they're built upon
* @param child about to be added
* @return Null if this node will be the parent (default) or some other child (or maybe even grandchild) node
*/
@Nullable
protected MPSPsiNodeBase getParentFor(MPSPsiNode child) {
return null;
}
@Override
public MPSPsiNodeBase getNextSibling() {
return listEntry != null && !listEntry.isLast() ? listEntry.list().next(this) : null;
}
@Override
public MPSPsiNodeBase getPrevSibling() {
return listEntry != null && !listEntry.isFirst() ? listEntry.list().prev(this) : null;
}
@Override
public PsiElement getParent() {
return listEntry != null ? listEntry.list().owner() : null;
}
@Override
public PsiReference getReference() {
return null;
}
@Override
public PsiReference[] getReferences() {
return PsiReference.EMPTY_ARRAY;
}
public MPSPsiRootNode getContainingRoot() {
PsiElement parent = this;
while (parent != null && !(parent instanceof MPSPsiRootNode)) {
parent = parent.getParent();
}
if (parent == null) {
throw new PsiInvalidElementAccessException(this);
}
return (MPSPsiRootNode) parent;
}
@Override
public boolean isPhysical() {
return true;
}
protected final Iterable<MPSPsiNodeBase> children() {
return new Iterable<MPSPsiNodeBase>() {
@Override
public Iterator<MPSPsiNodeBase> iterator() {
return new Iterator<MPSPsiNodeBase>() {
MPSPsiNodeBase node = null;
@Override
public boolean hasNext() {
return (node == null && children.first() != null) ||
(node != null && node != children.last());
}
@Override
public MPSPsiNodeBase next() {
if (!hasNext()) throw new NoSuchElementException();
if (node == null) {
return (node = children.first());
} else {
return (node = children.next(node));
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
protected final void addChildFirst(@NotNull MPSPsiNodeBase node) {
children.addFirst(node);
}
protected final void addChildLast(@NotNull MPSPsiNodeBase node) {
children.addLast(node);
}
protected final void insertChildAfter(MPSPsiNodeBase anchor, @NotNull MPSPsiNodeBase node) {
children.insertAfter(anchor, node);
}
protected final void insertChildBefore(MPSPsiNodeBase anchor, @NotNull MPSPsiNodeBase node) {
children.insertBefore(anchor, node);
}
protected final void updateChildren() {
for(MPSPsiNodeBase element : children()) {
Entry entry = children.findEntry(element);
element.setEntry(entry);
element.updateChildren();
}
}
@Deprecated
protected final void addChild(MPSPsiNodeBase anchor, @NotNull MPSPsiNodeBase node) {
if (anchor == null) {
children.addFirst(node);
} else {
children.insertAfter(anchor, node);
}
}
protected final void removeChild(@NotNull MPSPsiNodeBase node) {
children.remove(node);
}
protected final void replaceChild(@NotNull MPSPsiNodeBase oldNode, @NotNull MPSPsiNodeBase replacementNode) {
children.replace(oldNode, replacementNode);
}
protected final void clearChildren() {
children.clear();
}
/*package*/ void setEntry(Entry newEntry) {
assert (listEntry == null && newEntry != null) || (listEntry != null && newEntry == null);
listEntry = newEntry;
if (newEntry == null) {
// means invalidating, propagating it down
MPSPsiNodeBase curr = children.first();
while (curr != null) {
MPSPsiNodeBase next = curr.getNextSibling();
curr.setEntry(null);
curr = next;
}
}
}
/*package*/ Entry getEntry() {
return listEntry;
}
}