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.module.ModuleUtilCore;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.util.SmartList;
import com.siberika.idea.pascal.lang.parser.NamespaceRec;
import com.siberika.idea.pascal.lang.parser.PascalParserUtil;
import com.siberika.idea.pascal.lang.psi.PasEntityScope;
import com.siberika.idea.pascal.lang.psi.PasModule;
import com.siberika.idea.pascal.lang.psi.PascalNamedElement;
import com.siberika.idea.pascal.lang.psi.PascalQualifiedIdent;
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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
/**
* Author: George Bakhtadze
* Date: 14/09/2013
*/
public class PascalModuleImpl extends PasScopeImpl implements PascalModule {
private static final UnitMembers EMPTY_MEMBERS = new UnitMembers();
private static final Idents EMPTY_IDENTS = new Idents();
private static final Cache<String, Members> privateCache = CacheBuilder.newBuilder().softValues().build();
private static final Cache<String, Members> publicCache = CacheBuilder.newBuilder().softValues().build();
private static final Cache<String, Idents> identCache = CacheBuilder.newBuilder().softValues().build();
private static final String INTERFACE_PREFIX = "interface.";
private final Callable<? extends Members> PRIVATE_BUILDER = this.new PrivateBuilder();
private final Callable<? extends Members> PUBLIC_BUILDER = this.new PublicBuilder();
private final Callable<Idents> IDENTS_BUILDER = this.new IdentsBuilder();
public PascalModuleImpl(ASTNode node) {
super(node);
}
@Override
protected String calcKey() {
return PsiUtil.getContainingFilePath(this);
}
@Override
public ModuleType getModuleType() {
PasModule pm = (PasModule) this;
if (pm.getUnitModuleHead() != null) {
return ModuleType.UNIT;
} else if (pm.getLibraryModuleHead() != null) {
return ModuleType.LIBRARY;
} else if (pm.getPackageModuleHead() != null) {
return ModuleType.PACKAGE;
}
return ModuleType.PROGRAM;
}
@NotNull
private UnitMembers getMembers(Cache<String, Members> cache, Callable<? extends Members> builder) {
ensureChache(cache);
try {
return (UnitMembers) 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;
}
}
@Override
@Nullable
public final PasField getField(final String name) {
PasField result = getPublicField(name);
if (null == result) {
result = getPrivateField(name);
}
return result;
}
@Override
@Nullable
public final PasField getPrivateField(final String name) {
return getMembers(privateCache, PRIVATE_BUILDER).all.get(name.toUpperCase());
}
@Override
@NotNull
public Collection<PasField> getPrivateFields() {
return getMembers(privateCache, PRIVATE_BUILDER).all.values();
}
@Override
public List<SmartPsiElementPointer<PasEntityScope>> getPrivateUnits() {
return getMembers(privateCache, PRIVATE_BUILDER).units;
}
@Override
@Nullable
public final PasField getPublicField(final String name) {
return getMembers(publicCache, PUBLIC_BUILDER).all.get(name.toUpperCase());
}
@Override
@NotNull
public Collection<PasField> getPubicFields() {
return getMembers(publicCache, PUBLIC_BUILDER).all.values();
}
@Override
public List<SmartPsiElementPointer<PasEntityScope>> getPublicUnits() {
return getMembers(publicCache, PUBLIC_BUILDER).units;
}
@NotNull
private Idents getIdents(Cache<String, Idents> cache, Callable<? extends Idents> 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 idents for: " + this, e.getCause());
}
invalidateCaches(getKey());
return EMPTY_IDENTS;
}
}
@Override
public Pair<List<PascalNamedElement>, List<PascalNamedElement>> getIdentsFrom(@NotNull String module) {
Idents idents = getIdents(identCache, IDENTS_BUILDER);
Pair<List<PascalNamedElement>, List<PascalNamedElement>> res = new Pair<List<PascalNamedElement>, List<PascalNamedElement>>(
new SmartList<PascalNamedElement>(), new SmartList<PascalNamedElement>());
for (Map.Entry<String, PasField> entry : idents.idents.entrySet()) {
PasField field = entry.getValue();
if ((field != null) && PasField.isAllowed(field.visibility, PasField.Visibility.PRIVATE)
&& PasField.TYPES_STRUCTURE.contains(field.fieldType)
&& (field.owner != null) && (module.equalsIgnoreCase(field.owner.getName()))) {
if (entry.getKey().startsWith(INTERFACE_PREFIX)) {
res.getFirst().add(field.getElement());
} else {
res.getSecond().add(field.getElement());
}
}
}
return res;
}
public static void invalidate(String key) {
privateCache.invalidate(key);
publicCache.invalidate(key);
identCache.invalidate(key);
}
private static class Idents extends Cached {
Map<String, PasField> idents = new HashMap<String, PasField>();
}
private class IdentsBuilder implements Callable<Idents> {
@Override
public Idents call() throws Exception {
Idents res = new Idents();
//noinspection unchecked
for (PascalNamedElement namedElement : PsiUtil.findChildrenOfAnyType(PascalModuleImpl.this, PasSubIdentImpl.class, PasRefNamedIdentImpl.class)) {
if (!PsiUtil.isLastPartOfMethodImplName(namedElement)) {
Collection<PasField> refs = PasReferenceUtil.resolveExpr(null, NamespaceRec.fromElement(namedElement), PasField.TYPES_ALL, true, 0);
if (!refs.isEmpty()) {
String name = (PsiUtil.belongsToInterface(namedElement) ? INTERFACE_PREFIX : "") + PsiUtil.getUniqueName(namedElement);
res.idents.put(name, refs.iterator().next());
}
}
}
res.stamp = getStamp(getContainingFile());
return res;
}
}
private class PrivateBuilder implements Callable<UnitMembers> {
@Override
public UnitMembers call() throws Exception {
PsiElement section = PsiUtil.getModuleImplementationSection(PascalModuleImpl.this);
UnitMembers res = new UnitMembers();
if (!PsiUtil.checkeElement(section)) {
//throw new PasInvalidElementException(section);
return res;
}
collectFields(section, PasField.Visibility.PRIVATE, res.all, res.redeclared);
res.units = retrieveUsedUnits(section);
for (SmartPsiElementPointer<PasEntityScope> unitPtr : res.units) {
PasEntityScope unit = unitPtr.getElement();
if (unit != null) {
res.all.put(unit.getName().toUpperCase(), new PasField(PascalModuleImpl.this, unit, unit.getName(), PasField.FieldType.UNIT, PasField.Visibility.PRIVATE));
}
}
res.stamp = getStamp(getContainingFile());
LOG.debug(String.format("Unit %s private: %d, used: %d", getName(), res.all.size(), res.units != null ? res.units.size() : 0));
return res;
}
}
private class PublicBuilder implements Callable<UnitMembers> {
@Override
public UnitMembers call() throws Exception {
UnitMembers res = new UnitMembers();
res.all.put(getName().toUpperCase(), new PasField(PascalModuleImpl.this, PascalModuleImpl.this, getName(), PasField.FieldType.UNIT, PasField.Visibility.PRIVATE));
res.stamp = getStamp(getContainingFile());
PsiElement section = PsiUtil.getModuleInterfaceSection(PascalModuleImpl.this);
if (null == section) {
//throw new PasInvalidElementException(section);
return res;
}
if ((!PsiUtil.checkeElement(section))) {
//throw new PasInvalidElementException(section);
//return res;
}
collectFields(section, PasField.Visibility.PUBLIC, res.all, res.redeclared);
res.units = retrieveUsedUnits(section);
for (SmartPsiElementPointer<PasEntityScope> unitPtr : res.units) {
PasEntityScope unit = unitPtr.getElement();
if (unit != null) {
res.all.put(unit.getName().toUpperCase(), new PasField(PascalModuleImpl.this, unit, unit.getName(), PasField.FieldType.UNIT, PasField.Visibility.PRIVATE));
}
}
LOG.debug(String.format("Unit %s public: %d, used: %d", getName(), res.all.size(), res.units != null ? res.units.size() : 0));
return res;
}
}
@NotNull
@Override
public Collection<PasField> getAllFields() {
if (!PsiUtil.checkeElement(this)) {
invalidateCaches(getKey());
}
Collection<PasField> result = new LinkedHashSet<PasField>();
result.addAll(getPubicFields());
result.addAll(getPrivateFields());
return result;
}
@SuppressWarnings("unchecked")
private List<SmartPsiElementPointer<PasEntityScope>> retrieveUsedUnits(PsiElement section) {
List<SmartPsiElementPointer<PasEntityScope>> result;
List<PascalQualifiedIdent> usedNames = PsiUtil.getUsedUnits(section);
result = new ArrayList<SmartPsiElementPointer<PasEntityScope>>(usedNames.size());
List<VirtualFile> unitFiles = PasReferenceUtil.findUnitFiles(section.getProject(), ModuleUtilCore.findModuleForPsiElement(section));
Project project = section.getProject();
for (PascalQualifiedIdent ident : usedNames) {
addUnit(result, PasReferenceUtil.findUnit(section.getProject(), unitFiles, ident.getName()), project);
}
for (String unitName : PascalParserUtil.EXPLICIT_UNITS) {
if (!unitName.equalsIgnoreCase(getName())) {
addUnit(result, PasReferenceUtil.findUnit(section.getProject(), unitFiles, unitName), project);
}
}
return result;
}
private static void addUnit(List<SmartPsiElementPointer<PasEntityScope>> result, PasEntityScope unit, Project project) {
if (unit != null) {
result.add(SmartPointerManager.getInstance(project).createSmartPsiElementPointer(unit));
}
}
@NotNull
@Override
public List<SmartPsiElementPointer<PasEntityScope>> getParentScope() {
return Collections.emptyList();
}
@Nullable
@Override
public PasEntityScope getContainingScope() {
return null;
}
}