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.progress.ProcessCanceledException;
import com.intellij.psi.PsiElement;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.util.PsiTreeUtil;
import com.siberika.idea.pascal.ide.actions.SectionToggle;
import com.siberika.idea.pascal.lang.parser.NamespaceRec;
import com.siberika.idea.pascal.lang.psi.PasClassQualifiedIdent;
import com.siberika.idea.pascal.lang.psi.PasEntityScope;
import com.siberika.idea.pascal.lang.psi.PasExportedRoutine;
import com.siberika.idea.pascal.lang.psi.PasFormalParameterSection;
import com.siberika.idea.pascal.lang.psi.PasNamedIdent;
import com.siberika.idea.pascal.lang.psi.PasRoutineImplDecl;
import com.siberika.idea.pascal.lang.psi.PasTypeDecl;
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 com.siberika.idea.pascal.util.SyncUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.locks.ReentrantLock;
/**
* Author: George Bakhtadze
* Date: 06/09/2013
*/
public abstract class PascalRoutineImpl extends PasScopeImpl implements PasEntityScope, PasDeclSection {
private static final Cache<String, Members> cache = CacheBuilder.newBuilder().softValues().build();
private ReentrantLock parentLock = new ReentrantLock();
private boolean parentBuilding = false;
private final Callable<? extends Members> MEMBER_BUILDER = this.new MemberBuilder();
@Nullable
public abstract PasFormalParameterSection getFormalParameterSection();
public PascalRoutineImpl(ASTNode node) {
super(node);
}
@Override
protected String calcKey() {
StringBuilder sb = new StringBuilder(PsiUtil.getFieldName(this));
sb.append(PsiUtil.isForwardProc(this) ? "-fwd" : "");
if (this instanceof PasExportedRoutine) {
sb.append("^intf");
} else {
sb.append("^impl");
}
PasEntityScope scope = this.getContainingScope();
sb.append(scope != null ? "." + scope.getKey() : "");
// System.out.println(String.format("%s:%d - %s", PsiUtil.getFieldName(this), this.getTextOffset(), sb.toString()));
return sb.toString();
}
@NotNull
private Members getMembers(Cache<String, Members> cache, Callable<? extends Members> builder) {
ensureChache(cache);
try {
Members res = cache.get(getKey(), builder);
if (!res.isChachable()) {
cache.invalidate(getKey());
}
return res;
} 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();
}
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(PascalRoutineImpl.this);
return null;
}
if (building) {
LOG.info("WARNING: Reentered in routine.buildXXX");
return Members.createNotCacheable();
// throw new ProcessCanceledException();
}
building = true;
try {
Members res = new Members();
res.stamp = getStamp(getContainingFile());
collectFormalParameters(res);
collectFields(PascalRoutineImpl.this, PasField.Visibility.STRICT_PRIVATE, res.all, res.redeclared);
addSelf(res);
LOG.debug(PsiUtil.getFieldName(PascalRoutineImpl.this) + ": buildMembers: " + res.all.size() + " members");
// System.out.println(PsiUtil.getFieldName(PascalRoutineImpl.this) + ": buildMembers: " + res.all.size() + " members");
return res;
} finally {
building = false;
}
}
}
private void collectFormalParameters(Members res) {
PascalRoutineImpl routine = this;
List<PasNamedIdent> params = PsiUtil.getFormalParameters(getFormalParameterSection());
if (params.isEmpty() && (this instanceof PasRoutineImplDecl)) { // If this is implementation with formal parameters omitted take formal parameters from routine declaration
PsiElement decl = SectionToggle.retrieveDeclaration(this, true);
if (decl instanceof PascalRoutineImpl) {
routine = (PascalRoutineImpl) decl;
params = PsiUtil.getFormalParameters(routine.getFormalParameterSection());
}
}
for (PasNamedIdent parameter : params) {
addField(res, parameter, PasField.FieldType.VARIABLE);
}
if (!res.all.containsKey(BUILTIN_RESULT.toUpperCase())) {
res.all.put(BUILTIN_RESULT.toUpperCase(), new PasField(this, routine, BUILTIN_RESULT, PasField.FieldType.PSEUDO_VARIABLE, PasField.Visibility.STRICT_PRIVATE));
}
}
private void addSelf(Members res) {
PasEntityScope scope = getContainingScope();
if ((scope != null) && (scope.getParent() instanceof PasTypeDecl)) {
PasField field = new PasField(this, scope, BUILTIN_SELF, PasField.FieldType.PSEUDO_VARIABLE, PasField.Visibility.STRICT_PRIVATE);
PasTypeDecl typeDecl = (PasTypeDecl) scope.getParent();
field.setValueType(new PasField.ValueType(field, PasField.Kind.STRUCT, null, typeDecl));
res.all.put(BUILTIN_SELF.toUpperCase(), field);
}
}
private void addField(Members res, PascalNamedElement element, PasField.FieldType fieldType) {
PasField field = new PasField(this, element, element.getName(), fieldType, PasField.Visibility.STRICT_PRIVATE);
res.all.put(field.name.toUpperCase(), field);
}
@Nullable
public PasTypeID getFunctionTypeIdent() {
PasTypeDecl type = findChildByClass(PasTypeDecl.class);
return PsiTreeUtil.findChildOfType(type, PasTypeID.class);
}
@NotNull
public String getFunctionTypeStr() {
PasTypeDecl type = findChildByClass(PasTypeDecl.class);
PasTypeID typeId = PsiTreeUtil.findChildOfType(type, PasTypeID.class);
if (typeId != null) {
return typeId.getFullyQualifiedIdent().getName();
}
return type != null ? type.getText() : "";
}
@NotNull
@Override
public List<SmartPsiElementPointer<PasEntityScope>> getParentScope() {
if (!SyncUtil.tryLockQuiet(parentLock, SyncUtil.LOCK_TIMEOUT_MS)) {
return Collections.emptyList();
}
try {
if (parentBuilding) {
return Collections.emptyList();
}
parentBuilding = true;
ensureChache(parentCache);
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 parents for: " + this, e.getCause());
invalidateCaches(getKey());
return Collections.emptyList();
}
} finally {
parentBuilding = false;
parentLock.unlock();
}
}
private class ParentBuilder implements Callable<Parents> {
@Override
public Parents call() throws Exception {
if (null == getContainingFile()) {
PascalPsiImplUtil.logNullContainingFile(PascalRoutineImpl.this);
return null;
}
Parents res = new Parents();
res.stamp = getStamp(getContainingFile());
PasClassQualifiedIdent ident = PsiTreeUtil.getChildOfType(PascalRoutineImpl.this, PasClassQualifiedIdent.class);
if ((ident != null) && (ident.getSubIdentList().size() > 1)) { // Should contain at least class name and method name parts
NamespaceRec fqn = NamespaceRec.fromElement(ident.getSubIdentList().get(ident.getSubIdentList().size() - 2));
res.scopes = Collections.emptyList(); // To prevent infinite recursion
PasEntityScope type = PasReferenceUtil.resolveTypeScope(fqn, true);
if (type != null) {
res.scopes = Collections.singletonList(SmartPointerManager.getInstance(type.getProject()).createSmartPsiElementPointer(type));
}
} else {
res.scopes = Collections.emptyList();
}
return res;
}
}
}