From 45d92bebc85bcc1cb414a2aa83989a7d741154f3 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Mon, 23 Apr 2012 20:25:15 +0400 Subject: [PATCH] Inner classes are taken out to facilitate testing --- .../compiler/CompilerProcessListener.java | 222 ++++++++++++++++++ .../jet/plugin/compiler/JetCompiler.java | 221 +---------------- .../plugin/compiler/NullProcessHandler.java | 53 +++++ .../plugin/compiler/OutputItemsCollector.java | 24 ++ 4 files changed, 303 insertions(+), 217 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/plugin/compiler/CompilerProcessListener.java create mode 100644 idea/src/org/jetbrains/jet/plugin/compiler/NullProcessHandler.java create mode 100644 idea/src/org/jetbrains/jet/plugin/compiler/OutputItemsCollector.java diff --git a/idea/src/org/jetbrains/jet/plugin/compiler/CompilerProcessListener.java b/idea/src/org/jetbrains/jet/plugin/compiler/CompilerProcessListener.java new file mode 100644 index 00000000000..eff2687c2df --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/compiler/CompilerProcessListener.java @@ -0,0 +1,222 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * 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.jetbrains.jet.plugin.compiler; + +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompilerMessageCategory; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.Nullable; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.intellij.openapi.compiler.CompilerMessageCategory.*; + +/** +* @author abreslav +*/ +public class CompilerProcessListener extends ProcessAdapter { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.jet.plugin.compiler.CompilerProcessListener"); + + private static final Pattern DIAGNOSTIC_PATTERN = Pattern.compile("<(ERROR|WARNING|INFO|EXCEPTION|LOGGING)", Pattern.MULTILINE); + private static final Pattern OPEN_TAG_END_PATTERN = Pattern.compile(">", Pattern.MULTILINE | Pattern.DOTALL); + private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\s*(path|line|column)\\s*=\\s*\"(.*?)\"", Pattern.MULTILINE | Pattern.DOTALL); + private static final Pattern MESSAGE_PATTERN = Pattern.compile("(.*?)", Pattern.MULTILINE | Pattern.DOTALL); + + private enum State { + WAITING, ATTRIBUTES, MESSAGE + } + + private static class CompilerMessage { + private CompilerMessageCategory messageCategory; + private boolean isException; + private @Nullable String url; + private @Nullable Integer line; + private @Nullable Integer column; + private String message; + + public void setMessageCategoryFromString(String tagName) { + if ("ERROR".equals(tagName)) { + messageCategory = ERROR; + } + else if ("EXCEPTION".equals(tagName)) { + messageCategory = ERROR; + isException = true; + } + else if ("WARNING".equals(tagName)) { + messageCategory = WARNING; + } + else if ("LOGGING".equals(tagName)) { + messageCategory = STATISTICS; + } + else { + messageCategory = INFORMATION; + } + } + + public void setAttributeFromStrings(String name, String value) { + if ("path".equals(name)) { + url = "file://" + value.trim(); + } + else if ("line".equals(name)) { + line = safeParseInt(value); + } + else if ("column".equals(name)) { + column = safeParseInt(value); + } + } + + @Nullable + private static Integer safeParseInt(String value) { + try { + return Integer.parseInt(value.trim()); + } + catch (NumberFormatException e) { + return null; + } + } + + public void setMessage(String message) { + this.message = message; + } + + public void reportTo(CompileContext compileContext) { + if (messageCategory == STATISTICS) { + compileContext.getProgressIndicator().setText(message); + } + else { + compileContext.addMessage(messageCategory, message, url == null ? "" : url, line == null ? -1 : line, + column == null + ? -1 + : column); + if (isException) { + LOG.error(message); + } + } + } + } + + private final CompileContext compileContext; + private final OutputItemsCollector collector; + private final StringBuilder output = new StringBuilder(); + private int firstUnprocessedIndex = 0; + private State state = State.WAITING; + private CompilerMessage currentCompilerMessage; + + public CompilerProcessListener(CompileContext compileContext, OutputItemsCollector collector) { + this.compileContext = compileContext; + this.collector = collector; + } + + @Override + public void onTextAvailable(ProcessEvent event, Key outputType) { + String text = event.getText(); + if (outputType == ProcessOutputTypes.STDERR) { + output.append(text); + + // We loop until the state stabilizes + State lastState; + do { + lastState = state; + switch (state) { + case WAITING: { + Matcher matcher = matcher(DIAGNOSTIC_PATTERN); + if (find(matcher)) { + currentCompilerMessage = new CompilerMessage(); + currentCompilerMessage.setMessageCategoryFromString(matcher.group(1)); + state = State.ATTRIBUTES; + } + break; + } + case ATTRIBUTES: { + Matcher matcher = matcher(ATTRIBUTE_PATTERN); + int indexDelta = 0; + while (matcher.find()) { + handleSkippedOutput(output.subSequence(firstUnprocessedIndex + indexDelta, firstUnprocessedIndex + matcher.start())); + currentCompilerMessage.setAttributeFromStrings(matcher.group(1), matcher.group(2)); + indexDelta = matcher.end(); + + } + firstUnprocessedIndex += indexDelta; + + Matcher endMatcher = matcher(OPEN_TAG_END_PATTERN); + if (find(endMatcher)) { + state = State.MESSAGE; + } + break; + } + case MESSAGE: { + Matcher matcher = matcher(MESSAGE_PATTERN); + if (find(matcher)) { + String message = matcher.group(1); + currentCompilerMessage.setMessage(message); + currentCompilerMessage.reportTo(compileContext); + + if (currentCompilerMessage.messageCategory == STATISTICS) { + collector.learn(message); + } + + state = State.WAITING; + } + break; + } + } + } + while (state != lastState); + + } + else { + compileContext.addMessage(INFORMATION, text, "", -1, -1); + } + } + + private boolean find(Matcher matcher) { + boolean result = matcher.find(); + if (result) { + handleSkippedOutput(output.subSequence(firstUnprocessedIndex, firstUnprocessedIndex + matcher.start())); + firstUnprocessedIndex += matcher.end(); + } + return result; + } + + private Matcher matcher(Pattern pattern) { + return pattern.matcher(output.subSequence(firstUnprocessedIndex, output.length())); + } + + @Override + public void processTerminated(ProcessEvent event) { + if (firstUnprocessedIndex < output.length()) { + handleSkippedOutput(output.substring(firstUnprocessedIndex).trim()); + } + int exitCode = event.getExitCode(); + // 0 is normal, 1 is "errors found" - handled by the messages above + if (exitCode != 0 && exitCode != 1) { + compileContext.addMessage(ERROR, "Compiler terminated with exit code: " + exitCode, "", -1, -1); + } + } + + private void handleSkippedOutput(CharSequence substring) { + String message = substring.toString(); + if (!message.trim().isEmpty()) { + compileContext.addMessage(ERROR, message, "", -1, -1); + } + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java b/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java index 6e337505b1e..cc49331ef43 100644 --- a/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java +++ b/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java @@ -25,7 +25,6 @@ import com.intellij.execution.configurations.SimpleJavaParameters; import com.intellij.execution.process.*; import com.intellij.openapi.compiler.CompileContext; import com.intellij.openapi.compiler.CompileScope; -import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.compiler.TranslatingCompiler; import com.intellij.openapi.compiler.ex.CompileContextEx; import com.intellij.openapi.diagnostic.Logger; @@ -34,14 +33,12 @@ import com.intellij.openapi.projectRoots.JavaSdkType; import com.intellij.openapi.projectRoots.JdkUtil; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SimpleJavaSdkType; -import com.intellij.openapi.util.Key; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Chunk; import com.intellij.util.SystemProperties; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.plugin.JetFileType; import org.jetbrains.jet.utils.PathUtil; @@ -53,8 +50,6 @@ import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.Charset; import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static com.intellij.openapi.compiler.CompilerMessageCategory.*; @@ -138,7 +133,7 @@ public class JetCompiler implements TranslatingCompiler { return; } - OutputItemsCollector collector = new OutputItemsCollector(outputDir.getPath()); + OutputItemsCollectorImpl collector = new OutputItemsCollectorImpl(outputDir.getPath()); if (RUN_OUT_OF_PROCESS) { runOutOfProcess(compileContext, collector, outputDir, kotlinHome, scriptFile); @@ -354,7 +349,7 @@ public class JetCompiler implements TranslatingCompiler { } } - private static class OutputItemsCollector { + public static class OutputItemsCollectorImpl implements OutputItemsCollector { private static final String FOR_SOURCE_PREFIX = "For source: "; private static final String EMITTING_PREFIX = "Emitting: "; private final String outputPath; @@ -362,10 +357,11 @@ public class JetCompiler implements TranslatingCompiler { private List answer = new ArrayList(); private List sources = new ArrayList(); - private OutputItemsCollector(String outputPath) { + public OutputItemsCollectorImpl(String outputPath) { this.outputPath = outputPath; } + @Override public void learn(String message) { message = message.trim(); if (message.startsWith(FOR_SOURCE_PREFIX)) { @@ -402,213 +398,4 @@ public class JetCompiler implements TranslatingCompiler { return path; } - - private static class NullProcessHandler extends ProcessHandler { - public static NullProcessHandler INSTANCE = new NullProcessHandler(); - @Override - protected void destroyProcessImpl() { - throw new UnsupportedOperationException("destroyProcessImpl is not implemented"); - } - - @Override - protected void detachProcessImpl() { - throw new UnsupportedOperationException("detachProcessImpl is not implemented"); // TODO - } - - @Override - public boolean detachIsDefault() { - throw new UnsupportedOperationException("detachIsDefault is not implemented"); - } - - @Override - public OutputStream getProcessInput() { - throw new UnsupportedOperationException("getProcessInput is not implemented"); - } - } - - private static class CompilerProcessListener extends ProcessAdapter { - private static final Pattern DIAGNOSTIC_PATTERN = Pattern.compile("<(ERROR|WARNING|INFO|EXCEPTION|LOGGING)", Pattern.MULTILINE); - private static final Pattern OPEN_TAG_END_PATTERN = Pattern.compile(">", Pattern.MULTILINE | Pattern.DOTALL); - private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\s*(path|line|column)\\s*=\\s*\"(.*?)\"", Pattern.MULTILINE | Pattern.DOTALL); - private static final Pattern MESSAGE_PATTERN = Pattern.compile("(.*?)", Pattern.MULTILINE | Pattern.DOTALL); - - private enum State { - WAITING, ATTRIBUTES, MESSAGE - } - - private static class CompilerMessage { - private CompilerMessageCategory messageCategory; - private boolean isException; - private @Nullable String url; - private @Nullable Integer line; - private @Nullable Integer column; - private String message; - - public void setMessageCategoryFromString(String tagName) { - if ("ERROR".equals(tagName)) { - messageCategory = ERROR; - } - else if ("EXCEPTION".equals(tagName)) { - messageCategory = ERROR; - isException = true; - } - else if ("WARNING".equals(tagName)) { - messageCategory = WARNING; - } - else if ("LOGGING".equals(tagName)) { - messageCategory = STATISTICS; - } - else { - messageCategory = INFORMATION; - } - } - - public void setAttributeFromStrings(String name, String value) { - if ("path".equals(name)) { - url = "file://" + value.trim(); - } - else if ("line".equals(name)) { - line = safeParseInt(value); - } - else if ("column".equals(name)) { - column = safeParseInt(value); - } - } - - @Nullable - private static Integer safeParseInt(String value) { - try { - return Integer.parseInt(value.trim()); - } - catch (NumberFormatException e) { - return null; - } - } - - public void setMessage(String message) { - this.message = message; - } - - public void reportTo(CompileContext compileContext) { - if (messageCategory == STATISTICS) { - compileContext.getProgressIndicator().setText(message); - } - else { - compileContext.addMessage(messageCategory, message, url == null ? "" : url, line == null ? -1 : line, - column == null - ? -1 - : column); - if (isException) { - LOG.error(message); - } - } - } - } - - private final CompileContext compileContext; - private final OutputItemsCollector collector; - private final StringBuilder output = new StringBuilder(); - private int firstUnprocessedIndex = 0; - private State state = State.WAITING; - private CompilerMessage currentCompilerMessage; - - public CompilerProcessListener(CompileContext compileContext, OutputItemsCollector collector) { - this.compileContext = compileContext; - this.collector = collector; - } - - @Override - public void onTextAvailable(ProcessEvent event, Key outputType) { - String text = event.getText(); - if (outputType == ProcessOutputTypes.STDERR) { - output.append(text); - - // We loop until the state stabilizes - State lastState; - do { - lastState = state; - switch (state) { - case WAITING: { - Matcher matcher = matcher(DIAGNOSTIC_PATTERN); - if (find(matcher)) { - currentCompilerMessage = new CompilerMessage(); - currentCompilerMessage.setMessageCategoryFromString(matcher.group(1)); - state = State.ATTRIBUTES; - } - break; - } - case ATTRIBUTES: { - Matcher matcher = matcher(ATTRIBUTE_PATTERN); - int indexDelta = 0; - while (matcher.find()) { - handleSkippedOutput(output.subSequence(firstUnprocessedIndex + indexDelta, firstUnprocessedIndex + matcher.start())); - currentCompilerMessage.setAttributeFromStrings(matcher.group(1), matcher.group(2)); - indexDelta = matcher.end(); - - } - firstUnprocessedIndex += indexDelta; - - Matcher endMatcher = matcher(OPEN_TAG_END_PATTERN); - if (find(endMatcher)) { - state = State.MESSAGE; - } - break; - } - case MESSAGE: { - Matcher matcher = matcher(MESSAGE_PATTERN); - if (find(matcher)) { - String message = matcher.group(1); - currentCompilerMessage.setMessage(message); - currentCompilerMessage.reportTo(compileContext); - - if (currentCompilerMessage.messageCategory == STATISTICS) { - collector.learn(message); - } - - state = State.WAITING; - } - break; - } - } - } - while (state != lastState); - - } - else { - compileContext.addMessage(INFORMATION, text, "", -1, -1); - } - } - - private boolean find(Matcher matcher) { - boolean result = matcher.find(); - if (result) { - handleSkippedOutput(output.subSequence(firstUnprocessedIndex, firstUnprocessedIndex + matcher.start())); - firstUnprocessedIndex += matcher.end(); - } - return result; - } - - private Matcher matcher(Pattern pattern) { - return pattern.matcher(output.subSequence(firstUnprocessedIndex, output.length())); - } - - @Override - public void processTerminated(ProcessEvent event) { - if (firstUnprocessedIndex < output.length()) { - handleSkippedOutput(output.substring(firstUnprocessedIndex).trim()); - } - int exitCode = event.getExitCode(); - // 0 is normal, 1 is "errors found" - handled by the messages above - if (exitCode != 0 && exitCode != 1) { - compileContext.addMessage(ERROR, "Compiler terminated with exit code: " + exitCode, "", -1, -1); - } - } - - private void handleSkippedOutput(CharSequence substring) { - String message = substring.toString(); - if (!message.trim().isEmpty()) { - compileContext.addMessage(ERROR, message, "", -1, -1); - } - } - } } diff --git a/idea/src/org/jetbrains/jet/plugin/compiler/NullProcessHandler.java b/idea/src/org/jetbrains/jet/plugin/compiler/NullProcessHandler.java new file mode 100644 index 00000000000..1e97a38fd4b --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/compiler/NullProcessHandler.java @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * 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.jetbrains.jet.plugin.compiler; + +import com.intellij.execution.process.ProcessHandler; + +import java.io.OutputStream; + +/** + * @author max + * @author abreslav + */ +public class NullProcessHandler extends ProcessHandler { + public static final NullProcessHandler INSTANCE = new NullProcessHandler(); + + private NullProcessHandler() { + + } + + @Override + protected void destroyProcessImpl() { + throw new UnsupportedOperationException("destroyProcessImpl is not implemented"); + } + + @Override + protected void detachProcessImpl() { + throw new UnsupportedOperationException("detachProcessImpl is not implemented"); // TODO + } + + @Override + public boolean detachIsDefault() { + throw new UnsupportedOperationException("detachIsDefault is not implemented"); + } + + @Override + public OutputStream getProcessInput() { + throw new UnsupportedOperationException("getProcessInput is not implemented"); + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/compiler/OutputItemsCollector.java b/idea/src/org/jetbrains/jet/plugin/compiler/OutputItemsCollector.java new file mode 100644 index 00000000000..49bf2881b77 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/compiler/OutputItemsCollector.java @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * 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.jetbrains.jet.plugin.compiler; + +/** + * @author abreslav + */ +public interface OutputItemsCollector { + void learn(String message); +}