From b4190fb1feea1e429e7f5720374c6cb13e1fe8c9 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Thu, 4 Jun 2015 19:16:30 +0300 Subject: [PATCH] Rewrite KotlinOutputParser for Android plugin to make it works with Android Studio 1.3 #KT-7862 Fixed --- .../kotlin/android/KotlinOutputParser.java | 30 ++ .../kotlin/android/KotlinOutputParser.kt | 133 -------- .../android/KotlinOutputParserHelper.kt | 296 ++++++++++++++++++ 3 files changed, 326 insertions(+), 133 deletions(-) create mode 100644 idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParser.java delete mode 100644 idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParser.kt create mode 100644 idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParserHelper.kt diff --git a/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParser.java b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParser.java new file mode 100644 index 00000000000..a4c05e1960d --- /dev/null +++ b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParser.java @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2015 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.kotlin.android; + +import com.android.ide.common.blame.parser.PatternAwareOutputParser; +import com.android.ide.common.blame.parser.util.OutputLineReader; +import com.android.utils.ILogger; + +import java.util.List; + +public class KotlinOutputParser implements PatternAwareOutputParser { + @Override + public boolean parse(String s, OutputLineReader reader, List list, ILogger logger) { + return AndroidPackage.parse(s, reader, list, logger); + } +} diff --git a/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParser.kt b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParser.kt deleted file mode 100644 index f070bdb2364..00000000000 --- a/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParser.kt +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2010-2015 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.kotlin.android - -import com.android.ide.common.blame.output.GradleMessage -import com.android.ide.common.blame.parser.PatternAwareOutputParser -import com.android.ide.common.blame.parser.util.OutputLineReader -import com.android.utils.ILogger -import com.intellij.openapi.util.io.FileUtilRt -import com.intellij.openapi.util.text.StringUtil -import java.io.File -import java.util.regex.Pattern - -public class KotlinOutputParser : PatternAwareOutputParser { - - override fun parse(line: String, reader: OutputLineReader, messages: MutableList, logger: ILogger): Boolean { - val colonIndex1 = line.colon() - val severity = if (colonIndex1 >= 0) line.substringBeforeAndTrim(colonIndex1).parseSeverity() else null - if (severity == null) return false - - val lineWoSeverity = line.substringAfterAndTrim(colonIndex1) - val colonIndex2 = lineWoSeverity.colon().skipDriveOnWin(lineWoSeverity) - if (colonIndex2 >= 0) { - val path = lineWoSeverity.substringBeforeAndTrim(colonIndex2) - val file = File(path) - if (!file.isFile() && FileUtilRt.getExtension(file.getName()) != "kt") { - addMessage(GradleMessage(severity, lineWoSeverity.amendNextLinesIfNeeded(reader)), messages) - return true - } - - val lineWoPath = lineWoSeverity.substringAfterAndTrim(colonIndex2) - val colonIndex3 = lineWoPath.colon() - if (colonIndex3 >= 0) { - val position = lineWoPath.substringBeforeAndTrim(colonIndex3) - - val matcher = POSITION_PATTERN.matcher(position) - val message = lineWoPath.substringAfterAndTrim(colonIndex3).amendNextLinesIfNeeded(reader) - - if (matcher.matches()) { - val lineNumber = matcher.group(1) - val symbolNumber = matcher.group(2) - if (lineNumber != null && symbolNumber != null) { - try { - addMessage(GradleMessage(severity, message, path, lineNumber.toInt(), symbolNumber.toInt()), messages) - return true - } - catch (e: NumberFormatException) { - // ignore - } - - } - } - - addMessage(GradleMessage(severity, message), messages) - return true - } - else { - addMessage(GradleMessage(severity, lineWoSeverity.amendNextLinesIfNeeded(reader)), messages) - return true - } - } - - return false - } -} - -private val COLON = ":" -private val POSITION_PATTERN = Pattern.compile("\\(([0-9]*), ([0-9]*)\\)") - -private fun String.amendNextLinesIfNeeded(reader: OutputLineReader): String { - var nextLine = reader.readLine() - - val builder = StringBuilder(this) - while (nextLine != null && nextLine!!.isNextMessage().not()) { - builder.append("\n").append(nextLine) - if (!reader.hasNextLine()) break - - nextLine = reader.readLine() - } - - if (nextLine != null) reader.pushBack(nextLine!!) - - return builder.toString() -} - -private fun String.isNextMessage(): Boolean { - val colonIndex1 = indexOf(COLON) - return (colonIndex1 >= 0 && substring(0, colonIndex1).parseSeverity() != null) || StringUtil.containsIgnoreCase(this, "FAILURE") -} - -private fun String.parseSeverity(): GradleMessage.Kind? { - return when (this.trim()) { - "e" -> GradleMessage.Kind.ERROR - "w" -> GradleMessage.Kind.WARNING - "i" -> GradleMessage.Kind.INFO - "v" -> GradleMessage.Kind.SIMPLE - else -> null - } -} - -private fun String.substringAfterAndTrim(index: Int) = substring(index + 1).trim() -private fun String.substringBeforeAndTrim(index: Int) = substring(0, index).trim() -private fun String.colon() = indexOf(COLON) -private fun Int.skipDriveOnWin(line: String): Int { - return if (this == 1) line.indexOf(COLON, this + 1) else this -} - -private fun addMessage(message: GradleMessage, messages: MutableList) { - var duplicatesPrevious = false - val messageCount = messages.size() - if (messageCount > 0) { - val lastMessage = messages.get(messageCount - 1) - duplicatesPrevious = lastMessage == message - } - if (!duplicatesPrevious) { - messages.add(message) - } -} - diff --git a/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParserHelper.kt b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParserHelper.kt new file mode 100644 index 00000000000..d0f6aed7714 --- /dev/null +++ b/idea/kotlin-android-plugin/src/org/jetbrains/kotlin/android/KotlinOutputParserHelper.kt @@ -0,0 +1,296 @@ +/* + * Copyright 2010-2015 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.kotlin.android + +import com.android.ide.common.blame.parser.util.OutputLineReader +import com.android.utils.ILogger +import com.google.common.collect.ImmutableList +import com.intellij.openapi.util.io.FileUtilRt +import com.intellij.openapi.util.text.StringUtil +import java.io.File +import java.lang.reflect.Constructor +import java.util.HashMap +import java.util.regex.Pattern +import kotlin.properties.Delegates +import kotlin.reflect.jvm.java +import java.lang.reflect.Array as RArray + +fun parse(line: String, reader: OutputLineReader, messages: MutableList, logger: ILogger): Boolean { + val colonIndex1 = line.colon() + val severity = if (colonIndex1 >= 0) line.substringBeforeAndTrim(colonIndex1) else return false + if (!severity.startsWithSeverityPrefix()) return false + + val lineWoSeverity = line.substringAfterAndTrim(colonIndex1) + val colonIndex2 = lineWoSeverity.colon().skipDriveOnWin(lineWoSeverity) + if (colonIndex2 >= 0) { + val path = lineWoSeverity.substringBeforeAndTrim(colonIndex2) + val file = File(path) + if (!file.isFile() && FileUtilRt.getExtension(file.getName()) != "kt") { + return addMessage(KotlinOutputParserHelper.createMessage(logger, severity, lineWoSeverity.amendNextLinesIfNeeded(reader)), messages) + } + + val lineWoPath = lineWoSeverity.substringAfterAndTrim(colonIndex2) + val colonIndex3 = lineWoPath.colon() + if (colonIndex3 >= 0) { + val position = lineWoPath.substringBeforeAndTrim(colonIndex3) + + val matcher = POSITION_PATTERN.matcher(position) + val message = lineWoPath.substringAfterAndTrim(colonIndex3).amendNextLinesIfNeeded(reader) + + if (matcher.matches()) { + val lineNumber = matcher.group(1) + val symbolNumber = matcher.group(2) + if (lineNumber != null && symbolNumber != null) { + try { + return addMessage(KotlinOutputParserHelper.createMessage(logger, severity, message, path, lineNumber.toInt(), symbolNumber.toInt(), symbolNumber.toInt()), messages) + } + catch (e: NumberFormatException) { + // ignore + } + + } + } + + return addMessage(KotlinOutputParserHelper.createMessage(logger, severity, message), messages) + } + else { + return addMessage(KotlinOutputParserHelper.createMessage(logger, severity, lineWoSeverity.amendNextLinesIfNeeded(reader)), messages) + } + } + + return false +} + +private val COLON = ":" +private val POSITION_PATTERN = Pattern.compile("\\(([0-9]*), ([0-9]*)\\)") + +private fun String.amendNextLinesIfNeeded(reader: OutputLineReader): String { + var nextLine = reader.readLine() + + val builder = StringBuilder(this) + while (nextLine != null && nextLine!!.isNextMessage().not()) { + builder.append("\n").append(nextLine) + if (!reader.hasNextLine()) break + + nextLine = reader.readLine() + } + + if (nextLine != null) reader.pushBack(nextLine!!) + + return builder.toString() +} + +private fun String.isNextMessage(): Boolean { + val colonIndex1 = indexOf(COLON) + return (colonIndex1 >= 0 && substring(0, colonIndex1).startsWithSeverityPrefix()) + || StringUtil.containsIgnoreCase(this, "FAILURE") + || StringUtil.containsIgnoreCase(this, "FAILED") +} + +private fun String.startsWithSeverityPrefix(): Boolean { + return when (this.trim()) { + "e", "w", "i", "v" -> true + else -> false + } +} + +private fun String.substringAfterAndTrim(index: Int) = substring(index + 1).trim() +private fun String.substringBeforeAndTrim(index: Int) = substring(0, index).trim() +private fun String.colon() = indexOf(COLON) +private fun Int.skipDriveOnWin(line: String): Int { + return if (this == 1) line.indexOf(COLON, this + 1) else this +} + +private fun addMessage(message: Any?, messages: MutableList): Boolean { + if (message == null) return false + var duplicatesPrevious = false + val messageCount = messages.size() + if (messageCount > 0) { + val lastMessage = messages.get(messageCount - 1) + duplicatesPrevious = lastMessage == message + } + if (!duplicatesPrevious) { + messages.add(message) + } + return true +} + +object KotlinOutputParserHelper { + private val isNewAndroidPlugin: Boolean + private val packagePrefix: String + private val severityObjectMap: HashMap = HashMap() + + init { + isNewAndroidPlugin = try { + Class.forName("com.android.ide.common.blame.Message") + true + } + catch (e: ClassNotFoundException) { + false + } + + packagePrefix = if (isNewAndroidPlugin) "com.android.ide.common.blame" else "com.android.ide.common.blame.output" + + loadSeverityEnums() + } + + private val simpleMessageConstructor: Constructor<*> by Delegates.lazy { + if (!isNewAndroidPlugin) { + val messageClass = Class.forName("$packagePrefix.GradleMessage") + val messageKindClass = Class.forName("$packagePrefix.GradleMessage\$Kind") + messageClass.getConstructor(messageKindClass, String::class.java) + } + else { + val messageClass = Class.forName("$packagePrefix.Message") + val messageKindClass = Class.forName("$packagePrefix.Message\$Kind") + messageClass.getConstructor( + messageKindClass, + String::class.java, + String::class.java, + ImmutableList::class.java) + } + } + + private val complexMessageConstructor: Constructor<*> by Delegates.lazy { + if (!isNewAndroidPlugin) { + val messageClass = Class.forName("$packagePrefix.GradleMessage") + val messageKindClass = Class.forName("$packagePrefix.GradleMessage\$Kind") + messageClass.getConstructor( + messageKindClass, + String::class.java, String::class.java, + Int::class.java, Int::class.java) + } + else { + val messageClass = Class.forName("$packagePrefix.Message") + val messageKindClass = Class.forName("$packagePrefix.Message\$Kind") + val sourceFilePositionClass = Class.forName("$packagePrefix.SourceFilePosition") + val sourceFilePositionArrayClass = Class.forName("[L$packagePrefix.SourceFilePosition;") + messageClass.getConstructor( + messageKindClass, + String::class.java, + sourceFilePositionClass, + sourceFilePositionArrayClass) + } + } + + private val sourceFilePositionConstructor: Constructor<*> by Delegates.lazy { + assert(isNewAndroidPlugin) { "This property should be used only for New Android Plugin" } + val sourcePositionClass = Class.forName("$packagePrefix.SourcePosition") + val sourceFilePositionClass = Class.forName("$packagePrefix.SourceFilePosition") + sourceFilePositionClass.getConstructor(File::class.java, sourcePositionClass) + } + + private val sourcePositionConstructor: Constructor<*> by Delegates.lazy { + assert(isNewAndroidPlugin) { "This property should be used only for New Android Plugin" } + val sourcePositionClass = Class.forName("$packagePrefix.SourcePosition") + sourcePositionClass.getConstructor(Int::class.java, Int::class.java, Int::class.java) + } + + private val sourcePositionVarargArg: Any by Delegates.lazy { + assert(isNewAndroidPlugin) { "This property should be used only for New Android Plugin" } + val sourceFilePositionClass = Class.forName("$packagePrefix.SourceFilePosition") + RArray.newInstance(sourceFilePositionClass, 0) + } + + private fun loadSeverityEnums() { + val messageKindClass = if (isNewAndroidPlugin) + Class.forName("$packagePrefix.Message\$Kind") + else + Class.forName("$packagePrefix.GradleMessage\$Kind") + + val messageKindConstants = messageKindClass.getEnumConstants() + for (kind in messageKindConstants) { + when(kind.toString()) { + "ERROR" -> severityObjectMap.put("e", kind) + "WARNING" -> severityObjectMap.put("w", kind) + "INFO" -> severityObjectMap.put("i", kind) + "SIMPLE" -> severityObjectMap.put("v", kind) + } + } + } + + fun createMessage( + logger: ILogger, + severity: String, + text: String, + file: String? = null, + lineNumber: Int? = null, + columnIndex: Int? = null, + offset: Int? = null + ): Any? { + try { + val severityConst: Any = severityObjectMap[severity] ?: return null + + if (isNewAndroidPlugin) { + return createNewMessage(severityConst, text.trim(), file, lineNumber, columnIndex, offset) + } + else { + return createOldMessage(severityConst, text.trim(), file, lineNumber, columnIndex) + } + } + catch(e: Throwable) { + logger.error(e, "Exception from KotlinOutputParser") + } + + return null + } + + private fun createNewMessage( + severityConst: Any, + text: String, + file: String?, + lineNumber: Int?, + columnIndex: Int?, + offset: Int? + ): Any? { + if (file == null || lineNumber == null || columnIndex == null || offset == null) { + return simpleMessageConstructor.newInstance( + severityConst, + text, + text, + ImmutableList.of()) + } + else { + val sourcePositionObj = sourcePositionConstructor.newInstance(lineNumber - 1, columnIndex - 1, offset) + val sourceFilePositionObj = sourceFilePositionConstructor.newInstance(File(file), sourcePositionObj) + + return complexMessageConstructor.newInstance( + severityConst, + text, + sourceFilePositionObj, + sourcePositionVarargArg) + } + } + + private fun createOldMessage( + severityConst: Any, + text: String, + file: String?, + lineNumber: Int?, + columnIndex: Int? + ): Any? { + if (file == null || lineNumber == null || columnIndex == null) { + return simpleMessageConstructor.newInstance(severityConst, text) + } + else { + return complexMessageConstructor.newInstance( + severityConst, + text, file, + lineNumber, columnIndex) + } + } +}