/*
* Copyright 2012-2014 Sergey Ignatov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.intellij.erlang.psi.impl;
import com.intellij.extapi.psi.PsiFileBase;
import com.intellij.ide.scratch.ScratchFileType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashMap;
import org.intellij.erlang.ErlangFileType;
import org.intellij.erlang.ErlangLanguage;
import org.intellij.erlang.ErlangTypes;
import org.intellij.erlang.parser.ErlangParserUtil;
import org.intellij.erlang.psi.*;
import org.intellij.erlang.stubs.ErlangCallbackSpecStub;
import org.intellij.erlang.stubs.ErlangFileStub;
import org.intellij.erlang.stubs.types.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static java.util.Collections.*;
import static org.intellij.erlang.psi.impl.ErlangPsiImplUtil.*;
public class ErlangFileImpl extends PsiFileBase implements ErlangFile, PsiNameIdentifierOwner {
private final CachedValue<ErlangModule> myModuleValue =
createCachedValue(new ValueProvider<ErlangModule>() {
@Nullable
@Override
protected ErlangModule computeValue() {
return calcModule();
}
});
private final CachedValue<List<ErlangRule>> myRulesValue =
createCachedValue(new ValueProvider<List<ErlangRule>>() {
@NotNull
@Override
protected List<ErlangRule> computeValue() {
return unmodifiableList(calcRules());
}
});
private final CachedValue<List<ErlangFunction>> myFunctionValue =
createCachedValue(new ValueProvider<List<ErlangFunction>>() {
@NotNull
@Override
public List<ErlangFunction> computeValue() {
return unmodifiableList(calcFunctions());
}
});
private final CachedValue<List<ErlangImportFunction>> myImportValue =
createCachedValue(new ValueProvider<List<ErlangImportFunction>>() {
@NotNull
@Override
public List<ErlangImportFunction> computeValue() {
return unmodifiableList(calcImports());
}
});
private final CachedValue<Set<ErlangFunction>> myExportedFunctionValue =
createCachedValue(new ValueProvider<Set<ErlangFunction>>() {
@NotNull
@Override
public Set<ErlangFunction> computeValue() {
return unmodifiableSet(calcExportedFunctions());
}
});
private final CachedValue<List<ErlangAttribute>> myAttributeValue =
createCachedValue(new ValueProvider<List<ErlangAttribute>>() {
@NotNull
@Override
public List<ErlangAttribute> computeValue() {
return unmodifiableList(calcAttributes());
}
});
private final CachedValue<List<ErlangRecordDefinition>> myRecordValue =
createCachedValue(new ValueProvider<List<ErlangRecordDefinition>>() {
@NotNull
@Override
public List<ErlangRecordDefinition> computeValue() {
return unmodifiableList(calcRecords());
}
});
private final CachedValue<List<ErlangInclude>> myIncludeValue =
createCachedValue(new ValueProvider<List<ErlangInclude>>() {
@NotNull
@Override
public List<ErlangInclude> computeValue() {
return unmodifiableList(calcIncludes());
}
});
private final CachedValue<List<ErlangIncludeLib>> myIncludeLibValue =
createCachedValue(new ValueProvider<List<ErlangIncludeLib>>() {
@NotNull
@Override
public List<ErlangIncludeLib> computeValue() {
return unmodifiableList(calcIncludeLibs());
}
});
private final CachedValue<MultiMap<String, ErlangFunction>> myFunctionsMap =
createCachedValue(new ValueProvider<MultiMap<String, ErlangFunction>>() {
@NotNull
@Override
public MultiMap<String, ErlangFunction> computeValue() {
MultiMap<String, ErlangFunction> map = new MultiMap<>();
for (ErlangFunction function : getFunctions()) {
map.putValue(function.getName(), function);
}
return map;
}
});
private final CachedValue<MultiMap<String, ErlangImportFunction>> myImportsMap =
createCachedValue(new ValueProvider<MultiMap<String, ErlangImportFunction>>() {
@NotNull
@Override
public MultiMap<String, ErlangImportFunction> computeValue() {
MultiMap<String, ErlangImportFunction> map = new MultiMap<>();
for (ErlangImportFunction importFunction : getImportedFunctions()) {
map.putValue(ErlangPsiImplUtil.getName(importFunction), importFunction);
}
return map;
}
});
private final CachedValue<Map<String, ErlangRecordDefinition>> myRecordsMap =
createCachedValue(new ValueProvider<Map<String, ErlangRecordDefinition>>() {
@NotNull
@Override
public Map<String, ErlangRecordDefinition> computeValue() {
Map<String, ErlangRecordDefinition> map = new THashMap<>();
for (ErlangRecordDefinition record : getRecords()) {
String recordName = record.getName();
if (!map.containsKey(recordName)) {
map.put(recordName, record);
}
}
return map;
}
});
private final CachedValue<List<ErlangMacrosDefinition>> myMacrosValue =
createCachedValue(new ValueProvider<List<ErlangMacrosDefinition>>() {
@NotNull
@Override
public List<ErlangMacrosDefinition> computeValue() {
return unmodifiableList(calcMacroses());
}
});
private final CachedValue<Map<String, ErlangMacrosDefinition>> myMacrosesMap =
createCachedValue(new ValueProvider<Map<String, ErlangMacrosDefinition>>() {
@NotNull
@Override
public Map<String, ErlangMacrosDefinition> computeValue() {
Map<String, ErlangMacrosDefinition> map = new THashMap<>();
for (ErlangMacrosDefinition macros : getMacroses()) {
String macrosName = ErlangPsiImplUtil.getName(macros);
if (!map.containsKey(macrosName)) {
map.put(macrosName, macros);
}
}
return map;
}
});
private final CachedValue<List<ErlangTypeDefinition>> myTypeValue =
createCachedValue(new ValueProvider<List<ErlangTypeDefinition>>() {
@NotNull
@Override
public List<ErlangTypeDefinition> computeValue() {
return unmodifiableList(calcTypes());
}
});
private final CachedValue<Map<String, ErlangTypeDefinition>> myTypeMap =
createCachedValue(new ValueProvider<Map<String, ErlangTypeDefinition>>() {
@NotNull
@Override
public Map<String, ErlangTypeDefinition> computeValue() {
Map<String, ErlangTypeDefinition> map = new THashMap<>();
for (ErlangTypeDefinition type : getTypes()) {
String mName = type.getName();
if (!map.containsKey(mName)) {
map.put(mName, type);
}
}
return map;
}
});
private final CachedValue<Map<String, ErlangCallbackSpec>> myCallbackMap =
createCachedValue(new ValueProvider<Map<String, ErlangCallbackSpec>>() {
@NotNull
@Override
public Map<String, ErlangCallbackSpec> computeValue() {
return unmodifiableMap(calcCallbacks());
}
});
private final CachedValue<List<ErlangBehaviour>> myBehavioursValue =
createCachedValue(new ValueProvider<List<ErlangBehaviour>>() {
@NotNull
@Override
public List<ErlangBehaviour> computeValue() {
return unmodifiableList(calcBehaviours());
}
});
private final CachedValue<Collection<ErlangCallbackFunction>> myOptionalCallbacks =
createCachedValue(new ValueProvider<Collection<ErlangCallbackFunction>>() {
@NotNull
@Override
public Collection<ErlangCallbackFunction> computeValue() {
return unmodifiableCollection(calcOptionalCallbacks());
}
});
private final CachedValue<List<ErlangSpecification>> mySpecificationsValue =
createCachedValue(new ValueProvider<List<ErlangSpecification>>() {
@NotNull
@Override
public List<ErlangSpecification> computeValue() {
return unmodifiableList(calcSpecifications());
}
});
private final CachedValue<Boolean> myExportAll =
createCachedValue(new ValueProvider<Boolean>() {
@NotNull
@Override
public Boolean computeValue() {
return calcExportAll();
}
});
private final CachedValue<Boolean> myNoAutoImportAll =
createCachedValue(new ValueProvider<Boolean>() {
@NotNull
@Override
public Boolean computeValue() {
return calcNoAutoImportAll();
}
});
private final CachedValue<Set<String>> myExportedFunctionsSignatures =
createCachedValue(new ValueProvider<Set<String>>() {
@NotNull
@Override
public Set<String> computeValue() {
return unmodifiableSet(calcExportedSignatures());
}
});
private final CachedValue<Set<String>> myNoAutoImportFunctionsSignatures =
createCachedValue(new ValueProvider<Set<String>>() {
@NotNull
@Override
public Set<String> computeValue() {
return unmodifiableSet(calcNoAutoImportSignatures());
}
});
public ErlangFileImpl(@NotNull FileViewProvider viewProvider) {
super(viewProvider, ErlangLanguage.INSTANCE);
}
@Override
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
String nameWithoutExtension = FileUtil.getNameWithoutExtension(name);
for (ErlangAttribute moduleAttributes : getAttributes()) {
ErlangModule module = moduleAttributes.getModule();
if (module != null) {
// todo: use module with dependencies scope
if (!DumbService.isDumb(getProject())) {
Query<PsiReference> search = ReferencesSearch.search(module, GlobalSearchScope.allScope(module.getProject()));
for (PsiReference psiReference : search) {
psiReference.handleElementRename(nameWithoutExtension);
}
}
module.setName(nameWithoutExtension);
}
}
return super.setName(name);
}
@NotNull
@Override
public FileType getFileType() {
FileType fileType = getViewProvider().getFileType();
if ((fileType instanceof ScratchFileType)) return fileType;
if (!(fileType instanceof ErlangFileType) && ApplicationManager.getApplication().isUnitTestMode()) {
return getFileTypeForTests();
}
return fileType;
}
@NotNull
private FileType getFileTypeForTests() {
// when in unit test mode fileViewProvider gets replaced by a mock and we don't get filetype set correctly
assert ApplicationManager.getApplication().isUnitTestMode() :
"This method should only be called in unit tests";
String extension = PathUtil.getFileExtension(getName());
for (ErlangFileType type : ErlangFileType.TYPES) {
for (String defaultExtension : type.getDefaultExtensions()) {
if (StringUtil.equalsIgnoreCase(defaultExtension, extension)) {
return type;
}
}
}
return ErlangFileType.MODULE;
}
@Nullable
@Override
public ErlangFileStub getStub() {
StubElement stub = super.getStub();
return (ErlangFileStub) stub;
}
@Nullable
@Override
public ErlangModule getModule() {
return myModuleValue.getValue();
}
@Nullable
private ErlangModule calcModule() {
ErlangFileStub stub = getStub();
if (stub != null) {
return ArrayUtil.getFirstElement(stub.getChildrenByType(ErlangTypes.ERL_MODULE, ErlangModuleStubElementType.ARRAY_FACTORY));
}
for (ErlangAttribute attribute : getAttributes()) {
ErlangModule module = attribute.getModule();
if (module != null) {
return module;
}
}
return null;
}
@Override
public boolean isExported(@NotNull String signature) {
if (isExportedAll()) return true;
return myExportedFunctionsSignatures.getValue().contains(signature);
}
@Override
public boolean isNoAutoImport(@NotNull String name, int arity) {
if (isNoAutoImportAll()) return true;
return myNoAutoImportFunctionsSignatures.getValue().contains(name + "/" + arity);
}
@NotNull
private Set<String> calcExportedSignatures() {
Set<String> result = ContainerUtil.newHashSet();
for (ErlangAttribute attribute : getAttributes()) {
ErlangExport export = attribute.getExport();
ErlangExportFunctions exportFunctions = export != null ? export.getExportFunctions() : null;
if (exportFunctions == null) continue;
List<ErlangExportFunction> list = exportFunctions.getExportFunctionList();
for (ErlangExportFunction exportFunction : list) {
PsiElement integer = exportFunction.getInteger();
if (integer == null) continue;
String s = ErlangPsiImplUtil.getExportFunctionName(exportFunction) + "/" + integer.getText();
result.add(s);
}
}
return result;
}
@NotNull
private List<ErlangExpression> getCompileDirectiveExpressions() {
List<ErlangExpression> result = ContainerUtil.newArrayList();
for (ErlangAttribute attribute : getAttributes()) {
ErlangAtomAttribute atomAttribute = attribute.getAtomAttribute();
if (atomAttribute != null && "compile".equals(atomAttribute.getName()) && atomAttribute.getAttrVal() != null) {
result.addAll(atomAttribute.getAttrVal().getExpressionList());
}
}
return result;
}
@NotNull
private Set<String> calcNoAutoImportSignatures() {
Set<String> result = ContainerUtil.newHashSet();
for (ErlangExpression expression : getCompileDirectiveExpressions()) {
if (expression instanceof ErlangListExpression) {
for (ErlangExpression tuple : ((ErlangListExpression) expression).getExpressionList()) {
if (tuple instanceof ErlangTupleExpression) {
result.addAll(getNoAutoImportFunctionSignaturesFromTuple((ErlangTupleExpression) tuple));
}
}
}
else if (expression instanceof ErlangTupleExpression) {
result.addAll(getNoAutoImportFunctionSignaturesFromTuple((ErlangTupleExpression) expression));
}
}
return result;
}
@NotNull
private static Set<String> getNoAutoImportFunctionSignaturesFromTuple(@Nullable ErlangTupleExpression tupleExpression) {
final Set<String> result = ContainerUtil.newHashSet();
if (tupleExpression == null || tupleExpression.getExpressionList().size() != 2) return result;
ErlangExpression first = ContainerUtil.getFirstItem(tupleExpression.getExpressionList());
ErlangExpression second = ContainerUtil.getLastItem(tupleExpression.getExpressionList());
if (!(first instanceof ErlangMaxExpression)
|| !(second instanceof ErlangListExpression)
|| !"no_auto_import".equals(getAtomName((ErlangMaxExpression) first))) {
return result;
}
second.accept(new ErlangRecursiveVisitor() {
@Override
public void visitAtomWithArityExpression(@NotNull ErlangAtomWithArityExpression o) {
result.add(createFunctionPresentation(o));
}
@Override
public void visitTupleExpression(@NotNull ErlangTupleExpression o) {
List<ErlangExpression> exprs = o.getExpressionList();
if (exprs.size() != 2) return;
String functionName = getAtomName(ObjectUtils.tryCast(exprs.get(0), ErlangMaxExpression.class));
int functionArity = getArity(ObjectUtils.tryCast(exprs.get(1), ErlangMaxExpression.class));
if (functionName == null || functionArity == -1) return;
result.add(createFunctionPresentation(functionName, functionArity));
}
});
return result;
}
@Override
public boolean isExportedAll() {
//TODO do we use stubs?
ErlangFileStub stub = getStub();
if (stub != null) {
return stub.isExportAll();
}
return myExportAll.getValue();
}
private boolean containsCompileDirectiveWithOption(@NotNull String option) {
for (ErlangExpression expression : getCompileDirectiveExpressions()) {
if (expression instanceof ErlangListExpression) {
for (ErlangExpression e : ((ErlangListExpression) expression).getExpressionList()) {
if (e instanceof ErlangMaxExpression && option.equals(getAtomName((ErlangMaxExpression) e))) {
return true;
}
}
}
else if (expression instanceof ErlangMaxExpression && option.equals(getAtomName((ErlangMaxExpression) expression))) {
return true;
}
}
return false;
}
@Override
public boolean isNoAutoImportAll() {
return myNoAutoImportAll.getValue();
}
@Override
public boolean isBehaviour() {
ErlangFileStub stub = getStub();
if (stub != null) return stub.isBehaviour();
ErlangFunction behaviourInfo = getFunction("behaviour_info", 1);
return behaviourInfo != null && behaviourInfo.isExported() || !getCallbackMap().isEmpty();
}
private boolean calcNoAutoImportAll() {
return containsCompileDirectiveWithOption("no_auto_import");
}
private boolean calcExportAll() {
return containsCompileDirectiveWithOption("export_all");
}
@NotNull
@Override
public List<ErlangRule> getRules() {
return myRulesValue.getValue();
}
@NotNull
@Override
public List<ErlangAttribute> getAttributes() {
return myAttributeValue.getValue();
}
@Nullable
@Override
public ErlangCallbackSpec getCallbackByName(@NotNull String fullName) {
return getCallbackMap().get(fullName);
}
@NotNull
@Override
public Map<String, ErlangCallbackSpec> getCallbackMap() {
//TODO do we use stubs?
ErlangFileStub stub = getStub();
if (stub != null) {
Map<String, ErlangCallbackSpec> callbacksMap = new LinkedHashMap<>();
for (StubElement child : stub.getChildrenStubs()) {
if (child instanceof ErlangCallbackSpecStub) {
String name = ((ErlangCallbackSpecStub) child).getName();
int arity = ((ErlangCallbackSpecStub) child).getArity();
callbacksMap.put(name + "/" + arity, ((ErlangCallbackSpecStub) child).getPsi());
}
}
return callbacksMap;
}
return myCallbackMap.getValue();
}
@NotNull
private Map<String, ErlangCallbackSpec> calcCallbacks() {
Map<String, ErlangCallbackSpec> callbacksMap = new LinkedHashMap<>();
for (ErlangAttribute a : getAttributes()) {
ErlangCallbackSpec spec = a.getCallbackSpec();
if (spec != null) {
String name = ErlangPsiImplUtil.getCallbackSpecName(spec);
int arity = ErlangPsiImplUtil.getCallBackSpecArguments(spec).size();
callbacksMap.put(name + "/" + arity, spec);
}
}
return callbacksMap;
}
@NotNull
@Override
public List<ErlangFunction> getFunctions() {
return myFunctionValue.getValue();
}
@NotNull
@Override
public Collection<ErlangFunction> getExportedFunctions() {
return myExportedFunctionValue.getValue();
}
private Set<ErlangFunction> calcExportedFunctions() {
return ContainerUtil.map2SetNotNull(getFunctions(), f -> f.isExported() ? f : null);
}
@Nullable
@Override
public ErlangFunction getFunction(@NotNull String name, int argsCount) {
MultiMap<String, ErlangFunction> value = myFunctionsMap.getValue();
return getFunctionFromMap(value, name, argsCount);
}
@Override
@NotNull
public Collection<ErlangFunction> getFunctionsByName(@NotNull String name) {
return myFunctionsMap.getValue().get(name);
}
@Nullable
private static ErlangFunction getFunctionFromMap(MultiMap<String, ErlangFunction> value, String name, final int argsCount) {
Collection<ErlangFunction> candidates = value.get(name);
return ContainerUtil.getFirstItem(ContainerUtil.filter(candidates, erlangFunction -> erlangFunction.getArity() == argsCount));
}
@NotNull
@Override
public List<ErlangRecordDefinition> getRecords() {
return myRecordValue.getValue();
}
@NotNull
@Override
public List<ErlangTypeDefinition> getTypes() {
return myTypeValue.getValue();
}
private List<ErlangTypeDefinition> calcTypes() {
return calcChildren(ErlangTypeDefinition.class,
ErlangTypes.ERL_TYPE_DEFINITION,
ErlangTypeDefinitionElementType.ARRAY_FACTORY);
}
@Override
public ErlangTypeDefinition getType(@NotNull String name) {
return myTypeMap.getValue().get(name);
}
@NotNull
@Override
public List<ErlangMacrosDefinition> getMacroses() {
return myMacrosValue.getValue();
}
private List<ErlangMacrosDefinition> calcMacroses() {
return calcChildren(ErlangMacrosDefinition.class,
ErlangTypes.ERL_MACROS_DEFINITION,
ErlangMacrosDefinitionElementType.ARRAY_FACTORY);
}
@Override
public ErlangMacrosDefinition getMacros(@NotNull String name) {
return myMacrosesMap.getValue().get(name);
}
private List<ErlangRecordDefinition> calcRecords() {
return calcChildren(ErlangRecordDefinition.class,
ErlangTypes.ERL_RECORD_DEFINITION,
ErlangRecordDefinitionElementType.ARRAY_FACTORY);
}
@NotNull
@Override
public List<ErlangInclude> getIncludes() {
return myIncludeValue.getValue();
}
@NotNull
@Override
public List<ErlangIncludeLib> getIncludeLibs() {
return myIncludeLibValue.getValue();
}
private List<ErlangInclude> calcIncludes() {
return calcChildren(ErlangInclude.class,
ErlangTypes.ERL_INCLUDE,
ErlangIncludeElementType.ARRAY_FACTORY);
}
private List<ErlangIncludeLib> calcIncludeLibs() {
return calcChildren(ErlangIncludeLib.class,
ErlangTypes.ERL_INCLUDE_LIB,
ErlangIncludeLibElementType.ARRAY_FACTORY);
}
@NotNull
@Override
public List<ErlangBehaviour> getBehaviours() {
return myBehavioursValue.getValue();
}
private List<ErlangBehaviour> calcBehaviours() {
ErlangFileStub stub = getStub();
if (stub != null) {
return getChildrenByType(stub, ErlangTypes.ERL_BEHAVIOUR, ErlangBehaviourStubElementType.ARRAY_FACTORY);
}
return ContainerUtil.mapNotNull(getAttributes(), ErlangAttribute::getBehaviour);
}
@NotNull
@Override
public Collection<ErlangCallbackFunction> getOptionalCallbacks() {
return myOptionalCallbacks.getValue();
}
@NotNull
private Collection<ErlangCallbackFunction> calcOptionalCallbacks() {
ErlangFileStub stub = getStub();
if (stub != null) {
return getChildrenByType(stub, ErlangTypes.ERL_CALLBACK_FUNCTION,
ErlangCallbackFunctionStubElementType.ARRAY_FACTORY);
}
List<ErlangCallbackFunction> optionalCallbacks = ContainerUtil.newArrayList();
for (ErlangAttribute attr : getAttributes()) {
ErlangOptionalCallbacks callbacks = attr.getOptionalCallbacks();
ErlangOptionalCallbackFunctions opts = callbacks != null ? callbacks.getOptionalCallbackFunctions() : null;
optionalCallbacks.addAll(opts != null ? opts.getCallbackFunctionList() : ContainerUtil.<ErlangCallbackFunction>emptyList());
}
return optionalCallbacks;
}
@NotNull
@Override
public List<ErlangSpecification> getSpecifications() {
return mySpecificationsValue.getValue();
}
private List<ErlangSpecification> calcSpecifications() {
ErlangFileStub stub = getStub();
if (stub != null) {
return getChildrenByType(stub, ErlangTypes.ERL_SPECIFICATION, ErlangSpecificationElementType.ARRAY_FACTORY);
}
return ContainerUtil.mapNotNull(getAttributes(), ErlangAttribute::getSpecification);
}
@Override
public ErlangRecordDefinition getRecord(String name) {
return myRecordsMap.getValue().get(name);
}
@Nullable
public ErlangImportFunction getImportedFunction(String name, final int arity) {
MultiMap<String, ErlangImportFunction> importsMap = myImportsMap.getValue();
Collection<ErlangImportFunction> importFunctions = importsMap.get(name);
return ContainerUtil.find(importFunctions, importFunction -> arity == ErlangPsiImplUtil.getArity(importFunction));
}
@NotNull
@Override
public List<ErlangImportFunction> getImportedFunctions() {
return myImportValue.getValue();
}
private List<ErlangImportFunction> calcImports() {
ArrayList<ErlangImportFunction> result = new ArrayList<>();
for (ErlangAttribute attribute : getAttributes()) {
ErlangImportDirective importDirective = attribute.getImportDirective();
ErlangImportFunctions importFunctions = importDirective != null ? importDirective.getImportFunctions() : null;
List<ErlangImportFunction> functions = importFunctions != null ? importFunctions.getImportFunctionList() : null;
ContainerUtil.addAll(result, ContainerUtil.notNullize(functions));
}
return result;
}
private List<ErlangFunction> calcFunctions() {
return calcChildren(ErlangFunction.class,
ErlangTypes.ERL_FUNCTION,
ErlangFunctionStubElementType.ARRAY_FACTORY);
}
private List<ErlangAttribute> calcAttributes() {
return collectChildrenDummyAware(ErlangAttribute.class);
}
private List<ErlangRule> calcRules() {
return collectChildrenDummyAware(ErlangRule.class);
}
@Override
public void addDeclaredParseTransforms(@NotNull Set<String> parseTransforms) {
ErlangFileStub stub = getStub();
if (stub != null) {
String fromStub = stub.getParseTransforms();
List<String> split = fromStub != null ? StringUtil.split(fromStub, ",") : ContainerUtil.<String>emptyList();
parseTransforms.addAll(split);
return;
}
for (ErlangAttribute attribute : getAttributes()) {
ErlangAtomAttribute atomAttribute = attribute.getAtomAttribute();
String attributeName = null != atomAttribute ? atomAttribute.getName() : null;
ErlangAttrVal attrVal = atomAttribute != null ? atomAttribute.getAttrVal() : null;
if (!"compile".equals(attributeName) || attrVal == null) continue;
for (ErlangExpression expression : attrVal.getExpressionList()) {
//TODO support macros
if (expression instanceof ErlangListExpression) {
ErlangPsiImplUtil.extractParseTransforms((ErlangListExpression) expression, parseTransforms);
}
if (expression instanceof ErlangTupleExpression) {
ErlangPsiImplUtil.extractParseTransforms((ErlangTupleExpression) expression, parseTransforms);
}
}
}
}
@Nullable
@Override
public PsiElement getNameIdentifier() {
return null; // hack for inplace rename: InplaceRefactoring#getVariable()
}
private <T extends StubBasedPsiElement<?>> List<T> calcChildren(@NotNull Class<T> clazz,
@NotNull IElementType elementType,
@NotNull ArrayFactory<T> arrayFactory) {
ErlangFileStub stub = getStub();
return stub != null ? getChildrenByType(stub, elementType, arrayFactory) : collectChildrenDummyAware(clazz);
}
@NotNull
private <T> CachedValue<T> createCachedValue(@NotNull CachedValueProvider<T> provider) {
return CachedValuesManager.getManager(getProject()).createCachedValue(provider, false);
}
@NotNull
private <T extends PsiElement> List<T> collectChildrenDummyAware(@NotNull final Class<T> clazz) {
final List<T> result = ContainerUtil.newArrayList();
processChildrenDummyAware(this, element -> {
if (clazz.isInstance(element)) {
//noinspection unchecked
result.add((T)element);
}
return true;
});
return result;
}
private static boolean processChildrenDummyAware(ErlangFileImpl file, final Processor<PsiElement> processor) {
return new Processor<PsiElement>() {
@Override
public boolean process(PsiElement psiElement) {
for (PsiElement child = psiElement.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child instanceof ErlangParserUtil.DummyBlock) {
if (!process(child)) return false;
}
else if (!processor.process(child)) return false;
}
return true;
}
}.process(file);
}
@NotNull
private static <E extends StubBasedPsiElement<?>> List<E> getChildrenByType(@NotNull ErlangFileStub stub,
@NotNull IElementType elementType,
@NotNull ArrayFactory<E> arrayFactory) {
return ContainerUtil.list(stub.getChildrenByType(elementType, arrayFactory));
}
private abstract class ValueProvider<T> implements CachedValueProvider<T> {
@NotNull
@Override
public final Result<T> compute() {
return Result.create(computeValue(), ErlangFileImpl.this);
}
@Nullable
protected abstract T computeValue();
}
}