package com.siberika.idea.pascal.lang.psi.impl; import com.intellij.extapi.psi.ASTWrapperPsiElement; import com.intellij.lang.ASTNode; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.roots.FileIndexFacade; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import com.intellij.psi.search.ProjectScopeImpl; import com.intellij.psi.search.SearchScope; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import com.siberika.idea.pascal.lang.PascalReference; import com.siberika.idea.pascal.lang.parser.PascalParserUtil; import com.siberika.idea.pascal.lang.psi.PasClassQualifiedIdent; import com.siberika.idea.pascal.lang.psi.PasNamespaceIdent; import com.siberika.idea.pascal.lang.psi.PasRefNamedIdent; import com.siberika.idea.pascal.lang.psi.PasSubIdent; import com.siberika.idea.pascal.lang.psi.PasTypes; import com.siberika.idea.pascal.lang.psi.PascalNamedElement; import com.siberika.idea.pascal.lang.psi.PascalQualifiedIdent; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Iterator; /** * Author: George Bakhtadze * Date: 1/4/13 */ public abstract class PascalNamedElementImpl extends ASTWrapperPsiElement implements PascalNamedElement { private static final int MAX_SHORT_TEXT_LENGTH = 32; private static final TokenSet NAME_TYPE_SET = TokenSet.create(PasTypes.NAME, PasTypes.KEYWORD_IDENT, PasTypes.ESCAPED_IDENT); private volatile String myCachedName; public PascalNamedElementImpl(ASTNode node) { super(node); } @Override public void subtreeChanged() { super.subtreeChanged(); myCachedName = null; } @NotNull @Override public String getName() { // TODO: synchronize? if ((myCachedName == null) || (myCachedName.length() == 0)) { myCachedName = calcName(getNameElement()); } return myCachedName; } private static String calcName(PsiElement nameElement) { if ((nameElement != null) && (nameElement.getClass() == PasClassQualifiedIdentImpl.class)) { Iterator<PasSubIdent> it = ((PasClassQualifiedIdent) nameElement).getSubIdentList().iterator(); StringBuilder sb = new StringBuilder(it.next().getName()); while (it.hasNext()) { String name = it.next().getName(); name = !name.startsWith("&") ? name : name.substring(1); sb.append(".").append(name); } return sb.toString(); } else { if (nameElement != null) { String name = nameElement.getText(); return !name.startsWith("&") ? name : name.substring(1); } else { return ""; } } } @Override public String getNamespace() { String name = getName(); int pos = name.lastIndexOf("."); return pos >= 0 ? name.substring(0, pos) : null; } @Override public String getNamePart() { String name = getName(); int pos = name.lastIndexOf("."); return pos >= 0 ? name.substring(pos + 1) : name; } @Nullable protected PsiElement getNameElement() { if ((this instanceof PasNamespaceIdent) || (this instanceof PascalQualifiedIdent)) { return this; } PsiElement result = findChildByType(PasTypes.NAMESPACE_IDENT); if (null == result) { PascalNamedElement namedChild = PsiTreeUtil.getChildOfType(this, PascalNamedElement.class); result = namedChild != null ? namedChild.getNameIdentifier() : null; } if (null == result) { result = findChildByType(NAME_TYPE_SET); } return result; } @Override public PsiElement setName(@NonNls @NotNull String s) throws IncorrectOperationException { PsiElement element = getNameElement(); if (element != null) { element.replace(PasElementFactory.createLeafFromText(getProject(), s)); } return this; } @Override public int getTextOffset() { PsiElement element = getNameElement(); return (element != null) && (element != this) ? element.getTextOffset() : getNode().getStartOffset(); } @NotNull @Override public SearchScope getUseScope() { return new ProjectScopeImpl(getProject(), FileIndexFacade.getInstance(getProject())); } @Override public PsiElement getNameIdentifier() { return getNameElement(); } @Override public String toString() { try { return "[" + getClass().getSimpleName() + "]\"" + getName() + "\" ^" + getParent() + "..." + getShortText(getParent()); } catch (NullPointerException e) { return "<NPE>"; } } private static String getShortText(PsiElement parent) { if (null == parent) { return ""; } int lfPos = parent.getText().indexOf("\n"); if (lfPos > 0) { return parent.getText().substring(0, lfPos); } else { return parent.getText().substring(0, Math.min(parent.getText().length(), MAX_SHORT_TEXT_LENGTH)); } } @Override public ItemPresentation getPresentation() { return PascalParserUtil.getPresentation(this); } @Override public PsiReference getReference() { PsiReference[] refs = getReferences(); return refs.length > 0 ? refs[0] : null; } @Override @NotNull public PsiReference[] getReferences() { if ((this instanceof PasSubIdent) || (this instanceof PasRefNamedIdent) || (this.getParent() instanceof PascalRoutineImpl)) { if ((getNameElement() != null) && getTextRange().intersects(getNameElement().getTextRange())) { return new PsiReference[]{ new PascalReference(this, new TextRange(0, getName().length())) }; } } return PsiReference.EMPTY_ARRAY; } @Override public boolean equals(Object o) { // TODO: remove if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PascalNamedElementImpl that = (PascalNamedElementImpl) o; if (getParent() != that.getParent()) return false; if (!getName().equalsIgnoreCase(that.getName())) return false; if ((this instanceof PascalRoutineImpl) && (this != that)) { return false; } return true; } @Override public int hashCode() { int result = getName().toUpperCase().hashCode(); result = 31 * result + (getParent() != null ? getParent().hashCode() : 0); return result; } }