package org.elixir_lang.beam.psi.impl;
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.*;
import com.intellij.psi.impl.PsiElementBase;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import org.elixir_lang.ElixirLanguage;
import org.elixir_lang.beam.psi.BeamFileImpl;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
// See com.intellij.psi.impl.compiled.ClsElementImpl
public abstract class ModuleElementImpl extends PsiElementBase implements PsiCompiledElement {
private static final Key<PsiCompiledElement> COMPILED_ELEMENT = Key.create("COMPILED_ELEMENT");
private static final Logger LOGGER = Logger.getInstance(ModuleElementImpl.class);
private volatile TreeElement mirror = null;
protected static void appendText(@NotNull PsiElement stub, @NotNull StringBuilder buffer, int indentLevel) {
((ModuleElementImpl) stub).appendMirrorText(buffer, indentLevel);
}
protected static <T extends PsiElement> void setMirrors(@NotNull T[] stubs,
@NotNull T[] mirrors) throws InvalidMirrorException {
setMirrors(Arrays.asList(stubs), Arrays.asList(mirrors));
}
private static <T extends PsiElement> void setMirrors(@NotNull List<T> stubs,
@NotNull List<T> mirrors) throws InvalidMirrorException {
if (stubs.size() != mirrors.size()) {
throw new InvalidMirrorException(stubs, mirrors);
}
for (int i = 0; i < stubs.size(); i++) {
setMirror(stubs.get(i), mirrors.get(i));
}
}
private static <T extends PsiElement> void setMirror(@Nullable T stub,
@Nullable T mirror) throws InvalidMirrorException {
if (stub == null || mirror == null) {
throw new InvalidMirrorException(stub, mirror);
}
((ModuleElementImpl) stub).setMirror(SourceTreeToPsiMap.psiToTreeNotNull(mirror));
}
/**
* Returns the text of the PSI element.
*
* @return the element text.
*/
@Override
public String getText() {
PsiElement mirror = getMirror();
String text;
if (mirror != null) {
text = mirror.getText();
} else {
StringBuilder buffer = new StringBuilder();
appendMirrorText(buffer, 0);
LOGGER.warn(
"Mirror wasn't set for " + this + " in " + getContainingFile() +
", expected text '" + buffer + "'"
);
text = buffer.toString();
}
return text;
}
/**
* Returns the AST node corresponding to the element.
*
* @return the AST node instance.
*/
@Override
public ASTNode getNode() {
return null;
}
public abstract void appendMirrorText(@NotNull StringBuilder buffer, int indentLevel);
/**
* Returns the text of the PSI element as a character array.
*
* @return the element text as a character array.
*/
@NotNull
@Override
public char[] textToCharArray() {
PsiElement mirror = getMirror();
char[] charArray = ArrayUtil.EMPTY_CHAR_ARRAY;
if (mirror != null) {
charArray = mirror.textToCharArray();
}
return charArray;
}
/**
* Returns the corresponding PSI element in a decompiled file created by IDEA from
* the library element.
*
* @return the counterpart of the element in decompiled file.
*/
@Override
public PsiElement getMirror() {
TreeElement nonVolatileMirror = mirror;
if (nonVolatileMirror == null) {
((BeamFileImpl) getContainingFile()).getMirror();
nonVolatileMirror = mirror;
}
return SourceTreeToPsiMap.treeElementToPsi(nonVolatileMirror);
}
public abstract void setMirror(@NotNull TreeElement element) throws InvalidMirrorException;
/**
* Returns the language of the PSI element.
*
* @return the language instance.
*/
@NotNull
@Override
public Language getLanguage() {
return ElixirLanguage.INSTANCE;
}
/**
* Returns the text range in the document occupied by the PSI element.
*
* @return the text range.
*/
@Override
public TextRange getTextRange() {
PsiElement mirror = getMirror();
TextRange textRange = TextRange.EMPTY_RANGE;
if (mirror != null) {
textRange = mirror.getTextRange();
}
return textRange;
}
/**
* Finds a leaf PSI element at the specified offset from the start of the text range of this node.
*
* @param offset the relative offset for which the PSI element is requested.
* @return the element at the offset, or null if none is found.
*/
@Nullable
@Override
public PsiElement findElementAt(int offset) {
PsiElement mirror = getMirror();
PsiElement elementAt = null;
if (mirror != null) {
PsiElement mirrorAt = mirror.findElementAt(offset);
while (true) {
if (mirrorAt == null || mirrorAt instanceof PsiFile) {
break;
}
elementAt = mirrorToElement(mirrorAt);
if (elementAt != null) {
break;
}
mirrorAt = mirrorAt.getParent();
}
}
return elementAt;
}
@Nullable
private PsiElement mirrorToElement(PsiElement mirror) {
final PsiElement ownMirror = getMirror();
PsiElement element = null;
if (ownMirror == mirror) {
element = this;
} else {
PsiElement[] children = getChildren();
if (children.length > 0) {
for (PsiElement child : children) {
ModuleElementImpl moduleElement = (ModuleElementImpl) child;
if (PsiTreeUtil.isAncestor(moduleElement.getMirror(), mirror, false)) {
element = moduleElement.mirrorToElement(mirror);
if (element != null) {
break;
}
}
}
}
}
return element;
}
/**
* Finds a reference at the specified offset from the start of the text range of this node.
*
* @param offset the relative offset for which the reference is requested.
* @return the reference at the offset, or null if none is found.
*/
@Nullable
@Override
public PsiReference findReferenceAt(int offset) {
PsiElement mirror = getMirror();
PsiReference referenceAt = null;
if (mirror != null) {
PsiReference mirrorRef = mirror.findReferenceAt(offset);
if (mirrorRef != null) {
PsiElement mirrorElement = mirrorRef.getElement();
PsiElement element = mirrorToElement(mirrorElement);
if (element != null) {
referenceAt = element.getReference();
}
}
}
return referenceAt;
}
/**
* Returns the offset in the file to which the caret should be placed
* when performing the navigation to the element. (For classes implementing
* {@link PsiNamedElement}, this should return the offset in the file of the
* name identifier.)
*
* @return the offset of the PSI element.
*/
@Override
public int getTextOffset() {
PsiElement mirror = getMirror();
int textOffset = -1;
if (mirror != null) {
textOffset = mirror.getTextOffset();
}
return textOffset;
}
/**
* Returns the length of text of the PSI element.
*
* @return the text length.
*/
@Override
public int getTextLength() {
String text = getText();
int textLength = 0;
if (text != null) {
textLength = text.length();
}
return textLength;
}
@Override
public int getStartOffsetInParent() {
PsiElement mirror = getMirror();
int startOffsetInParent = -1;
if (mirror != null) {
startOffsetInParent = mirror.getStartOffsetInParent();
}
return startOffsetInParent;
}
@SuppressWarnings("PMD.DefaultPackage")
void setMirrorCheckingType(@NotNull TreeElement element,
@Nullable IElementType type) throws InvalidMirrorException {
if (type != null && element.getElementType() != type) {
throw new InvalidMirrorException(element.getElementType() + " != " + type);
}
element.getPsi().putUserData(COMPILED_ELEMENT, this);
mirror = element;
}
protected static class InvalidMirrorException extends RuntimeException {
public InvalidMirrorException(@NotNull @NonNls String message) {
super(message);
}
@SuppressWarnings("PMD.DefaultPackage")
InvalidMirrorException(@Nullable PsiElement stubElement, @Nullable PsiElement mirrorElement) {
this("stub:" + stubElement + "; mirror:" + mirrorElement);
}
@SuppressWarnings("PMD.DefaultPackage")
InvalidMirrorException(@NotNull List<? extends PsiElement> stubElements,
@NotNull List<? extends PsiElement> mirrorElements) {
this("stub:" + stubElements + "; mirror:" + mirrorElements);
}
}
}