package com.siberika.idea.pascal.lang.psi.impl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.SmartList;
import com.siberika.idea.pascal.lang.parser.NamespaceRec;
import com.siberika.idea.pascal.lang.psi.PasClassParent;
import com.siberika.idea.pascal.lang.psi.PasClassTypeDecl;
import com.siberika.idea.pascal.lang.psi.PasEntityScope;
import com.siberika.idea.pascal.lang.psi.PasInterfaceTypeDecl;
import com.siberika.idea.pascal.lang.psi.PasNamedIdent;
import com.siberika.idea.pascal.lang.psi.PasRecordDecl;
import com.siberika.idea.pascal.lang.psi.PasTypeDecl;
import com.siberika.idea.pascal.lang.psi.PasTypeDeclaration;
import com.siberika.idea.pascal.lang.psi.PasTypeID;
import com.siberika.idea.pascal.lang.psi.PascalNamedElement;
import com.siberika.idea.pascal.lang.references.PasReferenceUtil;
import com.siberika.idea.pascal.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
/**
* Author: George Bakhtadze
* Date: 07/09/2013
*/
public abstract class PasStructTypeImpl extends PasScopeImpl implements PasEntityScope {
public static final Logger LOG = Logger.getInstance(PasStructTypeImpl.class.getName());
private static final Cache<String, Members> cache = CacheBuilder.newBuilder().softValues().build();
private static final Map<String, PasField.Visibility> STR_TO_VIS;
private final Callable<? extends Members> MEMBER_BUILDER = this.new MemberBuilder();
static {
STR_TO_VIS = new HashMap<String, PasField.Visibility>(PasField.Visibility.values().length);
STR_TO_VIS.put("INTERNAL", PasField.Visibility.INTERNAL);
STR_TO_VIS.put("STRICTPRIVATE", PasField.Visibility.STRICT_PRIVATE);
STR_TO_VIS.put("PRIVATE", PasField.Visibility.PRIVATE);
STR_TO_VIS.put("STRICTPROTECTED", PasField.Visibility.STRICT_PROTECTED);
STR_TO_VIS.put("PROTECTED", PasField.Visibility.PROTECTED);
STR_TO_VIS.put("PUBLIC", PasField.Visibility.PUBLIC);
STR_TO_VIS.put("PUBLISHED", PasField.Visibility.PUBLISHED);
STR_TO_VIS.put("AUTOMATED", PasField.Visibility.AUTOMATED);
assert STR_TO_VIS.size() == PasField.Visibility.values().length;
}
public PasStructTypeImpl(ASTNode node) {
super(node);
}
// Returns structured type owning the field
@Nullable
@SuppressWarnings("unchecked")
public static PasStructTypeImpl findOwnerStruct(PsiElement element) {
return PsiTreeUtil.getParentOfType(element,
PasClassHelperDeclImpl.class, PasClassTypeDeclImpl.class, PasInterfaceTypeDeclImpl.class, PasObjectDeclImpl.class, PasRecordHelperDeclImpl.class, PasRecordDeclImpl.class);
}
@Override
@Nullable
@SuppressWarnings("unchecked")
protected PsiElement getNameElement() {
PasTypeDeclaration typeDecl = PsiTreeUtil.getParentOfType(this, PasTypeDeclaration.class);
return PsiUtil.findImmChildOfAnyType(typeDecl, PasGenericTypeIdentImpl.class);
}
/**
* Returns structured type declaration element by its name element
* @param namedElement name element
* @return structured type declaration element
*/
@Nullable
public static PasEntityScope getStructByNameElement(@NotNull final PascalNamedElement namedElement) { // TODO: all scopes comes after name?
PsiElement sibling = PsiUtil.getNextSibling(namedElement);
sibling = sibling != null ? PsiUtil.getNextSibling(sibling) : null;
if ((sibling instanceof PasTypeDecl) && (sibling.getFirstChild() instanceof PasEntityScope)) {
return (PasEntityScope) sibling.getFirstChild();
}
return null;
}
@NotNull
private Members getMembers(Cache<String, Members> cache, Callable<? extends Members> builder) {
ensureChache(cache);
try {
return cache.get(getKey(), builder);
} catch (Exception e) {
if (e.getCause() instanceof ProcessCanceledException) {
throw (ProcessCanceledException) e.getCause();
} else {
LOG.warn("Error occured during building members for: " + this, e.getCause());
invalidateCaches(getKey());
return EMPTY_MEMBERS;
}
}
}
@Nullable
@Override
public PasField getField(String name) {
return getMembers(cache, MEMBER_BUILDER).all.get(name.toUpperCase());
}
@NotNull
@Override
public Collection<PasField> getAllFields() {
return getMembers(cache, MEMBER_BUILDER).all.values();
}
private PasField.Visibility getVisibility(PsiElement element) {
StringBuilder sb = new StringBuilder();
PsiElement psiChild = element.getFirstChild();
while (psiChild != null) {
if (psiChild.getClass() == LeafPsiElement.class) {
sb.append(psiChild.getText().toUpperCase());
}
psiChild = psiChild.getNextSibling();
}
return STR_TO_VIS.get(sb.toString());
}
public static void invalidate(String key) {
cache.invalidate(key);
parentCache.invalidate(key);
}
private class MemberBuilder implements Callable<Members> {
@Override
public Members call() throws Exception {
if (null == getContainingFile()) {
PascalPsiImplUtil.logNullContainingFile(PasStructTypeImpl.this);
return null;
}
Members res = new Members();
PasField.Visibility visibility = PasField.Visibility.PUBLISHED;
PsiElement child = getFirstChild();
while (child != null) {
if (child.getClass() == PasClassFieldImpl.class) {
addFields(res, child, PasField.FieldType.VARIABLE, visibility);
} else if (child.getClass() == PasConstSectionImpl.class) { // nested constants
addFields(res, child, PasField.FieldType.CONSTANT, visibility);
} else if (child.getClass() == PasTypeSectionImpl.class) {
addFields(res, child, PasField.FieldType.TYPE, visibility);
} else if (child.getClass() == PasVarSectionImpl.class) {
addFields(res, child, PasField.FieldType.VARIABLE, visibility);
} else if (child.getClass() == PasExportedRoutineImpl.class) {
addField(res, (PascalNamedElement) child, PasField.FieldType.ROUTINE, visibility);
} else if (child.getClass() == PasClassPropertyImpl.class) {
addField(res, (PascalNamedElement) child, PasField.FieldType.PROPERTY, visibility);
} else if (child.getClass() == PasVisibilityImpl.class) {
visibility = getVisibility(child);
} else if (child.getClass() == PasRecordVariantImpl.class) {
addFields(res, child, PasField.FieldType.VARIABLE, visibility);
} else if ((child.getClass() == PasNamedIdentImpl.class) && (PasStructTypeImpl.this instanceof PasRecordDecl)) {
addField(res, (PascalNamedElement) child, PasField.FieldType.VARIABLE, visibility);
}
child = PsiTreeUtil.skipSiblingsForward(child, PsiWhiteSpace.class, PsiComment.class);
}
res.stamp = getStamp(getContainingFile());
LOG.debug(getName() + ": buildMembers: " + res.all.size() + " members");
return res;
}
}
private void addFields(Members res, PsiElement element, PasField.FieldType fieldType, @NotNull PasField.Visibility visibility) {
PsiElement child = element.getFirstChild();
while (child != null) {
if (child.getClass() == PasNamedIdentImpl.class) {
addField(res, (PascalNamedElement) child, fieldType, visibility);
} else if (child.getClass() == PasConstDeclarationImpl.class) {
addField(res, ((PasConstDeclarationImpl) child).getNamedIdent(), fieldType, visibility);
} else if (child.getClass() == PasVarDeclarationImpl.class) {
for (PasNamedIdent namedIdent : ((PasVarDeclarationImpl) child).getNamedIdentList()) {
addField(res, namedIdent, fieldType, visibility);
}
} else if (child.getClass() == PasTypeDeclarationImpl.class) {
addField(res, ((PasTypeDeclarationImpl) child).getGenericTypeIdent(), fieldType, visibility);
} else if (child.getClass() == PasRecordVariantImpl.class) {
addFields(res, child, fieldType, visibility);
}
child = PsiTreeUtil.skipSiblingsForward(child, PsiWhiteSpace.class, PsiComment.class);
}
}
private void addField(Members res, @NotNull PascalNamedElement element, PasField.FieldType fieldType, @NotNull PasField.Visibility visibility) {
addField(res, element, element.getName(), fieldType, visibility);
if (fieldType == PasField.FieldType.ROUTINE) {
addField(res, element, PsiUtil.getFieldName(element), fieldType, visibility); // add with signature included in name
}
}
private PasField addField(Members res, PascalNamedElement element, String name, PasField.FieldType fieldType, @NotNull PasField.Visibility visibility) {
PasField field = new PasField(this, element, name, fieldType, visibility);
res.all.put(name.toUpperCase(), field);
return field;
}
@NotNull
@Override
public List<SmartPsiElementPointer<PasEntityScope>> getParentScope() {
ensureChache(parentCache);
try {
return parentCache.get(getKey(), new ParentBuilder()).scopes;
} catch (Exception e) {
if (e.getCause() instanceof ProcessCanceledException) {
throw (ProcessCanceledException) e.getCause();
} else {
LOG.warn("Error occured during building members for: " + this, e.getCause());
invalidateCaches(getKey());
return Collections.emptyList();
}
}
}
public PasClassParent getClassParent() {
return null;
}
private class ParentBuilder implements Callable<Parents> {
@Override
public Parents call() throws Exception {
if (null == getContainingFile()) {
PascalPsiImplUtil.logNullContainingFile(PasStructTypeImpl.this);
return null;
}
Parents res = new Parents();
res.stamp = getStamp(getContainingFile());
res.scopes = new SmartList<SmartPsiElementPointer<PasEntityScope>>();
PasClassParent parent = getClassParent();
if (parent != null) {
for (PasTypeID typeID : parent.getTypeIDList()) {
NamespaceRec fqn = NamespaceRec.fromElement(typeID.getFullyQualifiedIdent());
PasEntityScope scope = PasReferenceUtil.resolveTypeScope(fqn, true);
if (scope != PasStructTypeImpl.this) {
addScope(res, scope);
}
}
} else {
PasEntityScope defEntity = null;
if (PasStructTypeImpl.this instanceof PasClassTypeDecl) {
defEntity = PasReferenceUtil.resolveTypeScope(NamespaceRec.fromFQN(PasStructTypeImpl.this, "system.TObject"), true);
} else if (PasStructTypeImpl.this instanceof PasInterfaceTypeDecl) {
defEntity = PasReferenceUtil.resolveTypeScope(NamespaceRec.fromFQN(PasStructTypeImpl.this, "system.IInterface"), true);
}
if (defEntity != PasStructTypeImpl.this) {
addScope(res, defEntity);
}
}
return res;
}
private void addScope(Parents res, PasEntityScope scope) {
if (scope != null) {
res.scopes.add(SmartPointerManager.getInstance(scope.getProject()).createSmartPsiElementPointer(scope));
}
}
}
}