package com.intellij.lang.javascript.flex.build;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.execution.configurations.CommandLineTokenizer;
import com.intellij.flex.FlexCommonUtils;
import com.intellij.flex.build.FlexCompilerConfigFileUtilBase;
import com.intellij.lang.javascript.JSConditionalCompilationDefinitionsProvider;
import com.intellij.lang.javascript.flex.FlexModuleType;
import com.intellij.lang.javascript.flex.FlexUtils;
import com.intellij.lang.javascript.flex.projectStructure.FlexProjectLevelCompilerOptionsHolder;
import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfiguration;
import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfigurationManager;
import com.intellij.lang.javascript.flex.projectStructure.model.ModuleOrProjectCompilerOptions;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Processor;
import gnu.trove.THashMap;
import org.jdom.Element;
import org.jdom.JDOMException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static com.intellij.lang.javascript.flex.build.FlexCompilerConfigFileUtil.*;
public class JSConditionalCompilationDefinitionsProviderImpl implements JSConditionalCompilationDefinitionsProvider {
private static final String[] CONDITIONAL_COMPILATION_DEFINITION_OPTION_ALIASES = {"define", "compiler.define"};
private final Map<VirtualFile, Long> configFileToTimestamp = new THashMap<>();
private final Map<VirtualFile, Collection<Pair<String, String>>> configFileToConditionalCompilerDefinitions =
new THashMap<>();
@Override
public boolean containsConstant(final Module module, final String namespace, final String constantName) {
if (module != null && ModuleType.get(module) instanceof FlexModuleType
&& !StringUtil.isEmpty(namespace) && !StringUtil.isEmpty(constantName)) {
final boolean searchForPrefix = constantName.contains(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED);
final String searchedName = namespace + "::" + (searchForPrefix ? "" : constantName);
final Ref<Boolean> result = new Ref<>(false);
processConditionalCompilationDefinitions(module, nameAndValue -> {
if ((searchForPrefix && nameAndValue.first.startsWith(searchedName)) ||
(!searchForPrefix && nameAndValue.first.equals(searchedName))) {
result.set(true);
return false;
}
return true;
});
return result.get();
}
return false;
}
@Override
public Collection<String> getConstantNamesForNamespace(final Module module, final String namespace) {
final Collection<String> result = new ArrayList<>();
if (module != null && ModuleType.get(module) instanceof FlexModuleType && !StringUtil.isEmpty(namespace)) {
final String beginning = namespace + "::";
processConditionalCompilationDefinitions(module, nameAndValue -> {
if (nameAndValue.first.startsWith(beginning)) {
result.add(nameAndValue.first.substring(beginning.length()));
}
return true;
});
}
return result;
}
@Override
public Collection<String> getAllConstants(final Module module) {
final Collection<String> result = new ArrayList<>();
if (module != null && ModuleType.get(module) instanceof FlexModuleType) {
processConditionalCompilationDefinitions(module, nameAndValue -> {
result.add(nameAndValue.first);
return true;
});
}
return result;
}
private void processConditionalCompilationDefinitions(final Module module, final Processor<Pair<String, String>> processor) {
final FlexBuildConfigurationManager manager = FlexBuildConfigurationManager.getInstance(module);
final FlexBuildConfiguration bc = manager.getActiveConfiguration();
final ModuleOrProjectCompilerOptions moduleLevelOptions = manager.getModuleLevelCompilerOptions();
final ModuleOrProjectCompilerOptions projectLevelOptions =
FlexProjectLevelCompilerOptionsHolder.getInstance(module.getProject()).getProjectLevelCompilerOptions();
if (!FlexUtils.processCompilerOption(module, bc, "compiler.define", processor)) return;
if (!processDefinitionsFromCompilerOptions(projectLevelOptions.getAdditionalOptions(), processor)) return;
if (!processDefinitionsFromCompilerOptions(moduleLevelOptions.getAdditionalOptions(), processor)) return;
if (!processDefinitionsFromCompilerOptions(bc.getCompilerOptions().getAdditionalOptions(), processor)) return;
for (Pair<String, String> nameAndValue : getDefinitionsFromConfigFile(bc.getCompilerOptions().getAdditionalConfigFilePath())) {
if (!processor.process(nameAndValue)) return;
}
}
private Collection<Pair<String, String>> getDefinitionsFromConfigFile(final String configFilePath) {
if (StringUtil.isEmptyOrSpaces(configFilePath)) return Collections.emptyList();
final VirtualFile configFile = LocalFileSystem.getInstance().findFileByPath(configFilePath);
if (configFile == null || configFile.isDirectory()) return Collections.emptyList();
final FileDocumentManager documentManager = FileDocumentManager.getInstance();
final Document cachedDocument = documentManager.getCachedDocument(configFile);
final Long currentTimestamp = cachedDocument != null ? cachedDocument.getModificationStamp() : configFile.getModificationCount();
final Long cachedTimestamp = configFileToTimestamp.get(configFile);
if (cachedTimestamp == null || !cachedTimestamp.equals(currentTimestamp)) {
configFileToTimestamp.remove(configFile);
configFileToConditionalCompilerDefinitions.remove(configFile);
try {
final Element rootElement = cachedDocument == null
? JDOMUtil.load(configFile.getInputStream())
: JDOMUtil.load(cachedDocument.getCharsSequence());
final Collection<Pair<String, String>> result = new ArrayList<>();
if (rootElement.getName().equals(FlexCompilerConfigFileUtilBase.FLEX_CONFIG)) {
// noinspection unchecked
for (Element compilerElement : rootElement.getChildren(FlexCompilerConfigFileUtilBase.COMPILER, rootElement.getNamespace())) {
// noinspection unchecked
for (Element defineElement : compilerElement.getChildren(DEFINE, rootElement.getNamespace())) {
final String name = defineElement.getChildText(NAME, rootElement.getNamespace());
final String value = defineElement.getChildText(VALUE, rootElement.getNamespace());
if (!StringUtil.isEmpty(name) && value != null) {
result.add(Pair.create(name, value));
}
}
}
}
configFileToTimestamp.put(configFile, currentTimestamp);
configFileToConditionalCompilerDefinitions.put(configFile, result);
return result;
}
catch (JDOMException ignored) {/*ignore*/}
catch (IOException ignored) {/*ignore*/}
return Collections.emptyList();
}
else {
return configFileToConditionalCompilerDefinitions.get(configFile);
}
}
private static boolean processDefinitionsFromCompilerOptions(final String compilerOptions,
final Processor<Pair<String, String>> processor) {
if (StringUtil.isEmpty(compilerOptions)) return true;
for (CommandLineTokenizer stringTokenizer = new CommandLineTokenizer(compilerOptions); stringTokenizer.hasMoreTokens(); ) {
final String token = stringTokenizer.nextToken();
for (String option : CONDITIONAL_COMPILATION_DEFINITION_OPTION_ALIASES) {
if (token.startsWith("-" + option + "=") || token.startsWith("-" + option + "+=")) {
final String optionValue = token.substring(token.indexOf("=") + 1);
final int commaIndex = optionValue.indexOf(',');
if (commaIndex > 0) {
if (!processor.process(Pair.create(optionValue.substring(0, commaIndex), optionValue.substring(commaIndex + 1)))) return false;
}
}
else if (token.equals("-" + option) && stringTokenizer.countTokens() >= 2) {
final String name = stringTokenizer.peekNextToken();
stringTokenizer.nextToken(); // advance tokenizer position
final String value = stringTokenizer.peekNextToken();
if (FlexCommonUtils.canBeCompilerOptionValue(value)) {
stringTokenizer.nextToken(); // advance tokenizer position
if (!processor.process(Pair.create(name, value))) return false;
}
}
}
}
return true;
}
}