From 7da92872a8e3c8bbc2782b84a47a6d37c5e2b72a Mon Sep 17 00:00:00 2001 From: Sergey Mashkov Date: Fri, 18 Mar 2016 19:29:51 +0300 Subject: [PATCH] KT-11642 Kotlin Maven configurator tags order --- .../KotlinJavaMavenConfigurator.java | 10 +- .../KotlinJavascriptMavenConfigurator.java | 9 +- .../KotlinMavenConfigurator.java | 197 +++------- .../kotlin/idea/configuration/PomFile.kt | 351 ++++++++++++++++++ .../ConfigureDialogWithModulesAndVersion.java | 3 +- .../js-maven/libraryMissed/pom_after.xml | 15 +- .../js-maven/pluginMissed/pom_after.xml | 30 +- .../js-maven/simpleProject/pom_after.xml | 32 +- .../simpleProjectSnapshot/pom_after.xml | 84 ++--- .../configuration/maven/fixExisting/pom.xml | 42 +++ .../maven/fixExisting/pom_after.xml | 62 ++++ .../maven/libraryMissed/pom_after.xml | 20 +- .../maven/pluginMissed/pom_after.xml | 42 ++- .../maven/simpleProject/pom_after.xml | 44 ++- .../maven/simpleProjectSnapshot/pom_after.xml | 96 ++--- ...ureProjectByChangingFileTestGenerated.java | 6 + 16 files changed, 723 insertions(+), 320 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/configuration/PomFile.kt create mode 100644 idea/testData/configuration/maven/fixExisting/pom.xml create mode 100644 idea/testData/configuration/maven/fixExisting/pom_after.xml diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinJavaMavenConfigurator.java b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinJavaMavenConfigurator.java index 6b462832377..52b17d3f9d9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinJavaMavenConfigurator.java +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinJavaMavenConfigurator.java @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.idea.configuration; import com.intellij.openapi.module.Module; -import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.maven.dom.model.MavenDomPlugin; import org.jetbrains.kotlin.resolve.TargetPlatform; @@ -26,10 +25,11 @@ import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform; public class KotlinJavaMavenConfigurator extends KotlinMavenConfigurator { private static final String NAME = "maven"; private static final String STD_LIB_ID = "kotlin-stdlib"; + private static final String TEST_LIB_ID = "kotlin-test-junit"; private static final String PRESENTABLE_TEXT = "Maven"; public KotlinJavaMavenConfigurator() { - super(STD_LIB_ID, NAME, PRESENTABLE_TEXT); + super(STD_LIB_ID, TEST_LIB_ID, true, NAME, PRESENTABLE_TEXT); } @Override @@ -38,9 +38,9 @@ public class KotlinJavaMavenConfigurator extends KotlinMavenConfigurator { } @Override - protected void createExecutions(VirtualFile virtualFile, MavenDomPlugin kotlinPlugin, Module module) { - createExecution(virtualFile, kotlinPlugin, module, false); - createExecution(virtualFile, kotlinPlugin, module, true); + protected void createExecutions(@NotNull PomFile pomFile, @NotNull MavenDomPlugin kotlinPlugin, @NotNull Module module) { + createExecution(pomFile, kotlinPlugin, module, false); + createExecution(pomFile, kotlinPlugin, module, true); } @NotNull diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinJavascriptMavenConfigurator.java b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinJavascriptMavenConfigurator.java index 0810c7241e8..92e113e1599 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinJavascriptMavenConfigurator.java +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinJavascriptMavenConfigurator.java @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.idea.configuration; import com.intellij.openapi.module.Module; -import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.maven.dom.model.MavenDomPlugin; import org.jetbrains.kotlin.js.resolve.JsPlatform; @@ -32,7 +31,7 @@ public class KotlinJavascriptMavenConfigurator extends KotlinMavenConfigurator { private static final String PRESENTABLE_TEXT = "JavaScript Maven - experimental"; public KotlinJavascriptMavenConfigurator() { - super(STD_LIB_ID, NAME, PRESENTABLE_TEXT); + super(STD_LIB_ID, null, false, NAME, PRESENTABLE_TEXT); } @Override @@ -41,9 +40,9 @@ public class KotlinJavascriptMavenConfigurator extends KotlinMavenConfigurator { } @Override - protected void createExecutions(VirtualFile virtualFile, MavenDomPlugin kotlinPlugin, Module module) { - createExecution(virtualFile, kotlinPlugin, module, false); - createExecution(virtualFile, kotlinPlugin, module, true); + protected void createExecutions(@NotNull PomFile pomFile, @NotNull MavenDomPlugin kotlinPlugin, @NotNull Module module) { + createExecution(pomFile, kotlinPlugin, module, false); + createExecution(pomFile, kotlinPlugin, module, true); } @NotNull diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMavenConfigurator.java b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMavenConfigurator.java index 9acdf764155..e095fc154ba 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMavenConfigurator.java +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMavenConfigurator.java @@ -35,18 +35,21 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.FileTypeIndex; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.xml.XmlTag; -import com.intellij.util.xml.DomElement; +import com.intellij.psi.xml.XmlFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.maven.dom.MavenDomUtil; import org.jetbrains.idea.maven.dom.model.*; +import org.jetbrains.idea.maven.model.MavenId; import org.jetbrains.idea.maven.project.MavenProjectsManager; +import org.jetbrains.idea.maven.utils.MavenArtifactScope; import org.jetbrains.jps.model.java.JavaSourceRootType; import org.jetbrains.kotlin.idea.KotlinPluginUtil; import org.jetbrains.kotlin.idea.framework.ui.ConfigureDialogWithModulesAndVersion; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; public abstract class KotlinMavenConfigurator implements KotlinProjectConfigurator { @@ -65,17 +68,20 @@ public abstract class KotlinMavenConfigurator implements KotlinProjectConfigurat private static final String TEST_COMPILE_EXECUTION_ID = "test-compile"; private static final String COMPILE_EXECUTION_ID = "compile"; - private final String libraryId; + private final String stdlibArtifactId; + private final String testArtifactId; + private final boolean addJunit; private final String name; private final String presentableText; - protected KotlinMavenConfigurator(@NotNull String libraryId, @NotNull String name, @NotNull String presentableText) { - this.libraryId = libraryId; + protected KotlinMavenConfigurator(@NotNull String stdlibArtifactId, @Nullable String testArtifactId, boolean addJunit, @NotNull String name, @NotNull String presentableText) { + this.stdlibArtifactId = stdlibArtifactId; + this.testArtifactId = testArtifactId; + this.addJunit = addJunit; this.name = name; this.presentableText = presentableText; } - @Override public boolean isApplicable(@NotNull Module module) { return KotlinPluginUtil.isMavenModule(module); @@ -99,10 +105,18 @@ public abstract class KotlinMavenConfigurator implements KotlinProjectConfigurat return true; } - MavenDomProjectModel domProjectModel = getMavenDomProjectModel(module); - if (domProjectModel == null) return false; + PsiFile psi = findModulePomFile(module); + if (psi == null + || !psi.isValid() + || !(psi instanceof XmlFile) + || psi.getVirtualFile() == null + || MavenDomUtil.getMavenDomProjectModel(module.getProject(), psi.getVirtualFile()) == null) { + return false; + } - return hasKotlinMavenPlugin(domProjectModel) && hasDependencyOnLibrary(domProjectModel); + PomFile pom = new PomFile((XmlFile) psi); + + return pom.hasPlugin(new MavenId(GROUP_ID, MAVEN_PLUGIN_ID, null)) && pom.hasDependency(new MavenId(GROUP_ID, stdlibArtifactId, null), MavenArtifactScope.COMPILE); } @Override @@ -129,7 +143,7 @@ public abstract class KotlinMavenConfigurator implements KotlinProjectConfigurat protected abstract boolean isKotlinModule(@NotNull Module module); - protected abstract void createExecutions(VirtualFile virtualFile, MavenDomPlugin kotlinPlugin, Module module); + protected abstract void createExecutions(@NotNull PomFile pomFile, @NotNull MavenDomPlugin kotlinPlugin, @NotNull Module module); @NotNull protected String getGoal(boolean isTest) { @@ -147,30 +161,39 @@ public abstract class KotlinMavenConfigurator implements KotlinProjectConfigurat @NotNull final String version, @NotNull NotificationMessageCollector collector ) { - final VirtualFile virtualFile = file.getVirtualFile(); + VirtualFile virtualFile = file.getVirtualFile(); assert virtualFile != null : "Virtual file should exists for psi file " + file.getName(); - final MavenDomProjectModel domModel = MavenDomUtil.getMavenDomProjectModel(module.getProject(), virtualFile); + MavenDomProjectModel domModel = MavenDomUtil.getMavenDomProjectModel(module.getProject(), virtualFile); if (domModel == null) { showErrorMessage(module.getProject(), null); return; } + new WriteCommandAction(file.getProject()) { @Override protected void run(@NotNull Result result) { - addKotlinVersionPropertyIfNeeded(domModel, version); + PomFile pom = new PomFile((XmlFile) file); + pom.addProperty(KOTLIN_VERSION_PROPERTY, version); - if (ConfigureKotlinInProjectUtilsKt.isSnapshot(version)) { - addPluginRepositoryIfNeeded(domModel, ConfigureKotlinInProjectUtilsKt.SNAPSHOT_REPOSITORY); - addLibraryRepositoryIfNeeded(domModel, ConfigureKotlinInProjectUtilsKt.SNAPSHOT_REPOSITORY); + pom.addDependency(new MavenId(GROUP_ID, stdlibArtifactId, "${" + KOTLIN_VERSION_PROPERTY + "}"), MavenArtifactScope.COMPILE, null, false, null); + if (testArtifactId != null) { + pom.addDependency(new MavenId(GROUP_ID, testArtifactId, "${" + KOTLIN_VERSION_PROPERTY + "}"), MavenArtifactScope.TEST, null, false, null); + } + if (addJunit) { + pom.addDependency(new MavenId("junit", "junit", "4.12"), MavenArtifactScope.TEST, null, false, null); } + if (isSnapshot(version)) { + pom.addLibraryRepository(ConfigureKotlinInProjectUtilsKt.SNAPSHOT_REPOSITORY, true, false); + pom.addPluginRepository(ConfigureKotlinInProjectUtilsKt.SNAPSHOT_REPOSITORY, true, false); + } if (ConfigureKotlinInProjectUtilsKt.isEap(version)) { - addPluginRepositoryIfNeeded(domModel, ConfigureKotlinInProjectUtilsKt.EAP_REPOSITORY); - addLibraryRepositoryIfNeeded(domModel, ConfigureKotlinInProjectUtilsKt.EAP_REPOSITORY); + pom.addLibraryRepository(ConfigureKotlinInProjectUtilsKt.EAP_REPOSITORY, true, false); + pom.addPluginRepository(ConfigureKotlinInProjectUtilsKt.EAP_REPOSITORY, true, false); } - addPluginIfNeeded(domModel, module, virtualFile); - addLibraryDependencyIfNeeded(domModel); + MavenDomPlugin plugin = pom.addPlugin(new MavenId(GROUP_ID, MAVEN_PLUGIN_ID, "${" + KOTLIN_VERSION_PROPERTY + "}")); + createExecutions(pom, plugin, module); CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(file); } @@ -180,32 +203,28 @@ public abstract class KotlinMavenConfigurator implements KotlinProjectConfigurat } protected void createExecution( - @NotNull VirtualFile virtualFile, + @NotNull PomFile pomFile, @NotNull MavenDomPlugin kotlinPlugin, @NotNull Module module, boolean isTest ) { - MavenDomPluginExecution execution = kotlinPlugin.getExecutions().addExecution(); - String tagValue = getExecutionId(isTest); - execution.getId().setStringValue(tagValue); - execution.getPhase().setStringValue(getPhase(module, isTest)); - createTagIfNeeded(execution.getGoals(), "goal", getGoal(isTest)); - - XmlTag sourcesTag = createTagIfNeeded(execution.getConfiguration(), "sourceDirs", ""); + MavenDomPluginExecution execution = pomFile.addExecution(kotlinPlugin, getExecutionId(isTest), getPhase(module, isTest), Collections.singletonList(getGoal(isTest))); + List sourceDirs = new ArrayList(); for (ContentEntry contentEntry : ModuleRootManager.getInstance(module).getContentEntries()) { SourceFolder[] folders = contentEntry.getSourceFolders(); for (SourceFolder sourceFolder : folders) { if (isRelatedSourceRoot(isTest, sourceFolder)) { VirtualFile sourceFolderFile = sourceFolder.getFile(); if (sourceFolderFile != null) { - String relativePath = VfsUtilCore.getRelativePath(sourceFolderFile, virtualFile.getParent(), '/'); - XmlTag newTag = sourcesTag.createChildTag("source", sourcesTag.getNamespace(), relativePath, false); - sourcesTag.addSubTag(newTag, true); + String relativePath = VfsUtilCore.getRelativePath(sourceFolderFile, pomFile.getXmlFile().getVirtualFile().getParent(), '/'); + sourceDirs.add(relativePath); } } } } + + pomFile.executionSourceDirs(execution, sourceDirs); } private static boolean isRelatedSourceRoot(boolean isTest, SourceFolder folder) { @@ -213,111 +232,6 @@ public abstract class KotlinMavenConfigurator implements KotlinProjectConfigurat (!isTest && folder.getRootType() == JavaSourceRootType.SOURCE); } - @Nullable - private static MavenDomProjectModel getMavenDomProjectModel(@NotNull Module module) { - PsiFile pomFile = findModulePomFile(module); - if (pomFile == null) return null; - - VirtualFile virtualFile = pomFile.getVirtualFile(); - assert virtualFile != null : "Virtual file should exists for psi file " + pomFile.getName(); - - MavenDomProjectModel domModel = MavenDomUtil.getMavenDomProjectModel(pomFile.getProject(), virtualFile); - assert domModel != null : "maven dom model should not be null"; - return domModel; - } - - private static boolean checkCoordinates( - @NotNull MavenDomShortArtifactCoordinates mavenDomElement, - @NotNull String groupId, - @NotNull String artifactId - ) { - return groupId.equals(mavenDomElement.getGroupId().getRawText()) && artifactId.equals(mavenDomElement.getArtifactId().getRawText()); - } - - private static boolean hasKotlinMavenPlugin(@NotNull MavenDomProjectModel domModel) { - for(MavenDomPlugin mavenDomPlugin : domModel.getBuild().getPlugins().getPlugins()) { - if (checkCoordinates(mavenDomPlugin, GROUP_ID, MAVEN_PLUGIN_ID)) return true; - } - - return false; - } - - private boolean hasDependencyOnLibrary(@NotNull MavenDomProjectModel domModel) { - for(MavenDomDependency mavenDomDependency : domModel.getDependencies().getDependencies()) { - if (checkCoordinates(mavenDomDependency, GROUP_ID, libraryId)) return true; - } - - return false; - } - - private static void addKotlinVersionPropertyIfNeeded(MavenDomProjectModel domModel, String version) { - createTagIfNeeded(domModel.getProperties(), KOTLIN_VERSION_PROPERTY, version); - } - - private static void addLibraryRepositoryIfNeeded(MavenDomProjectModel domModel, RepositoryDescription description) { - MavenDomRepositories repositories = domModel.getRepositories(); - if (!isRepositoryConfigured(repositories.getRepositories(), description.getId())) { - MavenDomRepository newPluginRepository = repositories.addRepository(); - configureRepository(newPluginRepository, description); - } - } - - private static void addPluginRepositoryIfNeeded(MavenDomProjectModel domModel, RepositoryDescription description) { - MavenDomPluginRepositories pluginRepositories = domModel.getPluginRepositories(); - if (!isRepositoryConfigured(pluginRepositories.getPluginRepositories(), description.getId())) { - MavenDomRepository newPluginRepository = pluginRepositories.addPluginRepository(); - configureRepository(newPluginRepository, description); - } - } - - private void addLibraryDependencyIfNeeded(MavenDomProjectModel domModel) { - for (MavenDomDependency dependency : domModel.getDependencies().getDependencies()) { - if (libraryId.equals(dependency.getArtifactId().getStringValue())) { - return; - } - } - - MavenDomDependency dependency = MavenDomUtil.createDomDependency(domModel, null); - dependency.getGroupId().setStringValue("org.jetbrains.kotlin"); - dependency.getArtifactId().setStringValue(libraryId); - dependency.getVersion().setStringValue("${" + KOTLIN_VERSION_PROPERTY + "}"); - } - - private void addPluginIfNeeded(MavenDomProjectModel domModel, Module module, VirtualFile virtualFile) { - MavenDomPlugins plugins = domModel.getBuild().getPlugins(); - for (MavenDomPlugin plugin : plugins.getPlugins()) { - if (MAVEN_PLUGIN_ID.equals(plugin.getArtifactId().getStringValue())) { - return; - } - } - MavenDomPlugin kotlinPlugin = plugins.addPlugin(); - kotlinPlugin.getArtifactId().setStringValue("kotlin-maven-plugin"); - kotlinPlugin.getGroupId().setStringValue("org.jetbrains.kotlin"); - kotlinPlugin.getVersion().setStringValue("${" + KOTLIN_VERSION_PROPERTY + "}"); - createExecutions(virtualFile, kotlinPlugin, module); - } - - private static boolean isRepositoryConfigured(List pluginRepositories, String repositoryId) { - for (MavenDomRepository repository : pluginRepositories) { - if (repositoryId.equals(repository.getId().getStringValue())) { - return true; - } - } - return false; - } - - private static void configureRepository( - @NotNull MavenDomRepository repository, - @NotNull RepositoryDescription repositoryDescription - ) { - repository.getId().setStringValue(repositoryDescription.getId()); - repository.getName().setStringValue(repositoryDescription.getName()); - repository.getUrl().setStringValue(repositoryDescription.getUrl()); - - createTagIfNeeded(repository.getReleases(), "enabled", Boolean.toString(!repositoryDescription.isSnapshot())); - createTagIfNeeded(repository.getSnapshots(), "enabled", Boolean.toString(repositoryDescription.isSnapshot())); - } - @NotNull private static String getPhase(@NotNull Module module, boolean isTest) { if (hasJavaFiles(module)) { @@ -344,15 +258,8 @@ public abstract class KotlinMavenConfigurator implements KotlinProjectConfigurat return null; } - @NotNull - private static XmlTag createTagIfNeeded(@NotNull DomElement parent, @NotNull String tagName, @NotNull String value) { - XmlTag parentTag = parent.ensureTagExists(); - XmlTag tagWithGivenName = parentTag.findFirstSubTag(tagName); - if (tagWithGivenName != null) { - return tagWithGivenName; - } - XmlTag newTag = parentTag.createChildTag(tagName, parentTag.getNamespace(), value, false); - return parentTag.addSubTag(newTag, true); + private static boolean isSnapshot(@NotNull String version) { + return version.contains("SNAPSHOT"); } private static boolean canConfigureFile(@NotNull PsiFile file) { diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/PomFile.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/PomFile.kt new file mode 100644 index 00000000000..739dfba7ca7 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/PomFile.kt @@ -0,0 +1,351 @@ +/* + * 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.configuration + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.XmlElementFactory +import com.intellij.psi.xml.XmlFile +import com.intellij.psi.xml.XmlTag +import com.intellij.psi.xml.XmlText +import com.intellij.util.xml.GenericDomValue +import org.jetbrains.idea.maven.dom.MavenDomElement +import org.jetbrains.idea.maven.dom.MavenDomUtil +import org.jetbrains.idea.maven.dom.model.* +import org.jetbrains.idea.maven.model.MavenId +import org.jetbrains.idea.maven.utils.MavenArtifactScope +import java.util.* + +class PomFile(val xmlFile: XmlFile) { + private val domModel = MavenDomUtil.getMavenDomProjectModel(xmlFile.project, xmlFile.virtualFile) ?: throw IllegalStateException("No DOM model found for pom ${xmlFile.name}") + private val nodesByName = HashMap() + private var projectElement: XmlTag? = null + + init { + xmlFile.document?.accept(object : PsiElementVisitor() { + override fun visitElement(element: PsiElement) { + super.visitElement(element) + + if (element is XmlTag && element.localName in recommendedElementsOrder) { + nodesByName[element.localName] = element + } + else if (element is XmlTag && element.localName == "project") { + projectElement = element + element.acceptChildren(this) + } + else { + element.acceptChildren(this) + } + } + }) + } + + fun addProperty(name: String, value: String) { + projectElement?.let { project -> + val properties = ensureElement(project, "properties") + val existing = properties.children.filterIsInstance().filter { it.localName == name } + + if (existing.isNotEmpty()) { + for (tag in existing) { + val textNode = tag.children.filterIsInstance().firstOrNull() + if (textNode != null) { + textNode.value = value + } + else { + tag.replace(project.createChildTag(name, project.namespace, value, false)) + } + } + } + else { + properties.add(project.createChildTag(name, project.namespace, value, false)) + } + } + } + + fun addDependency(artifact: MavenId, scope: MavenArtifactScope? = null, classifier: String? = null, optional: Boolean = false, systemPath: String? = null): MavenDomDependency { + require(systemPath == null || scope == MavenArtifactScope.SYSTEM) { "systemPath is only applicable for system scope dependency" } + + ensureDependencies() + val versionless = artifact.withNoVersion() + val dependency = domModel.dependencies.dependencies.firstOrNull { it.matches(versionless) } ?: domModel.dependencies.addDependency() + dependency.groupId.stringValue = artifact.groupId + dependency.artifactId.stringValue = artifact.artifactId + dependency.version.stringValue = artifact.version + dependency.classifier.stringValue = classifier + + if (scope != null && scope != MavenArtifactScope.COMPILE) { + dependency.scope.stringValue = scope.name.toLowerCase() + } + + if (optional) { + dependency.optional.value = optional + } + + dependency.systemPath.stringValue = systemPath + dependency.ensureTagExists() + + return dependency + } + + fun addPlugin(artifact: MavenId): MavenDomPlugin { + ensureBuild() + + val groupArtifact = artifact.withNoVersion() + val plugin = domModel.build.plugins.plugins.firstOrNull { it.matches(groupArtifact) } ?: domModel.build.plugins.addPlugin() + plugin.groupId.stringValue = artifact.groupId + plugin.artifactId.stringValue = artifact.artifactId + plugin.version.stringValue = artifact.version + plugin.ensureTagExists() + + return plugin + } + + fun addExecution(plugin: MavenDomPlugin, executionId: String, phase: String, goals: List): MavenDomPluginExecution { + require(goals.isNotEmpty()) { "Execution $executionId requires at least one goal but empty list has been provided" } + require(executionId.isNotEmpty()) { "executionId shouldn't be empty" } + require(phase.isNotEmpty()) { "phase shouldn't be empty" } + + val execution = plugin.executions.executions.firstOrNull { it.id.stringValue == executionId } ?: plugin.executions.addExecution() + execution.id.stringValue = executionId + execution.phase.stringValue = phase + execution.goals.ensureTagExists() + + val existingGoals = execution.goals.goals.mapNotNull { it.rawText } + for (goal in goals.filter { it !in existingGoals }) { + val goalTag = execution.goals.xmlTag.createChildTag("goal", plugin.xmlElementNamespace, goal, false) + execution.goals.xmlTag.add(goalTag) + } + + return execution + } + + fun executionSourceDirs(execution: MavenDomPluginExecution, sourceDirs: List) { + ensureBuild() + + val isTest = execution.goals.goals.any { it.stringValue?.let { "test" in it } ?: false } + val defaultDir = if (isTest) "test" else "main" + val singleDirectoryElement = if (isTest) { + domModel.build.testSourceDirectory + } + else { + domModel.build.sourceDirectory + } + + if (sourceDirs.isEmpty() || sourceDirs.singleOrNull() == "src/$defaultDir/java") { + execution.configuration.xmlTag?.findSubTags("sourceDirs")?.forEach { it.deleteCascade() } + singleDirectoryElement.undefine() + } + else if (sourceDirs.size == 1) { + singleDirectoryElement.stringValue = sourceDirs.single() + execution.configuration.xmlTag?.findSubTags("sourceDirs")?.forEach { it.deleteCascade() } + } + else { + val sourceDirsTag = executionConfiguration(execution, "sourceDirs") + val newSourceDirsTag = execution.configuration.createChildTag("sourceDirs") + for (dir in sourceDirs) { + newSourceDirsTag.add(newSourceDirsTag.createChildTag("source", dir)) + } + sourceDirsTag.replace(newSourceDirsTag) + } + } + + fun executionConfiguration(execution: MavenDomPluginExecution, name: String): XmlTag { + val configurationTag = execution.configuration.ensureTagExists()!! + + val existingTag = configurationTag.findSubTags(name).firstOrNull() + if (existingTag != null) { + return existingTag + } + + val newTag = configurationTag.createChildTag(name, configurationTag.namespace, null, false)!! + return configurationTag.add(newTag) as XmlTag + } + + fun addPluginRepository(id: String, name: String, url: String, snapshots: Boolean = false, releases: Boolean = true): MavenDomRepository { + ensurePluginRepositories() + + return addRepository(id, name, url, snapshots, releases, { domModel.pluginRepositories.pluginRepositories }, { domModel.pluginRepositories.addPluginRepository() }) + } + + fun addLibraryRepository(id: String, name: String, url: String, snapshots: Boolean = false, releases: Boolean = true): MavenDomRepository { + ensureRepositories() + + return addRepository(id, name, url, snapshots, releases, { domModel.repositories.repositories }, { domModel.repositories.addRepository() }) + } + + private fun addRepository(id: String, name: String, url: String, snapshots: Boolean, releases: Boolean, existing: () -> List, create: () -> MavenDomRepository): MavenDomRepository { + + val repository = + existing().firstOrNull { it.id.stringValue == id } ?: + existing().firstOrNull { it.url.stringValue == url } ?: + create() + + if (repository.id.isEmpty()) { + repository.id.stringValue = id + } + if (repository.name.isEmpty()) { + repository.name.stringValue = name + } + if (repository.url.isEmpty()) { + repository.url.stringValue = url + } + repository.releases.enabled.value = repository.releases.enabled.value?.let { it || releases } ?: releases + repository.snapshots.enabled.value = repository.snapshots.enabled.value?.let { it || snapshots } ?: snapshots + + repository.ensureTagExists() + + return repository + } + + fun hasPlugin(artifact: MavenId) = domModel.build.plugins.plugins.any { it.matches(artifact) } + + fun hasDependency(artifact: MavenId, scope: MavenArtifactScope? = null) = + domModel.dependencies.dependencies.any { it.matches(artifact) && (it.scope.stringValue == scope?.name || scope == null && it.scope.stringValue == "compile") } + + fun ensureBuild(): XmlTag = ensureElement(projectElement!!, "build") + fun ensureDependencies(): XmlTag = ensureElement(projectElement!!, "dependencies") + fun ensurePluginRepositories(): XmlTag = ensureElement(projectElement!!, "pluginRepositories") + fun ensureRepositories(): XmlTag = ensureElement(projectElement!!, "repositories") + + private fun MavenDomArtifactCoordinates.matches(artifact: MavenId) = + (artifact.groupId == null || groupId.stringValue == artifact.groupId) + && (artifact.artifactId == null || artifactId.stringValue == artifact.artifactId) + && (artifact.version == null || version.stringValue == artifact.version) + + private fun MavenId.withNoVersion() = MavenId(groupId, artifactId, null) + + private fun MavenDomElement.createChildTag(name: String, value: String? = null) = xmlTag.createChildTag(name, value) + private fun XmlTag.createChildTag(name: String, value: String? = null) = createChildTag(name, namespace, value, false) + + private fun XmlTag.deleteCascade() { + val oldParent = this.parentTag + delete() + + if (oldParent != null && oldParent.subTags.isEmpty()) { + oldParent.deleteCascade() + } + } + + private fun ensureElement(projectElement: XmlTag, localName: String): XmlTag { + require(localName in recommendedElementsOrder) { "You can only ensure presence or the elements from the recommendation list" } + + return nodesByName.getOrPut(localName) { + val tag = projectElement.createChildTag(localName, projectElement.namespace, null, false)!! + val newTag = insertTagImpl(projectElement, tag) + + insertEmptyLines(newTag) + + newTag + } + } + + private fun insertTagImpl(projectElement: XmlTag, tag: XmlTag): XmlTag { + val middle = recommendedOrderAsList.indexOf(tag.localName) + require(middle != -1) { "You can only insert element from the recommendation list" } + + for (idx in middle - 1 downTo 0) { + val reference = nodesByName[recommendedOrderAsList[idx]] + if (reference != null) { + return projectElement.addAfter(tag, reference) as XmlTag + } + } + + for (idx in middle + 1..recommendedOrderAsList.lastIndex) { + val reference = nodesByName[recommendedOrderAsList[idx]] + if (reference != null) { + return projectElement.addBefore(tag, reference) as XmlTag + } + } + + return projectElement.add(tag) as XmlTag + } + + private fun insertEmptyLines(node: XmlTag) { + node.prevSibling?.let { before -> + if (!(before.hasEmptyLine() || before.lastChild?.hasEmptyLine() ?: false)) { + node.parent.addBefore(createEmptyLine(), node) + } + } + node.nextSibling?.let { after -> + if (!(after.hasEmptyLine() || after.firstChild?.hasEmptyLine() ?: false)) { + node.parent.addAfter(createEmptyLine(), node) + } + } + } + + private fun PsiElement.hasEmptyLine() = this is PsiWhiteSpace && text.count { it == '\n' } > 1 + + private fun createEmptyLine(): XmlText { + return XmlElementFactory.getInstance(xmlFile.project).createTagFromText("\n\n").children.first { it is XmlText } as XmlText + } + + private fun GenericDomValue.isEmpty() = !exists() || stringValue.isNullOrEmpty() + + companion object { + // from maven code convention: https://maven.apache.org/developers/conventions/code.html + val recommendedElementsOrder = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """.lines() + .map { it.trim().removePrefix("<").removeSuffix("/>").trim() } + .filter { it.isNotEmpty() } + .toCollection(LinkedHashSet()) + + val recommendedOrderAsList = recommendedElementsOrder.toList() + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/framework/ui/ConfigureDialogWithModulesAndVersion.java b/idea/src/org/jetbrains/kotlin/idea/framework/ui/ConfigureDialogWithModulesAndVersion.java index 8f6ec2f2d43..e9a5ab36291 100644 --- a/idea/src/org/jetbrains/kotlin/idea/framework/ui/ConfigureDialogWithModulesAndVersion.java +++ b/idea/src/org/jetbrains/kotlin/idea/framework/ui/ConfigureDialogWithModulesAndVersion.java @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.framework.ui; import com.google.common.collect.Lists; +import com.google.common.io.Closeables; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonParser; @@ -209,7 +210,7 @@ public class ConfigureDialogWithModulesAndVersion extends DialogWrapper { } } finally { - streamReader.close(); + Closeables.closeQuietly(streamReader); } } finally { diff --git a/idea/testData/configuration/js-maven/libraryMissed/pom_after.xml b/idea/testData/configuration/js-maven/libraryMissed/pom_after.xml index 6eaa12da828..7082e96db15 100644 --- a/idea/testData/configuration/js-maven/libraryMissed/pom_after.xml +++ b/idea/testData/configuration/js-maven/libraryMissed/pom_after.xml @@ -8,6 +8,7 @@ maventest 1.0-SNAPSHOT + org.jetbrains.kotlin @@ -21,10 +22,21 @@ js + + js-test + test-compile + + test-js + + + + $VERSION$ + + org.jetbrains.kotlin @@ -32,9 +44,6 @@ ${kotlin.version} - - $VERSION$ - \ No newline at end of file diff --git a/idea/testData/configuration/js-maven/pluginMissed/pom_after.xml b/idea/testData/configuration/js-maven/pluginMissed/pom_after.xml index c5ffea482d7..dc6ae174135 100644 --- a/idea/testData/configuration/js-maven/pluginMissed/pom_after.xml +++ b/idea/testData/configuration/js-maven/pluginMissed/pom_after.xml @@ -7,7 +7,19 @@ maventest maventest 1.0-SNAPSHOT + + $VERSION$ + + + + org.jetbrains.kotlin + kotlin-js-library + ${kotlin.version} + + + + org.jetbrains.kotlin @@ -20,11 +32,6 @@ js - - - - - js-test @@ -32,24 +39,11 @@ test-js - - - - - $VERSION$ - - - - org.jetbrains.kotlin - kotlin-js-library - ${kotlin.version} - - \ No newline at end of file diff --git a/idea/testData/configuration/js-maven/simpleProject/pom_after.xml b/idea/testData/configuration/js-maven/simpleProject/pom_after.xml index 281c3cc00e0..77b26462c3c 100644 --- a/idea/testData/configuration/js-maven/simpleProject/pom_after.xml +++ b/idea/testData/configuration/js-maven/simpleProject/pom_after.xml @@ -7,7 +7,21 @@ maventest maventest 1.0-SNAPSHOT + + + $VERSION$ + + + + + org.jetbrains.kotlin + kotlin-js-library + ${kotlin.version} + + + + org.jetbrains.kotlin @@ -20,11 +34,6 @@ js - - - - - js-test @@ -32,24 +41,11 @@ test-js - - - - - - org.jetbrains.kotlin - kotlin-js-library - ${kotlin.version} - - - - $VERSION$ - \ No newline at end of file diff --git a/idea/testData/configuration/js-maven/simpleProjectSnapshot/pom_after.xml b/idea/testData/configuration/js-maven/simpleProjectSnapshot/pom_after.xml index 1b80c35adbd..7c71557ce2a 100644 --- a/idea/testData/configuration/js-maven/simpleProjectSnapshot/pom_after.xml +++ b/idea/testData/configuration/js-maven/simpleProjectSnapshot/pom_after.xml @@ -7,39 +7,19 @@ maventest maventest 1.0-SNAPSHOT - - - - org.jetbrains.kotlin - kotlin-maven-plugin - ${kotlin.version} - - - js - compile - - js - - - - - - - - - js-test - test-compile - - test-js - - - - - - - - - + + + $VERSION$ + + + + + org.jetbrains.kotlin + kotlin-js-library + ${kotlin.version} + + + @@ -53,6 +33,7 @@ http://oss.sonatype.org/content/repositories/snapshots + @@ -66,16 +47,33 @@ http://oss.sonatype.org/content/repositories/snapshots - - - org.jetbrains.kotlin - kotlin-js-library - ${kotlin.version} - - - - $VERSION$ - + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + js + compile + + js + + + + js-test + test-compile + + test-js + + + + + + diff --git a/idea/testData/configuration/maven/fixExisting/pom.xml b/idea/testData/configuration/maven/fixExisting/pom.xml new file mode 100644 index 00000000000..6d0565a6e1e --- /dev/null +++ b/idea/testData/configuration/maven/fixExisting/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + maventest + maventest + 1.0-SNAPSHOT + + wrong-kotlin-version + + + + org.jetbrains.kotlin + kotlin-stdlib + wrong-version + + + + + wrong-source-dir + + + org.jetbrains.kotlin + kotlin-maven-plugin + wrong-plugin-version + + + compile + compile + + wrong-goal + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/configuration/maven/fixExisting/pom_after.xml b/idea/testData/configuration/maven/fixExisting/pom_after.xml new file mode 100644 index 00000000000..9425933e7ed --- /dev/null +++ b/idea/testData/configuration/maven/fixExisting/pom_after.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + maventest + maventest + 1.0-SNAPSHOT + + $VERSION$ + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + test + + + junit + junit + 4.12 + test + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + wrong-goal + compile + + + + test-compile + test-compile + + test-compile + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/configuration/maven/libraryMissed/pom_after.xml b/idea/testData/configuration/maven/libraryMissed/pom_after.xml index e1d3c6f429f..8d12490b706 100644 --- a/idea/testData/configuration/maven/libraryMissed/pom_after.xml +++ b/idea/testData/configuration/maven/libraryMissed/pom_after.xml @@ -8,6 +8,7 @@ maventest 1.0-SNAPSHOT + org.jetbrains.kotlin @@ -32,16 +33,29 @@ + + $VERSION$ + + org.jetbrains.kotlin kotlin-stdlib ${kotlin.version} + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + test + + + junit + junit + 4.12 + test + - - $VERSION$ - \ No newline at end of file diff --git a/idea/testData/configuration/maven/pluginMissed/pom_after.xml b/idea/testData/configuration/maven/pluginMissed/pom_after.xml index 7a854a6a48c..81211a2d680 100644 --- a/idea/testData/configuration/maven/pluginMissed/pom_after.xml +++ b/idea/testData/configuration/maven/pluginMissed/pom_after.xml @@ -7,7 +7,31 @@ maventest maventest 1.0-SNAPSHOT + + $VERSION$ + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + test + + + junit + junit + 4.12 + test + + + + org.jetbrains.kotlin @@ -20,11 +44,6 @@ compile - - - - - test-compile @@ -32,24 +51,11 @@ test-compile - - - - - $VERSION$ - - - - org.jetbrains.kotlin - kotlin-stdlib - ${kotlin.version} - - \ No newline at end of file diff --git a/idea/testData/configuration/maven/simpleProject/pom_after.xml b/idea/testData/configuration/maven/simpleProject/pom_after.xml index f38d4f528a7..1510baefe22 100644 --- a/idea/testData/configuration/maven/simpleProject/pom_after.xml +++ b/idea/testData/configuration/maven/simpleProject/pom_after.xml @@ -7,7 +7,33 @@ maventest maventest 1.0-SNAPSHOT + + + $VERSION$ + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + test + + + junit + junit + 4.12 + test + + + + org.jetbrains.kotlin @@ -20,11 +46,6 @@ compile - - - - - test-compile @@ -32,24 +53,11 @@ test-compile - - - - - - org.jetbrains.kotlin - kotlin-stdlib - ${kotlin.version} - - - - $VERSION$ - \ No newline at end of file diff --git a/idea/testData/configuration/maven/simpleProjectSnapshot/pom_after.xml b/idea/testData/configuration/maven/simpleProjectSnapshot/pom_after.xml index 122ee2f2a96..5e490231067 100644 --- a/idea/testData/configuration/maven/simpleProjectSnapshot/pom_after.xml +++ b/idea/testData/configuration/maven/simpleProjectSnapshot/pom_after.xml @@ -7,39 +7,31 @@ maventest maventest 1.0-SNAPSHOT - - - - org.jetbrains.kotlin - kotlin-maven-plugin - ${kotlin.version} - - - compile - compile - - compile - - - - - - - - - test-compile - test-compile - - test-compile - - - - - - - - - + + + $VERSION$ + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + test + + + junit + junit + 4.12 + test + + + @@ -53,6 +45,7 @@ http://oss.sonatype.org/content/repositories/snapshots + @@ -66,16 +59,33 @@ http://oss.sonatype.org/content/repositories/snapshots - - - org.jetbrains.kotlin - kotlin-stdlib - ${kotlin.version} - - - - $VERSION$ - + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + diff --git a/idea/tests/org/jetbrains/kotlin/idea/configuration/ConfigureProjectByChangingFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/configuration/ConfigureProjectByChangingFileTestGenerated.java index 849f74fd03d..e599583b59f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/configuration/ConfigureProjectByChangingFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/configuration/ConfigureProjectByChangingFileTestGenerated.java @@ -76,6 +76,12 @@ public class ConfigureProjectByChangingFileTestGenerated extends AbstractConfigu KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/configuration/maven"), Pattern.compile("^([^\\.]+)$"), false); } + @TestMetadata("fixExisting") + public void testFixExisting() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/maven/fixExisting/"); + doTestWithMaven(fileName); + } + @TestMetadata("libraryMissed") public void testLibraryMissed() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/maven/libraryMissed/");