[maven][IDEA-382043] reading plugin model build by maven-plugin-tools 4.0

Merge-request: IJ-MR-182407
Merged-by: Aleksandr Bubenchikov <alexander.bubenchikov@jetbrains.com>

GitOrigin-RevId: 5d6f1dad86988fd8b80ba4ee753d7379890b5c73
This commit is contained in:
Alexander Bubenchikov
2025-11-19 15:54:20 +00:00
committed by intellij-monorepo-bot
parent a6337bdc13
commit 388d08b91c
5 changed files with 2987 additions and 15 deletions
@@ -1,6 +1,7 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.dom;
import com.intellij.openapi.util.IntellijInternalApi;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.text.StringUtil;
@@ -16,6 +17,7 @@ import com.intellij.util.xml.XmlName;
import com.intellij.util.xml.reflect.DomExtender;
import com.intellij.util.xml.reflect.DomExtension;
import com.intellij.util.xml.reflect.DomExtensionsRegistrar;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.maven.dom.converters.MavenDomConvertersRegistry;
@@ -29,18 +31,30 @@ import org.jetbrains.idea.maven.dom.plugin.MavenDomPluginModel;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.function.Function;
public final class MavenPluginConfigurationDomExtender extends DomExtender<MavenDomConfiguration> {
public static final Key<ParameterData> PLUGIN_PARAMETER_KEY = Key.create("MavenPluginConfigurationDomExtender.PLUGIN_PARAMETER_KEY");
private static final Set<String> COLLECTIONS_TYPE_NAMES = Set.of("java.util.Collection", CommonClassNames.JAVA_UTIL_SET,
CommonClassNames.JAVA_UTIL_LIST,
"java.util.ArrayList", "java.util.HashSet",
CommonClassNames.JAVA_UTIL_LINKED_LIST);
CommonClassNames.JAVA_UTIL_LIST,
"java.util.ArrayList", "java.util.HashSet",
CommonClassNames.JAVA_UTIL_LINKED_LIST);
private final Function<@NotNull MavenDomConfiguration, @Nullable MavenDomPluginModel> myRetriever;
@IntellijInternalApi
@ApiStatus.Internal
public MavenPluginConfigurationDomExtender(Function<@NotNull MavenDomConfiguration, @Nullable MavenDomPluginModel> modelRetriever) {
myRetriever = modelRetriever;
}
public MavenPluginConfigurationDomExtender() {
this(MavenPluginDomUtil::getMavenPluginModel);
}
@Override
public void registerExtensions(@NotNull MavenDomConfiguration config, @NotNull DomExtensionsRegistrar r) {
MavenDomPluginModel pluginModel = MavenPluginDomUtil.getMavenPluginModel(config);
MavenDomPluginModel pluginModel = myRetriever.apply(config);
if (pluginModel == null) {
r.registerCustomChildrenExtension(MavenDomConfigurationParameter.class);
return;
@@ -68,6 +82,7 @@ public final class MavenPluginConfigurationDomExtender extends DomExtender<Maven
return pluginManagementTag instanceof XmlTag && "pluginManagement".equals(((XmlTag)pluginManagementTag).getName());
}
private static Collection<ParameterData> collectParameters(MavenDomPluginModel pluginModel, MavenDomConfiguration config) {
List<String> selectedGoals = null;
@@ -94,12 +109,13 @@ public final class MavenPluginConfigurationDomExtender extends DomExtender<Maven
if (goal == null) continue;
if (selectedGoals == null || selectedGoals.contains(goal)) {
var mojoConfig = getMojoConfigurationAsMap(eachMojo);
for (MavenDomParameter eachParameter : eachMojo.getParameters().getParameters()) {
String name = eachParameter.getName().getStringValue();
if (name == null) continue;
ParameterData data = new ParameterData(eachParameter);
fillParameterData(name, data, eachMojo);
fillParameterData(name, data, mojoConfig);
ParameterData oldParameter = namesWithParameters.get(name);
if (oldParameter == null || hasMorePriority(data, oldParameter, executionElement != null)) {
@@ -122,17 +138,31 @@ public final class MavenPluginConfigurationDomExtender extends DomExtender<Maven
return d1.getRequiringLevel() > d2.getRequiringLevel();
}
private static void fillParameterData(String name, ParameterData data, MavenDomMojo mojo) {
XmlTag config = mojo.getConfiguration().getXmlTag();
if (config == null) return;
private static Map<String, XmlTag> getMojoConfigurationAsMap(MavenDomMojo mojo) {
if (mojo == null) return Collections.emptyMap();
var config = mojo.getConfiguration().getXmlTag();
if (config == null) return Collections.emptyMap();
Map<String, XmlTag> result = new HashMap<>();
for (XmlTag each : config.getSubTags()) {
if (!name.equals(each.getName())) continue;
data.defaultValue = each.getAttributeValue("default-value");
data.expression = each.getValue().getTrimmedText();
result.put(each.getName(), each);
}
return result;
}
private static void fillParameterData(String name, ParameterData data, Map<String, XmlTag> mojoConfig) {
XmlTag configForMaven3 = mojoConfig.get(name);
if (configForMaven3 != null) {
data.defaultValue = configForMaven3.getAttributeValue("default-value");
data.expression = configForMaven3.getValue().getTrimmedText();
}
else {
data.defaultValue = data.parameter.getDefaultValue().getStringValue();
data.expression = data.parameter.getExpression().getStringValue();
}
}
private static void registerPluginParameter(boolean isInPluginManagement, DomExtensionsRegistrar r, final ParameterData parameter) {
String paramName = parameter.parameter.getName().getStringValue();
String alias = parameter.parameter.getAlias().getStringValue();
@@ -141,7 +171,10 @@ public final class MavenPluginConfigurationDomExtender extends DomExtender<Maven
if (alias != null) registerPluginParameter(isInPluginManagement, r, parameter, alias);
}
private static void registerPluginParameter(boolean isInPluginManagement, DomExtensionsRegistrar r, final ParameterData data, final String parameterName) {
private static void registerPluginParameter(boolean isInPluginManagement,
DomExtensionsRegistrar r,
final ParameterData data,
final String parameterName) {
DomExtension e = r.registerFixedNumberChildExtension(new XmlName(parameterName), MavenDomConfigurationParameter.class);
if (isCollection(data.parameter)) {
@@ -178,7 +211,7 @@ public final class MavenPluginConfigurationDomExtender extends DomExtender<Maven
if (Boolean.parseBoolean(data.parameter.getRequired().getStringValue())
&& StringUtil.isEmptyOrSpaces(data.defaultValue)
&& StringUtil.isEmptyOrSpaces(data.expression)) {
e.addCustomAnnotation(new Required(){
e.addCustomAnnotation(new Required() {
@Override
public boolean value() {
return true;
@@ -193,9 +226,10 @@ public final class MavenPluginConfigurationDomExtender extends DomExtender<Maven
public boolean identifier() {
return false;
}
@Override
public Class<? extends Annotation> annotationType() {
return Required.class;
return Required.class;
}
});
}
@@ -243,5 +277,4 @@ public final class MavenPluginConfigurationDomExtender extends DomExtender<Maven
return 2;
}
}
}
@@ -35,5 +35,17 @@ public interface MavenDomParameter extends MavenDomElement {
@NotNull
GenericDomValue<String> getDescription();
/**
* since maven-plugin-tools 4.0
*/
@NotNull
GenericDomValue<String> getDefaultValue();
/**
* since maven-plugin-tools 4.0
*/
@NotNull
GenericDomValue<String> getExpression();
GenericDomValue<Boolean> getRequired();
}
@@ -0,0 +1,191 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.dom
import com.intellij.maven.testFramework.MavenDomTestCase
import com.intellij.openapi.application.readAction
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.io.StreamUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.util.ResourceUtil
import com.intellij.util.xml.Converter
import com.intellij.util.xml.DomElement
import com.intellij.util.xml.Required
import com.intellij.util.xml.XmlName
import com.intellij.util.xml.reflect.CustomDomChildrenDescription
import com.intellij.util.xml.reflect.DomExtender
import com.intellij.util.xml.reflect.DomExtension
import com.intellij.util.xml.reflect.DomExtensionsRegistrar
import kotlinx.coroutines.runBlocking
import org.jetbrains.idea.maven.dom.plugin.MavenDomPluginModel
import org.junit.Test
import java.lang.reflect.Type
import java.util.function.Supplier
class MavenPluginConfigurationDomExtenderTest : MavenDomTestCase() {
@Test
fun testShouldCorrectlyAddTagsForMaven3Plugin() = runBlocking {
assumeMaven3()
doTestWith(pluginModelFileName = "compiler-3-14-1.xml")
}
@Test
fun testShouldCorrectlyAddTagsForMaven4Plugin() = runBlocking {
assumeMaven4()
doTestWith(pluginModelFileName = "compiler-4-0-beta3.xml")
}
private suspend fun doTestWith(pluginModelFileName: String) {
importProjectAsync("""
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-some-arg</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
"""
)
val pluginFile = createFromFile(pluginModelFileName, "plugin-test/$pluginModelFileName")
readAction {
val dom = MavenDomUtil.getMavenDomProjectModel(project, projectPom)!!
val compilerConfiguration = dom
.build
.plugins
.plugins
.single { it.artifactId.stringValue == "maven-compiler-plugin" }
.configuration
val pluginFileModel = MavenDomUtil.getMavenDomModel(project, pluginFile, MavenDomPluginModel::class.java)
val extender = MavenPluginConfigurationDomExtender {
pluginFileModel
}
val registeredExtensions = ArrayList<MyDomExtention>()
val registrar = MyRegistrar(registeredExtensions)
extender.registerExtensions(compilerConfiguration, registrar)
val basedir2ForTest = registeredExtensions.singleOrNull() { it.xmlName.localName == "basedir2ForTest" }
assertNotNull(basedir2ForTest)
assertEquals(1, basedir2ForTest!!.customAnnotations.size)
assertTrue(basedir2ForTest.customAnnotations[0] is Required)
val basedir = registeredExtensions.singleOrNull() { it.xmlName.localName == "basedir" }
assertNotNull(basedir)
assertEmpty(basedir!!.customAnnotations)
}
}
private fun createFromFile(file: String, resourceName: String): VirtualFile {
val result = ResourceUtil.getResourceAsStream(this::class.java.classLoader, "org/jetbrains/maven/", resourceName).use {
StreamUtil.readBytes(it).toString(Charsets.UTF_8)
}
return createProjectSubFile(file, result)
}
}
private class MyDomExtention(val xmlName: XmlName, private val type: Type) : DomExtension {
var declaringElement: DomElement? = null
var declaringElementFinder: Supplier<out DomElement?>? = null
var declaringPsiElement: PsiElement? = null
var converter: Converter<*>? = null
var isSoft: Boolean = false
val customAnnotations: MutableList<Annotation> = ArrayList()
val userData: MutableMap<Key<*>, Any?> = HashMap()
val extenders: MutableList<DomExtender<*>> = ArrayList()
override fun getType(): Type {
return type
}
override fun setDeclaringElement(declaringElement: DomElement): DomExtension {
this.declaringElement = declaringElement
return this
}
override fun setDeclaringDomElement(declarationFinder: Supplier<out DomElement?>): DomExtension {
this.declaringElementFinder = declarationFinder
return this
}
override fun setDeclaringElement(declaringElement: PsiElement): DomExtension {
this.declaringPsiElement = declaringElement
return this
}
override fun setConverter(converter: Converter<*>): DomExtension {
this.converter = converter
return this
}
override fun setConverter(converter: Converter<*>, soft: Boolean): DomExtension {
this.converter = converter
this.isSoft = soft
return this
}
override fun addCustomAnnotation(anno: Annotation): DomExtension {
customAnnotations.add(anno)
return this
}
override fun <T : Any?> putUserData(key: Key<T?>?, value: T?) {
if (key != null) {
@Suppress("UNCHECKED_CAST")
(userData as MutableMap<Key<T?>, T?>)[key] = value
}
}
override fun addExtender(extender: DomExtender<*>?): DomExtension {
if (extender != null) {
extenders.add(extender)
}
return this
}
}
private class MyRegistrar(val registeredExtensions: MutableList<MyDomExtention>) : DomExtensionsRegistrar {
override fun registerFixedNumberChildExtension(name: XmlName, type: Type): DomExtension {
return MyDomExtention(name, type).also { registeredExtensions.add(it) }
}
override fun registerCollectionChildrenExtension(name: XmlName, type: Type): DomExtension {
return MyDomExtention(name, type).also { registeredExtensions.add(it) }
}
override fun registerGenericAttributeValueChildExtension(name: XmlName, parameterType: Type?): DomExtension {
TODO()
}
override fun registerAttributeChildExtension(name: XmlName, type: Type): DomExtension {
TODO()
}
override fun registerCustomChildrenExtension(type: Type): DomExtension {
TODO()
}
override fun registerCustomChildrenExtension(type: Type, descriptor: CustomDomChildrenDescription.TagNameDescriptor): DomExtension {
TODO()
}
override fun registerCustomChildrenExtension(type: Type, attributeDescriptor: CustomDomChildrenDescription.AttributeDescriptor): DomExtension {
TODO()
}
}