From b94efffb4483e8f2079fae53afb8c3f1bb5ff691 Mon Sep 17 00:00:00 2001 From: Nikolay Chashnikov Date: Mon, 16 Jun 2025 23:57:44 +0300 Subject: [PATCH] [project model] report an error if 'moduleConfigurationEditorProvider' extension requires 'Module' as constructor argument (IJPL-179175) 'moduleConfigurationEditorProvider' extension point was registered as a module-level. However, it seems that no implementations use this ability, and can be converted to application-level extensions automatically. To avoid breaking compatibility, the extension point was converted to 'bean class', and tries to call the constructor with 'Module' parameter if no-arg constructor isn't available, and report an error in such cases. GitOrigin-RevId: 8390fda7aa09f09b6df8178d40b6269b26b0292f --- .../roots/ui/configuration/ModuleEditor.java | 6 +- .../ModuleConfigurationEditorProviderEp.kt | 80 +++++++++++++++++++ .../ModuleConfigurationEditorProvider.java | 3 + .../src/META-INF/LangExtensionPoints.xml | 6 +- 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/impl/ModuleConfigurationEditorProviderEp.kt diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleEditor.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleEditor.java index 909faf639629..441af28577ba 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleEditor.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleEditor.java @@ -20,6 +20,7 @@ import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.impl.libraries.LibraryEx; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.roots.ui.configuration.impl.ModuleConfigurationEditorProviderEp; import com.intellij.platform.workspace.storage.MutableEntityStorage; import com.intellij.ui.navigation.History; import com.intellij.ui.navigation.Place; @@ -43,7 +44,7 @@ import java.util.List; * @author Eugene Zhuravlev */ public abstract class ModuleEditor implements Place.Navigator, Disposable { - private static final ExtensionPointName EP_NAME = + private static final ExtensionPointName EP_NAME = new ExtensionPointName<>("com.intellij.moduleConfigurationEditorProvider"); private static final Logger LOG = Logger.getInstance(ModuleEditor.class); @@ -160,7 +161,8 @@ public abstract class ModuleEditor implements Place.Navigator, Disposable { private void createEditors(@NotNull Module module) { ModuleConfigurationState state = createModuleConfigurationState(); - for (ModuleConfigurationEditorProvider provider : EP_NAME.getExtensionList(module)) { + for (ModuleConfigurationEditorProviderEp providerEp : EP_NAME.getExtensionList()) { + ModuleConfigurationEditorProvider provider = providerEp.getOrCreateInstance(module); ModuleConfigurationEditor[] editors = provider.createEditors(state); if (editors.length > 0 && provider instanceof ModuleConfigurationEditorProviderEx && ((ModuleConfigurationEditorProviderEx)provider).isCompleteEditorSet()) { diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/impl/ModuleConfigurationEditorProviderEp.kt b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/impl/ModuleConfigurationEditorProviderEp.kt new file mode 100644 index 000000000000..d176f72c0b4e --- /dev/null +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/impl/ModuleConfigurationEditorProviderEp.kt @@ -0,0 +1,80 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.roots.ui.configuration.impl + +import com.intellij.diagnostic.PluginException +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.fileLogger +import com.intellij.openapi.extensions.PluginAware +import com.intellij.openapi.extensions.PluginDescriptor +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleConfigurationEditor +import com.intellij.openapi.roots.ui.configuration.ModuleConfigurationEditorProvider +import com.intellij.util.xmlb.annotations.Attribute +import org.jetbrains.annotations.ApiStatus +import java.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType + +/** + * Implements [ModuleConfigurationEditorProvider] extension point supporting the deprecated way when an extension is instantiated with + * [Module] as a constructor parameter. + * When this possibility is removed, the extension point can be converted to a regular 'interface' extension point. + */ +class ModuleConfigurationEditorProviderEp : PluginAware { + private var pluginDescriptor: PluginDescriptor? = null + + @Attribute("implementation") + @JvmField + var implementationClass: String? = null + + @Volatile + private var cachedInstance: ModuleConfigurationEditorProvider? = null + + override fun setPluginDescriptor(pluginDescriptor: PluginDescriptor) { + this.pluginDescriptor = pluginDescriptor + } + + @ApiStatus.Internal + fun getOrCreateInstance(module: Module): ModuleConfigurationEditorProvider { + try { + cachedInstance?.let { + return it + } + + val implementationClass = implementationClass ?: error("'implementation' attribute is not specified'") + val pluginDescriptor = pluginDescriptor ?: error("plugin descriptor is not set") + val instanceClass = ApplicationManager.getApplication().loadClass(implementationClass, pluginDescriptor) + val lookup = MethodHandles.privateLookupIn(instanceClass, methodLookup) + val defaultConstructor: MethodHandle + try { + defaultConstructor = lookup.findConstructor(instanceClass, emptyConstructorMethodType) + } + catch (_: NoSuchMethodException) { + val constructorWithModule = lookup.findConstructor(instanceClass, moduleMethodType) + LOG.error(PluginException(""" + |'moduleConfigurationEditorProvider' extensions must have a constructor without parameters and take Module instance from + |the parameter of 'createEditors' function ('state.getCurrentRootModel().getModule()'), but '$implementationClass' + |has a constructor taking 'Module' as parameter. + """.trimMargin(), pluginDescriptor.pluginId)) + return constructorWithModule.invoke(module) as ModuleConfigurationEditorProvider + } + synchronized(this) { + cachedInstance?.let { + return it + } + val provider = defaultConstructor.invoke() as ModuleConfigurationEditorProvider + cachedInstance = provider + return provider + } + } + catch (t: Throwable) { + throw PluginException("Failed to instantiate ModuleConfigurationEditorProvider ($implementationClass)", t, pluginDescriptor?.pluginId) + } + } +} + +private val LOG = fileLogger() +private val methodLookup = MethodHandles.lookup() +private val emptyConstructorMethodType: MethodType = MethodType.methodType(Void.TYPE) +private val moduleMethodType = MethodType.methodType(Void.TYPE, Module::class.java) + diff --git a/platform/lang-core/src/com/intellij/openapi/roots/ui/configuration/ModuleConfigurationEditorProvider.java b/platform/lang-core/src/com/intellij/openapi/roots/ui/configuration/ModuleConfigurationEditorProvider.java index d30a35ce91d4..1a998ee8e64b 100644 --- a/platform/lang-core/src/com/intellij/openapi/roots/ui/configuration/ModuleConfigurationEditorProvider.java +++ b/platform/lang-core/src/com/intellij/openapi/roots/ui/configuration/ModuleConfigurationEditorProvider.java @@ -11,6 +11,9 @@ import org.jetbrains.annotations.NotNull; *   <moduleConfigurationEditorProvider implementation="qualified-class-name"/> * </extensions> * + * + * The implementation must have a no-arg constructor. The {@link com.intellij.openapi.module.Module} instance can be taken from + * {@code state} parameter of {@link #createEditors} ({@code state.getCurrentRootModel().getModule()}). */ public interface ModuleConfigurationEditorProvider { @NotNull ModuleConfigurationEditor @NotNull [] createEditors(@NotNull ModuleConfigurationState state); diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml index 097921f3a5b4..1037d015d578 100644 --- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml @@ -543,8 +543,10 @@ - + + +