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.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.util.SmartList;
import com.siberika.idea.pascal.lang.psi.PasClassQualifiedIdent;
import com.siberika.idea.pascal.lang.psi.PasEntityScope;
import com.siberika.idea.pascal.lang.psi.PasGenericTypeIdent;
import com.siberika.idea.pascal.lang.psi.PasNamedIdent;
import com.siberika.idea.pascal.lang.psi.PasNamespaceIdent;
import com.siberika.idea.pascal.lang.psi.PasRoutineImplDecl;
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.Nullable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
/**
* Author: George Bakhtadze
* Date: 07/09/2013
*/
public abstract class PasScopeImpl extends PascalNamedElementImpl implements PasEntityScope {
protected static final Logger LOG = Logger.getInstance(PasScopeImpl.class.getName());
protected static final Members EMPTY_MEMBERS = new Members();
protected static final Cache<String, Parents> parentCache = CacheBuilder.newBuilder().softValues().build();
protected ReentrantLock containingScopeLock = new ReentrantLock();
volatile protected boolean building = false;
volatile protected SmartPsiElementPointer<PasEntityScope> containingScope;
volatile protected String cachedKey;
public PasScopeImpl(ASTNode node) {
super(node);
}
public void invalidateCaches(String key) {
PascalModuleImpl.invalidate(key);
PascalRoutineImpl.invalidate(key);
PasStructTypeImpl.invalidate(key);
containingScope = null;
cachedKey = null;
}
protected static long getStamp(PsiFile file) {
//return System.currentTimeMillis();
return file.getModificationStamp();
}
public final String getKey() {
String key = cachedKey;
if (null == key) {
key = calcKey();
cachedKey = key;
}
return key;
}
protected String calcKey() {
PasEntityScope scope = this.getContainingScope();
return String.format("%s%s", PsiUtil.getFieldName(this), scope != null ? "." + scope.getKey() : "");
}
protected <T extends Cached> void ensureChache(Cache<String, T> cache) {
/* if (!PsiUtil.checkeElement(this)) {
return false;
}*/
/* if (null == getContainingFile()) {
PascalPsiImplUtil.logNullContainingFile(this);
return false;
}*/
if (!PsiUtil.isElementValid(this)) {
invalidateCaches(getKey());
throw new ProcessCanceledException();
}
Cached members = cache.getIfPresent(getKey());
if ((members != null) && (getStamp(getContainingFile()) != members.stamp)) {
invalidateCaches(getKey());
}
}
@Nullable
@Override
public PasEntityScope getContainingScope() {
if (SyncUtil.tryLockQuiet(containingScopeLock, SyncUtil.LOCK_TIMEOUT_MS)) {
try {
if (null == containingScope) {
calcContainingScope();
}
return containingScope != null ? containingScope.getElement() : null;
} finally {
containingScopeLock.unlock();
}
} else {
return null;
}
}
/**
* 1. For methods and method implementations returns containing class
* 2. For routines returns containing module
* 3. For nested routines returns containing routine
* 4. For structured types returns containing module
* 5. For nested structured types returns containing type
*/
private void calcContainingScope() {
PasEntityScope scope = PsiUtil.getNearestAffectingScope(this); // 2, 3, 4, 5, 1 for method declarations
containingScope = SmartPointerManager.getInstance(scope.getProject()).createSmartPsiElementPointer(scope);
if ((scope instanceof PascalModuleImpl) && (this instanceof PasRoutineImplDecl)) { // 1 for method implementations
String[] names = PsiUtil.getQualifiedMethodName(this).split("\\.");
if (names.length <= 1) { // should not be true
containingScope = SmartPointerManager.getInstance(scope.getProject()).createSmartPsiElementPointer(scope);
return;
}
PasField field = scope.getField(PsiUtil.cleanGenericDef(names[0]));
scope = updateContainingScope(field);
for (int i = 1; (i < names.length - 1) && (scope != null); i++) {
scope = updateContainingScope(scope.getField(PsiUtil.cleanGenericDef(names[i])));
}
}
}
private PasEntityScope updateContainingScope(PasField field) {
if (null == field) {
return null;
}
PasEntityScope scope = PasReferenceUtil.retrieveFieldTypeScope(field);
if (scope != null) {
containingScope = SmartPointerManager.getInstance(scope.getProject()).createSmartPsiElementPointer(scope);
}
return scope;
}
@SuppressWarnings("unchecked")
protected void collectFields(PsiElement section, PasField.Visibility visibility,
final Map<String, PasField> members, final Set<PascalNamedElement> redeclaredMembers) {
if (null == section) {
return;
}
for (PascalNamedElement namedElement : PsiUtil.findChildrenOfAnyType(section, PasNamedIdent.class, PasGenericTypeIdent.class, PasNamespaceIdent.class, PasClassQualifiedIdent.class)) {
if (PsiUtil.isSameAffectingScope(PsiUtil.getNearestAffectingDeclarationsRoot(namedElement), section)) {
if (!PsiUtil.isFormalParameterName(namedElement) && !PsiUtil.isUsedUnitName(namedElement)) {
if (PsiUtil.isRoutineName(namedElement)) {
namedElement = (PascalNamedElement) namedElement.getParent();
}
String name = namedElement.getName();
String memberName = PsiUtil.getFieldName(namedElement).toUpperCase();
PasField existing = members.get(memberName);
if (shouldAddField(existing, namedElement)) { // Otherwise replace with full declaration
PasField field = addField(this, name, namedElement, visibility);
if ((existing != null) && (field.offset > existing.offset)) {
field.offset = existing.offset; // replace field but keep offset to resolve fields declared later
}
if (field.fieldType == PasField.FieldType.ROUTINE) {
members.put(memberName, field);
}
members.put(name.toUpperCase(), field);
} else {
redeclaredMembers.add(namedElement);
}
}
}
}
}
// Add forward declared field even if it exists as we need full declaration
// Routines can have various signatures
private static boolean shouldAddField(PasField existing, PascalNamedElement namedElement) {
return (null == existing) || (PsiUtil.isForwardClassDecl(existing.getElement())
|| ((existing.fieldType == PasField.FieldType.ROUTINE) && (existing.offset > namedElement.getTextRange().getStartOffset())));
}
private static PasField addField(PasEntityScope owner, String name, PascalNamedElement namedElement, PasField.Visibility visibility) {
PasField.FieldType fieldType = getFieldType(namedElement);
return new PasField(owner, namedElement, name, fieldType, visibility);
}
private static PasField.FieldType getFieldType(PascalNamedElement namedElement) {
PasField.FieldType type = PasField.FieldType.VARIABLE;
if (PsiUtil.isTypeName(namedElement)) {
type = PasField.FieldType.TYPE;
} else if (namedElement instanceof PascalRoutineImpl) {
type = PasField.FieldType.ROUTINE;
} else if (PsiUtil.isConstDecl(namedElement) || PsiUtil.isEnumDecl(namedElement)) {
type = PasField.FieldType.CONSTANT;
}
return type;
}
static class Cached {
protected static final int UNCACHEABLE_STAMP = -1000000000;
long stamp;
public boolean isChachable() {
return stamp != UNCACHEABLE_STAMP;
}
}
static class Members extends Cached {
Map<String, PasField> all = new LinkedHashMap<String, PasField>();
Set<PascalNamedElement> redeclared = new LinkedHashSet<PascalNamedElement>();
static Members createNotCacheable() {
Members res = new Members();
res.stamp = UNCACHEABLE_STAMP;
return res;
}
}
static class UnitMembers extends Members {
List<SmartPsiElementPointer<PasEntityScope>> units = Collections.emptyList();
}
static class Parents extends Cached {
List<SmartPsiElementPointer<PasEntityScope>> scopes = new SmartList<SmartPsiElementPointer<PasEntityScope>>();
}
}