Extract parts of plugin that depend on Java-only plugins to idea-jvn

This commit is contained in:
Dmitry Jemerov
2017-08-30 15:19:58 +02:00
parent 8b0d606ab5
commit 895d5fda46
18 changed files with 59 additions and 19 deletions
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="PROVIDED" name="idea-full" level="project" />
<orderEntry type="library" scope="PROVIDED" name="junit-plugin" level="project" />
<orderEntry type="library" scope="PROVIDED" name="testng-plugin" level="project" />
<orderEntry type="library" scope="PROVIDED" name="coverage-plugin" level="project" />
<orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="idea" />
<orderEntry type="module" module-name="light-classes" />
<orderEntry type="library" name="java-decompiler-plugin" level="project" />
</component>
</module>
+5
View File
@@ -0,0 +1,5 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<javaCoverageEngineExtension implementation="org.jetbrains.kotlin.idea.coverage.KotlinCoverageExtension"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,7 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceInterface="org.jetbrains.kotlin.idea.internal.KotlinDecompilerService"
serviceImplementation="org.jetbrains.kotlin.idea.internal.KotlinDecompilerServiceImpl"/>
<writingAccessProvider implementation="org.jetbrains.kotlin.idea.internal.DecompiledFileWritingAccessProvider"/>
</extensions>
</idea-plugin>
+6
View File
@@ -0,0 +1,6 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinJUnitRunConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinPatternConfigurationProducer"/>
</extensions>
</idea-plugin>
+5
View File
@@ -0,0 +1,5 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinTestNgConfigurationProducer"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,168 @@
/*
* Copyright 2010-2017 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.idea.coverage
import com.intellij.coverage.CoverageSuitesBundle
import com.intellij.coverage.JavaCoverageAnnotator
import com.intellij.coverage.JavaCoverageEngineExtension
import com.intellij.coverage.PackageAnnotator
import com.intellij.execution.configurations.RunConfigurationBase
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.roots.CompilerModuleExtension
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiNamedElement
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
import org.jetbrains.kotlin.idea.run.JetRunConfiguration
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
class KotlinCoverageExtension : JavaCoverageEngineExtension() {
private val LOG = Logger.getInstance(KotlinCoverageExtension::class.java)
override fun isApplicableTo(conf: RunConfigurationBase?): Boolean = conf is JetRunConfiguration
override fun suggestQualifiedName(sourceFile: PsiFile, classes: Array<out PsiClass>, names: MutableSet<String>): Boolean {
if (sourceFile is KtFile) {
val qNames = collectGeneratedClassQualifiedNames(findOutputRoot(sourceFile), sourceFile)
if (qNames != null) {
names.addAll(qNames)
return true
}
}
return false
}
// Implements API added in IDEA 14.1
override fun getSummaryCoverageInfo(coverageAnnotator: JavaCoverageAnnotator,
element: PsiNamedElement): PackageAnnotator.ClassCoverageInfo? {
if (element !is KtFile) {
return null
}
LOG.info("Retrieving coverage for " + element.name)
val qualifiedNames = collectGeneratedClassQualifiedNames(findOutputRoot(element), element)
return if (qualifiedNames == null) null else totalCoverageForQualifiedNames(coverageAnnotator, qualifiedNames)
}
// Implements API added in IDEA 14.1
override fun keepCoverageInfoForClassWithoutSource(bundle: CoverageSuitesBundle, classFile: File): Boolean {
// TODO check scope and source roots
return true // keep everything, sort it out later
}
override fun collectOutputFiles(srcFile: PsiFile,
output: VirtualFile?,
testoutput: VirtualFile?,
suite: CoverageSuitesBundle,
classFiles: MutableSet<File>): Boolean {
if (srcFile is KtFile) {
val fileIndex = ProjectRootManager.getInstance(srcFile.getProject()).fileIndex
if (fileIndex.isInLibraryClasses(srcFile.getVirtualFile()) ||
fileIndex.isInLibrarySource(srcFile.getVirtualFile())) {
return false
}
runReadAction {
val outputRoot = findOutputRoot(srcFile)
val existingClassFiles = getClassesGeneratedFromFile(outputRoot, srcFile)
existingClassFiles.mapTo(classFiles) { File(it.path) }
}
return true
}
return false
}
companion object {
private val LOG = Logger.getInstance(KotlinCoverageExtension::class.java)
fun collectGeneratedClassQualifiedNames(outputRoot: VirtualFile?, file: KtFile): List<String>? {
val existingClassFiles = getClassesGeneratedFromFile(outputRoot, file)
if (existingClassFiles.isEmpty()) {
return null
}
LOG.debug("Classfiles: [${existingClassFiles.joinToString { it.name }}]")
return existingClassFiles.map {
val relativePath = VfsUtilCore.getRelativePath(it, outputRoot!!)!!
StringUtil.trimEnd(relativePath, ".class").replace("/", ".")
}
}
private fun totalCoverageForQualifiedNames(coverageAnnotator: JavaCoverageAnnotator,
qualifiedNames: List<String>): PackageAnnotator.ClassCoverageInfo {
val result = PackageAnnotator.ClassCoverageInfo()
result.totalClassCount = 0
qualifiedNames.forEach {
val classInfo = coverageAnnotator.getClassCoverageInfo(it)
if (classInfo != null) {
result.totalClassCount += classInfo.totalClassCount
result.coveredClassCount += classInfo.coveredClassCount
result.totalMethodCount += classInfo.totalMethodCount
result.coveredMethodCount += classInfo.coveredMethodCount
result.totalLineCount += classInfo.totalLineCount
result.fullyCoveredLineCount += classInfo.fullyCoveredLineCount
result.partiallyCoveredLineCount += classInfo.partiallyCoveredLineCount
}
else {
LOG.debug("Found no coverage for $it")
}
}
return result
}
private fun getClassesGeneratedFromFile(outputRoot: VirtualFile?, file: KtFile): List<VirtualFile> {
val relativePath = file.packageFqName.asString().replace('.', '/')
val packageOutputDir = outputRoot?.findFileByRelativePath(relativePath)
if (packageOutputDir == null) return listOf()
val prefixes = collectClassFilePrefixes(file)
LOG.debug("Classfile prefixes: [${prefixes.joinToString(", ")}]")
return packageOutputDir.children.filter {
file -> prefixes.any {
(file.name.startsWith(it + "$") && FileUtilRt.getExtension(file.name) == "class") ||
file.name == it + ".class"
}
}
}
private fun findOutputRoot(file: KtFile): VirtualFile? {
val module = ModuleUtilCore.findModuleForPsiElement(file)
if (module == null) return null
val fileIndex = ProjectRootManager.getInstance(file.project).fileIndex
val inTests = fileIndex.isInTestSourceContent(file.virtualFile)
val compilerOutputExtension = CompilerModuleExtension.getInstance(module)
return if (inTests)
compilerOutputExtension!!.compilerOutputPathForTests
else
compilerOutputExtension!!.compilerOutputPath
}
private fun collectClassFilePrefixes(file: KtFile): Collection<String> {
val result = file.children.filter { it is KtClassOrObject }.map { (it as KtClassOrObject).name!! }
val packagePartFqName = JvmFileClassUtil.getFileClassInfoNoResolve(file).fileClassFqName
return result.union(arrayListOf(packagePartFqName.shortName().asString()))
}
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2017 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.idea.internal
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.WritingAccessProvider
import com.intellij.openapi.vfs.ex.dummy.DummyFileSystem
class DecompiledFileWritingAccessProvider : WritingAccessProvider() {
override fun isPotentiallyWritable(file: VirtualFile): Boolean {
if (file.fileSystem is DummyFileSystem && file.parent?.name == KOTLIN_DECOMPILED_FOLDER) {
return false
}
return true
}
override fun requestWriting(vararg files: VirtualFile): Collection<VirtualFile> = emptyList()
}
@@ -0,0 +1,124 @@
/*
* Copyright 2010-2017 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.idea.internal
import com.intellij.openapi.fileTypes.StdFileTypes
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.java.decompiler.IdeaLogger
import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
import org.jetbrains.java.decompiler.main.extern.IResultSaver
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.idea.actions.canBeDecompiledToJava
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
import java.util.jar.Manifest
class KotlinDecompilerServiceImpl : KotlinDecompilerService {
override fun decompile(file: KtFile): String? {
val bytecodeMap = when {
file.canBeDecompiledToJava() -> bytecodeMapForExistingClassfile(file.virtualFile)
!file.isCompiled -> bytecodeMapForSourceFile(file)
else -> return null
}
val resultSaver = KotlinResultSaver()
val options = hashMapOf<String, Any>(
IFernflowerPreferences.REMOVE_BRIDGE to "0"
)
val bytecodeProvider = IBytecodeProvider {
externalPath, _ ->
val path = File(FileUtil.toSystemIndependentName(externalPath))
bytecodeMap[path]?.invoke()
}
val decompiler = BaseDecompiler(bytecodeProvider, resultSaver, options, IdeaLogger())
for (path in bytecodeMap.keys) {
decompiler.addSpace(path, true)
}
decompiler.decompileContext()
return resultSaver.resultText
}
private fun bytecodeMapForExistingClassfile(file: VirtualFile): Map<File, () -> ByteArray> {
val mask = "${file.nameWithoutExtension}$"
val files =
mapOf(file.path to file) +
file.parent.children.filter {
it.nameWithoutExtension.startsWith(mask) && it.fileType === StdFileTypes.CLASS
}.map { it.path to it }
return files.entries.associate {
Pair(File(it.key), { it.value.contentsToByteArray(false) })
}
}
private fun bytecodeMapForSourceFile(file: KtFile): Map<File, () -> ByteArray> {
val configuration = CompilerConfiguration().apply {
languageVersionSettings = file.languageVersionSettings
}
val generationState = KotlinBytecodeToolWindow.compileSingleFile(file, configuration)
val bytecodeMap = hashMapOf<File, () -> ByteArray>()
generationState.factory.asList().filter { FileUtilRt.extensionEquals(it.relativePath, "class") }.forEach {
bytecodeMap[File("/${it.relativePath}").absoluteFile] = { it.asByteArray() }
}
return bytecodeMap
}
class KotlinResultSaver : IResultSaver {
private val decompiledText = mutableMapOf<String, String>()
val resultText: String
get() {
decompiledText.values.singleOrNull()?.let { return it }
return buildString {
for ((filename, content) in decompiledText) {
appendln("// $filename")
append(content)
}
}
}
override fun saveFolder(path: String?) { }
override fun closeArchive(path: String?, archiveName: String?) { }
override fun copyEntry(source: String?, path: String?, archiveName: String?, entry: String?) { }
override fun createArchive(path: String?, archiveName: String?, manifest: Manifest?) { }
override fun saveClassFile(path: String?, qualifiedName: String?, entryName: String?, content: String?, mapping: IntArray?) {
if (entryName != null && content != null) {
decompiledText[entryName] = content
}
}
override fun copyFile(source: String?, path: String?, entryName: String?) { }
override fun saveClassEntry(path: String?, archiveName: String?, qualifiedName: String?, entryName: String?, content: String?) { }
override fun saveDirEntry(path: String?, archiveName: String?, entryName: String?) { }
}
}
@@ -0,0 +1,171 @@
/*
* Copyright 2010-2017 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.idea.run
import com.intellij.execution.*
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.actions.RunConfigurationProducer
import com.intellij.execution.configurations.ModuleBasedConfiguration
import com.intellij.execution.junit.*
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer<JUnitConfiguration>(JUnitConfigurationType.getInstance()) {
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
return other.isProducedBy(JUnitConfigurationProducer::class.java) || other.isProducedBy(PatternConfigurationProducer::class.java)
}
override fun isConfigurationFromContext(configuration: JUnitConfiguration,
context: ConfigurationContext): Boolean {
if (RunConfigurationProducer.getInstance(PatternConfigurationProducer::class.java).isMultipleElementsSelected(context)) {
return false
}
val leaf = context.location?.psiElement ?: return false
val methodLocation = getTestMethodLocation(leaf)
val testClass = getTestClass(leaf)
val testObject = configuration.testObject
if (!testObject.isConfiguredByElement(configuration, testClass, methodLocation?.psiElement, null, null)) {
return false
}
return settingsMatchTemplate(configuration, context)
}
// copied from JUnitConfigurationProducer in IDEA
private fun settingsMatchTemplate(configuration: JUnitConfiguration, context: ConfigurationContext): Boolean {
val predefinedConfiguration = context.getOriginalConfiguration(JUnitConfigurationType.getInstance())
val vmParameters = (predefinedConfiguration as? CommonJavaRunConfigurationParameters)?.vmParameters
if (vmParameters != null && configuration.vmParameters != vmParameters) return false
val template = RunManager.getInstance(configuration.project).getConfigurationTemplate(configurationFactory)
val predefinedModule = (template.configuration as ModuleBasedConfiguration<*>).configurationModule.module
val configurationModule = configuration.configurationModule.module
return configurationModule == context.location?.module || configurationModule == predefinedModule
}
override fun setupConfigurationFromContext(configuration: JUnitConfiguration,
context: ConfigurationContext,
sourceElement: Ref<PsiElement>): Boolean {
if (DumbService.getInstance(context.project).isDumb) return false
val location = context.location ?: return false
val leaf = location.psiElement
if (!ProjectRootsUtil.isInProjectOrLibSource(leaf)) {
return false
}
if (leaf.containingFile !is KtFile) {
return false
}
val ktFile = leaf.containingFile as KtFile
if (TargetPlatformDetector.getPlatform(ktFile) != JvmPlatform) {
return false
}
val methodLocation = getTestMethodLocation(leaf)
if (methodLocation != null) {
val originalModule = configuration.configurationModule.module
configuration.beMethodConfiguration(methodLocation)
configuration.restoreOriginalModule(originalModule)
JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location)
return true
}
val testClass = getTestClass(leaf)
if (testClass != null) {
val originalModule = configuration.configurationModule.module
configuration.beClassConfiguration(testClass)
configuration.restoreOriginalModule(originalModule)
JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location)
return true
}
return false
}
override fun onFirstRun(fromContext: ConfigurationFromContext, context: ConfigurationContext, performRunnable: Runnable) {
val leaf = fromContext.sourceElement
getTestClass(leaf)?.let { testClass ->
val fromContextSubstitute = object : ConfigurationFromContext() {
override fun getConfigurationSettings() = fromContext.configurationSettings
override fun setConfigurationSettings(configurationSettings: RunnerAndConfigurationSettings) {
fromContext.configurationSettings = configurationSettings
}
override fun getSourceElement() = testClass
}
// TODO: use TestClassConfigurationProducer when constructor becomes public
return object : AbstractTestClassConfigurationProducer(JUnitConfigurationType.getInstance()){}
.onFirstRun(fromContextSubstitute, context, performRunnable)
}
super.onFirstRun(fromContext, context, performRunnable)
}
companion object {
fun getTestClass(leaf: PsiElement): PsiClass? {
val containingFile = leaf.containingFile as? KtFile ?: return null
var ktClass = leaf.getParentOfType<KtClass>(false)
if (!ktClass.isJUnitTestClass()) {
ktClass = getTestClassInFile(containingFile)
}
return ktClass?.toLightClass()
}
fun getTestMethodLocation(leaf: PsiElement): Location<PsiMethod>? {
val function = leaf.getParentOfType<KtNamedFunction>(false) ?: return null
val owner = PsiTreeUtil.getParentOfType(function, KtFunction::class.java, KtClass::class.java)
if (owner is KtClass) {
val delegate = owner.toLightClass() ?: return null
val method = delegate.methods.firstOrNull() { it.navigationElement == function } ?: return null
val methodLocation = PsiLocation.fromPsiElement(method)
if (JUnitUtil.isTestMethod(methodLocation, false)) {
return methodLocation
}
}
return null
}
private fun KtClass?.isJUnitTestClass() =
this?.toLightClass()?.let { JUnitUtil.isTestClass(it, false, true) } ?: false
private fun getTestClassInFile(ktFile: KtFile) =
ktFile.declarations.filterIsInstance<KtClass>().singleOrNull { it.isJUnitTestClass() }
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2016 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.idea.run
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.junit.JUnitConfiguration
import com.intellij.execution.junit.PatternConfigurationProducer
import com.intellij.execution.junit.TestClassConfigurationProducer
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiElement
import com.intellij.psi.search.PsiElementProcessor
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.psi.KtClassOrObject
class KotlinPatternConfigurationProducer : PatternConfigurationProducer() {
override fun setupConfigurationFromContext(
configuration: JUnitConfiguration,
context: ConfigurationContext,
sourceElement: Ref<PsiElement>
): Boolean {
return super.setupConfigurationFromContext(configuration, context, sourceElement)
}
override fun collectTestMembers(
psiElements: Array<out PsiElement>,
checkAbstract: Boolean,
checkIsTest: Boolean,
collectingProcessor: PsiElementProcessor.CollectElements<PsiElement>
) {
val adjustedElements = psiElements.mapNotNull { if (it is KtClassOrObject) it.toLightClass() else it }.toTypedArray()
super.collectTestMembers(adjustedElements, checkAbstract, checkIsTest, collectingProcessor)
}
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
return other.isProducedBy(PatternConfigurationProducer::class.java)
|| other.isProducedBy(TestClassConfigurationProducer::class.java)
|| other.isProducedBy(KotlinJUnitRunConfigurationProducer::class.java)
}
}
@@ -0,0 +1,233 @@
/*
* 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.idea.run;
import com.intellij.execution.JavaRunConfigurationExtensionManager;
import com.intellij.execution.Location;
import com.intellij.execution.PsiLocation;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.actions.ConfigurationFromContext;
import com.intellij.execution.junit.InheritorChooser;
import com.intellij.execution.junit2.info.MethodLocation;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiClassUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.theoryinpractice.testng.configuration.TestNGConfiguration;
import com.theoryinpractice.testng.configuration.TestNGConfigurationProducer;
import com.theoryinpractice.testng.util.TestNGUtil;
import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.asJava.LightClassUtilsKt;
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector;
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform;
import java.util.List;
import static org.jetbrains.kotlin.asJava.LightClassUtilsKt.toLightClass;
public class KotlinTestNgConfigurationProducer extends TestNGConfigurationProducer {
@Override
public boolean shouldReplace(ConfigurationFromContext self, ConfigurationFromContext other) {
return other.isProducedBy(TestNGConfigurationProducer.class);
}
@Override
protected boolean setupConfigurationFromContext(
TestNGConfiguration configuration, ConfigurationContext context, Ref<PsiElement> sourceElement
) {
// TODO: check TestNG Pattern running first, before method/class (see TestNGInClassConfigurationProducer for logic)
// TODO: and PsiClassOwner not handled, which is in TestNGInClassConfigurationProducer
Location location = context.getLocation();
if (location == null) {
return false;
}
Project project = context.getProject();
PsiElement leaf = location.getPsiElement();
if (!ProjectRootsUtil.isInProjectOrLibSource(leaf)) {
return false;
}
if (!(leaf.getContainingFile() instanceof KtFile)) {
return false;
}
KtFile ktFile = (KtFile) leaf.getContainingFile();
if (TargetPlatformDetector.getPlatform(ktFile) != JvmPlatform.INSTANCE) {
return false;
}
KtNamedDeclaration declarationToRun = getDeclarationToRun(leaf);
if (declarationToRun instanceof KtNamedFunction) {
KtNamedFunction function = (KtNamedFunction) declarationToRun;
@SuppressWarnings("unchecked")
KtElement owner = PsiTreeUtil.getParentOfType(function, KtFunction.class, KtClass.class);
if (owner instanceof KtClass) {
PsiClass delegate = toLightClass((KtClass) owner);
if (delegate != null) {
for (PsiMethod method : delegate.getMethods()) {
if (method.getNavigationElement() == function) {
if (TestNGUtil.hasTest(method)) {
return configure(configuration, location, context, project, delegate, method);
}
break;
}
}
}
}
}
if (declarationToRun instanceof KtClass) {
PsiClass delegate = toLightClass((KtClassOrObject) declarationToRun);
if (!isTestNGClass(delegate)) {
return false;
}
return configure(configuration, location, context, project, delegate, null);
}
return false;
}
@Override
public void onFirstRun(ConfigurationFromContext configuration, ConfigurationContext context, Runnable startRunnable) {
KtNamedDeclaration declarationToRun = getDeclarationToRun(configuration.getSourceElement());
final PsiNamedElement lightElement = CollectionsKt.firstOrNull(LightClassUtilsKt.toLightElements(declarationToRun));
// Copied from TestNGInClassConfigurationProducer.onFirstRun()
if (lightElement instanceof PsiMethod || lightElement instanceof PsiClass) {
PsiMethod psiMethod;
PsiClass containingClass;
if (lightElement instanceof PsiMethod) {
psiMethod = (PsiMethod)lightElement;
containingClass = psiMethod.getContainingClass();
} else {
psiMethod = null;
containingClass = (PsiClass)lightElement;
}
InheritorChooser inheritorChooser = new InheritorChooser() {
@Override
protected void runForClasses(List<PsiClass> classes, PsiMethod method, ConfigurationContext context, Runnable performRunnable) {
((TestNGConfiguration)context.getConfiguration().getConfiguration()).bePatternConfiguration(classes, method);
super.runForClasses(classes, method, context, performRunnable);
}
@Override
protected void runForClass(PsiClass aClass,
PsiMethod psiMethod,
ConfigurationContext context,
Runnable performRunnable) {
if (lightElement instanceof PsiMethod) {
Project project = psiMethod.getProject();
MethodLocation methodLocation = new MethodLocation(project, psiMethod, PsiLocation.fromPsiElement(aClass));
((TestNGConfiguration)context.getConfiguration().getConfiguration()).setMethodConfiguration(methodLocation);
} else {
((TestNGConfiguration)context.getConfiguration().getConfiguration()).setClassConfiguration(aClass);
}
super.runForClass(aClass, psiMethod, context, performRunnable);
}
};
if (inheritorChooser.runMethodInAbstractClass(context,
startRunnable,
psiMethod,
containingClass,
new Condition<PsiClass>() {
@Override
public boolean value(PsiClass aClass) {
return aClass.hasModifierProperty(PsiModifier.ABSTRACT) &&
TestNGUtil.hasTest(aClass);
}
})) return;
}
super.onFirstRun(configuration, context, startRunnable);
}
@Nullable
private static KtNamedDeclaration getDeclarationToRun(@NotNull PsiElement leaf) {
if (!(leaf.getContainingFile() instanceof KtFile)) return null;
KtFile jetFile = (KtFile) leaf.getContainingFile();
KtNamedFunction function = PsiTreeUtil.getParentOfType(leaf, KtNamedFunction.class, false);
if (function != null) return function;
KtClass ktClass = PsiTreeUtil.getParentOfType(leaf, KtClass.class, false);
if (ktClass != null) return ktClass;
return getClassDeclarationInFile(jetFile);
}
private boolean configure(
TestNGConfiguration configuration, Location location, ConfigurationContext context, Project project,
@Nullable PsiClass delegate, @Nullable PsiMethod method
) {
if (delegate == null) {
return false;
}
setupConfigurationModule(context, configuration);
Module originalModule = configuration.getConfigurationModule().getModule();
configuration.setClassConfiguration(delegate);
if (method != null) {
configuration.setMethodConfiguration(PsiLocation.fromPsiElement(project, method));
}
configuration.restoreOriginalModule(originalModule);
configuration.setName(configuration.getName());
JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location);
return true;
}
private static boolean isTestNGClass(PsiClass psiClass) {
return psiClass != null && PsiClassUtil.isRunnableClass(psiClass, true, false) && TestNGUtil.hasTest(psiClass);
}
@Nullable
static KtClass getClassDeclarationInFile(KtFile jetFile) {
KtClass tempSingleDeclaration = null;
for (KtDeclaration ktDeclaration : jetFile.getDeclarations()) {
if (ktDeclaration instanceof KtClass) {
KtClass declaration = (KtClass) ktDeclaration;
if (tempSingleDeclaration == null) {
tempSingleDeclaration = declaration;
}
else {
// There are several class declarations in file
return null;
}
}
}
return tempSingleDeclaration;
}
}