package com.intellij.lang.javascript.flex.build;
import com.intellij.CommonBundle;
import com.intellij.ProjectTopics;
import com.intellij.execution.RunManager;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.flex.FlexCommonBundle;
import com.intellij.flex.FlexCommonUtils;
import com.intellij.lang.javascript.flex.FlexBundle;
import com.intellij.lang.javascript.flex.FlexModuleType;
import com.intellij.lang.javascript.flex.FlexUtils;
import com.intellij.lang.javascript.flex.flexunit.FlexUnitAfterCompileTask;
import com.intellij.lang.javascript.flex.flexunit.FlexUnitPrecompileTask;
import com.intellij.lang.javascript.flex.flexunit.FlexUnitRunConfiguration;
import com.intellij.lang.javascript.flex.projectStructure.model.impl.FlexBuildConfigurationChangeListener;
import com.intellij.lang.javascript.flex.projectStructure.ui.ActiveBuildConfigurationWidget;
import com.intellij.lang.javascript.flex.run.FlashRunConfiguration;
import com.intellij.lang.javascript.flex.sdk.FlexSdkUtils;
import com.intellij.lang.javascript.flex.sdk.FlexmojosSdkType;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.CompileContext;
import com.intellij.openapi.compiler.CompilerManager;
import com.intellij.openapi.compiler.CompilerMessageCategory;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.project.ModuleListener;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ModuleRootListener;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.util.Alarm;
import com.intellij.util.Function;
import com.intellij.util.containers.BidirectionalMap;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.text.StringTokenizer;
import gnu.trove.THashMap;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
/**
* @author Maxim.Mossienko
* Date: Jul 26, 2008
* Time: 3:55:46 PM
*/
public class FlexCompilerHandler extends AbstractProjectComponent {
private static final Logger LOG = Logger.getInstance(FlexCompilerHandler.class.getName());
private final FlexCompilerDependenciesCache myCompilerDependenciesCache;
private BuiltInFlexCompilerHandler myBuiltInFlexCompilerHandler;
private final TObjectIntHashMap<String> commandToIdMap = new TObjectIntHashMap<>();
private final Map<FlexBuildConfiguration.Type, ModuleOrFacetCompileCache> myCompileCache =
new EnumMap<>(FlexBuildConfiguration.Type.class);
@NonNls private static final String FCSH_ASSIGNED_MARKER = "fcsh: Assigned ";
private boolean mySavingConfigOurselves;
private boolean myRequestedQuit;
public static Key<FlexBuildConfiguration> OVERRIDE_BUILD_CONFIG = Key.create("OVERRIDE_FLEX_BUILD_CONFIG");
private ActiveBuildConfigurationWidget myWidget;
private String myLastCompilationMessages;
public String getLastCompilationMessages() {
assert ApplicationManager.getApplication().isUnitTestMode();
return myLastCompilationMessages;
}
public void setLastCompilationMessages(final String lastCompilationMessages) {
assert ApplicationManager.getApplication().isUnitTestMode();
myLastCompilationMessages = lastCompilationMessages;
}
private static class ModuleOrFacetCompileCache {
public final THashMap<Object, String> moduleOrFacetToCommand = new THashMap<>();
public final BidirectionalMap<Object, VirtualFile> moduleOrFacetToAutoGeneratedConfig = new BidirectionalMap<>();
public final THashMap<VirtualFile, Long> configFileToTimestamp = new THashMap<>();
}
public FlexCompilerHandler(final Project project) {
super(project);
MessageBusConnection connection = project.getMessageBus().connect(project);
connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() {
@Override
public void rootsChanged(ModuleRootEvent event) {
quitCompilerShell();
}
});
connection.subscribe(ProjectTopics.MODULES, new ModuleListener() {
@Override
public void modulesRenamed(@NotNull Project project, @NotNull List<Module> modules, @NotNull Function<Module, String> oldNameProvider) {
for (RunnerAndConfigurationSettings settings : RunManager.getInstance(project).getAllSettings()) {
RunConfiguration runConfiguration = settings.getConfiguration();
if (runConfiguration instanceof FlashRunConfiguration) {
((FlashRunConfiguration)runConfiguration).getRunnerParameters().handleModulesRename(modules, oldNameProvider);
}
else if (runConfiguration instanceof FlexUnitRunConfiguration) {
((FlexUnitRunConfiguration)runConfiguration).getRunnerParameters().handleModulesRename(modules, oldNameProvider);
}
}
}
});
connection.subscribe(FlexBuildConfigurationChangeListener.TOPIC, new FlexBuildConfigurationChangeListener() {
@Override
public void buildConfigurationsRenamed(final Map<Pair<String, String>, String> renames) {
for (RunnerAndConfigurationSettings settings : RunManager.getInstance(project).getAllSettings()) {
RunConfiguration runConfiguration = settings.getConfiguration();
if (runConfiguration instanceof FlashRunConfiguration) {
((FlashRunConfiguration)runConfiguration).getRunnerParameters().handleBuildConfigurationsRename(renames);
}
else if (runConfiguration instanceof FlexUnitRunConfiguration) {
((FlexUnitRunConfiguration)runConfiguration).getRunnerParameters().handleBuildConfigurationsRename(renames);
}
}
}
});
myCompilerDependenciesCache = new FlexCompilerDependenciesCache(project);
final MyVirtualFileListener myFileListener = new MyVirtualFileListener();
LocalFileSystem.getInstance().addVirtualFileListener(myFileListener);
Disposer.register(project, new Disposable() {
public void dispose() {
LocalFileSystem.getInstance().removeVirtualFileListener(myFileListener);
}
});
myReadErrStreamAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD,project);
}
public FlexCompilerDependenciesCache getCompilerDependenciesCache() {
return myCompilerDependenciesCache;
}
public BuiltInFlexCompilerHandler getBuiltInFlexCompilerHandler() {
if (myBuiltInFlexCompilerHandler == null) {
myBuiltInFlexCompilerHandler = new BuiltInFlexCompilerHandler(myProject);
}
return myBuiltInFlexCompilerHandler;
}
private ModuleOrFacetCompileCache getCache(FlexBuildConfiguration.Type type) {
ModuleOrFacetCompileCache cache = myCompileCache.get(type);
if (cache == null) {
cache = new ModuleOrFacetCompileCache();
myCompileCache.put(type, cache);
}
return cache;
}
@NotNull
public String getComponentName() {
return "FlexCompilerHandler";
}
public static FlexCompilerHandler getInstance(Project project) {
return project.getComponent(FlexCompilerHandler.class);
}
public void projectOpened() {
CompilerManager compilerManager = CompilerManager.getInstance(myProject);
if (compilerManager != null) {
compilerManager.addBeforeTask(new ValidateFlashConfigurationsPrecompileTask());
compilerManager.addBeforeTask(new FlexUnitPrecompileTask(myProject));
compilerManager.addAfterTask(new FlexUnitAfterCompileTask());
compilerManager.setValidationEnabled(FlexModuleType.getInstance(), false);
}
myWidget = new ActiveBuildConfigurationWidget(myProject);
}
public void projectClosed() {
if (myBuiltInFlexCompilerHandler != null) {
myBuiltInFlexCompilerHandler.stopCompilerProcess();
}
quitCompilerShell();
myCompilerDependenciesCache.clear();
FlexCommonUtils.deleteTempFlexConfigFiles(myProject.getName());
FlexCompilationUtils.deleteUnzippedANEFiles();
myWidget.destroy();
}
public void quitCompilerShell() {
doQuit();
clearFcshRelatedCache();
}
private void doQuit() {
if (!processIsAlive()) return;
myRequestedQuit = true;
try {
sendCommand("quit", new CompilerMessagesBuffer(null, false)); // ignoring input/output
} catch (IOException ex) {
// process exits
}
}
private void clearFcshRelatedCache() {
for (ModuleOrFacetCompileCache compileCache : myCompileCache.values()) {
compileCache.moduleOrFacetToAutoGeneratedConfig.clear();
compileCache.moduleOrFacetToCommand.clear();
compileCache.configFileToTimestamp.clear();
}
commandToIdMap.clear();
}
enum Result {
OK, TARGET_NOT_FOUND, NEED_TO_REPEAT_COMMAND, OUT_OF_MEMORY
}
private Process process;
private OutputStream is;
private InputStreamReader out;
private LineNumberReader err;
private final char[] buf = new char[8192];
public void compileFlexModuleOrAllFlexFacets(final Module module, final CompileContext context) throws IOException {
final FlexBuildConfiguration overriddenConfig = context.getUserData(OVERRIDE_BUILD_CONFIG);
if (overriddenConfig != null && module == null /*overriddenConfig.getModule()*/) {
final Pair<Boolean, String> validationResultWithMessage = Pair.create(true, null);
//FlexCompiler.validateConfiguration(overriddenConfig, module, FlexBundle.message("module.name", module.getName()), false);
if (!validationResultWithMessage.first) {
if (validationResultWithMessage.second != null) {
context.addMessage(CompilerMessageCategory.ERROR, validationResultWithMessage.second, null, -1, -1);
}
return;
}
compileModuleOrFacet(module, context, overriddenConfig, false);
}
else {
final boolean nothingChangedSincePreviousCompilation = false; //myCompilerDependenciesCache.isNothingChangedSincePreviousCompilation(module);
if (ModuleType.get(module) instanceof FlexModuleType) {
final Pair<Boolean, List<VirtualFile>> compilationResult =
compileModuleOrFacet(module, context, null /*FlexBuildConfiguration.getInstance(module)*/, nothingChangedSincePreviousCompilation);
if (compilationResult.first && !compilationResult.second.isEmpty()) {
//myCompilerDependenciesCache.cacheBC(context, module, compilationResult.second);
}
}
else {
boolean wasFailure = false;
Collection<List<VirtualFile>> allConfigFiles = new ArrayList<>();
//for (final FlexFacet facet : FacetManager.getInstance(module).getFacetsByType(FlexFacet.ID)) {
// final Pair<Boolean, List<VirtualFile>> compilationResult =
// compileModuleOrFacet(module, facet, context, null /*FlexBuildConfiguration.getInstance(facet)*/, nothingChangedSincePreviousCompilation);
// if (!compilationResult.first) {
// wasFailure = true;
// }
// if (!compilationResult.second.isEmpty()) {
// allConfigFiles.add(compilationResult.second);
// }
//}
if (!wasFailure && !allConfigFiles.isEmpty()) {
//myCompilerDependenciesCache.cacheBC(context, module, allConfigFiles);
}
}
}
}
/**
* @return first is false if myCompilerDependenciesCache should not be cached;
* second is a list of config files, may be empty if compilation skipped
*/
private Pair<Boolean, List<VirtualFile>> compileModuleOrFacet(final Module module,
//@Nullable final FlexFacet flexFacet,
final CompileContext context,
@NotNull final FlexBuildConfiguration config,
final boolean nothingChangedSincePreviousCompilation) throws IOException {
if (context.getProgressIndicator().isCanceled()) {
return Pair.create(false, Collections.<VirtualFile>emptyList());
}
if (!config.DO_BUILD) {
return Pair.create(true, Collections.<VirtualFile>emptyList());
}
if (context.isMake() && nothingChangedSincePreviousCompilation) {
context.addMessage(CompilerMessageCategory.STATISTICS,
FlexBundle.message("compilation.skipped.because.nothing.changed.in", module.getName()), null, -1, -1);
return Pair.create(true, Collections.<VirtualFile>emptyList());
}
context.getProgressIndicator().setText(FlexBundle.message("compiling.module", module.getName()));
final Object moduleOrFacet = module;
final ModuleOrFacetCompileCache compileCache = getCache(config.getType());
if (!context.isMake()) {
dropIncrementalCompilation(moduleOrFacet, compileCache);
}
final Sdk flexSdk = FlexUtils.getSdkForActiveBC(module);
assert flexSdk != null; // checked in FlexCompiler.validateConfiguration()
final List<VirtualFile> configFiles = Collections.emptyList(); //getConfigFiles(config, module, flexFacet);
if (updateTimestamps(configFiles, compileCache, moduleOrFacet)) {
// force non-incremental compilation because fcsh sometimes doesn't detect some changes in custom compiler config file
dropIncrementalCompilation(moduleOrFacet, compileCache);
}
launchFcshIfNeeded(context, flexSdk);
boolean compilationSuccessful = true;
for (final String _cssFilePath : config.CSS_FILES_LIST) {
/*
final String cssFilePath = FileUtil.toSystemIndependentName(_cssFilePath);
final FlexBuildConfiguration cssConfig = FlexCompilationUtils.createCssConfig(config, cssFilePath);
final List<VirtualFile> cssConfigFiles = getConfigFiles(cssConfig, module, flexFacet, cssFilePath);
final String cssCommand = buildCommand(cssConfigFiles, cssConfig, flexSdk);
FlexCompilationUtils.ensureOutputFileWritable(myProject, cssConfig.getOutputFileFullPath());
compilationSuccessful &= sendCompilationCommand(context, flexSdk, cssCommand);
// no need in incrementality for css files compilation, it's better to release a piece of fcsh heap
final int commandIndex = commandToIdMap.get(cssCommand);
if (commandIndex > 0) {
sendCommand("clear " + commandIndex, new CompilerMessagesBuffer(null, false));
}
*/
}
final String command = buildCommand(configFiles, config, flexSdk);
final String s = compileCache.moduleOrFacetToCommand.get(moduleOrFacet);
final int previousCommandId = commandToIdMap.get(command);
if (!config.USE_CUSTOM_CONFIG_FILE) {
FlexCompilationUtils.ensureOutputFileWritable(myProject, "config.getOutputFileFullPath()");
}
if (s == null || !s.equals(command)) {
if (s != null) {
if (previousCommandId > 0) {
sendCommand("clear " + previousCommandId, new CompilerMessagesBuffer(context, false));
commandToIdMap.remove(command);
}
}
compileCache.moduleOrFacetToCommand.put(moduleOrFacet, command);
compilationSuccessful &= sendCompilationCommand(context, flexSdk, command);
}
else {
compilationSuccessful &= sendCompilationCommand(context, flexSdk, previousCommandId > 0 ? "compile " + previousCommandId : null, command);
}
if (config.getType() == FlexBuildConfiguration.Type.Default) {
if (!compilationSuccessful) {
//myCompilerDependenciesCache.markModuleDirty(module);
}
}
if (!compilationSuccessful) {
// force non-incremental compilation next time (bug in Flex incremental compiler: it doesn't recompile after failed compilation in some curcumstances)
dropIncrementalCompilation(moduleOrFacet, compileCache);
}
return Pair.create(compilationSuccessful, configFiles);
}
private void dropIncrementalCompilation(final Object moduleOrFacet,
final ModuleOrFacetCompileCache compileCache) throws IOException {
final String removedCommand = compileCache.moduleOrFacetToCommand.remove(moduleOrFacet);
final int commandId = commandToIdMap.remove(removedCommand);
if (commandId > 0) {
sendCommand("clear " + commandId, new CompilerMessagesBuffer(null, false));
}
}
/**
* @return true if some of timestamps exists and is out of date
*/
private static boolean updateTimestamps(List<VirtualFile> configFiles, ModuleOrFacetCompileCache compileCache, Object moduleOrFacet) {
boolean result = false;
for (VirtualFile configFile : configFiles) {
final long currentTimestamp = configFile.getModificationCount();
final Long previousTimestamp = compileCache.configFileToTimestamp.get(configFile);
if (previousTimestamp == null || !previousTimestamp.equals(currentTimestamp)) {
if (previousTimestamp != null) {
result = true;
}
compileCache.configFileToTimestamp.put(configFile, currentTimestamp);
}
}
return result;
}
private boolean sendCompilationCommand(final CompileContext context,
final Sdk flexSdk,
final String fullCommand) throws IOException {
return sendCompilationCommand(context, flexSdk, null, fullCommand, true);
}
private boolean sendCompilationCommand(final CompileContext context,
final Sdk flexSdk,
@Nullable final String incrementalCommand,
final String fullCommand) throws IOException {
return sendCompilationCommand(context, flexSdk, incrementalCommand, fullCommand, true);
}
/**
* @return true if compilation completes successfully
*/
private boolean sendCompilationCommand(final CompileContext context,
final Sdk flexSdk,
@Nullable final String incrementalCommand,
final String fullCommand,
final boolean relaunchIfOutOfMemory) throws IOException {
final CompilerMessagesBuffer messagesBuffer = new CompilerMessagesBuffer(context, true);
Result result = sendCommand(incrementalCommand != null ? incrementalCommand : fullCommand, messagesBuffer);
if (result != Result.OUT_OF_MEMORY && messagesBuffer.containsOutOfMemoryError()) {
result = Result.OUT_OF_MEMORY;
}
switch (result) {
case OK:
messagesBuffer.flush();
return !messagesBuffer.containsErrors();
case TARGET_NOT_FOUND:
messagesBuffer.flush();
commandToIdMap.remove(fullCommand);
return sendCompilationCommand(context, flexSdk, null, fullCommand, true);
case NEED_TO_REPEAT_COMMAND:
messagesBuffer.flush();
consumeOutput(null, messagesBuffer);
return sendCompilationCommand(context, flexSdk, null, fullCommand, true);
case OUT_OF_MEMORY:
quitCompilerShell();
messagesBuffer.removeErrorsAndStackTrace();
messagesBuffer.flush();
addOutOfMemoryMessage(context, relaunchIfOutOfMemory);
if (relaunchIfOutOfMemory) {
launchFcshIfNeeded(context, flexSdk);
return sendCompilationCommand(context, flexSdk, null, fullCommand, false);
}
else {
return false;
}
}
return false;
}
private static void addOutOfMemoryMessage(final CompileContext context, final boolean willBeRestarted) {
if (willBeRestarted) {
context.addMessage(CompilerMessageCategory.WARNING,
FlexBundle.message("fcsh.out.of.memory.and.restarted", CommonBundle.settingsActionPath()), null, -1, -1);
}
else {
context.addMessage(CompilerMessageCategory.ERROR,
FlexCommonBundle.message("increase.flex.compiler.heap", CommonBundle.settingsActionPath()), null, -1, -1);
}
}
private void launchFcshIfNeeded(final CompileContext context, final Sdk flexSdk) throws IOException {
if (!processIsAlive() || myRequestedQuit) {
final StringBuilder classpath = new StringBuilder();
classpath.append(FlexCommonUtils.getPathToBundledJar("idea-flex-compiler-fix.jar"));
classpath.append(File.pathSeparatorChar);
classpath.append(FlexCommonUtils.getPathToBundledJar("idea-fcsh-fix.jar"));
if (!(flexSdk.getSdkType() instanceof FlexmojosSdkType)) {
classpath.append(File.pathSeparator).append(FileUtil.toSystemDependentName(flexSdk.getHomePath() + "/lib/fcsh.jar"));
}
final List<String> cmdLineParams =
FlexSdkUtils.getCommandLineForSdkTool(myProject, flexSdk, classpath.toString(), "com.intellij.flex.FcshLauncher", null);
context.addMessage(CompilerMessageCategory.INFORMATION, StringUtil.join(cmdLineParams, " "), null, -1, -1);
final ProcessBuilder builder = new ProcessBuilder(cmdLineParams);
builder.directory(new File(FlexUtils.getFlexCompilerWorkDirPath(myProject, flexSdk)));
process = builder.start();
is = process.getOutputStream();
out = new InputStreamReader(process.getInputStream());
err = new LineNumberReader(new InputStreamReader(process.getErrorStream()));
consumeOutput(null, new CompilerMessagesBuffer(context, false));
clearFcshRelatedCache();
myRequestedQuit = false;
}
}
private boolean processIsAlive() {
boolean processIsAlive = process != null;
if (processIsAlive) {
try {
process.exitValue();
processIsAlive = false;
} catch (IllegalThreadStateException ignored) {}
}
return processIsAlive;
}
@NonNls
@NotNull
private String buildCommand(final List<VirtualFile> configFiles, final FlexBuildConfiguration config, final Sdk flexSdk) { // todo change to FlexCompilationUtils.buildCommand()
final StringBuilder configsParam = new StringBuilder();
final String workDirPathWithSlash = FlexUtils.getFlexCompilerWorkDirPath(myProject, flexSdk) + "/";
for (final VirtualFile configFile : configFiles) {
String relativePathToConfig = configFile.getPath();
if (configFile.getPath().startsWith(workDirPathWithSlash)) {
relativePathToConfig = configFile.getPath().substring(workDirPathWithSlash.length());
}
if (relativePathToConfig.indexOf(' ') >= 0) {
relativePathToConfig = "\"" + relativePathToConfig + "\"";
}
if (configsParam.length() > 0) {
configsParam.append(",");
}
boolean useSdkConfig = config.USE_DEFAULT_SDK_CONFIG_FILE && !(flexSdk.getSdkType() instanceof FlexmojosSdkType);
configsParam.append(" -load-config").append(useSdkConfig ? "+=" : "=").append(relativePathToConfig);
}
@NonNls String s = config.OUTPUT_TYPE.equals(FlexBuildConfiguration.APPLICATION) ? "mxmlc" : "compc";
/*
if (flexSdk.getSdkType() instanceof AirSdkType) {
s += " +configname=air";
}
else if (flexSdk.getSdkType() instanceof AirMobileSdkType) {
s += " +configname=airmobile";
}
*/
s += configsParam;
if(config.ADDITIONAL_COMPILER_OPTIONS != null && config.ADDITIONAL_COMPILER_OPTIONS.length() > 0) {
s += " " + FlexUtils.replacePathMacros(config.ADDITIONAL_COMPILER_OPTIONS, null /*config.getModule()*/, flexSdk.getHomePath());
}
return s;
}
@Nullable
public static VirtualFile getRealFile(final VirtualFile libFile) {
if (libFile.getFileSystem() instanceof JarFileSystem) {
return JarFileSystem.getInstance().getVirtualFileForJar(libFile);
}
return libFile;
}
private Result sendCommand(@NonNls final String command, final CompilerMessagesBuffer messagesBuffer) throws IOException {
trace(TraceType.IN, command);
messagesBuffer.addMessage(CompilerMessageCategory.INFORMATION, command, null, -1, -1);
if (processIsAlive()) {
is.write((command + "\n").getBytes());
is.flush();
final Runnable runnable = new Runnable() {
public void run() {
if (myCancelledReadErrStream) return;
scanErrorStream(messagesBuffer);
myReadErrStreamAlarm.addRequest(this, 100);
}
};
if ("quit".equals(command)) return Result.OK;
myCancelledReadErrStream = false;
myReadErrStreamAlarm.addRequest(runnable, 100);
}
return consumeOutput(command, messagesBuffer);
}
private volatile boolean myCancelledReadErrStream;
private final Alarm myReadErrStreamAlarm;
private void scanErrorStream(final CompilerMessagesBuffer messagesBuffer) {
try {
int available;
while ((available = process.getErrorStream().available()) > 2 || err.ready()) { // 2 is \r\n, prevent lock of read line to appear
@NonNls final String errLine = err.readLine();
trace(TraceType.ERR, errLine);
if (errLine == null || errLine.length() == 0) continue;
ApplicationManager.getApplication().runReadAction(() -> dispatchError(errLine, messagesBuffer));
}
}
catch (IOException ex) {
LOG.error(ex);
}
}
private static void dispatchError(final String errLine, final CompilerMessagesBuffer messagesBuffer) {
final Matcher matcher = FlexCommonUtils.ERROR_PATTERN.matcher(errLine);
if (matcher.matches()) {
final String file = matcher.group(1);
final String additionalInfo = matcher.group(2);
final String line = matcher.group(3);
final String column = matcher.group(4);
final String type = matcher.group(5);
final String message = matcher.group(6);
final CompilerMessageCategory messageCategory =
"Warning".equals(type) ? CompilerMessageCategory.WARNING : CompilerMessageCategory.ERROR;
final VirtualFile relativeFile = VfsUtil.findRelativeFile(file, null);
final StringBuilder fullMessage = new StringBuilder();
if (relativeFile == null) fullMessage.append(file).append(": ");
if (additionalInfo != null) fullMessage.append(additionalInfo).append(' ');
fullMessage.append(message);
messagesBuffer.addMessage(messageCategory, fullMessage.toString(), relativeFile != null ? relativeFile.getUrl() : null,
line != null ? Integer.parseInt(line) : 0, column != null ? Integer.parseInt(column) : 0);
}
else if (isErrorMessage(errLine)) {
final String errorPrefix = "Error: ";
final String errorText = errLine.startsWith(errorPrefix) ? errLine.substring(errorPrefix.length()) : errLine;
messagesBuffer.addMessage(CompilerMessageCategory.ERROR, errorText, null, -1, -1);
}
else {
messagesBuffer.addMessage(CompilerMessageCategory.INFORMATION, errLine, null, -1, -1);
}
}
private static boolean isErrorMessage(final String errLine) {
return errLine.startsWith("Error: ") || errLine.startsWith("Exception in thread \"main\" ");
}
private Result consumeOutput(final String command, @Nullable final CompilerMessagesBuffer messagesBuffer) throws IOException {
Result result = Result.OK;
String lastRead = "";
out:
while(true) {
int read = out.read(buf);
if (read == -1) {
read = err.read(buf);
if (read > 0) {
messagesBuffer.addMessage(CompilerMessageCategory.ERROR, new String(buf, 0, read), null, -1, -1);
}
break;
}
@NonNls String output = lastRead + new String(buf, 0, read);
trace(TraceType.OUT, output);
StringTokenizer tokenizer = new StringTokenizer(output, "\r\n");
while(tokenizer.hasMoreElements()) {
@NonNls String s = tokenizer.nextElement().trim();
if (s.length() == 0) continue;
if (s.startsWith("(fcsh)")) {
if (s.indexOf("need to repeat command") != -1) { // special marker from idea-fcsh-fix;
// if fcsh-idea-fix fails for any reason it launches standard fcsh, so we need to repeat command
result = Result.NEED_TO_REPEAT_COMMAND;
} else if (s.indexOf("out of memory") != -1) {
result = Result.OUT_OF_MEMORY;
}
myCancelledReadErrStream = true;
myReadErrStreamAlarm.cancelAllRequests();
scanErrorStream(messagesBuffer);
break out;
}
if (s.startsWith(FCSH_ASSIGNED_MARKER) && command != null) {
int id = Integer.parseInt(s.substring(FCSH_ASSIGNED_MARKER.length(), s.indexOf(' ', FCSH_ASSIGNED_MARKER.length())));
commandToIdMap.put(command, id);
continue;
} else if(s.startsWith("fcsh: Target") && s.indexOf("not found") != -1) {
result = Result.TARGET_NOT_FOUND;
continue;
}
if (!tokenizer.hasMoreElements() && tokenizer.getCurrentPosition() == output.length()) {
lastRead = s + "\n";
break;
}
messagesBuffer.addMessage(CompilerMessageCategory.INFORMATION, s, null, -1, -1);
}
}
return result;
}
enum TraceType {
IN, OUT, ERR
}
private static void trace(TraceType type, String message) {
System.out.println(type.toString() + ":" + message);
}
private class MyVirtualFileListener implements VirtualFileListener {
@Override
public void propertyChanged(@NotNull final VirtualFilePropertyEvent event) {
handleVirtualFileEvent(event.getFile());
}
@Override
public void contentsChanged(@NotNull final VirtualFileEvent event) {
handleVirtualFileEvent(event.getFile(), true);
}
@Override
public void fileCreated(@NotNull final VirtualFileEvent event) {
handleVirtualFileEvent(event.getFile());
}
@Override
public void fileDeleted(@NotNull final VirtualFileEvent event) {
handleVirtualFileEvent(event.getFile());
}
@Override
public void fileMoved(@NotNull final VirtualFileMoveEvent event) {
handleVirtualFileEvent(event.getFile());
}
@Override
public void fileCopied(@NotNull final VirtualFileCopyEvent event) {
handleVirtualFileEvent(event.getFile());
}
private void handleVirtualFileEvent(final VirtualFile file) {
handleVirtualFileEvent(file, false);
}
private void handleVirtualFileEvent(final VirtualFile file, boolean contentsChanged) {
if (file == null) return;
myCompilerDependenciesCache.markModuleDirtyIfInSourceRoot(file);
//clearAutoGeneratedConfigsIfNeeded(file, contentsChanged);
}
}
}