configuration and sorting using modules and not plugins

GitOrigin-RevId: 632049b4db917b88f453d774d8a1e404cb4641e6
This commit is contained in:
Vladimir Krivosheev
2021-08-08 18:34:28 +00:00
committed by intellij-monorepo-bot
parent 583d835f4a
commit c0b2f613a4
65 changed files with 2008 additions and 2044 deletions
@@ -15,7 +15,6 @@ public final class PathClassLoader extends UrlClassLoader {
private static final Function<Path, ResourceFile> RESOURCE_FILE_FACTORY = file -> new ZipResourceFile(file);
private static final boolean isParallelCapable = registerAsParallelCapable();
private static final ClassLoader appClassLoader = PathClassLoader.class.getClassLoader();
private final BytecodeTransformer transformer;
@@ -51,19 +50,6 @@ public final class PathClassLoader extends UrlClassLoader {
transformer = null;
registerInClassLoaderValueMap(parent, this);
// who knows
assert appClassLoader != this;
}
@Override
protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
if (name.startsWith("com.intellij.util.lang.")) {
return appClassLoader.loadClass(name);
}
else {
return super.findClass(name);
}
}
@Override
@@ -1,26 +1,23 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:Suppress("ReplacePutWithAssignment")
@file:Suppress("ReplacePutWithAssignment", "ReplaceGetOrSet")
package com.intellij.ide.plugins
import com.intellij.openapi.extensions.PluginId
import com.intellij.util.graph.Graph
import com.intellij.util.ArrayUtilRt
import com.intellij.util.graph.DFSTBuilder
import com.intellij.util.lang.Java11Shim
import org.jetbrains.annotations.ApiStatus
import java.util.*
import java.util.function.Supplier
import java.util.stream.Stream
import java.util.function.ToIntFunction
@ApiStatus.Internal
class CachingSemiGraph<Node> private constructor(
private val nodes: Collection<Node>,
private val pluginToDirectDependencies: Map<Node, List<Node>>,
) : Graph<Node> {
internal class CachingSemiGraph<Node> private constructor(
@JvmField val nodes: Collection<Node>,
@JvmField val moduleToDirectDependencies: Map<Node, List<Node>>,
) : DFSTBuilder.DFSTBuilderAwareGraph<Node> {
private val outs = IdentityHashMap<Node, MutableList<Node>>()
init {
val edges = Collections.newSetFromMap<Map.Entry<Node, Node>>(HashMap())
for (node in nodes) {
for (inNode in (pluginToDirectDependencies[node] ?: continue)) {
for (inNode in (moduleToDirectDependencies.get(node) ?: continue)) {
if (edges.add(AbstractMap.SimpleImmutableEntry(inNode, node))) {
// not a duplicate edge
outs.computeIfAbsent(inNode) { ArrayList() }.add(node)
@@ -30,132 +27,152 @@ class CachingSemiGraph<Node> private constructor(
}
companion object {
fun createPluginIdGraph(
descriptors: Collection<IdeaPluginDescriptorImpl>,
pluginSet: PluginSet,
withOptional: Boolean,
): CachingSemiGraph<PluginId> {
val hasAllModules = pluginSet.isPluginEnabled(PluginManagerCore.ALL_MODULES_MARKER)
val javaDep = Supplier {
pluginSet.findEnabledPlugin(PluginManagerCore.JAVA_MODULE_ID)
}
fun createModuleGraph(plugins: List<IdeaPluginDescriptorImpl>): CachingSemiGraph<IdeaPluginDescriptorImpl> {
val moduleMap = HashMap<String, IdeaPluginDescriptorImpl>(plugins.size * 2)
val modules = ArrayList<IdeaPluginDescriptorImpl>(moduleMap.size)
for (module in plugins) {
moduleMap.put(module.pluginId.idString, module)
for (v1Module in module.modules) {
moduleMap.put(v1Module.idString, module)
}
val uniqueCheck = Collections.newSetFromMap<PluginId>(IdentityHashMap())
val pluginToDirectDependencies = IdentityHashMap<PluginId, List<PluginId>>(descriptors.size)
val list = ArrayList<PluginId>(32)
val ids = arrayOfNulls<PluginId>(descriptors.size)
var index = 0
for (descriptor in descriptors) {
ids[index++] = descriptor.id
collectDirectDependencies(descriptor, pluginSet, withOptional, hasAllModules, javaDep, uniqueCheck, list)
if (!list.isEmpty()) {
pluginToDirectDependencies.put(descriptor.id, Java11Shim.INSTANCE.copyOf(list))
list.clear()
modules.add(module)
for (item in module.content.modules) {
val subModule = item.requireDescriptor()
modules.add(subModule)
moduleMap.put(item.name, subModule)
}
}
Arrays.sort(ids)
return CachingSemiGraph(
Java11Shim.INSTANCE.listOf<PluginId>(ids),
pluginToDirectDependencies,
)
}
}
override fun getOut(n: Node): Iterator<Node> = outs.get(n)?.iterator() ?: Collections.emptyIterator()
val hasAllModules = moduleMap.containsKey(PluginManagerCore.ALL_MODULES_MARKER.idString)
val result: MutableSet<IdeaPluginDescriptorImpl> = Collections.newSetFromMap(IdentityHashMap())
val moduleToDirectDependencies = IdentityHashMap<IdeaPluginDescriptorImpl, List<IdeaPluginDescriptorImpl>>(modules.size)
for (module in modules) {
val implicitDep = if (hasAllModules) getImplicitDependency(module, moduleMap) else null
if (implicitDep != null) {
if (module === implicitDep) {
PluginManagerCore.getLogger().error("Plugin $module depends on self")
}
else {
result.add(implicitDep)
}
}
override fun getNodes() = nodes
collectDirectDependenciesInOldFormat(module, moduleMap, result)
collectDirectDependenciesInNewFormat(module, moduleMap, result)
override fun getIn(node: Node): Iterator<Node> {
return pluginToDirectDependencies.get(node)?.iterator() ?: Collections.emptyIterator()
}
if (module.moduleName != null && module.pluginId != PluginManagerCore.CORE_ID) {
// add main as implicit dependency
val main = moduleMap.get(module.pluginId.idString)!!
assert(main !== module)
result.add(main)
}
fun getInStream(node: Node): Stream<Node> {
return pluginToDirectDependencies.get(node)?.stream() ?: Stream.empty()
}
}
private fun collectDirectDependencies(rootDescriptor: IdeaPluginDescriptorImpl,
pluginSet: PluginSet,
withOptional: Boolean,
hasAllModules: Boolean,
javaDep: Supplier<IdeaPluginDescriptorImpl?>,
uniqueCheck: MutableSet<PluginId>,
result: MutableList<PluginId>) {
val implicitDep = if (hasAllModules) PluginManagerCore.getImplicitDependency(rootDescriptor, javaDep) else null
uniqueCheck.clear()
if (implicitDep != null) {
if (rootDescriptor === implicitDep) {
PluginManagerCore.getLogger().error("Plugin $rootDescriptor depends on self")
}
else {
uniqueCheck.add(implicitDep.id)
result.add(implicitDep.id)
}
}
for (dependency in rootDescriptor.pluginDependencies) {
if (!withOptional && dependency.isOptional) {
continue
if (!result.isEmpty()) {
moduleToDirectDependencies.put(module, Java11Shim.INSTANCE.copyOfCollection(result))
result.clear()
}
}
return CachingSemiGraph(modules, moduleToDirectDependencies)
}
// check for missing optional dependency
val dep = pluginSet.findEnabledPlugin(dependency.pluginId) ?: continue
// if 'dep' refers to a module we need to check the real plugin containing this module only if it's still enabled,
// otherwise the graph will be inconsistent
// ultimate plugin it is combined plugin, where some included XML can define dependency on ultimate explicitly and for now not clear,
// can be such requirements removed or not
if (rootDescriptor === dep) {
if (PluginManagerCore.CORE_ID != rootDescriptor.pluginId) {
PluginManagerCore.getLogger().error("Plugin $rootDescriptor depends on self")
fun getTopologicalComparator(builder: DFSTBuilder<IdeaPluginDescriptorImpl>): Comparator<IdeaPluginDescriptorImpl> {
val comparator = builder.comparator()
// there is circular reference between core and implementation-detail plugin, as not all such plugins extracted from core,
// so, ensure that core plugin is always first (otherwise not possible to register actions - parent group not defined)
// don't use sortWith here - avoid loading kotlin stdlib
return Comparator { o1, o2 ->
when {
o1.moduleName == null && o1.pluginId == PluginManagerCore.CORE_ID -> -1
o2.moduleName == null && o2.pluginId == PluginManagerCore.CORE_ID -> 1
else -> comparator.compare(o1, o2)
}
}
}
else if (uniqueCheck.add(dep.id)) {
result.add(dep.id)
}
fun getIn(node: Node): List<Node> = moduleToDirectDependencies.get(node) ?: Collections.emptyList()
override fun buildOuts(nodeIndex: ToIntFunction<in Node>, node: Node): IntArray {
val out = outs.get(node) ?: return ArrayUtilRt.EMPTY_INT_ARRAY
return IntArray(out.size) {
nodeIndex.applyAsInt(out.get(it))
}
}
}
/**
* In 191.* and earlier builds Java plugin was part of the platform, so any plugin installed in IntelliJ IDEA might be able to use its
* classes without declaring explicit dependency on the Java module. This method is intended to add implicit dependency on the Java plugin
* for such plugins to avoid breaking compatibility with them.
*/
private fun getImplicitDependency(descriptor: IdeaPluginDescriptorImpl,
idMap: Map<String, IdeaPluginDescriptorImpl>): IdeaPluginDescriptorImpl? {
// skip our plugins as expected to be up-to-date whether bundled or not
if (descriptor.isBundled || descriptor.packagePrefix != null || descriptor.implementationDetail) {
return null
}
val pluginId = descriptor.pluginId
if (PluginManagerCore.CORE_ID == pluginId || PluginManagerCore.JAVA_PLUGIN_ID == pluginId ||
PluginManagerCore.hasModuleDependencies(descriptor)) {
return null
}
// If a plugin does not include any module dependency tags in its plugin.xml, it's assumed to be a legacy plugin
// and is loaded only in IntelliJ IDEA, so it may use classes from Java plugin.
return idMap.get(PluginManagerCore.JAVA_MODULE_ID.idString)
}
private fun collectDirectDependenciesInOldFormat(rootDescriptor: IdeaPluginDescriptorImpl,
idMap: Map<String, IdeaPluginDescriptorImpl>,
result: MutableSet<IdeaPluginDescriptorImpl>) {
for (dependency in rootDescriptor.pluginDependencies) {
// check for missing optional dependency
val dep = idMap.get(dependency.pluginId.idString) ?: continue
if (dep.pluginId != PluginManagerCore.CORE_ID) {
// ultimate plugin it is combined plugin, where some included XML can define dependency on ultimate explicitly and for now not clear,
// can be such requirements removed or not
if (rootDescriptor === dep) {
if (rootDescriptor.pluginId != PluginManagerCore.CORE_ID) {
PluginManagerCore.getLogger().error("Plugin $rootDescriptor depends on self")
}
}
else {
// e.g. `.env` plugin in an old format and doesn't explicitly specify dependency on a new extracted modules
dep.content.modules.mapTo(result) { it.requireDescriptor() }
result.add(dep)
}
}
dependency.subDescriptor?.let {
collectDirectDependenciesInOldFormat(it, idMap, result)
}
}
directDependenciesOfModule(rootDescriptor, pluginSet, uniqueCheck, result)
// graph for plugins, not for modules - so, dependency of content must be taken into account
if (rootDescriptor.id != PluginManagerCore.CORE_ID) {
for (module in rootDescriptor.content.modules) {
directDependenciesOfModule(module.requireDescriptor(), pluginSet, uniqueCheck, result)
}
}
for (moduleId in rootDescriptor.incompatibilities) {
val dep = pluginSet.findEnabledPlugin(moduleId)
if (dep != null && uniqueCheck.add(dep.id)) {
result.add(dep.id)
idMap.get(moduleId.idString)?.let {
result.add(it)
}
}
}
private fun directDependenciesOfModule(module: IdeaPluginDescriptorImpl,
pluginSet: PluginSet,
uniqueCheck: MutableSet<PluginId>,
result: MutableList<PluginId>) {
processDirectDependencies(module, pluginSet) {
if (uniqueCheck.add(it.id)) {
result.add(it.id)
}
}
}
inline fun processDirectDependencies(module: IdeaPluginDescriptorImpl,
pluginSet: PluginSet,
processor: (IdeaPluginDescriptorImpl) -> Unit) {
private fun collectDirectDependenciesInNewFormat(module: IdeaPluginDescriptorImpl,
idMap: Map<String, IdeaPluginDescriptorImpl>,
result: MutableCollection<IdeaPluginDescriptorImpl>) {
for (item in module.dependencies.modules) {
val descriptor = pluginSet.findEnabledModule(item.name)
val descriptor = idMap.get(item.name)
if (descriptor != null) {
processor(descriptor)
result.add(descriptor)
}
}
for (item in module.dependencies.plugins) {
val descriptor = pluginSet.findEnabledPlugin(item.id)
if (descriptor != null) {
processor(descriptor)
val descriptor = idMap.get(item.id.idString)
// fake v1 module maybe located in a core plugin
if (descriptor != null && descriptor.pluginId != PluginManagerCore.CORE_ID) {
result.add(descriptor)
}
}
}
@@ -1,10 +1,11 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:Suppress("ReplaceNegatedIsEmptyWithIsNotEmpty")
@file:Suppress("ReplaceNegatedIsEmptyWithIsNotEmpty", "ReplaceGetOrSet")
package com.intellij.ide.plugins
import com.intellij.diagnostic.PluginException
import com.intellij.ide.plugins.cl.PluginClassLoader
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.PluginId
import com.intellij.util.SmartList
import com.intellij.util.lang.ClassPath
import com.intellij.util.lang.ResourceFile
@@ -26,20 +27,19 @@ class ClassLoaderConfigurator(
val pluginSet: PluginSet,
private val coreLoader: ClassLoader = ClassLoaderConfigurator::class.java.classLoader,
) {
private var javaDep: Optional<IdeaPluginDescriptorImpl>? = null
// temporary set to produce arrays (avoid allocation for each plugin)
// set to remove duplicated classloaders
private val dependencies = LinkedHashSet<IdeaPluginDescriptorImpl>()
private val hasAllModules = pluginSet.isPluginEnabled(PluginManagerCore.ALL_MODULES_MARKER)
// todo for dynamic reload this guard doesn't contain all used plugin prefixes
private val pluginPackagePrefixUniqueGuard = HashMap<String, IdeaPluginDescriptorImpl>()
@Suppress("JoinDeclarationAndAssignment")
private val resourceFileFactory: Function<Path, ResourceFile>?
private val mainToClassPath = IdentityHashMap<PluginId, MainInfo>()
private class MainInfo(
@JvmField val classPath: ClassPath,
@JvmField val files: List<Path>,
@JvmField val libDirectories: MutableList<String>
)
init {
resourceFileFactory = try {
@Suppress("UNCHECKED_CAST")
@@ -56,199 +56,177 @@ class ClassLoaderConfigurator(
}
}
fun configureDependenciesIfNeeded(mainToModule: Map<IdeaPluginDescriptorImpl, List<IdeaPluginDescriptorImpl>>,
dependencyPlugin: IdeaPluginDescriptorImpl) {
fun configureDependenciesIfNeeded(mainToModule: Map<IdeaPluginDescriptorImpl, List<IdeaPluginDescriptorImpl>>) {
for ((mainDependent, modules) in mainToModule) {
val mainDependentClassLoader = mainDependent.classLoader as PluginClassLoader
if (mainDependent.packagePrefix != null) {
for (module in modules) {
assert(module.packagePrefix != null)
configureModule(module, mainDependent,
files = mainDependentClassLoader.files,
libDirectories = mainDependentClassLoader.libDirectories,
classPath = mainDependentClassLoader.classPath)
}
}
else {
mainDependentClassLoader.attachParent(dependencyPlugin)
mainToClassPath.put(mainDependent.pluginId, MainInfo(classPath = mainDependentClassLoader.classPath,
files = mainDependentClassLoader.files,
libDirectories = mainDependentClassLoader.libDirectories))
if (mainDependent.packagePrefix == null) {
for (module in modules) {
module.classLoader = mainDependentClassLoader
}
}
else {
for (module in modules) {
configureModule(module)
}
}
}
dependencies.clear()
}
fun configureAll() {
val postTasks = ArrayList<() -> Unit>()
for (plugin in pluginSet.enabledPlugins) {
// not only for core plugin, but also for a plugin from classpath (run TraverseUi)
if (plugin.pluginId == PluginManagerCore.CORE_ID || (plugin.isUseCoreClassLoader && !plugin.content.modules.isEmpty())) {
configureCorePlugin(plugin, postTasks)
fun configure() {
for (module in pluginSet.getRawListOfEnabledModules()) {
configureModule(module)
}
}
fun configureModule(module: IdeaPluginDescriptorImpl) {
checkPackagePrefixUniqueness(module)
val isMain = module.moduleName == null
val dependencies = pluginSet.moduleToDirectDependencies.get(module) ?: EMPTY_DESCRIPTOR_ARRAY
if (isMain) {
if (module.useCoreClassLoader || module.pluginId == PluginManagerCore.CORE_ID) {
setPluginClassLoaderForModuleAndOldSubDescriptors(module, coreLoader)
return
}
var files = module.jarFiles
if (files == null) {
if (!module.isUseIdeaClassLoader) {
log.error("jarFiles is not set for $module")
}
files = Collections.emptyList()!!
}
val libDirectories: MutableList<String> = SmartList()
val libDir = module.path.resolve("lib")
if (Files.exists(libDir)) {
libDirectories.add(libDir.toAbsolutePath().toString())
}
val mimicJarUrlConnection = !module.isBundled && module.vendor != "JetBrains"
val pluginClassPath = ClassPath(files, Collections.emptySet(), DEFAULT_CLASSLOADER_CONFIGURATION, resourceFileFactory,
mimicJarUrlConnection)
val mainInfo = MainInfo(classPath = pluginClassPath, files = files, libDirectories = libDirectories)
val existing = mainToClassPath.put(module.pluginId, mainInfo)
if (existing != null) {
log.error(PluginException("Main module with ${module.pluginId} is already added (existingClassPath=${existing.files}",
module.pluginId))
}
val mainDependentClassLoader = if (module.isUseIdeaClassLoader) {
configureUsingIdeaClassloader(mainInfo.files, module)
}
else {
configure(plugin)
createPluginClassLoader(module, mainInfo = mainInfo, dependencies = dependencies)
}
}
// intellij.profiler.ultimate depends on com.intellij.java but com.intellij.java depends on core plugin
for (postTask in postTasks) {
postTask()
}
}
fun configure(plugin: IdeaPluginDescriptorImpl) {
checkPackagePrefixUniqueness(plugin)
if (plugin.pluginId == PluginManagerCore.CORE_ID) {
throw IllegalStateException("Core plugin cannot be configured dynamically")
}
else if (plugin.isUseCoreClassLoader) {
setPluginClassLoaderForMainAndSubPlugins(plugin, coreLoader)
return
}
dependencies.clear()
// first, set class loader for main descriptor
if (hasAllModules) {
val implicitDependency = PluginManagerCore.getImplicitDependency(plugin) {
// first, set class loader for main descriptor
if (javaDep == null) {
javaDep = Optional.ofNullable(pluginSet.findEnabledPlugin(PluginManagerCore.JAVA_PLUGIN_ID))
}
javaDep!!.orElse(null)
}
implicitDependency?.let {
if (it.classLoader !== coreLoader) {
dependencies.add(it)
}
Unit
}
}
var files = plugin.jarFiles
if (files == null) {
log.error("jarFiles is not set for $plugin")
files = Collections.emptyList()!!
}
var oldActiveSubModules: MutableList<IdeaPluginDescriptorImpl>? = null
for (dependency in plugin.pluginDependencies) {
val p = pluginSet.findEnabledPlugin(dependency.pluginId) ?: continue
val loader = p.classLoader
if (loader == null) {
log.error(PluginLoadingError.formatErrorMessage(plugin, "requires missing class loader for $p"))
}
else if (loader !== coreLoader) {
// e.g. `.env` plugin in an old format and doesn't explicitly specify dependency on a new extracted modules
p.content.modules.mapTo(dependencies) { it.requireDescriptor() }
// must be after adding implicit module class loaders
dependencies.add(p)
}
dependency.subDescriptor?.let {
if (oldActiveSubModules == null) {
oldActiveSubModules = ArrayList()
}
oldActiveSubModules!!.add(it)
}
}
// new format
processDirectDependencies(plugin, pluginSet) {
if (it.classLoader !== coreLoader) {
dependencies.add(it)
}
}
val mimicJarUrlConnection = !plugin.isBundled && plugin.vendor != "JetBrains"
val pluginClassPath = ClassPath(files, Collections.emptySet(), DEFAULT_CLASSLOADER_CONFIGURATION, resourceFileFactory, mimicJarUrlConnection)
val libDirectories: MutableList<String> = SmartList()
val libDir = plugin.path.resolve("lib")
if (Files.exists(libDir)) {
libDirectories.add(libDir.toAbsolutePath().toString())
}
val mainDependentClassLoader = if (plugin.isUseIdeaClassLoader) {
configureUsingIdeaClassloader(files, plugin)
module.classLoader = mainDependentClassLoader
configureDependenciesInOldFormat(module, mainDependentClassLoader)
}
else {
createPluginClassLoader(plugin, files = files, libDirectories = libDirectories, classPath = pluginClassPath)
}
if (module.packagePrefix == null) {
throw PluginException("Package is not specified (module=$module)", module.pluginId)
}
// second, set class loaders for sub descriptors
plugin.classLoader = mainDependentClassLoader
for (module in plugin.content.modules) {
configureModule(module = module.requireDescriptor(),
plugin = plugin,
files = files,
libDirectories = libDirectories,
classPath = pluginClassPath)
}
assert(module.pluginDependencies.isEmpty()) { "Module $module shouldn't have plugin dependencies: ${module.pluginDependencies}" }
for (dependency in dependencies) {
// if the module depends on an unavailable plugin, it will not be loaded
if (dependency.classLoader == null) {
return
}
}
for (subDescriptor in (oldActiveSubModules ?: Collections.emptyList())) {
// classLoader must be set - otherwise sub descriptor considered as inactive
subDescriptor.classLoader = mainDependentClassLoader
}
if (module.useCoreClassLoader) {
module.classLoader = coreLoader
return
}
// reset to ensure that stalled data will be not reused somehow later
dependencies.clear()
}
private fun configureCorePlugin(plugin: IdeaPluginDescriptorImpl, postTasks: MutableList<() -> Unit>) {
plugin.classLoader = coreLoader
// do we really have pluginDependencies for core plugin?
for (dependency in plugin.pluginDependencies) {
if (dependency.subDescriptor != null && pluginSet.isPluginEnabled(dependency.pluginId)) {
configureCorePlugin(dependency.subDescriptor!!, postTasks)
val mainInfo = mainToClassPath.get(module.pluginId)
if (mainInfo == null) {
if (module.pluginId == PluginManagerCore.CORE_ID) {
configureCorePluginModuleClassLoader(module, dependencies)
}
else {
throw PluginException("Cannot find containing plugin ${module.pluginId} for module ${module.moduleName} ", module.pluginId)
}
}
else {
module.classLoader = PluginClassLoader(
mainInfo.files,
mainInfo.classPath,
dependencies,
module,
coreLoader,
createModuleResolveScopeManager(),
module.packagePrefix,
mainInfo.libDirectories
)
}
}
}
if (plugin.content.modules.isEmpty()) {
private fun configureDependenciesInOldFormat(module: IdeaPluginDescriptorImpl, mainDependentClassLoader: ClassLoader) {
for (dependency in module.pluginDependencies) {
val subDescriptor = dependency.subDescriptor ?: continue
if (pluginSet.findEnabledPlugin(dependency.pluginId)?.takeIf { it !== module } == null) {
continue
}
// classLoader must be set - otherwise sub descriptor considered as inactive
subDescriptor.classLoader = mainDependentClassLoader
configureDependenciesInOldFormat(subDescriptor, mainDependentClassLoader)
}
}
private fun configureCorePluginModuleClassLoader(module: IdeaPluginDescriptorImpl, deps: Array<IdeaPluginDescriptorImpl>) {
val coreUrlClassLoader = getCoreUrlClassLoaderIfPossible(module)
if (coreUrlClassLoader == null) {
setPluginClassLoaderForModuleAndOldSubDescriptors(module, coreLoader)
return
}
module.classLoader = PluginClassLoader(
Collections.emptyList(),
coreUrlClassLoader.classPath,
deps,
module,
coreLoader,
createModuleResolveScopeManager(),
module.packagePrefix,
ArrayList()
)
}
private fun getCoreUrlClassLoaderIfPossible(module: IdeaPluginDescriptorImpl): UrlClassLoader? {
val coreUrlClassLoader = coreLoader as? UrlClassLoader
for (item in plugin.content.modules) {
val module = item.requireDescriptor()
// skip if some dependency is not available
if (module.dependencies.modules.any { !pluginSet.isModuleEnabled(it.name) } ||
module.dependencies.plugins.any { !pluginSet.isPluginEnabled(it.id) }) {
continue
}
if (coreUrlClassLoader == null) {
@Suppress("SpellCheckingInspection")
log.error("You should run JVM with -Djava.system.class.loader=com.intellij.util.lang.PathClassLoader")
setPluginClassLoaderForModuleAndOldSubDescriptors(module, coreLoader)
return null
}
assert(module.content.modules.isEmpty())
if (coreUrlClassLoader == null) {
module.classLoader = coreLoader
if (coreUrlClassLoader.resolveScopeManager == null) {
val corePlugin = pluginSet.enabledPlugins.first()
assert(corePlugin.pluginId == PluginManagerCore.CORE_ID)
val resolveScopeManager = createPluginDependencyAndContentBasedScope(descriptor = corePlugin, pluginSet = pluginSet)
if (resolveScopeManager != null) {
coreUrlClassLoader.resolveScopeManager = BiPredicate { name, force ->
resolveScopeManager.isDefinitelyAlienClass(name, "", force) != null
}
}
else {
if (coreUrlClassLoader.resolveScopeManager == null) {
val resolveScopeManager = createPluginDependencyAndContentBasedScope(descriptor = plugin, pluginSet = pluginSet)
if (resolveScopeManager != null) {
coreUrlClassLoader.resolveScopeManager = BiPredicate { name, force ->
resolveScopeManager.isDefinitelyAlienClass(name, "", force) != null
}
}
}
}
if (module.dependencies.plugins.isEmpty()) {
configureModule(module = module,
plugin = plugin,
files = Collections.emptyList(),
libDirectories = ArrayList(),
classPath = coreUrlClassLoader.classPath)
}
else {
postTasks.add {
configureModule(module = module,
plugin = plugin,
files = Collections.emptyList(),
libDirectories = ArrayList(),
classPath = coreUrlClassLoader.classPath)
}
}
return coreUrlClassLoader
}
private fun setPluginClassLoaderForModuleAndOldSubDescriptors(rootDescriptor: IdeaPluginDescriptorImpl, classLoader: ClassLoader) {
rootDescriptor.classLoader = classLoader
for (dependency in rootDescriptor.pluginDependencies) {
val subDescriptor = dependency.subDescriptor
if (subDescriptor != null && pluginSet.isPluginEnabled(dependency.pluginId)) {
setPluginClassLoaderForModuleAndOldSubDescriptors(subDescriptor, classLoader)
}
}
}
@@ -263,94 +241,54 @@ class ClassLoaderConfigurator(
}
}
private fun createPluginClassLoader(descriptor: IdeaPluginDescriptorImpl,
files: List<Path>,
libDirectories: MutableList<String>,
classPath: ClassPath): PluginClassLoader {
val parents: Array<IdeaPluginDescriptorImpl> = if (dependencies.isEmpty()) {
EMPTY_DESCRIPTOR_ARRAY
private fun createPluginClassLoader(module: IdeaPluginDescriptorImpl,
dependencies: Array<IdeaPluginDescriptorImpl>,
mainInfo: MainInfo): PluginClassLoader {
val resolveScopeManager: PluginClassLoader.ResolveScopeManager?
// main plugin descriptor
if (module.moduleName == null) {
resolveScopeManager = when (module.pluginId.idString) {
"com.intellij.diagram" -> {
// multiple packages - intellij.diagram and intellij.diagram.impl modules
createScopeWithExtraPackage("com.intellij.diagram.")
}
"com.intellij.struts2" -> createScopeWithExtraPackage("com.intellij.lang.ognl.")
"com.intellij.properties" -> {
// todo ability to customize (cannot move due to backward compatibility)
PluginClassLoader.ResolveScopeManager { name, packagePrefix, force ->
if (force) {
null
}
else if (!name.startsWith(packagePrefix) &&
!name.startsWith("com.intellij.ultimate.PluginVerifier") &&
name != "com.intellij.codeInspection.unused.ImplicitPropertyUsageProvider") {
""
}
else {
null
}
}
}
else -> createPluginDependencyAndContentBasedScope(descriptor = module, pluginSet = pluginSet)
}
}
else {
dependencies.toArray(arrayOfNulls(dependencies.size))
}
return createPluginClassLoader(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
classPath = classPath,
libDirectories = libDirectories,
pluginSet = pluginSet)
}
private fun configureModule(module: IdeaPluginDescriptorImpl,
plugin: IdeaPluginDescriptorImpl,
files: List<Path>,
libDirectories: MutableList<String>,
classPath: ClassPath) {
if (module.packagePrefix == null) {
throw PluginException("Package is not specified (module=$module)", module.pluginId)
}
checkPackagePrefixUniqueness(module)
dependencies.clear()
// must be before main descriptor classloader
for (item in module.dependencies.modules) {
// Module dependency is always optional. If the module depends on an unavailable plugin, it will not be loaded.
dependencies.add(pluginSet.findEnabledModule(item.name) ?: return)
}
for (item in module.dependencies.plugins) {
val descriptor = pluginSet.findEnabledPlugin(item.id) ?: return
if (descriptor.classLoader !== coreLoader) {
dependencies.add(descriptor)
resolveScopeManager = if (module.content.modules.isEmpty()) {
createModuleResolveScopeManager()
}
else {
// see "The `content.module` element" section about content handling for a module
createModuleContentBasedScope(descriptor = module)
}
}
// add main descriptor classloader as parent
if (plugin.id != PluginManagerCore.CORE_ID) {
dependencies.add(plugin)
}
assert(module.pluginDependencies.isEmpty()) { "Module $module shouldn't have plugin dependencies: ${module.pluginDependencies}" }
val array = if (dependencies.isEmpty()) {
EMPTY_DESCRIPTOR_ARRAY
}
else {
dependencies.toArray(arrayOfNulls(dependencies.size))
}
module.classLoader = PluginClassLoader(
files,
classPath,
array,
module,
coreLoader,
createModuleResolveScopeManager(), module.packagePrefix,
libDirectories
)
}
private fun setPluginClassLoaderForMainAndSubPlugins(rootDescriptor: IdeaPluginDescriptorImpl, classLoader: ClassLoader?) {
rootDescriptor.classLoader = classLoader
for (dependency in rootDescriptor.pluginDependencies) {
if (dependency.subDescriptor != null && pluginSet.isPluginEnabled(dependency.pluginId)) {
setPluginClassLoaderForMainAndSubPlugins(dependency.subDescriptor!!, classLoader)
}
}
for (item in rootDescriptor.content.modules) {
val module = item.requireDescriptor()
// skip if some dependency is not available
if (module.dependencies.modules.any { !pluginSet.isModuleEnabled(it.name) } ||
module.dependencies.plugins.any { !pluginSet.isPluginEnabled(it.id) }) {
continue
}
setPluginClassLoaderForMainAndSubPlugins(module, classLoader)
}
return PluginClassLoader(mainInfo.files,
mainInfo.classPath,
dependencies,
module,
coreLoader,
resolveScopeManager,
module.packagePrefix,
mainInfo.libDirectories)
}
}
@@ -359,91 +297,6 @@ class ClassLoaderConfigurator(
private val log: Logger
get() = Logger.getInstance("#com.intellij.ide.plugins.PluginManager")
// static to ensure that anonymous classes will not hold ClassLoaderConfigurator
private fun createPluginClassLoader(parents: Array<IdeaPluginDescriptorImpl>,
descriptor: IdeaPluginDescriptorImpl,
files: List<Path>,
libDirectories: MutableList<String>,
coreLoader: ClassLoader,
classPath: ClassPath,
pluginSet: PluginSet): PluginClassLoader {
// main plugin descriptor
if (descriptor.descriptorPath == null) {
when (descriptor.id.idString) {
"com.intellij.diagram" -> {
// multiple packages - intellij.diagram and intellij.diagram.impl modules
return createPluginClassLoaderWithExtraPackage(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
classPath = classPath,
libDirectories = libDirectories,
customPackage = "com.intellij.diagram.")
}
"com.intellij.struts2" -> {
return createPluginClassLoaderWithExtraPackage(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
classPath = classPath,
libDirectories = libDirectories,
customPackage = "com.intellij.lang.ognl.")
}
"com.intellij.properties" -> {
// todo ability to customize (cannot move due to backward compatibility)
return createPluginClassloader(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
classPath = classPath,
libDirectories = libDirectories) { name, packagePrefix, force ->
if (force) {
null
}
else if (!name.startsWith(packagePrefix) &&
!name.startsWith("com.intellij.ultimate.PluginVerifier") &&
name != "com.intellij.codeInspection.unused.ImplicitPropertyUsageProvider") {
""
}
else {
null
}
}
}
}
}
else {
if (!descriptor.content.modules.isEmpty()) {
// see "The `content.module` element" section about content handling for a module
return createPluginClassloader(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
classPath = classPath,
libDirectories = libDirectories,
resolveScopeManager = createModuleContentBasedScope(descriptor))
}
else if (descriptor.packagePrefix != null) {
return createPluginClassloader(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
classPath = classPath,
libDirectories = libDirectories, resolveScopeManager = createModuleResolveScopeManager())
}
}
return createPluginClassloader(
parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
classPath = classPath,
libDirectories = libDirectories,
resolveScopeManager = createPluginDependencyAndContentBasedScope(descriptor = descriptor, pluginSet = pluginSet)
)
}
private fun createModuleResolveScopeManager(): PluginClassLoader.ResolveScopeManager {
return PluginClassLoader.ResolveScopeManager { name, packagePrefix, _ ->
// force flag is ignored for module - e.g., RailsViewLineMarkerProvider is referenced
@@ -452,40 +305,16 @@ private fun createModuleResolveScopeManager(): PluginClassLoader.ResolveScopeMan
}
}
private fun createPluginClassloader(parents: Array<IdeaPluginDescriptorImpl>,
descriptor: IdeaPluginDescriptorImpl,
files: List<Path>,
libDirectories: MutableList<String>,
coreLoader: ClassLoader,
classPath: ClassPath,
resolveScopeManager: PluginClassLoader.ResolveScopeManager?): PluginClassLoader {
return PluginClassLoader(files, classPath, parents, descriptor, coreLoader, resolveScopeManager, descriptor.packagePrefix,
libDirectories)
}
private fun createPluginClassLoaderWithExtraPackage(parents: Array<IdeaPluginDescriptorImpl>,
descriptor: IdeaPluginDescriptorImpl,
files: List<Path>,
libDirectories: MutableList<String>,
coreLoader: ClassLoader,
classPath: ClassPath,
customPackage: String): PluginClassLoader {
return createPluginClassloader(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
classPath = classPath,
libDirectories = libDirectories) { name, packagePrefix, force ->
if (force) {
null
private fun createScopeWithExtraPackage(customPackage: String): PluginClassLoader.ResolveScopeManager {
return PluginClassLoader.ResolveScopeManager { name, packagePrefix, force ->
if (!force &&
!name.startsWith(packagePrefix) &&
!name.startsWith("com.intellij.ultimate.PluginVerifier") &&
!name.startsWith(customPackage)) {
""
}
else {
if (!name.startsWith(packagePrefix) && !name.startsWith("com.intellij.ultimate.PluginVerifier") && !name.startsWith(customPackage)) {
""
}
else {
null
}
null
}
}
}
@@ -501,7 +330,7 @@ private fun createPluginDependencyAndContentBasedScope(descriptor: IdeaPluginDes
}
val pluginId = descriptor.pluginId.idString
return PluginClassLoader.ResolveScopeManager { name, _, force ->
return PluginClassLoader.ResolveScopeManager { name, packagePrefix, force ->
if (force) {
return@ResolveScopeManager null
}
@@ -5,7 +5,7 @@ import com.intellij.platform.util.plugins.DataLoader
import com.intellij.platform.util.plugins.LocalFsDataLoader
import java.nio.file.Files
internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, private val isRunningFromSources: Boolean) : PathResolver {
internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, val isRunningFromSources: Boolean) : PathResolver {
override val isFlat: Boolean
get() = true
@@ -15,7 +15,7 @@ internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, pr
base: String?,
relativePath: String): Boolean {
val path = PluginXmlPathResolver.toLoadPath(relativePath, base)
readModuleDescriptor(inputStream = classLoader.getResourceAsStream(path) ?: return false,
readModuleDescriptor(input = classLoader.getResourceAsStream(path) ?: return false,
readContext = readContext,
pathResolver = this,
dataLoader = dataLoader,
@@ -51,7 +51,7 @@ internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, pr
throw RuntimeException("Cannot resolve $path (dataLoader=$dataLoader, classLoader=$classLoader)")
}
}
return readModuleDescriptor(inputStream = resource,
return readModuleDescriptor(input = resource,
readContext = readContext,
pathResolver = this,
dataLoader = dataLoader,
@@ -66,7 +66,7 @@ internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, pr
readInto: RawPluginDescriptor?): RawPluginDescriptor? {
val path = PluginXmlPathResolver.toLoadPath(relativePath, null)
val resource = classLoader.getResourceAsStream(path)
return readModuleDescriptor(inputStream = resource ?: return null,
return readModuleDescriptor(input = resource ?: return null,
readContext = readContext,
pathResolver = this,
dataLoader = dataLoader,
@@ -67,12 +67,12 @@ class DescriptorListLoadingContext constructor(
get() = threadLocalXmlFactory.get()[0]!!.visitedFiles
fun checkOptionalConfigShortName(configFile: String, descriptor: IdeaPluginDescriptor): Boolean {
val pluginId = descriptor.pluginId ?: return false
val configNames = optionalConfigNames
if (configNames == null || configFile.startsWith("intellij.")) {
return false
}
val pluginId = descriptor.pluginId
val oldPluginId = configNames.put(configFile, pluginId)
if (oldPluginId == null || oldPluginId == pluginId) {
return false
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.plugins
import com.intellij.openapi.progress.ProcessCanceledException
@@ -34,6 +34,8 @@ interface DynamicPluginListener {
/**
* Checks if the plugin can be dynamically unloaded at this moment.
* Method should throw [CannotUnloadPluginException] if it isn't possible for some reason.
*
* Not dispatched for a content modules (plugin model V2).
*/
@Throws(CannotUnloadPluginException::class)
@JvmDefault
@@ -1,4 +1,5 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:Suppress("ReplaceGetOrSet")
package com.intellij.ide.plugins
import com.intellij.AbstractBundle
@@ -24,10 +25,12 @@ private val checkCompatibilityFlag = System.getProperty("idea.plugin.check.compa
@ApiStatus.Internal
class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
val path: Path,
@JvmField val path: Path,
private val isBundled: Boolean,
id: PluginId?) : IdeaPluginDescriptor {
val id: PluginId = id ?: PluginId.getId(raw.id ?: raw.name ?: throw RuntimeException("Neither id nor name are specified"))
id: PluginId?,
@JvmField val moduleName: String?,
@JvmField val useCoreClassLoader: Boolean = false) : IdeaPluginDescriptor {
private val id: PluginId = id ?: PluginId.getId(raw.id ?: raw.name ?: throw RuntimeException("Neither id nor name are specified"))
private val name = raw.name ?: id?.idString ?: raw.id
@Suppress("EnumEntryName")
@@ -74,10 +77,15 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
pluginDependencies = list ?: Collections.emptyList()
}
companion object {
@ApiStatus.Internal
@JvmField var disableNonBundledPlugins = false
}
@Transient @JvmField var jarFiles: List<Path>? = null
@JvmField var classLoader: ClassLoader? = null
@JvmField val actions: List<RawPluginDescriptor.ActionDescriptor>? = raw.actions
@JvmField val actions: List<RawPluginDescriptor.ActionDescriptor> = raw.actions ?: Collections.emptyList()
// extension point name -> list of extension descriptors
val epNameToExtensions: Map<String, MutableList<ExtensionDescriptor>>? = raw.epNameToExtensions
@@ -94,9 +102,6 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
@JvmField val isUseIdeaClassLoader = raw.isUseIdeaClassLoader
var isUseCoreClassLoader = false
private set
@JvmField val isBundledUpdateAllowed = raw.isBundledUpdateAllowed
@JvmField internal val implementationDetail = raw.implementationDetail
@@ -125,10 +130,12 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
descriptorPath: String,
pathResolver: PathResolver,
context: DescriptorListLoadingContext,
dataLoader: DataLoader): IdeaPluginDescriptorImpl {
dataLoader: DataLoader,
moduleName: String?): IdeaPluginDescriptorImpl {
raw.name = name
@Suppress("TestOnlyProblems")
val result = IdeaPluginDescriptorImpl(raw, path = path, isBundled = isBundled, id = id)
val result = IdeaPluginDescriptorImpl(raw, path = path, isBundled = isBundled, id = id, moduleName = moduleName,
useCoreClassLoader = useCoreClassLoader)
result.descriptorPath = descriptorPath
result.vendor = vendor
result.version = version
@@ -155,7 +162,8 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
descriptorPath = subDescriptorFile,
pathResolver = pathResolver,
context = context,
dataLoader = dataLoader)
dataLoader = dataLoader,
moduleName = module.name)
module.descriptor = subDescriptor
}
}
@@ -183,6 +191,11 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
markAsIncomplete(context, disabledDependency = null, shortMessage = null)
}
else {
checkCompatibility(context)
if (isIncomplete) {
return
}
for (pluginDependency in dependencies.plugins) {
if (context.isPluginDisabled(pluginDependency.id)) {
markAsIncomplete(context, pluginDependency.id, shortMessage = "plugin.loading.error.short.depends.on.disabled.plugin")
@@ -197,13 +210,13 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
}
}
processOldDependencies(descriptor = this,
context = context,
pathResolver = pathResolver,
dependencies = pluginDependencies,
dataLoader = dataLoader)
checkCompatibility(context)
if (!isIncomplete && moduleName == null) {
processOldDependencies(descriptor = this,
context = context,
pathResolver = pathResolver,
dependencies = pluginDependencies,
dataLoader = dataLoader)
}
}
private fun processOldDependencies(descriptor: IdeaPluginDescriptorImpl,
@@ -268,22 +281,53 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
descriptorPath = configFile,
pathResolver = pathResolver,
context = context,
dataLoader = dataLoader)
dataLoader = dataLoader,
moduleName = null)
dependency.subDescriptor = subDescriptor
visitedFiles.clear()
}
}
private fun checkCompatibility(context: DescriptorListLoadingContext) {
if (isBundled || !checkCompatibilityFlag || (sinceBuild == null && untilBuild == null)) {
if (isBundled) {
return
}
val error = PluginManagerCore.checkBuildNumberCompatibility(this, context.result.productBuildNumber.get()) ?: return
fun markAsIncompatible(error: PluginLoadingError) {
if (isIncomplete) {
return
}
// error will be added by reportIncompatiblePlugin
markAsIncomplete(context = context, disabledDependency = null, shortMessage = null)
context.result.reportIncompatiblePlugin(this, error)
isIncomplete = true
isEnabled = false
context.result.addIncompletePlugin(plugin = this, error = error)
}
if (disableNonBundledPlugins) {
markAsIncompatible(PluginLoadingError(
plugin = this,
detailedMessageSupplier = { CoreBundle.message("plugin.loading.error.long.custom.plugin.loading.disabled", getName()) },
shortMessageSupplier = { CoreBundle.message("plugin.loading.error.short.custom.plugin.loading.disabled") },
isNotifyUser = false
))
return
}
if (checkCompatibilityFlag && (sinceBuild != null || untilBuild != null)) {
PluginManagerCore.checkBuildNumberCompatibility(this, context.result.productBuildNumber.get())?.let {
markAsIncompatible(it)
return
}
}
// "Show broken plugins in Settings | Plugins so that users can uninstall them and resolve "Plugin Error" (IDEA-232675)"
if (context.result.isBroken(this)) {
markAsIncompatible(PluginLoadingError(
plugin = this,
detailedMessageSupplier = { CoreBundle.message("plugin.loading.error.long.marked.as.broken", name, version) },
shortMessageSupplier = { CoreBundle.message("plugin.loading.error.short.marked.as.broken") }
))
}
}
private fun markAsIncomplete(context: DescriptorListLoadingContext,
@@ -303,19 +347,13 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
else {
PluginLoadingError(plugin = this,
detailedMessageSupplier = null,
shortMessageSupplier = {
CoreBundle.message(shortMessage, pluginId!!)
},
shortMessageSupplier = { CoreBundle.message(shortMessage, pluginId!!) },
isNotifyUser = false,
disabledDependency = disabledDependency)
}
context.result.addIncompletePlugin(this, pluginError)
}
fun collectExtensionPoints() {
}
@ApiStatus.Internal
fun registerExtensions(nameToPoint: Map<String, ExtensionPointImpl<*>>,
containerDescriptor: ContainerDescriptor,
@@ -459,10 +497,6 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
override fun getPluginClassLoader(): ClassLoader = classLoader ?: javaClass.classLoader
fun setUseCoreClassLoader() {
isUseCoreClassLoader = true
}
override fun isEnabled() = isEnabled
override fun setEnabled(enabled: Boolean) {
@@ -499,6 +533,7 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor,
return "PluginDescriptor(" +
"name=$name, " +
"id=$id, " +
(if (moduleName == null) "" else "moduleName=$moduleName, ") +
"descriptorPath=${descriptorPath ?: "plugin.xml"}, " +
"path=${pluginPathToUserString(path)}, " +
"version=$version, " +
@@ -1,18 +1,17 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.plugins
import com.intellij.platform.util.plugins.DataLoader
import com.intellij.util.lang.ZipFilePool
import com.intellij.util.lang.ZipFilePool.EntryResolver
import org.jetbrains.annotations.ApiStatus
import java.io.InputStream
import java.nio.file.Path
@ApiStatus.Internal
class ImmutableZipFileDataLoader(private val resolver: EntryResolver,
private val zipPath: Path,
override val pool: ZipFilePool) : DataLoader {
override fun load(path: String): ByteArray? {
// well, path maybe specified as `/META-INF/*` in plugin descriptor and
internal class ImmutableZipFileDataLoader(private val resolver: EntryResolver,
private val zipPath: Path,
override val pool: ZipFilePool) : DataLoader {
override fun load(path: String): InputStream? {
// well, path maybe specified as `/META-INF/*` in plugin descriptor, and
// it is our responsibility to normalize path for ImmutableZipFile API
// do not use kotlin stdlib here
return resolver.loadZipEntry(if (path[0] == '/') path.substring(1) else path)
@@ -6,6 +6,7 @@ import com.intellij.openapi.extensions.PluginId
import org.jetbrains.annotations.ApiStatus
import java.util.*
@ApiStatus.Internal
class ModuleDependenciesDescriptor(@JvmField val modules: List<ModuleReference>, @JvmField val plugins: List<PluginReference>) {
companion object {
@JvmField val EMPTY = ModuleDependenciesDescriptor(Collections.emptyList(), Collections.emptyList())
@@ -29,8 +30,7 @@ class PluginContentDescriptor(@JvmField val modules: List<ModuleItem>) {
}
@ApiStatus.Internal
class ModuleItem(@JvmField val name: String,
@JvmField val configFile: String?) {
class ModuleItem(@JvmField val name: String, @JvmField val configFile: String?) {
@JvmField internal var descriptor: IdeaPluginDescriptorImpl? = null
fun requireDescriptor() = descriptor ?: throw IllegalStateException("Descriptor is not set for $this")
@@ -38,7 +38,5 @@ class PluginContentDescriptor(@JvmField val modules: List<ModuleItem>) {
override fun toString() = "ModuleItem(name=$name, descriptor=$descriptor, configFile=$configFile)"
}
override fun toString(): String {
return "PluginContentDescriptor(modules=$modules)"
}
override fun toString() = "PluginContentDescriptor(modules=$modules)"
}
@@ -11,7 +11,6 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.util.BuildNumber
import com.intellij.openapi.util.io.NioFiles
import com.intellij.openapi.util.text.Strings
import com.intellij.platform.util.plugins.DataLoader
import com.intellij.platform.util.plugins.LocalFsDataLoader
import com.intellij.util.PlatformUtils
@@ -24,6 +23,7 @@ import org.jetbrains.annotations.TestOnly
import java.io.Closeable
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.net.URL
import java.nio.charset.StandardCharsets
import java.nio.file.Files
@@ -32,6 +32,7 @@ import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.*
import java.util.function.BiConsumer
import java.util.function.Supplier
import java.util.zip.ZipFile
import javax.xml.stream.XMLStreamException
@@ -44,16 +45,15 @@ internal fun createPluginLoadingResult(buildNumber: BuildNumber?): PluginLoading
productBuildNumber = { buildNumber ?: PluginManagerCore.getBuildNumber() })
}
fun loadDescriptor(file: Path,
isBundled: Boolean,
parentContext: DescriptorListLoadingContext): IdeaPluginDescriptorImpl? {
@TestOnly
fun loadDescriptor(file: Path, parentContext: DescriptorListLoadingContext): IdeaPluginDescriptorImpl? {
return loadDescriptorFromFileOrDir(file = file,
pathName = PluginManagerCore.PLUGIN_XML,
context = parentContext,
pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER,
isBundled = isBundled,
isBundled = false,
isEssential = false,
isDirectory = Files.isDirectory(file))
isDirectory = Files.isDirectory(file),
useCoreClassLoader = false)
}
internal fun loadForCoreEnv(pluginRoot: Path, fileName: String): IdeaPluginDescriptorImpl? {
@@ -66,7 +66,8 @@ internal fun loadForCoreEnv(pluginRoot: Path, fileName: String): IdeaPluginDescr
context = parentContext,
isBundled = true,
isEssential = true,
pathResolver = pathResolver)
pathResolver = pathResolver,
useCoreClassLoader = false)
}
else {
return loadDescriptorFromJar(file = pluginRoot,
@@ -75,7 +76,8 @@ internal fun loadForCoreEnv(pluginRoot: Path, fileName: String): IdeaPluginDescr
parentContext = parentContext,
isBundled = true,
isEssential = true,
pluginPath = null)
pluginPath = null,
useCoreClassLoader = false)
}
}
@@ -85,6 +87,7 @@ private fun loadDescriptorFromDir(file: Path,
context: DescriptorListLoadingContext,
isBundled: Boolean,
isEssential: Boolean,
useCoreClassLoader: Boolean,
pathResolver: PathResolver): IdeaPluginDescriptorImpl? {
try {
val input = Files.readAllBytes(file.resolve(descriptorRelativePath))
@@ -96,7 +99,8 @@ private fun loadDescriptorFromDir(file: Path,
includeBase = null,
readInto = null,
locationSource = file.toString())
val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = pluginPath ?: file, isBundled = isBundled, id = null)
val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = pluginPath ?: file, isBundled = isBundled, id = null, moduleName = null,
useCoreClassLoader = useCoreClassLoader)
descriptor.readExternal(raw = raw, pathResolver = pathResolver, context = context, isSub = false, dataLoader = dataLoader)
descriptor.jarFiles = Collections.singletonList(file)
return descriptor
@@ -119,6 +123,7 @@ private fun loadDescriptorFromJar(file: Path,
parentContext: DescriptorListLoadingContext,
isBundled: Boolean,
isEssential: Boolean,
useCoreClassLoader: Boolean,
pluginPath: Path?): IdeaPluginDescriptorImpl? {
var closeable: Closeable? = null
try {
@@ -141,7 +146,8 @@ private fun loadDescriptorFromJar(file: Path,
readInto = null,
locationSource = file.toString())
val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = pluginPath ?: file, isBundled = isBundled, id = null)
val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = pluginPath ?: file, isBundled = isBundled, id = null, moduleName = null,
useCoreClassLoader = useCoreClassLoader)
descriptor.readExternal(raw = raw, pathResolver = pathResolver, context = parentContext, isSub = false, dataLoader = dataLoader)
descriptor.jarFiles = Collections.singletonList(descriptor.pluginPath)
return descriptor
@@ -162,44 +168,49 @@ private class JavaZipFileDataLoader(private val file: ZipFile) : DataLoader {
override val pool: ZipFilePool?
get() = null
override fun load(path: String): ByteArray? {
override fun load(path: String): InputStream? {
val entry = file.getEntry(if (path[0] == '/') path.substring(1) else path) ?: return null
return file.getInputStream(entry).use { it.readBytes() }
return file.getInputStream(entry)
}
override fun toString() = file.toString()
}
fun loadDescriptorFromFileOrDir(file: Path,
pathName: String,
context: DescriptorListLoadingContext,
pathResolver: PathResolver,
isBundled: Boolean,
isEssential: Boolean,
isDirectory: Boolean): IdeaPluginDescriptorImpl? {
isDirectory: Boolean,
useCoreClassLoader: Boolean): IdeaPluginDescriptorImpl? {
return when {
isDirectory -> {
val descriptorRelativePath = "${PluginManagerCore.META_INF}$pathName"
loadDescriptorFromDir(file = file,
descriptorRelativePath = descriptorRelativePath,
descriptorRelativePath = PluginManagerCore.PLUGIN_XML_PATH,
pluginPath = null,
context = context,
isBundled = isBundled,
isEssential = isEssential,
pathResolver = pathResolver)?.let {
pathResolver = pathResolver,
useCoreClassLoader = useCoreClassLoader)?.let {
return it
}
loadFromPluginDir(file = file, pathName = pathName, parentContext = context, isBundled = isBundled,
isEssential = isEssential, descriptorRelativePath = descriptorRelativePath, pathResolver = pathResolver)
loadFromPluginDir(file = file,
parentContext = context,
isBundled = isBundled,
isEssential = isEssential,
useCoreClassLoader = useCoreClassLoader,
pathResolver = pathResolver)
}
file.fileName.toString().endsWith(".jar", ignoreCase = true) -> {
loadDescriptorFromJar(file = file,
fileName = pathName,
fileName = PluginManagerCore.PLUGIN_XML,
pathResolver = pathResolver,
parentContext = context,
isBundled = isBundled,
isEssential = isEssential,
pluginPath = null)
pluginPath = null,
useCoreClassLoader = useCoreClassLoader)
}
else -> null
}
@@ -209,11 +220,10 @@ fun loadDescriptorFromFileOrDir(file: Path,
// or
// lib/*.jar
private fun loadFromPluginDir(file: Path,
pathName: String,
parentContext: DescriptorListLoadingContext,
isBundled: Boolean,
isEssential: Boolean,
descriptorRelativePath: String,
useCoreClassLoader: Boolean,
pathResolver: PathResolver): IdeaPluginDescriptorImpl? {
val pluginJarFiles = ArrayList<Path>()
try {
@@ -235,12 +245,13 @@ private fun loadFromPluginDir(file: Path,
val pluginPathResolver = PluginXmlPathResolver(pluginJarFiles)
for (jarFile in pluginJarFiles) {
loadDescriptorFromJar(file = jarFile,
fileName = pathName,
fileName = PluginManagerCore.PLUGIN_XML,
pathResolver = pluginPathResolver,
parentContext = parentContext,
isBundled = isBundled,
isEssential = isEssential,
pluginPath = file)?.let {
pluginPath = file,
useCoreClassLoader = useCoreClassLoader)?.let {
it.jarFiles = pluginJarFiles
return it
}
@@ -251,12 +262,13 @@ private fun loadFromPluginDir(file: Path,
if (!isBundled) {
val classesDir = file.resolve("classes")
loadDescriptorFromDir(file = classesDir,
descriptorRelativePath = descriptorRelativePath,
descriptorRelativePath = PluginManagerCore.PLUGIN_XML_PATH,
pluginPath = file,
context = parentContext,
isBundled = isBundled,
isBundled = false,
isEssential = isEssential,
pathResolver = pathResolver)?.let {
pathResolver = pathResolver,
useCoreClassLoader = useCoreClassLoader)?.let {
val classPath = ArrayList<Path>(pluginJarFiles.size + 1)
classPath.add(classesDir)
classPath.addAll(pluginJarFiles)
@@ -327,15 +339,19 @@ private fun loadDescriptorsFromProperty(result: PluginLoadingResult, context: De
// gradle-intellij-plugin heavily depends on this property in order to have core class loader plugins during tests
val useCoreClassLoaderForPluginsFromProperty = java.lang.Boolean.parseBoolean(
System.getProperty("idea.use.core.classloader.for.plugin.path"))
val t = StringTokenizer(pathProperty, File.pathSeparatorChar.toString() + ",")
val t = StringTokenizer(pathProperty, File.pathSeparatorChar + ",")
while (t.hasMoreTokens()) {
val s = t.nextToken()
loadDescriptor(Paths.get(s), false, context)?.let {
val file = Paths.get(s)
loadDescriptorFromFileOrDir(file = file,
context = context,
pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER,
isBundled = false,
isEssential = false,
isDirectory = Files.isDirectory(file),
useCoreClassLoader = useCoreClassLoaderForPluginsFromProperty)?.let {
// plugins added via property shouldn't be overridden to avoid plugin root detection issues when running external plugin tests
result.add(it, /* overrideUseIfCompatible = */true)
if (useCoreClassLoaderForPluginsFromProperty) {
it.setUseCoreClassLoader()
}
result.add(it, overrideUseIfCompatible = true)
}
}
}
@@ -385,9 +401,10 @@ private fun loadBundledDescriptorsAndDescriptorsFromDir(context: DescriptorListL
val platformPrefix = PlatformUtils.getPlatformPrefix()
// should be the only plugin in lib (only for Ultimate and WebStorm for now)
val pathResolver = ClassPathXmlPathResolver(classLoader, isRunningFromSources = isRunningFromSources)
val isInDevServerMode = java.lang.Boolean.getBoolean("idea.use.dev.build.server")
val pathResolver = ClassPathXmlPathResolver(classLoader, isRunningFromSources = isRunningFromSources && !isInDevServerMode)
if ((platformPrefix == PlatformUtils.IDEA_PREFIX || platformPrefix == PlatformUtils.WEB_PREFIX) &&
(java.lang.Boolean.getBoolean("idea.use.dev.build.server") || (!isUnitTestMode && !isRunningFromSources))) {
(isInDevServerMode || (!isUnitTestMode && !isRunningFromSources))) {
val dataLoader = object : DataLoader {
override val pool: ZipFilePool
get() = throw IllegalStateException("must be not called")
@@ -397,24 +414,28 @@ private fun loadBundledDescriptorsAndDescriptorsFromDir(context: DescriptorListL
override fun toString() = "product classpath"
}
val raw = readModuleDescriptor(inputStream = classLoader.getResourceAsStream(PluginManagerCore.PLUGIN_XML_PATH)!!,
val raw = readModuleDescriptor(input = classLoader.getResourceAsStream(PluginManagerCore.PLUGIN_XML_PATH)!!,
readContext = context,
pathResolver = pathResolver,
dataLoader = dataLoader,
includeBase = null,
readInto = null,
locationSource = null)
val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = Paths.get(PathManager.getLibPath()), isBundled = true, id = null)
val descriptor = IdeaPluginDescriptorImpl(raw = raw,
path = Paths.get(PathManager.getLibPath()),
isBundled = true,
id = null,
moduleName = null,
useCoreClassLoader = pathResolver.isRunningFromSources)
descriptor.readExternal(raw = raw, pathResolver = pathResolver, context = context, isSub = false, dataLoader = dataLoader)
descriptor.setUseCoreClassLoader()
context.result.add(descriptor, /* overrideUseIfCompatible = */false)
context.result.add(descriptor, overrideUseIfCompatible = false)
}
else {
val urlsFromClassPath = LinkedHashMap<URL, String>()
val platformPluginURL = computePlatformPluginUrlAndCollectPluginUrls(classLoader, urlsFromClassPath, platformPrefix)
if (!urlsFromClassPath.isEmpty()) {
val urlToFilename = LinkedHashMap<URL, String>()
val platformPluginURL = computePlatformPluginUrlAndCollectPluginUrls(classLoader, urlToFilename, platformPrefix)
if (!urlToFilename.isEmpty()) {
activity = activity.endAndStart("plugin from classpath loading")
pool.invoke(LoadDescriptorsFromClassPathAction(urls = urlsFromClassPath,
pool.invoke(LoadDescriptorsFromClassPathAction(urlToFilename = urlToFilename,
context = context,
platformPluginURL = platformPluginURL,
pathResolver = pathResolver))
@@ -474,12 +495,12 @@ fun loadDescriptorFromArtifact(file: Path, buildNumber: BuildNumber?): IdeaPlugi
transient = true)
val descriptor = loadDescriptorFromFileOrDir(file = file,
pathName = PluginManagerCore.PLUGIN_XML,
context = context,
pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER,
isBundled = false,
isEssential = false,
isDirectory = false)
isDirectory = false,
useCoreClassLoader = false)
if (descriptor != null || !file.toString().endsWith(".zip")) {
return descriptor
}
@@ -491,12 +512,12 @@ fun loadDescriptorFromArtifact(file: Path, buildNumber: BuildNumber?): IdeaPlugi
val rootDir = NioFiles.list(outputDir).firstOrNull()
if (rootDir != null) {
return loadDescriptorFromFileOrDir(file = rootDir,
pathName = PluginManagerCore.PLUGIN_XML,
context = context,
pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER,
isBundled = false,
isEssential = false,
isDirectory = true)
isDirectory = true,
useCoreClassLoader = false)
}
}
catch (ignore: NoSuchFileException) { }
@@ -514,12 +535,12 @@ fun loadDescriptor(file: Path,
pathResolver: PathResolver): IdeaPluginDescriptorImpl? {
DescriptorListLoadingContext(disabledPlugins = disabledPlugins).use { context ->
return loadDescriptorFromFileOrDir(file = file,
pathName = PluginManagerCore.PLUGIN_XML,
context = context,
pathResolver = pathResolver,
isBundled = isBundled,
isEssential = false,
isDirectory = Files.isDirectory(file))
isDirectory = Files.isDirectory(file),
useCoreClassLoader = false)
}
}
@@ -567,14 +588,14 @@ fun getDescriptorsToMigrate(dir: Path,
@TestOnly
fun testLoadDescriptorsFromClassPath(loader: ClassLoader): List<IdeaPluginDescriptor> {
val urlsFromClassPath = LinkedHashMap<URL, String>()
collectPluginFilesInClassPath(loader, urlsFromClassPath)
val urlToFilename = LinkedHashMap<URL, String>()
collectPluginFilesInClassPath(loader, urlToFilename)
val buildNumber = BuildNumber.fromString("2042.42")!!
val context = DescriptorListLoadingContext(disabledPlugins = emptySet(),
result = PluginLoadingResult(brokenPluginVersions = emptyMap(),
productBuildNumber = Supplier { buildNumber },
checkModuleDependencies = false))
LoadDescriptorsFromClassPathAction(urlsFromClassPath, context, null, ClassPathXmlPathResolver(loader, isRunningFromSources = false)).compute()
LoadDescriptorsFromClassPathAction(urlToFilename, context, null, ClassPathXmlPathResolver(loader, isRunningFromSources = false)).compute()
context.result.finishLoading()
return context.result.getEnabledPlugins()
}
@@ -591,12 +612,11 @@ private class LoadDescriptorsFromDirAction(private val dir: Path,
override fun compute(): IdeaPluginDescriptorImpl? {
if (Files.isDirectory(file)) {
return loadFromPluginDir(file = file,
pathName = PluginManagerCore.PLUGIN_XML,
parentContext = context,
isBundled = isBundled,
isEssential = false,
pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER,
descriptorRelativePath = "${PluginManagerCore.META_INF}${PluginManagerCore.PLUGIN_XML}")
useCoreClassLoader = false)
}
else if (file.fileName.toString().endsWith(".jar", ignoreCase = true)) {
return loadDescriptorFromJar(file = file,
@@ -605,7 +625,8 @@ private class LoadDescriptorsFromDirAction(private val dir: Path,
parentContext = context,
isBundled = isBundled,
isEssential = false,
pluginPath = null)
pluginPath = null,
useCoreClassLoader = false)
}
else {
return null
@@ -628,82 +649,91 @@ private class LoadDescriptorsFromDirAction(private val dir: Path,
}
}
private class LoadDescriptorsFromClassPathAction(private val urls: Map<URL, String>,
// urls here expected to be a file urls to plugin.xml
private class LoadDescriptorsFromClassPathAction(private val urlToFilename: Map<URL, String>,
private val context: DescriptorListLoadingContext,
private val platformPluginURL: URL?,
private val pathResolver: PathResolver) : RecursiveAction() {
private val pathResolver: ClassPathXmlPathResolver) : RecursiveAction() {
public override fun compute() {
val tasks = ArrayList<RecursiveTask<IdeaPluginDescriptorImpl?>>(urls.size)
for ((url, value) in urls) {
val tasks = ArrayList<ForkJoinTask<IdeaPluginDescriptorImpl?>>(urlToFilename.size)
urlToFilename.forEach(BiConsumer { url, filename ->
tasks.add(object : RecursiveTask<IdeaPluginDescriptorImpl?>() {
override fun compute(): IdeaPluginDescriptorImpl? {
val isEssential = url == platformPluginURL
try {
return loadDescriptorFromResource(resource = url, pathName = value, isEssential = isEssential)
return loadDescriptorFromResource(resource = url, filename = filename)
}
catch (e: Throwable) {
if (isEssential) {
throw e
}
LOG.info("Cannot load $url", e)
return null
}
}
})
}
})
val result = context.result
ForkJoinTask.invokeAll(tasks)
for (task in tasks) {
task.rawResult?.let {
it.setUseCoreClassLoader()
result.add(it, overrideUseIfCompatible = false)
}
}
}
private fun loadDescriptorFromResource(resource: URL, pathName: String, isEssential: Boolean): IdeaPluginDescriptorImpl? {
when {
URLUtil.FILE_PROTOCOL == resource.protocol -> {
val file = Paths.get(Strings.trimEnd(UrlClassLoader.urlToFilePath(resource.path).replace('\\', '/'), pathName)).parent
return loadDescriptorFromFileOrDir(file = file,
pathName = pathName,
context = context,
pathResolver = pathResolver,
isBundled = true,
isEssential = isEssential,
isDirectory = Files.isDirectory(file))
}
URLUtil.JAR_PROTOCOL == resource.protocol -> {
val file = Paths.get(UrlClassLoader.urlToFilePath(resource.path))
val parentFile = file.parent
if (parentFile == null || !parentFile.endsWith("lib")) {
return loadDescriptorFromJar(file = file,
fileName = pathName,
pathResolver = pathResolver,
parentContext = context,
isBundled = true,
isEssential = isEssential,
pluginPath = null)
// filename - plugin.xml or ${platformPrefix}Plugin.xml
private fun loadDescriptorFromResource(resource: URL, filename: String): IdeaPluginDescriptorImpl? {
val file = Paths.get(UrlClassLoader.urlToFilePath(resource.path))
var closeable: Closeable? = null
val dataLoader: DataLoader
val basePath: Path
try {
val input: InputStream
when {
URLUtil.FILE_PROTOCOL == resource.protocol -> {
basePath = file.parent.parent
dataLoader = LocalFsDataLoader(basePath)
input = Files.newInputStream(file)
}
else {
// Support for unpacked plugins in classpath. E.g. .../community/build/dependencies/build/kotlin/Kotlin/lib/kotlin-plugin.jar
val descriptor = loadDescriptorFromJar(file = file,
fileName = pathName,
pathResolver = pathResolver,
parentContext = context,
isBundled = true,
isEssential = isEssential,
pluginPath = file.parent.parent)
if (descriptor != null) {
descriptor.jarFiles = Collections.singletonList(file)
URLUtil.JAR_PROTOCOL == resource.protocol -> {
// support for unpacked plugins in classpath, e.g. .../community/build/dependencies/build/kotlin/Kotlin/lib/kotlin-plugin.jar
basePath = file.parent?.takeIf { !it.endsWith("lib") }?.parent ?: file
val pool = if (context.transient) null else ZipFilePool.POOL
if (pool == null) {
val zipFile = ZipFile(file.toFile(), StandardCharsets.UTF_8)
closeable = zipFile
dataLoader = JavaZipFileDataLoader(zipFile)
}
return descriptor
else {
dataLoader = ImmutableZipFileDataLoader(pool.load(file), file, pool)
}
input = dataLoader.load("META-INF/$filename") ?: return null
}
else -> return null
}
else -> {
return null
}
val raw = readModuleDescriptor(input = input,
readContext = context,
pathResolver = pathResolver,
dataLoader = dataLoader,
includeBase = null,
readInto = null,
locationSource = file.toString())
// it is very important to not set useCoreClassLoader = true blindly
// - product modules must uses own class loader if not running from sources
val descriptor = IdeaPluginDescriptorImpl(raw = raw, path = basePath, isBundled = true, id = null, moduleName = null,
useCoreClassLoader = pathResolver.isRunningFromSources)
descriptor.readExternal(raw = raw, pathResolver = pathResolver, context = context, isSub = false, dataLoader = dataLoader)
// do not set jarFiles by intention - doesn't make sense
return descriptor
}
finally {
closeable?.close()
}
}
}
@@ -1,10 +1,12 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:Suppress("ReplaceGetOrSet")
package com.intellij.ide.plugins
import com.intellij.core.CoreBundle
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.util.BuildNumber
import com.intellij.util.PlatformUtils
import com.intellij.util.lang.Java11Shim
import com.intellij.util.text.VersionComparatorUtil
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.TestOnly
@@ -55,6 +57,10 @@ class PluginLoadingResult(private val brokenPluginVersions: Map<PluginId, Set<St
return descriptor != null && set.contains(descriptor.version)
}
fun isBroken(descriptor: IdeaPluginDescriptorImpl): Boolean {
return (brokenPluginVersions.get(descriptor.pluginId) ?: return false).contains(descriptor.version)
}
internal fun getPluginErrors(): Map<PluginId, PluginLoadingError> = Collections.unmodifiableMap(pluginErrors)
fun getGlobalErrors(): List<Supplier<String>> {
@@ -64,22 +70,22 @@ class PluginLoadingResult(private val brokenPluginVersions: Map<PluginId, Set<St
}
internal fun addIncompletePlugin(plugin: IdeaPluginDescriptorImpl, error: PluginLoadingError?) {
if (!idMap.containsKey(plugin.pluginId)) {
val existingIncompletePlugin = incompletePlugins.get(plugin.pluginId)
if (existingIncompletePlugin == null || VersionComparatorUtil.compare(plugin.version, existingIncompletePlugin.version) > 0) {
incompletePlugins.put(plugin.pluginId, plugin)
}
}
if (error != null) {
pluginErrors.put(plugin.pluginId, error)
}
}
internal fun reportIncompatiblePlugin(plugin: IdeaPluginDescriptorImpl, error: PluginLoadingError) {
// do not report if some compatible plugin were already added
// no race condition here: plugins from classpath are loaded before and not in parallel to loading from plugin dir
if (!idMap.containsKey(plugin.pluginId)) {
pluginErrors.put(plugin.pluginId, error)
if (idMap.containsKey(plugin.pluginId)) {
return
}
val existingIncompletePlugin = incompletePlugins.putIfAbsent(plugin.pluginId, plugin)
if (existingIncompletePlugin != null && VersionComparatorUtil.compare(plugin.version, existingIncompletePlugin.version) > 0) {
incompletePlugins.put(plugin.pluginId, plugin)
if (error != null) {
// force put
pluginErrors.put(plugin.pluginId, error)
}
}
else if (error != null) {
pluginErrors.putIfAbsent(plugin.pluginId, error)
}
}
@@ -99,15 +105,12 @@ class PluginLoadingResult(private val brokenPluginVersions: Map<PluginId, Set<St
if (checkModuleDependencies &&
!descriptor.isBundled && descriptor.packagePrefix == null &&
!PluginManagerCore.hasModuleDependencies(descriptor)) {
val error = PluginLoadingError(plugin = descriptor,
detailedMessageSupplier = {
CoreBundle.message("plugin.loading.error.long.compatible.with.intellij.idea.only", descriptor.name)
},
shortMessageSupplier = {
CoreBundle.message("plugin.loading.error.short.compatible.with.intellij.idea.only")
},
isNotifyUser = true)
addIncompletePlugin(descriptor, error)
addIncompletePlugin(descriptor, PluginLoadingError(
plugin = descriptor,
detailedMessageSupplier = { CoreBundle.message("plugin.loading.error.long.compatible.with.intellij.idea.only", descriptor.name) },
shortMessageSupplier = { CoreBundle.message("plugin.loading.error.short.compatible.with.intellij.idea.only") },
isNotifyUser = true
))
return false
}
@@ -161,4 +164,15 @@ class PluginLoadingResult(private val brokenPluginVersions: Map<PluginId, Set<St
list.add(descriptor)
duplicateModuleMap!!.put(id, list)
}
}
// todo merge into PluginSetState?
@ApiStatus.Internal
class PluginManagerState internal constructor(@JvmField val pluginSet: PluginSet,
disabledRequiredIds: Set<IdeaPluginDescriptorImpl>,
effectiveDisabledIds: Set<IdeaPluginDescriptorImpl>) {
@JvmField val effectiveDisabledIds: Set<PluginId> =
Java11Shim.INSTANCE.copyOf(effectiveDisabledIds.mapTo(HashSet(effectiveDisabledIds.size), IdeaPluginDescriptorImpl::getPluginId))
@JvmField val disabledRequiredIds: Set<PluginId> =
Java11Shim.INSTANCE.copyOf(disabledRequiredIds.mapTo(HashSet(disabledRequiredIds.size), IdeaPluginDescriptorImpl::getPluginId))
}
@@ -164,13 +164,7 @@ public final class PluginManager {
}
public @Nullable IdeaPluginDescriptor findEnabledPlugin(@NotNull PluginId id) {
List<IdeaPluginDescriptorImpl> result = PluginManagerCore.getLoadedPlugins(null);
for (IdeaPluginDescriptor plugin : result) {
if (id.equals(plugin.getPluginId())) {
return plugin;
}
}
return null;
return PluginManagerCore.getPluginSet().findEnabledPlugin(id);
}
public boolean hideImplementationDetails() {
@@ -14,14 +14,11 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.PlatformUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.graph.DFSTBuilder;
import com.intellij.util.lang.Java11Shim;
import com.intellij.util.lang.UrlClassLoader;
import org.jetbrains.annotations.*;
@@ -88,10 +85,6 @@ public final class PluginManagerCore {
private static Set<PluginId> ourPluginsToDisable;
private static Set<PluginId> ourPluginsToEnable;
@SuppressWarnings("StaticNonFinalField")
@ApiStatus.Internal
public static boolean ourDisableNonBundledPlugins;
/**
* Bundled plugins that were updated.
* When we update bundled plugin it becomes not bundled, so it is more difficult for analytics to use that data.
@@ -114,7 +107,8 @@ public final class PluginManagerCore {
return getPluginSet().allPlugins.toArray(new IdeaPluginDescriptor[0]);
}
static @NotNull PluginSet getPluginSet() {
@ApiStatus.Internal
public static @NotNull PluginSet getPluginSet() {
return Objects.requireNonNull(pluginSet);
}
@@ -123,7 +117,7 @@ public final class PluginManagerCore {
* the plugins it depends on.
*/
public static @NotNull List<? extends IdeaPluginDescriptor> getLoadedPlugins() {
return getLoadedPlugins(null);
return getPluginSet().enabledPlugins;
}
@ApiStatus.Internal
@@ -394,33 +388,6 @@ public final class PluginManagerCore {
return false;
}
/**
* In 191.* and earlier builds Java plugin was part of the platform, so any plugin installed in IntelliJ IDEA might be able to use its
* classes without declaring explicit dependency on the Java module. This method is intended to add implicit dependency on the Java plugin
* for such plugins to avoid breaking compatibility with them.
*/
static @Nullable IdeaPluginDescriptorImpl getImplicitDependency(@NotNull IdeaPluginDescriptorImpl descriptor,
@NotNull Supplier<IdeaPluginDescriptorImpl> javaDepGetter) {
// skip our plugins as expected to be up-to-date whether bundled or not
if (descriptor.isBundled() || descriptor.packagePrefix != null || descriptor.implementationDetail) {
return null;
}
PluginId pluginId = descriptor.getPluginId();
if (CORE_ID.equals(pluginId) || JAVA_PLUGIN_ID.equals(pluginId)) {
return null;
}
IdeaPluginDescriptorImpl javaDep = javaDepGetter.get();
if (javaDep == null) {
return null;
}
// If a plugin does not include any module dependency tags in its plugin.xml, it's assumed to be a legacy plugin
// and is loaded only in IntelliJ IDEA, so it may use classes from Java plugin.
return hasModuleDependencies(descriptor) ? null : javaDep;
}
static boolean hasModuleDependencies(@NotNull IdeaPluginDescriptorImpl descriptor) {
for (PluginDependency dependency : descriptor.pluginDependencies) {
PluginId dependencyPluginId = dependency.getPluginId();
@@ -435,6 +402,12 @@ public final class PluginManagerCore {
public static synchronized void invalidatePlugins() {
pluginSet = null;
CompletableFuture<DescriptorListLoadingContext> future = descriptorListFuture;
if (future != null) {
descriptorListFuture = null;
future.cancel(false);
}
DisabledPluginsState.invalidate();
ourShadowedBundledPlugins = null;
}
@@ -549,93 +522,39 @@ public final class PluginManagerCore {
return pluginLoadingErrors.get(pluginId);
}
private static void checkPluginCycles(@NotNull List<IdeaPluginDescriptorImpl> descriptors,
@NotNull PluginSet pluginSet,
@NotNull List<Supplier<@Nls String>> errors,
@NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap) {
CachingSemiGraph<PluginId> graph = CachingSemiGraph.Companion.createPluginIdGraph(descriptors, pluginSet, true);
DFSTBuilder<PluginId> builder = new DFSTBuilder<>(graph);
if (builder.isAcyclic()) {
return;
private static @NotNull List<Supplier<HtmlChunk>> prepareActions(@NotNull Set<IdeaPluginDescriptorImpl> disabledIds,
@NotNull Set<IdeaPluginDescriptorImpl> disabledRequiredIds) {
if (disabledIds.isEmpty()) {
return Collections.emptyList();
}
for (Collection<PluginId> component : builder.getComponents()) {
if (component.size() < 2) {
continue;
}
for (PluginId id : component) {
IdeaPluginDescriptorImpl plugin = pluginSet.findEnabledPlugin(id);
if (plugin != null) {
plugin.setEnabled(false);
}
}
String pluginsString = component.stream().map(it -> "'" + idMap.get(it).getName() + "'").collect(Collectors.joining(", "));
errors.add(message("plugin.loading.error.plugins.cannot.be.loaded.because.they.form.a.dependency.cycle", pluginsString));
StringBuilder detailedMessage = new StringBuilder();
Function<PluginId, String> pluginToString = id -> {
IdeaPluginDescriptorImpl descriptor = idMap.get(id);
return "id = " + id.getIdString() + " (" + descriptor.getName() + ")";
};
detailedMessage.append("Detected plugin dependencies cycle details (only related dependencies are included):\n");
component.stream()
.map(p -> new Pair<>(p, pluginToString.apply(p)))
.sorted(Comparator.comparing(p -> p.second, String.CASE_INSENSITIVE_ORDER))
.forEach(p -> {
detailedMessage.append(" ").append(p.getSecond()).append(" depends on:\n");
graph.getInStream(p.first)
.filter(component::contains)
.map(pluginToString)
.sorted(String.CASE_INSENSITIVE_ORDER)
.forEach(dep -> detailedMessage.append(" ").append(dep).append("\n"));
});
getLogger().info(detailedMessage.toString());
}
}
private static @NotNull List<Supplier<HtmlChunk>> prepareActions(@NotNull Set<PluginId> disabledIds,
@NotNull Set<PluginId> disabledRequiredIds,
@NotNull Map<PluginId, ? extends IdeaPluginDescriptor> idMap) {
List<Supplier<HtmlChunk>> actions = new ArrayList<>();
if (!disabledIds.isEmpty()) {
String nameToDisable = getFirstPluginName(disabledIds, idMap, PluginId::getIdString);
String nameToDisable = getFirstPluginName(disabledIds);
actions.add(() -> {
return HtmlChunk.link(DISABLE, nameToDisable == null
? CoreBundle.message("link.text.disable.not.loaded.plugins")
: CoreBundle.message("link.text.disable.plugin", nameToDisable));
});
if (!disabledRequiredIds.isEmpty()) {
String nameToEnable = getFirstPluginName(disabledRequiredIds);
actions.add(() -> {
String text = nameToDisable != null ?
CoreBundle.message("link.text.disable.plugin", nameToDisable) :
CoreBundle.message("link.text.disable.not.loaded.plugins");
return HtmlChunk.link(DISABLE, text);
return HtmlChunk.link(ENABLE, nameToEnable == null
? CoreBundle.message("link.text.enable.all.necessary.plugins")
: CoreBundle.message("link.text.enable.plugin", nameToEnable));
});
if (!disabledRequiredIds.isEmpty()) {
String nameToEnable = getFirstPluginName(disabledRequiredIds, idMap, __ -> null);
actions.add(() -> {
String text = nameToEnable != null ?
CoreBundle.message("link.text.enable.plugin", nameToEnable) :
CoreBundle.message("link.text.enable.all.necessary.plugins");
return HtmlChunk.link(ENABLE, text);
});
}
actions.add(() -> HtmlChunk.link(EDIT, CoreBundle.message("link.text.open.plugin.manager")));
}
actions.add(() -> HtmlChunk.link(EDIT, CoreBundle.message("link.text.open.plugin.manager")));
return actions;
}
private static @Nullable @NlsSafe String getFirstPluginName(@NotNull Set<PluginId> pluginIds,
@NotNull Map<PluginId, ? extends IdeaPluginDescriptor> idMap,
@NotNull Function<? super PluginId, String> defaultName) {
int size = pluginIds.size();
if (size == 0) {
throw new IllegalArgumentException("Plugins set should not be empty");
}
else if (size == 1) {
PluginId pluginId = pluginIds.iterator().next();
IdeaPluginDescriptor descriptor = idMap.get(pluginId);
return descriptor != null ? descriptor.getName() : defaultName.apply(pluginId);
}
else {
return null;
private static @Nullable @NlsSafe String getFirstPluginName(@NotNull Set<IdeaPluginDescriptorImpl> modules) {
switch (modules.size()) {
case 0:
throw new IllegalArgumentException("Plugins set should not be empty");
case 1:
return modules.iterator().next().getName();
default:
return null;
}
}
@@ -647,11 +566,9 @@ public final class PluginManagerCore {
boolean applied = pluginIds != null;
if (applied) {
Map<PluginId, IdeaPluginDescriptorImpl> pluginIdMap = buildPluginIdMap();
for (PluginId pluginId : pluginIds) {
IdeaPluginDescriptor descriptor = pluginIdMap.get(pluginId);
if (descriptor != null) {
descriptor.setEnabled(enabled);
for (IdeaPluginDescriptorImpl module : getPluginSet().allPlugins) {
if (pluginIds.contains(module.getPluginId())) {
module.setEnabled(enabled);
}
}
@@ -690,13 +607,13 @@ public final class PluginManagerCore {
}
@ApiStatus.Internal
public static @NotNull CompletableFuture<List<IdeaPluginDescriptorImpl>> initPlugins(@NotNull ClassLoader coreClassLoader) {
public static @NotNull CompletableFuture<PluginSet> initPlugins(@NotNull ClassLoader coreClassLoader) {
CompletableFuture<DescriptorListLoadingContext> future = descriptorListFuture;
if (future == null) {
throw new IllegalStateException("Call scheduleDescriptorLoading() first");
}
return future.thenApply(context -> {
return loadAndInitializePlugins(context, coreClassLoader).enabledPlugins;
return loadAndInitializePlugins(context, coreClassLoader);
});
}
@@ -726,14 +643,9 @@ public final class PluginManagerCore {
private static void disableIncompatiblePlugins(@NotNull List<IdeaPluginDescriptorImpl> descriptors,
@NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap,
@NotNull Map<PluginId, PluginLoadingError> errors) {
boolean isNonBundledPluginDisabled = ourDisableNonBundledPlugins;
if (isNonBundledPluginDisabled) {
getLogger().info("Running with disableThirdPartyPlugins argument, third-party plugins will be disabled");
}
String selectedIds = System.getProperty("idea.load.plugins.id");
String selectedCategory = System.getProperty("idea.load.plugins.category");
IdeaPluginDescriptorImpl coreDescriptor = idMap.get(CORE_ID);
Set<IdeaPluginDescriptorImpl> explicitlyEnabled = null;
if (selectedIds != null) {
Set<PluginId> set = new HashSet<>();
@@ -772,21 +684,14 @@ public final class PluginManagerCore {
explicitlyEnabled.addAll(nonOptionalDependencies);
}
Map<PluginId, Set<String>> brokenPluginVersions = getBrokenPluginVersions();
IdeaPluginDescriptorImpl coreDescriptor = idMap.get(CORE_ID);
boolean shouldLoadPlugins = Boolean.parseBoolean(System.getProperty("idea.load.plugins", "true"));
for (IdeaPluginDescriptorImpl descriptor : descriptors) {
if (descriptor == coreDescriptor) {
continue;
}
Set<String> set = brokenPluginVersions.get(descriptor.getPluginId());
if (set != null && set.contains(descriptor.getVersion())) {
descriptor.setEnabled(false);
errors.put(descriptor.getPluginId(), new PluginLoadingError(descriptor,
message("plugin.loading.error.long.marked.as.broken", descriptor.getName(), descriptor.getVersion()),
message("plugin.loading.error.short.marked.as.broken")));
}
else if (explicitlyEnabled != null) {
if (explicitlyEnabled != null) {
if (!explicitlyEnabled.contains(descriptor)) {
descriptor.setEnabled(false);
getLogger().info("Plugin '" + descriptor.getName() + "' " +
@@ -802,15 +707,6 @@ public final class PluginManagerCore {
descriptor.getName()),
message("plugin.loading.error.short.plugin.loading.disabled")));
}
else if (isNonBundledPluginDisabled && !descriptor.isBundled()) {
descriptor.setEnabled(false);
errors.put(descriptor.getPluginId(), new PluginLoadingError(descriptor,
message("plugin.loading.error.long.custom.plugin.loading.disabled",
descriptor.getName()),
message("plugin.loading.error.short.custom.plugin.loading.disabled"),
false,
null));
}
}
}
@@ -900,45 +796,25 @@ public final class PluginManagerCore {
}
List<IdeaPluginDescriptorImpl> descriptors = loadingResult.getEnabledPlugins();
PluginSet rawPluginSet = PluginSet.Companion.createRawPluginSet(descriptors);
disableIncompatiblePlugins(descriptors, idMap, pluginErrorsById);
checkPluginCycles(descriptors, rawPluginSet, globalErrors, idMap);
PluginSetBuilder pluginSetBuilder = new PluginSetBuilder(descriptors);
pluginSetBuilder.checkPluginCycles(globalErrors);
Map<PluginId, String> disabledIds = new HashMap<>();
Set<IdeaPluginDescriptorImpl> disabledAfterInit = new HashSet<>();
Set<IdeaPluginDescriptorImpl> disabledRequired = new HashSet<>();
// topological sort based on required dependencies only
List<IdeaPluginDescriptorImpl> sortedRequired = rawPluginSet.sortTopologically(descriptors, false);
Map<PluginId, IdeaPluginDescriptorImpl> enabledPluginIds = new HashMap<>();
Map<String, PluginContentDescriptor.ModuleItem> enabledModuleV2Ids = new HashMap<>();
Set<PluginId> disabledRequiredIds = new HashSet<>();
Logger logger = getLogger();
boolean isDebugLogEnabled = logger.isDebugEnabled() || !System.getProperty("plugin.classloader.debug", "").isEmpty() || isUnitTestMode;
for (IdeaPluginDescriptorImpl descriptor : sortedRequired) {
boolean wasEnabled = descriptor.isEnabled();
if (wasEnabled && computePluginEnabled(descriptor,
enabledPluginIds, enabledModuleV2Ids,
idMap, disabledRequiredIds, context.disabledPlugins, pluginErrorsById)) {
PluginSet.Companion.addWithV1Modules(enabledPluginIds, descriptor);
if (!CORE_ID.equals(descriptor.getPluginId())) {
PluginSet.Companion.checkModules(descriptor, enabledPluginIds, enabledModuleV2Ids, isDebugLogEnabled, logger);
}
else {
for (PluginContentDescriptor.ModuleItem item : descriptor.content.modules) {
enabledModuleV2Ids.put(item.name, item);
}
}
pluginSetBuilder.computeEnabledModuleMap(descriptor -> {
if (pluginSetBuilder.initEnableState$intellij_platform_core_impl(descriptor, idMap, disabledRequired,
context.disabledPlugins, pluginErrorsById)) {
return false;
}
else {
descriptor.setEnabled(false);
if (wasEnabled) {
disabledIds.put(descriptor.getPluginId(), descriptor.getName());
}
}
}
List<Supplier<HtmlChunk>> actions = prepareActions(disabledIds.keySet(), disabledRequiredIds, idMap);
descriptor.setEnabled(false);
disabledAfterInit.add(descriptor);
return true;
});
List<Supplier<HtmlChunk>> actions = prepareActions(disabledAfterInit, disabledRequired);
pluginLoadingErrors = pluginErrorsById;
List<Supplier<HtmlChunk>> errorsList = preparePluginsError(globalErrors);
@@ -949,47 +825,13 @@ public final class PluginManagerCore {
}
}
// topological sort based on all (required and optional) dependencies
List<IdeaPluginDescriptorImpl> allPlugins = rawPluginSet.sortTopologically(sortedRequired, true);
List<IdeaPluginDescriptorImpl> enabledPlugins = PluginSet.Companion.getOnlyEnabledPlugins(allPlugins);
Java11Shim java11Shim = Java11Shim.INSTANCE;
if (!context.result.incompletePlugins.isEmpty()) {
List<IdeaPluginDescriptorImpl> result = new ArrayList<>(allPlugins.size() + context.result.incompletePlugins.size());
result.addAll(allPlugins);
result.addAll(context.result.incompletePlugins.values());
allPlugins = java11Shim.copyOf(result);
}
PluginSet pluginSet = new PluginSet(allPlugins, java11Shim.copyOf(enabledPlugins),
java11Shim.copyOf(enabledModuleV2Ids), java11Shim.copyOf(enabledPluginIds));
new ClassLoaderConfigurator(pluginSet, coreLoader).configureAll();
if (checkEssentialPlugins) {
checkEssentialPluginsAreAvailable(idMap);
}
return new PluginManagerState(pluginSet, disabledRequiredIds, java11Shim.copyOf(disabledIds.keySet()));
}
@ApiStatus.Internal
public static @NotNull Map<PluginId, IdeaPluginDescriptorImpl> buildPluginIdMap(@NotNull List<IdeaPluginDescriptorImpl> descriptors) {
Map<PluginId, IdeaPluginDescriptorImpl> idMap = new HashMap<>(descriptors.size());
Map<PluginId, List<IdeaPluginDescriptorImpl>> duplicateMap = null;
for (IdeaPluginDescriptorImpl descriptor : descriptors) {
Map<PluginId, List<IdeaPluginDescriptorImpl>> newDuplicateMap = checkAndPut(descriptor, descriptor.getPluginId(), idMap, duplicateMap);
if (newDuplicateMap != null) {
duplicateMap = newDuplicateMap;
continue;
}
for (PluginId module : descriptor.modules) {
newDuplicateMap = checkAndPut(descriptor, module, idMap, duplicateMap);
if (newDuplicateMap != null) {
duplicateMap = newDuplicateMap;
}
}
}
return idMap;
PluginSet pluginSet = pluginSetBuilder.createPluginSet(context.result.incompletePlugins.values());
new ClassLoaderConfigurator(pluginSet, coreLoader).configure();
return new PluginManagerState(pluginSet, disabledRequired, disabledAfterInit);
}
@SuppressWarnings("DuplicatedCode")
@@ -1023,119 +865,6 @@ public final class PluginManagerCore {
return duplicateMap;
}
private static boolean computePluginEnabled(@NotNull IdeaPluginDescriptorImpl descriptor,
@NotNull Map<PluginId, IdeaPluginDescriptorImpl> enabledPluginIds,
@NotNull Map<String, PluginContentDescriptor.ModuleItem> enabledModuleV2Ids,
@NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap,
@NotNull Set<PluginId> disabledRequiredIds,
@NotNull Set<PluginId> disabledPlugins,
@NotNull Map<PluginId, PluginLoadingError> errors) {
if (CORE_ID.equals(descriptor.getPluginId())) {
return true;
}
boolean notifyUser = !descriptor.isImplementationDetail();
boolean result = true;
for (PluginId incompatibleId : descriptor.incompatibilities) {
if (!enabledPluginIds.containsKey(incompatibleId) || disabledPlugins.contains(incompatibleId)) {
continue;
}
result = false;
String presentableName = incompatibleId.getIdString();
errors.put(descriptor.getPluginId(),
new PluginLoadingError(
descriptor,
message("plugin.loading.error.long.ide.contains.conflicting.module", descriptor.getName(), presentableName),
message("plugin.loading.error.short.ide.contains.conflicting.module", presentableName), notifyUser, null
));
}
for (PluginDependency dependency : descriptor.pluginDependencies) {
PluginId depId = dependency.getPluginId();
if (dependency.isOptional() || enabledPluginIds.containsKey(depId)) {
continue;
}
result = false;
IdeaPluginDescriptor dep = idMap.get(depId);
if (dep != null && disabledPlugins.contains(depId)) {
// broken/incompatible plugins can be updated, add them anyway
disabledRequiredIds.add(dep.getPluginId());
}
addCannotLoadError(descriptor, errors, notifyUser, depId, dep);
}
for (ModuleDependenciesDescriptor.PluginReference item : descriptor.dependencies.plugins) {
if (enabledPluginIds.containsKey(item.id)) {
continue;
}
result = false;
IdeaPluginDescriptorImpl dep = idMap.get(item.id);
if (dep != null && disabledPlugins.contains(item.id)) {
// broken/incompatible plugins can be updated, add them anyway
disabledRequiredIds.add(dep.getPluginId());
}
addCannotLoadError(descriptor, errors, notifyUser, item.id, dep);
}
for (ModuleDependenciesDescriptor.ModuleReference item : descriptor.dependencies.modules) {
if (enabledModuleV2Ids.containsKey(item.name)) {
continue;
}
result = false;
errors.put(descriptor.getPluginId(),
new PluginLoadingError(
descriptor,
message("plugin.loading.error.long.depends.on.not.installed.plugin", descriptor.getName(), item.name),
message("plugin.loading.error.short.depends.on.not.installed.plugin", item.name), notifyUser, null
));
}
return result;
}
private static void addCannotLoadError(@NotNull IdeaPluginDescriptorImpl descriptor,
@NotNull Map<PluginId, PluginLoadingError> errors,
boolean notifyUser,
PluginId depId,
@Nullable IdeaPluginDescriptor dep) {
String depName = dep == null ? null : dep.getName();
if (depName == null) {
@NlsSafe String depPresentableId = depId.getIdString();
if (errors.containsKey(depId)) {
PluginLoadingError depError = errors.get(depId);
String depNameFromError = depError.getPlugin().getName();
errors.put(descriptor.getPluginId(),
new PluginLoadingError(
descriptor,
message("plugin.loading.error.long.depends.on.failed.to.load.plugin", descriptor.getName(), depNameFromError != null ? depNameFromError : depPresentableId),
message("plugin.loading.error.short.depends.on.failed.to.load.plugin", depPresentableId), notifyUser, null
));
}
else {
errors.put(descriptor.getPluginId(),
new PluginLoadingError(
descriptor,
message("plugin.loading.error.long.depends.on.not.installed.plugin", descriptor.getName(), depPresentableId),
message("plugin.loading.error.short.depends.on.not.installed.plugin", depPresentableId), notifyUser, null
));
}
}
else {
errors.put(descriptor.getPluginId(),
new PluginLoadingError(
descriptor,
message("plugin.loading.error.long.depends.on.disabled.plugin", descriptor.getName(), depName),
message("plugin.loading.error.short.depends.on.disabled.plugin", depName), notifyUser, dep.getPluginId()
));
}
}
private static @NotNull @Nls Supplier<String> message(@NotNull @PropertyKey(resourceBundle = CoreBundle.BUNDLE) String key,
Object @NotNull ... params) {
//noinspection Convert2Lambda
@@ -1150,26 +879,24 @@ public final class PluginManagerCore {
@SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext")
private static synchronized @NotNull PluginSet loadAndInitializePlugins(@NotNull DescriptorListLoadingContext context,
@NotNull ClassLoader coreLoader) {
try {
Activity activity = StartUpMeasurer.startActivity("plugin initialization", ActivityCategory.DEFAULT);
PluginManagerState initResult = initializePlugins(context, coreLoader, !isUnitTestMode);
PluginLoadingResult result = context.result;
ourPluginsToDisable = initResult.effectiveDisabledIds;
ourPluginsToEnable = initResult.disabledRequiredIds;
ourShadowedBundledPlugins = result.shadowedBundledIds;
activity.end();
activity.setDescription("plugin count: " + initResult.pluginSet.enabledPlugins.size());
logPlugins(initResult.pluginSet.allPlugins, result.incompletePlugins.values());
pluginSet = initResult.pluginSet;
return initResult.pluginSet;
}
catch (RuntimeException e) {
getLogger().error(e);
throw e;
if (IdeaPluginDescriptorImpl.disableNonBundledPlugins) {
getLogger().info("Running with disableThirdPartyPlugins argument, third-party plugins will be disabled");
}
Activity activity = StartUpMeasurer.startActivity("plugin initialization", ActivityCategory.DEFAULT);
PluginManagerState initResult = initializePlugins(context, coreLoader, !isUnitTestMode);
PluginLoadingResult result = context.result;
ourPluginsToDisable = initResult.effectiveDisabledIds;
ourPluginsToEnable = initResult.disabledRequiredIds;
ourShadowedBundledPlugins = result.shadowedBundledIds;
activity.end();
activity.setDescription("plugin count: " + initResult.pluginSet.enabledPlugins.size());
logPlugins(initResult.pluginSet.allPlugins, result.incompletePlugins.values());
pluginSet = initResult.pluginSet;
return initResult.pluginSet;
}
@SuppressWarnings("RedundantSuppression")
@@ -1241,7 +968,23 @@ public final class PluginManagerCore {
@ApiStatus.Internal
public static @NotNull Map<PluginId, IdeaPluginDescriptorImpl> buildPluginIdMap() {
LoadingState.COMPONENTS_REGISTERED.checkOccurred();
return buildPluginIdMap(getPluginSet().allPlugins);
Map<PluginId, IdeaPluginDescriptorImpl> idMap = new HashMap<>(getPluginSet().allPlugins.size());
Map<PluginId, List<IdeaPluginDescriptorImpl>> duplicateMap = null;
for (IdeaPluginDescriptorImpl descriptor : getPluginSet().allPlugins) {
Map<PluginId, List<IdeaPluginDescriptorImpl>> newDuplicateMap = checkAndPut(descriptor, descriptor.getPluginId(), idMap, duplicateMap);
if (newDuplicateMap != null) {
duplicateMap = newDuplicateMap;
continue;
}
for (PluginId module : descriptor.modules) {
newDuplicateMap = checkAndPut(descriptor, module, idMap, duplicateMap);
if (newDuplicateMap != null) {
duplicateMap = newDuplicateMap;
}
}
}
return idMap;
}
@SuppressWarnings("UnusedReturnValue")
@@ -1,22 +0,0 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.plugins;
import com.intellij.openapi.extensions.PluginId;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
// todo merge into PluginSetState?
public final class PluginManagerState {
final Set<PluginId> effectiveDisabledIds;
final Set<PluginId> disabledRequiredIds;
final PluginSet pluginSet;
PluginManagerState(@NotNull PluginSet pluginSet,
@NotNull Set<PluginId> disabledRequiredIds,
@NotNull Set<PluginId> effectiveDisabledIds) {
this.pluginSet = pluginSet;
this.disabledRequiredIds = disabledRequiredIds;
this.effectiveDisabledIds = effectiveDisabledIds;
}
}
@@ -2,149 +2,57 @@
@file:Suppress("ReplacePutWithAssignment", "ReplaceGetOrSet")
package com.intellij.ide.plugins
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.PluginId
import com.intellij.util.graph.DFSTBuilder
import com.intellij.util.lang.Java11Shim
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.TestOnly
import java.util.*
// if otherwise not specified, `module` in terms of v2 plugin model
@ApiStatus.Internal
class PluginSet internal constructor(
@JvmField val allPlugins: List<IdeaPluginDescriptorImpl>,
@JvmField val enabledPlugins: List<IdeaPluginDescriptorImpl>,
private val enabledModuleMap: Map<String, PluginContentDescriptor.ModuleItem>,
private val enabledModuleMap: Map<String, IdeaPluginDescriptorImpl>,
private val enabledPluginAndV1ModuleMap: Map<PluginId, IdeaPluginDescriptorImpl>,
private val enabledModules: List<IdeaPluginDescriptorImpl>,
@JvmField val moduleToDirectDependencies: Map<IdeaPluginDescriptorImpl, Array<IdeaPluginDescriptorImpl>>
) {
companion object {
// special case - raw plugin set where everything is enabled and resolved
fun createRawPluginSet(plugins: List<IdeaPluginDescriptorImpl>): PluginSet {
val java11Shim = Java11Shim.INSTANCE
val enabledModuleV2Ids = HashMap<String, PluginContentDescriptor.ModuleItem>()
val enabledPluginAndModuleV1Map = HashMap<PluginId, IdeaPluginDescriptorImpl>(plugins.size)
for (descriptor in plugins) {
addWithV1Modules(enabledPluginAndModuleV1Map, descriptor)
for (item in descriptor.content.modules) {
enabledModuleV2Ids.put(item.name, item)
}
}
return PluginSet(
allPlugins = plugins,
enabledPlugins = plugins,
enabledModuleMap = java11Shim.copyOf(enabledModuleV2Ids),
enabledPluginAndV1ModuleMap = java11Shim.copyOf(enabledPluginAndModuleV1Map),
)
}
fun createPluginSet(allPlugins: List<IdeaPluginDescriptorImpl>, enabledPlugins: List<IdeaPluginDescriptorImpl>): PluginSet {
val enabledModuleV2Ids = HashMap<String, PluginContentDescriptor.ModuleItem>()
val enabledPluginAndModuleV1Map = HashMap<PluginId, IdeaPluginDescriptorImpl>(enabledPlugins.size)
val log = PluginManagerCore.getLogger()
val isDebugLogEnabled = log.isDebugEnabled || !System.getProperty("plugin.classloader.debug", "").isEmpty()
for (descriptor in enabledPlugins) {
addWithV1Modules(enabledPluginAndModuleV1Map, descriptor)
checkModules(descriptor, enabledPluginAndModuleV1Map, enabledModuleV2Ids, isDebugLogEnabled, log)
}
val java11Shim = Java11Shim.INSTANCE
return PluginSet(
allPlugins = java11Shim.copyOf(allPlugins),
enabledPlugins = java11Shim.copyOf(enabledPlugins),
enabledModuleMap = java11Shim.copyOf(enabledModuleV2Ids),
enabledPluginAndV1ModuleMap = java11Shim.copyOf(enabledPluginAndModuleV1Map),
)
}
fun addWithV1Modules(result: MutableMap<PluginId, IdeaPluginDescriptorImpl>, descriptor: IdeaPluginDescriptorImpl) {
result.put(descriptor.id, descriptor)
for (module in descriptor.modules) {
result.put(module, descriptor)
}
}
fun getOnlyEnabledPlugins(sortedAll: Collection<IdeaPluginDescriptorImpl>): List<IdeaPluginDescriptorImpl> {
return sortedAll.filterTo(ArrayList(sortedAll.size)) { it.isEnabled }
}
fun checkModules(descriptor: IdeaPluginDescriptorImpl,
enabledPluginIds: Map<PluginId, IdeaPluginDescriptorImpl>,
enabledModuleV2Ids: MutableMap<String, PluginContentDescriptor.ModuleItem>,
isDebugLogEnabled: Boolean,
log: Logger) {
m@ for (item in descriptor.content.modules) {
for (ref in item.requireDescriptor().dependencies.modules) {
if (!enabledModuleV2Ids.containsKey(ref.name)) {
if (isDebugLogEnabled) {
log.info("Module ${item.name} is not enabled because dependency ${ref.name} is not available")
}
continue@m
}
}
for (ref in item.requireDescriptor().dependencies.plugins) {
if (!enabledPluginIds.containsKey(ref.id)) {
if (isDebugLogEnabled) {
log.info("Module ${item.name} is not enabled because dependency ${ref.id} is not available")
}
continue@m
}
}
enabledModuleV2Ids.put(item.name, item)
}
fun getTopologicalComparator(allPlugins: List<IdeaPluginDescriptorImpl>): Comparator<IdeaPluginDescriptorImpl> {
val graph = CachingSemiGraph.createModuleGraph(allPlugins)
return CachingSemiGraph.getTopologicalComparator(DFSTBuilder(graph.nodes.toTypedArray(), null, true, graph))
}
}
fun getRawListOfEnabledModules() = enabledModules
/**
* You must not use this method before [ClassLoaderConfigurator.configure].
*/
fun getEnabledModules(): Sequence<IdeaPluginDescriptorImpl> = enabledModules.asSequence()
@TestOnly
fun getUnsortedEnabledModules(): Collection<PluginContentDescriptor.ModuleItem> = ArrayList(enabledModuleMap.values)
fun getUnsortedEnabledModules(): Collection<IdeaPluginDescriptorImpl> = ArrayList(enabledModuleMap.values)
fun isPluginEnabled(id: PluginId) = enabledPluginAndV1ModuleMap.containsKey(id)
fun findEnabledPlugin(id: PluginId): IdeaPluginDescriptorImpl? = enabledPluginAndV1ModuleMap.get(id)
fun findEnabledModule(id: String): IdeaPluginDescriptorImpl? = enabledModuleMap.get(id)?.requireDescriptor()
fun findEnabledModule(id: String): IdeaPluginDescriptorImpl? = enabledModuleMap.get(id)
fun isModuleEnabled(id: String) = enabledModuleMap.containsKey(id)
fun enablePlugin(descriptor: IdeaPluginDescriptorImpl): PluginSet {
fun enablePlugin(toEnable: IdeaPluginDescriptorImpl): PluginSet {
// in tests or on install plugin is not in all plugins
// linear search is ok here - not a hot method
PluginManagerCore.getLogger().assertTrue(!enabledPlugins.contains(descriptor))
return createPluginSet(
allPlugins = if (descriptor in allPlugins) allPlugins else sortTopologically(allPlugins + descriptor),
enabledPlugins = sortTopologically(enabledPlugins + descriptor),
)
PluginManagerCore.getLogger().assertTrue(!enabledPlugins.contains(toEnable) && toEnable.isEnabled)
return PluginSetBuilder(if (toEnable in allPlugins) allPlugins else allPlugins + toEnable).computeEnabledModuleMap().createPluginSet()
}
fun sortTopologically(descriptors: List<IdeaPluginDescriptorImpl>, withOptional: Boolean = true): List<IdeaPluginDescriptorImpl> {
val graph = CachingSemiGraph.createPluginIdGraph(descriptors, pluginSet = this, withOptional)
val comparator = DFSTBuilder(graph).comparator()
// there is circular reference between core and implementation-detail plugin, as not all such plugins extracted from core,
// so, ensure that core plugin is always first (otherwise not possible to register actions - parent group not defined)
// don't use sortWith here - avoid loading kotlin stdlib
val sortedRequired = descriptors.toTypedArray()
Arrays.sort(sortedRequired, Comparator { o1, o2 ->
when (PluginManagerCore.CORE_ID) {
o1.id -> -1
o2.id -> 1
else -> comparator.compare(o1.id, o2.id)
}
})
@Suppress("ReplaceJavaStaticMethodWithKotlinAnalog", "UNCHECKED_CAST")
return Java11Shim.INSTANCE.listOf(sortedRequired)
}
fun updateEnabledPlugins() = updateEnabledPlugins(allPlugins)
fun updateEnabledPlugins() = PluginSetBuilder(allPlugins).computeEnabledModuleMap().createPluginSet()
fun removePluginAndUpdateEnabledPlugins(descriptor: IdeaPluginDescriptorImpl): PluginSet {
// not just remove from enabledPlugins - maybe another plugins in list also disabled as result of plugin unloading
return updateEnabledPlugins(allPlugins = allPlugins - descriptor)
}
private fun updateEnabledPlugins(allPlugins: List<IdeaPluginDescriptorImpl>): PluginSet {
return createPluginSet(allPlugins = allPlugins, enabledPlugins = getOnlyEnabledPlugins(allPlugins))
return PluginSetBuilder(unsortedPlugins = allPlugins - descriptor).computeEnabledModuleMap().createPluginSet()
}
}
@@ -0,0 +1,314 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:Suppress("ReplaceGetOrSet", "ReplaceGetOrSet")
package com.intellij.ide.plugins
import com.intellij.core.CoreBundle
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.PluginId
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.graph.DFSTBuilder
import com.intellij.util.lang.Java11Shim
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.PropertyKey
import java.util.*
import java.util.function.BiConsumer
import java.util.function.Predicate
import java.util.function.Supplier
@ApiStatus.Internal
class PluginSetBuilder(val unsortedPlugins: List<IdeaPluginDescriptorImpl>) {
private val graph = CachingSemiGraph.createModuleGraph(unsortedPlugins)
private val sortedModules = graph.nodes.toTypedArray()
// useIdentityStrategy - performance, avoid equals
private val topologicalSorter = DFSTBuilder(sortedModules.clone(), null, true, graph)
private val topologicalComparator = CachingSemiGraph.getTopologicalComparator(topologicalSorter)
private val enabledPluginIds = HashMap<PluginId, IdeaPluginDescriptorImpl>(unsortedPlugins.size)
private val enabledModuleV2Ids = HashMap<String, IdeaPluginDescriptorImpl>(unsortedPlugins.size * 2)
init {
Arrays.sort(sortedModules, topologicalComparator)
}
fun checkPluginCycles(errors: MutableList<Supplier<String>>) {
if (topologicalSorter.isAcyclic) {
return
}
for (component in topologicalSorter.components) {
if (component.size < 2) {
continue
}
for (plugin in component) {
plugin.isEnabled = false
}
val pluginsString = component.joinToString(separator = ", ") { "'${it.name}'" }
errors.add(message("plugin.loading.error.plugins.cannot.be.loaded.because.they.form.a.dependency.cycle", pluginsString))
val detailedMessage = StringBuilder()
val pluginToString: (IdeaPluginDescriptorImpl) -> String = { "id = ${it.pluginId.idString} (${it.name})" }
detailedMessage.append("Detected plugin dependencies cycle details (only related dependencies are included):\n")
component
.asSequence()
.map { Pair(it, pluginToString(it)) }
.sortedWith(Comparator.comparing({ it.second }, String.CASE_INSENSITIVE_ORDER))
.forEach {
detailedMessage.append(" ").append(it.second).append(" depends on:\n")
graph.getIn(it.first).asSequence()
.filter { o: IdeaPluginDescriptorImpl -> component.contains(o) }
.map(pluginToString)
.sortedWith(java.lang.String.CASE_INSENSITIVE_ORDER)
.forEach { dep: String? ->
detailedMessage.append(" ").append(dep).append("\n")
}
}
PluginManagerCore.getLogger().info(detailedMessage.toString())
}
}
// Only plugins returned. Not modules. See PluginManagerTest.moduleSort test to understand the issue.
private fun getSortedPlugins(): Array<IdeaPluginDescriptorImpl> {
val pluginToNumber = Object2IntOpenHashMap<PluginId>(unsortedPlugins.size)
pluginToNumber.put(PluginManagerCore.CORE_ID, 0)
var number = 0
for (module in sortedModules) {
// no content, so will be no modules, add it
if (module.descriptorPath != null || module.content.modules.isEmpty()) {
pluginToNumber.putIfAbsent(module.pluginId, number++)
}
}
val sorted = unsortedPlugins.toTypedArray()
Arrays.sort(sorted, Comparator { o1, o2 ->
pluginToNumber.getInt(o1.pluginId) - pluginToNumber.getInt(o2.pluginId)
})
return sorted
}
private fun getEnabledModules(): List<IdeaPluginDescriptorImpl> {
val result = ArrayList<IdeaPluginDescriptorImpl>(sortedModules.size)
for (module in sortedModules) {
if (if (module.moduleName == null) module.isEnabled else enabledModuleV2Ids.containsKey(module.moduleName)) {
result.add(module)
}
}
return result
}
fun computeEnabledModuleMap(disabler: Predicate<IdeaPluginDescriptorImpl>? = null): PluginSetBuilder {
val logMessages = ArrayList<String>()
m@ for (module in sortedModules) {
if (module.moduleName == null) {
if (module.pluginId != PluginManagerCore.CORE_ID && (!module.isEnabled || (disabler != null && disabler.test(module)))) {
continue
}
}
else if (!enabledPluginIds.containsKey(module.pluginId)) {
continue
}
for (ref in module.dependencies.modules) {
if (!enabledModuleV2Ids.containsKey(ref.name)) {
logMessages.add("Module ${module.moduleName ?: module.pluginId} is not enabled because dependency ${ref.name} is not available")
continue@m
}
}
for (ref in module.dependencies.plugins) {
if (!enabledPluginIds.containsKey(ref.id)) {
logMessages.add("Module ${module.moduleName ?: module.pluginId} is not enabled because dependency ${ref.id} is not available")
continue@m
}
}
if (module.moduleName == null) {
enabledPluginIds.put(module.pluginId, module)
for (v1Module in module.modules) {
enabledPluginIds.put(v1Module, module)
}
if (module.packagePrefix != null) {
enabledModuleV2Ids.put(module.pluginId.idString, module)
}
}
else {
enabledModuleV2Ids.put(module.moduleName, module)
}
}
if (!logMessages.isEmpty()) {
PluginManagerCore.getLogger().info(logMessages.joinToString(separator = "\n"))
}
return this
}
fun createPluginSet(incompletePlugins: Collection<IdeaPluginDescriptorImpl> = Collections.emptyList()): PluginSet {
val java11Shim = Java11Shim.INSTANCE
val allPlugins: List<IdeaPluginDescriptorImpl>
val sortedPlugins = getSortedPlugins()
if (incompletePlugins.isEmpty()) {
allPlugins = ContainerUtil.immutableList(*sortedPlugins)
}
else {
val result = ArrayList<IdeaPluginDescriptorImpl>(sortedPlugins.size + incompletePlugins.size)
result.addAll(sortedPlugins)
result.addAll(incompletePlugins)
allPlugins = java11Shim.copyOfCollection(result)
}
val enabledPlugins = java11Shim.copyOfCollection(sortedPlugins.filterTo(ArrayList(sortedPlugins.size)) { it.isEnabled })
val moduleToSortedDirectDependencies =
IdentityHashMap<IdeaPluginDescriptorImpl, Array<IdeaPluginDescriptorImpl>>(graph.moduleToDirectDependencies.size)
graph.moduleToDirectDependencies.forEach(BiConsumer { k, v ->
val list = v.toTypedArray()
Arrays.sort(list, topologicalComparator)
moduleToSortedDirectDependencies.put(k, list)
})
return PluginSet(
allPlugins = allPlugins,
enabledPlugins = enabledPlugins,
enabledModuleMap = java11Shim.copyOf(enabledModuleV2Ids),
enabledPluginAndV1ModuleMap = java11Shim.copyOf(enabledPluginIds),
enabledModules = java11Shim.copyOfCollection(getEnabledModules()),
moduleToDirectDependencies = java11Shim.copyOf(moduleToSortedDirectDependencies),
)
}
fun checkModules(descriptor: IdeaPluginDescriptorImpl, isDebugLogEnabled: Boolean, log: Logger) {
m@ for (item in descriptor.content.modules) {
for (ref in item.requireDescriptor().dependencies.modules) {
if (!enabledModuleV2Ids.containsKey(ref.name)) {
if (isDebugLogEnabled) {
log.info("Module ${item.name} is not enabled because dependency ${ref.name} is not available")
}
continue@m
}
}
for (ref in item.requireDescriptor().dependencies.plugins) {
if (!enabledPluginIds.containsKey(ref.id)) {
if (isDebugLogEnabled) {
log.info("Module ${item.name} is not enabled because dependency ${ref.id} is not available")
}
continue@m
}
}
enabledModuleV2Ids.put(item.name, descriptor)
}
}
// use only for init plugins
internal fun initEnableState(descriptor: IdeaPluginDescriptorImpl,
idMap: Map<PluginId, IdeaPluginDescriptorImpl>,
disabledRequired: MutableSet<IdeaPluginDescriptorImpl>,
disabledPlugins: Set<PluginId>,
errors: MutableMap<PluginId, PluginLoadingError>): Boolean {
val notifyUser = !descriptor.isImplementationDetail
for (incompatibleId in descriptor.incompatibilities) {
if (!enabledPluginIds.containsKey(incompatibleId) || disabledPlugins.contains(incompatibleId)) {
continue
}
val presentableName = incompatibleId.idString
errors.put(descriptor.pluginId, PluginLoadingError(
plugin = descriptor,
detailedMessageSupplier = message("plugin.loading.error.long.ide.contains.conflicting.module", descriptor.name, presentableName),
shortMessageSupplier = message("plugin.loading.error.short.ide.contains.conflicting.module", presentableName),
isNotifyUser = notifyUser,
))
return false
}
for (dependency in descriptor.pluginDependencies) {
val depId = dependency.pluginId
if (dependency.isOptional || enabledPluginIds.containsKey(depId)) {
continue
}
val dep = idMap.get(depId)
if (dep != null && disabledPlugins.contains(depId)) {
// broken/incompatible plugins can be updated, add them anyway
disabledRequired.add(dep)
}
addCannotLoadError(descriptor, errors, notifyUser, depId, dep)
return false
}
for (item in descriptor.dependencies.plugins) {
if (enabledPluginIds.containsKey(item.id)) {
continue
}
val dep = idMap.get(item.id)
if (dep != null && disabledPlugins.contains(item.id)) {
// broken/incompatible plugins can be updated, add them anyway
disabledRequired.add(dep)
}
addCannotLoadError(descriptor, errors, notifyUser, item.id, dep)
return false
}
for (item in descriptor.dependencies.modules) {
if (enabledModuleV2Ids.containsKey(item.name)) {
continue
}
errors.put(descriptor.pluginId, PluginLoadingError(
plugin = descriptor,
detailedMessageSupplier = message("plugin.loading.error.long.depends.on.not.installed.plugin", descriptor.name, item.name),
shortMessageSupplier = message("plugin.loading.error.short.depends.on.not.installed.plugin", item.name),
isNotifyUser = notifyUser,
))
return false
}
return true
}
}
private fun addCannotLoadError(descriptor: IdeaPluginDescriptorImpl,
errors: MutableMap<PluginId, PluginLoadingError>,
notifyUser: Boolean,
depId: PluginId,
dep: IdeaPluginDescriptor?) {
val depName = dep?.name
if (depName == null) {
val depPresentableId = depId.idString
if (errors.containsKey(depId)) {
val depError = errors.get(depId)!!
val depNameFromError = depError.plugin.name
errors.put(descriptor.pluginId, PluginLoadingError(
plugin = descriptor,
detailedMessageSupplier = message("plugin.loading.error.long.depends.on.failed.to.load.plugin",
descriptor.name, depNameFromError ?: depPresentableId),
shortMessageSupplier = message("plugin.loading.error.short.depends.on.failed.to.load.plugin", depPresentableId),
isNotifyUser = notifyUser,
disabledDependency = null
))
}
else {
errors.put(descriptor.pluginId, PluginLoadingError(
plugin = descriptor,
detailedMessageSupplier = message("plugin.loading.error.long.depends.on.not.installed.plugin", descriptor.name, depPresentableId),
shortMessageSupplier = message("plugin.loading.error.short.depends.on.not.installed.plugin", depPresentableId),
isNotifyUser = notifyUser, disabledDependency = null
))
}
}
else {
errors.put(descriptor.pluginId, PluginLoadingError(
plugin = descriptor,
detailedMessageSupplier = message("plugin.loading.error.long.depends.on.disabled.plugin", descriptor.name, depName),
shortMessageSupplier = message("plugin.loading.error.short.depends.on.disabled.plugin", depName),
isNotifyUser = notifyUser,
disabledDependency = dep.pluginId
))
}
}
private fun message(key: @PropertyKey(resourceBundle = CoreBundle.BUNDLE) String, vararg params: Any): @Nls Supplier<String> {
return Supplier { CoreBundle.message(key, *params) }
}
@@ -25,7 +25,7 @@ class PluginXmlPathResolver(private val pluginJarFiles: List<Path>) : PathResolv
try {
// do not use kotlin stdlib here
val entry = zipFile.getEntry(if (relativePath.startsWith("/")) relativePath.substring(1) else relativePath) ?: return false
readModuleDescriptor(inputStream = zipFile.getInputStream(entry),
readModuleDescriptor(input = zipFile.getInputStream(entry),
readContext = readContext,
pathResolver = pathResolver,
dataLoader = dataLoader,
@@ -96,7 +96,7 @@ class PluginXmlPathResolver(private val pluginJarFiles: List<Path>) : PathResolv
// it is allowed to reference any platform XML file using href="/META-INF/EnforcedPlainText.xml"
if (path.startsWith("META-INF/")) {
PluginXmlPathResolver::class.java.classLoader.getResourceAsStream(path)?.let {
readModuleDescriptor(inputStream = it,
readModuleDescriptor(input = it,
readContext = readContext,
pathResolver = this,
dataLoader = dataLoader,
@@ -34,14 +34,17 @@ import javax.xml.stream.events.XMLEvent
private const val defaultXPointerValue = "xpointer(/idea-plugin/*)"
fun readModuleDescriptor(inputStream: InputStream,
/**
* Do not use [java.io.BufferedInputStream] - buffer is used internally already.
*/
fun readModuleDescriptor(input: InputStream,
readContext: ReadModuleContext,
pathResolver: PathResolver,
dataLoader: DataLoader,
includeBase: String?,
readInto: RawPluginDescriptor?,
locationSource: String?): RawPluginDescriptor {
return readModuleDescriptor(reader = createNonCoalescingXmlStreamReader(inputStream, locationSource),
return readModuleDescriptor(reader = createNonCoalescingXmlStreamReader(input, locationSource),
readContext = readContext,
pathResolver = pathResolver,
dataLoader = dataLoader,
@@ -691,7 +694,7 @@ private fun readContent(reader: XMLStreamReader2,
}
reader.skipElement()
}
descriptor.content = PluginContentDescriptor(Java11Shim.INSTANCE.copyOf(items))
descriptor.content = PluginContentDescriptor(Java11Shim.INSTANCE.copyOfCollection(items))
assert(reader.isEndElement)
}
@@ -113,7 +113,7 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
logStream = logStreamCandidate;
}
private IdeaPluginDescriptorImpl[] parents;
private final IdeaPluginDescriptorImpl[] parents;
// cache of computed list of all parents (not only direct)
private volatile ClassLoader[] allParents;
@@ -553,10 +553,10 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
}
private static final class DeepEnumeration implements Enumeration<URL> {
private final @NotNull List<? extends Enumeration<URL>> list;
private final @NotNull List<Enumeration<URL>> list;
private int myIndex;
DeepEnumeration(@NotNull List<? extends Enumeration<URL>> enumerations) {
DeepEnumeration(@NotNull List<Enumeration<URL>> enumerations) {
list = enumerations;
}
@@ -582,50 +582,11 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
}
@TestOnly
@ApiStatus.Internal
public @NotNull List<IdeaPluginDescriptorImpl> _getParents() {
//noinspection SSBasedInspection
return Collections.unmodifiableList(Arrays.asList(parents));
}
@ApiStatus.Internal
public void attachParent(@NotNull IdeaPluginDescriptorImpl parent) {
//noinspection SSBasedInspection
if (Arrays.stream(parents).anyMatch(it -> it == parent)) {
return;
}
int length = parents.length;
IdeaPluginDescriptorImpl[] result = new IdeaPluginDescriptorImpl[length + 1];
System.arraycopy(parents, 0, result, 0, length);
result[length] = parent;
parents = result;
allParents = null;
parentListCacheIdCounter.incrementAndGet();
}
/**
* You must clear allParents cache for all loaded plugins.
*/
@ApiStatus.Internal
public boolean detachParent(@NotNull IdeaPluginDescriptorImpl parent) {
for (int i = 0; i < parents.length; i++) {
if (parent != parents[i]) {
continue;
}
int length = parents.length;
IdeaPluginDescriptorImpl[] result = new IdeaPluginDescriptorImpl[length - 1];
System.arraycopy(parents, 0, result, 0, i);
System.arraycopy(parents, i + 1, result, i, length - i - 1);
parents = result;
allParents = null;
parentListCacheIdCounter.incrementAndGet();
return true;
}
return false;
}
@Override
protected ProtectionDomain getProtectionDomain() {
return PROTECTION_DOMAIN;
@@ -1,11 +1,15 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:Suppress("ReplaceGetOrSet")
package com.intellij.serviceContainer
import com.intellij.ide.plugins.IdeaPluginDescriptor
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.extensions.ExtensionDescriptor
import com.intellij.openapi.extensions.ExtensionPointDescriptor
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
class PrecomputedExtensionModel(
@JvmField val extensionPoints: List<List<ExtensionPointDescriptor>>,
@JvmField val pluginDescriptors: List<IdeaPluginDescriptor>,
@@ -14,14 +18,15 @@ class PrecomputedExtensionModel(
@JvmField val nameToExtensions: Map<String, MutableList<Pair<IdeaPluginDescriptor, List<ExtensionDescriptor>>>>
)
fun precomputeExtensionModel(plugins: List<IdeaPluginDescriptorImpl>): PrecomputedExtensionModel {
fun precomputeExtensionModel(): PrecomputedExtensionModel {
val extensionPointDescriptors = ArrayList<List<ExtensionPointDescriptor>>()
val pluginDescriptors = ArrayList<IdeaPluginDescriptor>()
var extensionPointTotalCount = 0
val nameToExtensions = HashMap<String, MutableList<Pair<IdeaPluginDescriptor, List<ExtensionDescriptor>>>>()
// step 1 - collect container level extension points
executeRegisterTask(plugins) { pluginDescriptor ->
val modules = PluginManagerCore.getPluginSet().getEnabledModules()
executeRegisterTask(modules) { pluginDescriptor ->
pluginDescriptor.moduleContainerDescriptor.extensionPoints?.let {
extensionPointDescriptors.add(it)
pluginDescriptors.add(pluginDescriptor)
@@ -34,7 +39,7 @@ fun precomputeExtensionModel(plugins: List<IdeaPluginDescriptorImpl>): Precomput
}
// step 2 - collect container level extensions
executeRegisterTask(plugins) { pluginDescriptor ->
executeRegisterTask(modules) { pluginDescriptor ->
val unsortedMap = pluginDescriptor.epNameToExtensions ?: return@executeRegisterTask
for ((name, list) in unsortedMap.entries) {
nameToExtensions.get(name)?.add(pluginDescriptor to list)
@@ -50,15 +55,15 @@ fun precomputeExtensionModel(plugins: List<IdeaPluginDescriptorImpl>): Precomput
)
}
inline fun executeRegisterTask(plugins: List<IdeaPluginDescriptorImpl>, crossinline task: (IdeaPluginDescriptorImpl) -> Unit) {
for (plugin in plugins) {
task(plugin)
executeRegisterTaskForContent(mainPluginDescriptor = plugin, task = task)
private inline fun executeRegisterTask(modules: Sequence<IdeaPluginDescriptorImpl>, crossinline task: (IdeaPluginDescriptorImpl) -> Unit) {
for (module in modules) {
task(module)
executeRegisterTaskForOldContent(mainPluginDescriptor = module, task = task)
}
}
inline fun executeRegisterTaskForContent(mainPluginDescriptor: IdeaPluginDescriptorImpl,
crossinline task: (IdeaPluginDescriptorImpl) -> Unit) {
inline fun executeRegisterTaskForOldContent(mainPluginDescriptor: IdeaPluginDescriptorImpl,
crossinline task: (IdeaPluginDescriptorImpl) -> Unit) {
for (dep in mainPluginDescriptor.pluginDependencies) {
val subDescriptor = dep.subDescriptor
if (subDescriptor?.classLoader == null) {
@@ -75,11 +80,4 @@ inline fun executeRegisterTaskForContent(mainPluginDescriptor: IdeaPluginDescrip
}
}
}
for (item in mainPluginDescriptor.content.modules) {
val module = item.requireDescriptor()
if (module.classLoader != null) {
task(module)
}
}
}
@@ -1,11 +1,11 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:Suppress("ReplaceGetOrSet")
package com.intellij.diagnostic.startUpPerformanceReporter
import com.fasterxml.jackson.core.JsonGenerator
import com.intellij.diagnostic.ActivityImpl
import com.intellij.diagnostic.StartUpMeasurer
import com.intellij.diagnostic.ThreadNameManager
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.ide.plugins.cl.PluginAwareClassLoader
import com.intellij.openapi.application.ApplicationInfo
@@ -155,11 +155,11 @@ private fun writeServiceStats(writer: JsonGenerator) {
val component = StatItem("component")
val service = StatItem("service")
val plugins = PluginManagerCore.getLoadedPlugins(null).sortedBy { it.pluginId }
for (plugin in plugins) {
service.app += (plugin as IdeaPluginDescriptorImpl).appContainerDescriptor.services?.size ?: 0
service.project += plugin.projectContainerDescriptor.services?.size ?: 0
service.module += plugin.moduleContainerDescriptor.services?.size ?: 0
val pluginSet = PluginManagerCore.getPluginSet()
for (plugin in pluginSet.getEnabledModules()) {
service.app += plugin.appContainerDescriptor.services.size
service.project += plugin.projectContainerDescriptor.services.size
service.module += plugin.moduleContainerDescriptor.services.size
component.app += plugin.appContainerDescriptor.components?.size ?: 0
component.project += plugin.projectContainerDescriptor.components?.size ?: 0
@@ -167,7 +167,7 @@ private fun writeServiceStats(writer: JsonGenerator) {
}
writer.obj("stats") {
writer.writeNumberField("plugin", plugins.size)
writer.writeNumberField("plugin", pluginSet.enabledPlugins.size)
for (statItem in listOf(component, service)) {
writer.obj(statItem.name) {
writer.writeNumberField("app", statItem.app)
@@ -178,7 +178,7 @@ private fun writeServiceStats(writer: JsonGenerator) {
}
writer.array("plugins") {
for (plugin in plugins) {
for (plugin in pluginSet.enabledPlugins) {
val classLoader = plugin.pluginClassLoader as? PluginAwareClassLoader ?: continue
writer.obj {
writer.writeStringField("id", plugin.pluginId.idString)
@@ -1,5 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.intention.impl;
import com.intellij.codeInsight.CodeInsightBundle;
@@ -442,7 +441,7 @@ public final class IntentionHintComponent implements Disposable, ScrollAwareHint
myComponentHint = new MyComponentHint(myPanel);
EditorUtil.disposeWithEditor(myEditor, this);
DynamicPlugins.onPluginUnload(this, () -> Disposer.dispose(this));
DynamicPlugins.INSTANCE.onPluginUnload(this, () -> Disposer.dispose(this));
}
public void hide() {
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.fileTemplates.impl;
import com.intellij.ide.fileTemplates.FileTemplateManager;
@@ -177,13 +177,14 @@ class FileTemplatesLoader implements Disposable {
FileTemplateLoadResult result = new FileTemplateLoadResult(new MultiMap<>());
Set<URL> processedUrls = new HashSet<>();
Set<ClassLoader> processedLoaders = new HashSet<>();
for (IdeaPluginDescriptorImpl plugin : PluginManagerCore.getLoadedPlugins(null)) {
for (IdeaPluginDescriptorImpl plugin : PluginManagerCore.getPluginSet().enabledPlugins) {
ClassLoader loader = plugin.getPluginClassLoader();
if (loader instanceof PluginAwareClassLoader && ((PluginAwareClassLoader)loader).getFiles().isEmpty() ||
!processedLoaders.add(loader)) {
// test or development mode, when IDEA_CORE's loader contains all the classpath
continue;
}
try {
Enumeration<URL> systemResources = loader.getResources(DEFAULT_TEMPLATES_ROOT);
while (systemResources.hasMoreElements()) {
@@ -1,11 +1,10 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.module.impl;
import com.intellij.configurationStore.RenameableStateStorageManager;
import com.intellij.ide.highlighter.ModuleFileType;
import com.intellij.ide.plugins.ContainerDescriptor;
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.*;
import com.intellij.openapi.components.impl.stores.IComponentStore;
@@ -90,7 +89,7 @@ public class ModuleImpl extends ComponentManagerImpl implements ModuleEx {
public void init(@Nullable Runnable beforeComponentCreation) {
// do not measure (activityNamePrefix method not overridden by this class)
// because there are a lot of modules and no need to measure each one
registerComponents(PluginManagerCore.getLoadedPlugins(null), ApplicationManager.getApplication(), null, null);
registerComponents();
if (!isPersistent()) {
registerService(IComponentStore.class,
NonPersistentModuleStore.class,
@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.workspaceModel.ide.impl.legacyBridge.module
import com.intellij.ide.plugins.IdeaPluginDescriptor
@@ -66,12 +66,12 @@ internal class ModuleBridgeImpl(
super<ModuleImpl>.rename(newName, notifyStorage)
}
override fun registerComponents(plugins: List<IdeaPluginDescriptorImpl>,
override fun registerComponents(modules: Sequence<IdeaPluginDescriptorImpl>,
app: Application?,
precomputedExtensionModel: PrecomputedExtensionModel?,
listenerCallbacks: List<Runnable>?) {
registerComponents(corePlugin = plugins.find { it.pluginId == PluginManagerCore.CORE_ID },
plugins = plugins,
registerComponents(corePlugin = modules.find { it.pluginId == PluginManagerCore.CORE_ID },
modules = modules,
precomputedExtensionModel = precomputedExtensionModel,
app = app,
listenerCallbacks = listenerCallbacks)
@@ -82,15 +82,17 @@ internal class ModuleBridgeImpl(
}
override fun registerComponents(corePlugin: IdeaPluginDescriptor?,
plugins: List<IdeaPluginDescriptorImpl>,
precomputedExtensionModel: PrecomputedExtensionModel?,
app: Application?,
listenerCallbacks: List<Runnable>?) {
super.registerComponents(plugins = plugins,
modules: Sequence<IdeaPluginDescriptorImpl>,
precomputedExtensionModel: PrecomputedExtensionModel?,
app: Application?,
listenerCallbacks: List<Runnable>?) {
super.registerComponents(modules = modules,
app = app,
precomputedExtensionModel = precomputedExtensionModel,
listenerCallbacks = listenerCallbacks)
if (corePlugin == null) return
if (corePlugin == null) {
return
}
unregisterComponent(DeprecatedModuleOptionManager::class.java)
try {
@@ -75,7 +75,7 @@ final class CheckRequiredPluginsActivity implements StartupActivity.RequiredForS
if (!plugin.isEnabled() || pluginTracker.isDisabled(pluginId)) {
boolean canEnableWithoutRestart = false;
if (Registry.is("ide.plugins.load.automatically")) {
String message = DynamicPlugins.checkCanUnloadWithoutRestart((IdeaPluginDescriptorImpl)plugin);
String message = DynamicPlugins.INSTANCE.checkCanUnloadWithoutRestart((IdeaPluginDescriptorImpl)plugin);
if (message == null) {
canEnableWithoutRestart = true;
pluginsToEnableWithoutRestart.add(plugin);
@@ -20,7 +20,7 @@ internal class ClassLoaderTreeChecker(private val unloadedMainDescriptor: IdeaPl
}
@Suppress("TestOnlyProblems")
for (it in pluginSet.getUnsortedEnabledModules()) {
checkThatClassloaderNotReferenced(it.requireDescriptor())
checkThatClassloaderNotReferenced(it)
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.plugins
import com.fasterxml.jackson.databind.type.TypeFactory
@@ -80,7 +80,6 @@ import com.intellij.util.containers.WeakList
import com.intellij.util.messages.impl.MessageBusEx
import com.intellij.util.ref.GCWatcher
import net.sf.cglib.core.ClassNameReader
import org.jetbrains.annotations.NonNls
import java.awt.KeyboardFocusManager
import java.awt.Window
import java.nio.channels.FileChannel
@@ -94,18 +93,17 @@ import javax.swing.ToolTipManager
import kotlin.collections.component1
import kotlin.collections.component2
private val LOG = logger<DynamicPlugins>()
private val LOG = logger<DynamicPlugins>()
private val classloadersFromUnloadedPlugins = mutableMapOf<PluginId, WeakList<PluginClassLoader>>()
private const val GROUP_ID = "Dynamic plugin installation"
object DynamicPlugins {
private const val GROUP_ID = "Dynamic plugin installation"
@JvmStatic
@JvmOverloads
fun allowLoadUnloadWithoutRestart(descriptor: IdeaPluginDescriptorImpl,
baseDescriptor: IdeaPluginDescriptorImpl? = null,
context: List<IdeaPluginDescriptorImpl> = emptyList()): Boolean {
val reason = checkCanUnloadWithoutRestart(descriptor, baseDescriptor, context = context)
val reason = checkCanUnloadWithoutRestart(module = descriptor, parentModule = baseDescriptor, context = context)
if (reason != null) {
LOG.info(reason)
}
@@ -145,11 +143,8 @@ object DynamicPlugins {
}
val pluginSet = PluginManagerCore.getPluginSet()
val descriptors = plugins
.asSequence()
.filterIsInstance<IdeaPluginDescriptorImpl>()
.filterNot { pluginSet.isPluginEnabled(it.pluginId) == load }
.toList()
@Suppress("UNCHECKED_CAST")
val descriptors = (plugins as Collection<IdeaPluginDescriptorImpl>).filter { pluginSet.isPluginEnabled(it.pluginId) != load }
val operationText = if (load) "load" else "unload"
val message = descriptors.joinToString(prefix = "Plugins to $operationText: [", postfix = "]")
@@ -159,7 +154,19 @@ object DynamicPlugins {
return false
}
for (descriptor in pluginsSortedByDependency(descriptors, load)) {
// todo plugin installation should be done not in this method
var allPlugins = pluginSet.allPlugins
for (descriptor in descriptors) {
if (!allPlugins.contains(descriptor)) {
allPlugins = allPlugins + descriptor
}
}
var comparator = PluginSet.getTopologicalComparator(allPlugins)
if (!load) {
comparator = comparator.reversed()
}
for (descriptor in descriptors.sortedWith(comparator)) {
descriptor.isEnabled = load
if (!executor.invoke(descriptor)) {
LOG.info("Failed to $operationText: $descriptor, restart required")
@@ -171,81 +178,73 @@ object DynamicPlugins {
return true
}
private fun pluginsSortedByDependency(descriptors: List<IdeaPluginDescriptorImpl>, load: Boolean): List<IdeaPluginDescriptorImpl> {
val plugins = PluginManagerCore.getPluginSet().sortTopologically(descriptors)
return if (load) plugins else plugins.asReversed()
fun checkCanUnloadWithoutRestart(module: IdeaPluginDescriptorImpl): String? {
return checkCanUnloadWithoutRestart(module, parentModule = null)
}
/**
* @param context Plugins which are being loaded at the same time as [descriptor]
* @param context Plugins which are being loaded at the same time as [module]
*/
@JvmStatic
@JvmOverloads
@NonNls
fun checkCanUnloadWithoutRestart(descriptor: IdeaPluginDescriptorImpl,
baseDescriptor: IdeaPluginDescriptorImpl? = null,
optionalDependencyPluginId: PluginId? = null,
context: List<IdeaPluginDescriptorImpl> = emptyList(),
checkImplementationDetailDependencies: Boolean = true): String? {
if (descriptor.isRequireRestart) {
return "Plugin ${descriptor.pluginId} is explicitly marked as requiring restart"
}
if (descriptor.productCode != null && !descriptor.isBundled && !PluginManagerCore.isDevelopedByJetBrains(descriptor)) {
return "Plugin ${descriptor.pluginId} is a paid plugin"
}
if (InstalledPluginsState.getInstance().isRestartRequired) {
return InstalledPluginsState.RESTART_REQUIRED_MESSAGE
}
if (classloadersFromUnloadedPlugins[descriptor.pluginId]?.isEmpty() == false) {
return "Not allowing load/unload of ${descriptor.pluginId} because of incomplete previous unload operation for that plugin"
}
findMissingRequiredDependency(descriptor, context)?.let { pluginDependency ->
return "Required dependency ${pluginDependency} of plugin ${descriptor.pluginId} is not currently loaded"
}
if (!RegistryManager.getInstance().`is`("ide.plugins.allow.unload")) {
val canLoadSynchronously = allowLoadUnloadSynchronously(descriptor)
if (!canLoadSynchronously) {
return "ide.plugins.allow.unload is disabled and synchronous load/unload is not possible for ${descriptor.pluginId}"
private fun checkCanUnloadWithoutRestart(module: IdeaPluginDescriptorImpl,
parentModule: IdeaPluginDescriptorImpl?,
optionalDependencyPluginId: PluginId? = null,
context: List<IdeaPluginDescriptorImpl> = emptyList(),
checkImplementationDetailDependencies: Boolean = true): String? {
if (parentModule == null) {
if (module.isRequireRestart) {
return "Plugin ${module.pluginId} is explicitly marked as requiring restart"
}
if (module.productCode != null && !module.isBundled && !PluginManagerCore.isDevelopedByJetBrains(module)) {
return "Plugin ${module.pluginId} is a paid plugin"
}
if (InstalledPluginsState.getInstance().isRestartRequired) {
return InstalledPluginsState.RESTART_REQUIRED_MESSAGE
}
return null
}
val app = ApplicationManager.getApplication()
try {
app.messageBus.syncPublisher(DynamicPluginListener.TOPIC).checkUnloadPlugin(descriptor)
}
catch (e: CannotUnloadPluginException) {
return e.cause?.localizedMessage ?: "checkUnloadPlugin listener blocked plugin unload"
}
val pluginSet = PluginManagerCore.getPluginSet()
if (!Registry.`is`("ide.plugins.allow.unload.from.sources")) {
val loadedPluginDescriptor = if (descriptor === baseDescriptor) {
pluginSet.findEnabledPlugin(descriptor.pluginId)
}
else {
null
if (classloadersFromUnloadedPlugins[module.pluginId]?.isEmpty() == false) {
return "Not allowing load/unload of ${module.pluginId} because of incomplete previous unload operation for that plugin"
}
findMissingRequiredDependency(module, context, pluginSet)?.let { pluginDependency ->
return "Required dependency ${pluginDependency} of plugin ${module.pluginId} is not currently loaded"
}
val app = ApplicationManager.getApplication()
if (parentModule == null) {
if (!RegistryManager.getInstance().`is`("ide.plugins.allow.unload")) {
if (!allowLoadUnloadSynchronously(module)) {
return "ide.plugins.allow.unload is disabled and synchronous load/unload is not possible for ${module.pluginId}"
}
return null
}
if (loadedPluginDescriptor != null && !descriptor.isUseIdeaClassLoader &&
pluginSet.isPluginEnabled(loadedPluginDescriptor.pluginId)) {
val pluginClassLoader = loadedPluginDescriptor.classLoader
try {
app.messageBus.syncPublisher(DynamicPluginListener.TOPIC).checkUnloadPlugin(module)
}
catch (e: CannotUnloadPluginException) {
return e.cause?.localizedMessage ?: "checkUnloadPlugin listener blocked plugin unload"
}
}
if (!Registry.`is`("ide.plugins.allow.unload.from.sources")) {
if (pluginSet.findEnabledPlugin(module.pluginId) != null && module === parentModule && !module.isUseIdeaClassLoader) {
val pluginClassLoader = module.classLoader
if (pluginClassLoader != null && pluginClassLoader !is PluginClassLoader && !app.isUnitTestMode) {
return "Plugin ${descriptor.pluginId} is not unload-safe because of use of ${pluginClassLoader.javaClass.name} as the default class loader. " +
return "Plugin ${module.pluginId} is not unload-safe because of use of ${pluginClassLoader.javaClass.name} as the default class loader. " +
"For example, the IDE is started from the sources with the plugin."
}
}
}
val epNameToExtensions = descriptor.epNameToExtensions
val epNameToExtensions = module.epNameToExtensions
if (epNameToExtensions != null) {
doCheckExtensionsCanUnloadWithoutRestart(
extensions = epNameToExtensions,
descriptor = descriptor,
baseDescriptor = baseDescriptor,
descriptor = module,
baseDescriptor = parentModule,
app = app,
optionalDependencyPluginId = optionalDependencyPluginId,
context = context,
@@ -253,28 +252,39 @@ object DynamicPlugins {
)?.let { return it }
}
val pluginId = descriptor.pluginId
checkNoComponentsOrServiceOverrides(pluginId, descriptor)?.let { return it }
ActionManagerImpl.checkUnloadActions(pluginId, descriptor)?.let { return it }
checkNoComponentsOrServiceOverrides(module)?.let { return it }
ActionManagerImpl.checkUnloadActions(module)?.let { return it }
for (dependency in descriptor.pluginDependencies) {
for (moduleRef in module.content.modules) {
if (pluginSet.isModuleEnabled(moduleRef.name)) {
val subModule = moduleRef.requireDescriptor()
checkCanUnloadWithoutRestart(module = subModule,
parentModule = module,
optionalDependencyPluginId = null,
context = context)?.let {
return "$it in optional dependency on ${subModule.pluginId}"
}
}
}
for (dependency in module.pluginDependencies) {
if (pluginSet.isPluginEnabled(dependency.pluginId)) {
checkCanUnloadWithoutRestart(dependency.subDescriptor ?: continue, baseDescriptor ?: descriptor, null, context)?.let {
checkCanUnloadWithoutRestart(dependency.subDescriptor ?: continue, parentModule ?: module, null, context)?.let {
return "$it in optional dependency on ${dependency.pluginId}"
}
}
}
// if not a sub plugin descriptor, then check that any dependent plugin also reloadable
if (baseDescriptor != null && descriptor !== baseDescriptor) {
if (parentModule != null && module !== parentModule) {
return null
}
var dependencyMessage: String? = null
processOptionalDependenciesOnPlugin(descriptor, pluginSet, isLoaded = true) { mainDescriptor, subDescriptor ->
processOptionalDependenciesOnPlugin(module, pluginSet, isLoaded = true) { mainDescriptor, subDescriptor ->
if (subDescriptor.packagePrefix == null
|| mainDescriptor.pluginId.idString == "org.jetbrains.kotlin" || mainDescriptor.pluginId == PluginManagerCore.JAVA_PLUGIN_ID) {
dependencyMessage = "Plugin ${subDescriptor.pluginId} that optionally depends on ${descriptor.pluginId}" +
dependencyMessage = "Plugin ${subDescriptor.pluginId} that optionally depends on ${module.pluginId}" +
" does not have a separate classloader for the dependency"
return@processOptionalDependenciesOnPlugin false
}
@@ -284,25 +294,26 @@ object DynamicPlugins {
true
}
else {
dependencyMessage = "Plugin ${subDescriptor.pluginId} that optionally depends on ${descriptor.pluginId} requires restart: $dependencyMessage"
dependencyMessage = "Plugin ${subDescriptor.pluginId} that optionally depends on ${module.pluginId} requires restart: $dependencyMessage"
false
}
}
if (dependencyMessage == null && checkImplementationDetailDependencies) {
val contextWithImplementationDetails = context.toMutableList()
contextWithImplementationDetails.add(descriptor)
processImplementationDetailDependenciesOnPlugin(descriptor, pluginSet, contextWithImplementationDetails::add)
contextWithImplementationDetails.add(module)
processImplementationDetailDependenciesOnPlugin(module, pluginSet, contextWithImplementationDetails::add)
processImplementationDetailDependenciesOnPlugin(descriptor, pluginSet) { dependentDescriptor ->
processImplementationDetailDependenciesOnPlugin(module, pluginSet) { dependentDescriptor ->
// don't check a plugin that is an implementation-detail dependency on the current plugin if it has other disabled dependencies
// and won't be loaded anyway
if (findMissingRequiredDependency(dependentDescriptor, contextWithImplementationDetails) == null) {
dependencyMessage = checkCanUnloadWithoutRestart(descriptor = dependentDescriptor,
if (findMissingRequiredDependency(dependentDescriptor, contextWithImplementationDetails, pluginSet) == null) {
dependencyMessage = checkCanUnloadWithoutRestart(module = dependentDescriptor,
parentModule = null,
context = contextWithImplementationDetails,
checkImplementationDetailDependencies = false)
if (dependencyMessage != null) {
dependencyMessage = "implementation-detail plugin ${dependentDescriptor.pluginId} which depends on ${descriptor.pluginId}" +
dependencyMessage = "implementation-detail plugin ${dependentDescriptor.pluginId} which depends on ${module.pluginId}" +
" requires restart: $dependencyMessage"
}
}
@@ -313,13 +324,13 @@ object DynamicPlugins {
}
private fun findMissingRequiredDependency(descriptor: IdeaPluginDescriptorImpl,
context: List<IdeaPluginDescriptorImpl>): PluginId? {
context: List<IdeaPluginDescriptorImpl>,
pluginSet: PluginSet): PluginId? {
for (dependency in descriptor.pluginDependencies) {
if (!dependency.isOptional &&
!PluginManagerCore.isModuleDependency(dependency.pluginId) &&
PluginManagerCore.getLoadedPlugins(null).none { it.pluginId == dependency.pluginId } &&
context.none { it.pluginId == dependency.pluginId }
) {
!pluginSet.isPluginEnabled(dependency.pluginId) &&
context.none { it.pluginId == dependency.pluginId }) {
return dependency.pluginId
}
}
@@ -331,22 +342,22 @@ object DynamicPlugins {
* plugins settings, without pressing the Apply button.
*/
@JvmStatic
fun allowLoadUnloadSynchronously(pluginDescriptor: IdeaPluginDescriptorImpl): Boolean {
val extensions = (pluginDescriptor.unsortedEpNameToExtensionElements.takeIf { it.isNotEmpty() } ?: pluginDescriptor.appContainerDescriptor.extensions)
fun allowLoadUnloadSynchronously(module: IdeaPluginDescriptorImpl): Boolean {
val extensions = (module.unsortedEpNameToExtensionElements.takeIf { it.isNotEmpty() } ?: module.appContainerDescriptor.extensions)
if (extensions != null && !extensions.all {
it.key == UIThemeProvider.EP_NAME.name ||
it.key == BundledKeymapBean.EP_NAME.name
}) {
return false
}
return checkNoComponentsOrServiceOverrides(pluginDescriptor.pluginId,
pluginDescriptor) == null && pluginDescriptor.actions.isNullOrEmpty()
return checkNoComponentsOrServiceOverrides(module) == null && module.actions.isEmpty()
}
private fun checkNoComponentsOrServiceOverrides(pluginId: PluginId?, pluginDescriptor: IdeaPluginDescriptorImpl): String? {
return checkNoComponentsOrServiceOverrides(pluginId, pluginDescriptor.appContainerDescriptor)
?: checkNoComponentsOrServiceOverrides(pluginId, pluginDescriptor.projectContainerDescriptor)
?: checkNoComponentsOrServiceOverrides(pluginId, pluginDescriptor.moduleContainerDescriptor)
private fun checkNoComponentsOrServiceOverrides(module: IdeaPluginDescriptorImpl): String? {
val id = module.pluginId
return checkNoComponentsOrServiceOverrides(id, module.appContainerDescriptor)
?: checkNoComponentsOrServiceOverrides(id, module.projectContainerDescriptor)
?: checkNoComponentsOrServiceOverrides(id, module.moduleContainerDescriptor)
}
private fun checkNoComponentsOrServiceOverrides(pluginId: PluginId?, containerDescriptor: ContainerDescriptor): String? {
@@ -359,8 +370,6 @@ object DynamicPlugins {
return null
}
@JvmStatic
@JvmOverloads
fun unloadPluginWithProgress(project: Project? = null,
parentComponent: JComponent?,
pluginDescriptor: IdeaPluginDescriptorImpl,
@@ -468,7 +477,7 @@ object DynamicPlugins {
unloadLoadedOptionalDependenciesOnPlugin(pluginDescriptor, pluginSet = pluginSet, classLoaders = classLoaders)
unloadDependencyDescriptors(pluginDescriptor, pluginSet, classLoaders)
unloadPluginDescriptorNotRecursively(pluginDescriptor)
unloadModuleDescriptorNotRecursively(pluginDescriptor)
clearPluginClassLoaderParentListCache(pluginSet)
@@ -613,7 +622,7 @@ object DynamicPlugins {
val dependencyClassloader = dependencyPlugin.classLoader
processOptionalDependenciesOnPlugin(dependencyPlugin, pluginSet, isLoaded = true) { mainDescriptor, subDescriptor ->
val classLoader = subDescriptor.classLoader
unloadPluginDescriptorNotRecursively(subDescriptor)
unloadModuleDescriptorNotRecursively(subDescriptor)
// this additional code is required because in unit tests PluginClassLoader is not used
if (mainDescriptor !== subDescriptor) {
@@ -626,9 +635,6 @@ object DynamicPlugins {
classLoaders.add(classLoader)
classLoader.state = PluginClassLoader.UNLOAD_IN_PROGRESS
}
else if (!classLoader.detachParent(dependencyPlugin)) {
LOG.warn("Classloader $dependencyClassloader doesn't have $classLoader as parent")
}
}
true
}
@@ -651,7 +657,7 @@ object DynamicPlugins {
}
unloadDependencyDescriptors(subDescriptor, pluginSet, classLoaders)
unloadPluginDescriptorNotRecursively(subDescriptor)
unloadModuleDescriptorNotRecursively(subDescriptor)
subDescriptor.classLoader = null
}
@@ -663,7 +669,7 @@ object DynamicPlugins {
classLoaders.add(classLoader)
}
unloadPluginDescriptorNotRecursively(subDescriptor)
unloadModuleDescriptorNotRecursively(subDescriptor)
subDescriptor.classLoader = null
}
}
@@ -678,28 +684,28 @@ object DynamicPlugins {
// PluginId cannot be used to unload related resources because one plugin descriptor may consist of several sub descriptors,
// each of them depends on presense of another plugin, here not the whole plugin is unloaded, but only one part.
private fun unloadPluginDescriptorNotRecursively(pluginDescriptor: IdeaPluginDescriptorImpl) {
private fun unloadModuleDescriptorNotRecursively(module: IdeaPluginDescriptorImpl) {
val app = ApplicationManager.getApplication() as ApplicationImpl
(ActionManager.getInstance() as ActionManagerImpl).unloadActions(pluginDescriptor)
(ActionManager.getInstance() as ActionManagerImpl).unloadActions(module)
val openedProjects = ProjectUtil.getOpenProjects().asList()
val appExtensionArea = app.extensionArea
val priorityUnloadListeners = mutableListOf<Runnable>()
val unloadListeners = mutableListOf<Runnable>()
unregisterUnknownLevelExtensions(pluginDescriptor.unsortedEpNameToExtensionElements, pluginDescriptor, appExtensionArea, openedProjects,
unregisterUnknownLevelExtensions(module.unsortedEpNameToExtensionElements, module, appExtensionArea, openedProjects,
priorityUnloadListeners, unloadListeners)
for ((epName, epExtensions) in (pluginDescriptor.appContainerDescriptor.extensions ?: emptyMap())) {
appExtensionArea.unregisterExtensions(epName, pluginDescriptor, epExtensions, priorityUnloadListeners, unloadListeners)
for ((epName, epExtensions) in (module.appContainerDescriptor.extensions ?: emptyMap())) {
appExtensionArea.unregisterExtensions(epName, module, epExtensions, priorityUnloadListeners, unloadListeners)
}
for ((epName, epExtensions) in (pluginDescriptor.projectContainerDescriptor.extensions ?: emptyMap())) {
for ((epName, epExtensions) in (module.projectContainerDescriptor.extensions ?: emptyMap())) {
for (project in openedProjects) {
(project.extensionArea as ExtensionsAreaImpl).unregisterExtensions(epName, pluginDescriptor, epExtensions, priorityUnloadListeners,
(project.extensionArea as ExtensionsAreaImpl).unregisterExtensions(epName, module, epExtensions, priorityUnloadListeners,
unloadListeners)
}
}
// not an error - unsorted goes to module level, see registerExtensions
unregisterUnknownLevelExtensions(pluginDescriptor.moduleContainerDescriptor.extensions, pluginDescriptor, appExtensionArea, openedProjects,
unregisterUnknownLevelExtensions(module.moduleContainerDescriptor.extensions, module, appExtensionArea, openedProjects,
priorityUnloadListeners, unloadListeners)
for (priorityUnloadListener in priorityUnloadListeners) {
@@ -710,39 +716,39 @@ object DynamicPlugins {
}
// first, reset all plugin extension points before unregistering, so that listeners don't see plugin in semi-torn-down state
processExtensionPoints(pluginDescriptor, openedProjects) { points, area ->
area.resetExtensionPoints(points, pluginDescriptor)
processExtensionPoints(module, openedProjects) { points, area ->
area.resetExtensionPoints(points, module)
}
// unregister plugin extension points
processExtensionPoints(pluginDescriptor, openedProjects) { points, area ->
area.unregisterExtensionPoints(points, pluginDescriptor)
processExtensionPoints(module, openedProjects) { points, area ->
area.unregisterExtensionPoints(points, module)
}
val pluginId = pluginDescriptor.pluginId
app.unloadServices(pluginDescriptor.appContainerDescriptor.services, pluginId)
val pluginId = module.pluginId
app.unloadServices(module.appContainerDescriptor.services, pluginId)
val appMessageBus = app.messageBus as MessageBusEx
pluginDescriptor.appContainerDescriptor.listeners?.let { appMessageBus.unsubscribeLazyListeners(pluginDescriptor, it) }
module.appContainerDescriptor.listeners?.let { appMessageBus.unsubscribeLazyListeners(module, it) }
for (project in openedProjects) {
(project as ComponentManagerImpl).unloadServices(pluginDescriptor.projectContainerDescriptor.services, pluginId)
pluginDescriptor.projectContainerDescriptor.listeners?.let {
((project as ComponentManagerImpl).messageBus as MessageBusEx).unsubscribeLazyListeners(pluginDescriptor, it)
(project as ComponentManagerImpl).unloadServices(module.projectContainerDescriptor.services, pluginId)
module.projectContainerDescriptor.listeners?.let {
((project as ComponentManagerImpl).messageBus as MessageBusEx).unsubscribeLazyListeners(module, it)
}
val moduleServices = pluginDescriptor.moduleContainerDescriptor.services
for (module in ModuleManager.getInstance(project).modules) {
(module as ComponentManagerImpl).unloadServices(moduleServices, pluginId)
createDisposeTreePredicate(pluginDescriptor)?.let { Disposer.disposeChildren(module, it) }
val moduleServices = module.moduleContainerDescriptor.services
for (ideaModule in ModuleManager.getInstance(project).modules) {
(ideaModule as ComponentManagerImpl).unloadServices(moduleServices, pluginId)
createDisposeTreePredicate(module)?.let { Disposer.disposeChildren(ideaModule, it) }
}
createDisposeTreePredicate(pluginDescriptor)?.let { Disposer.disposeChildren(project, it) }
createDisposeTreePredicate(module)?.let { Disposer.disposeChildren(project, it) }
}
appMessageBus.disconnectPluginConnections(Predicate { aClass ->
(aClass.classLoader as? PluginClassLoader)?.pluginDescriptor == pluginDescriptor
(aClass.classLoader as? PluginClassLoader)?.pluginDescriptor === module
})
createDisposeTreePredicate(pluginDescriptor)?.let { Disposer.disposeChildren(ApplicationManager.getApplication(), it) }
createDisposeTreePredicate(module)?.let { Disposer.disposeChildren(ApplicationManager.getApplication(), it) }
}
private fun unregisterUnknownLevelExtensions(extensionMap: Map<String, List<ExtensionDescriptor>>?,
@@ -803,18 +809,20 @@ object DynamicPlugins {
}
val loadStartTime = System.currentTimeMillis()
val app = ApplicationManager.getApplication() as ApplicationImpl
val pluginSet = PluginManagerCore.getPluginSet().enablePlugin(pluginDescriptor)
val classLoaderConfigurator = ClassLoaderConfigurator(pluginSet)
classLoaderConfigurator.configure(pluginDescriptor)
// todo loadPlugin should be called per each module, getPluginWithContentModules is a temporary solution
val pluginWithContentModules = getPluginWithContentModules(pluginDescriptor, pluginSet)
pluginWithContentModules.forEach(classLoaderConfigurator::configureModule)
val app = ApplicationManager.getApplication() as ApplicationImpl
app.messageBus.syncPublisher(DynamicPluginListener.TOPIC).beforePluginLoaded(pluginDescriptor)
app.runWriteAction {
try {
PluginManagerCore.setPluginSet(pluginSet)
val listenerCallbacks = mutableListOf<Runnable>()
loadPluginDescriptor(pluginDescriptor, app, listenerCallbacks)
loadModule(pluginWithContentModules, app, listenerCallbacks)
loadOptionalDependenciesOnPlugin(pluginDescriptor, classLoaderConfigurator, pluginSet, listenerCallbacks)
clearPluginClassLoaderParentListCache(pluginSet)
@@ -850,7 +858,6 @@ object DynamicPlugins {
return true
}
@JvmStatic
fun onPluginUnload(parentDisposable: Disposable, callback: Runnable) {
ApplicationManager.getApplication().messageBus.connect(parentDisposable)
.subscribe(DynamicPluginListener.TOPIC, object : DynamicPluginListener {
@@ -859,109 +866,108 @@ object DynamicPlugins {
}
})
}
}
private fun clearTemporaryLostComponent() {
private fun clearTemporaryLostComponent() {
try {
val clearMethod = Window::class.java.declaredMethods.find { it.name == "setTemporaryLostComponent" }
if (clearMethod == null) {
LOG.info("setTemporaryLostComponent method not found")
return
}
clearMethod.isAccessible = true
loop@ for (frame in WindowManager.getInstance().allProjectFrames) {
val window = when (frame) {
is ProjectFrameHelper -> frame.frame
is Window -> frame
else -> continue@loop
}
clearMethod.invoke(window, null)
}
}
catch (e: Throwable) {
LOG.info("Failed to clear Window.temporaryLostComponent", e)
}
}
private fun hideTooltip() {
try {
val showMethod = ToolTipManager::class.java.declaredMethods.find { it.name == "show" }
if (showMethod == null) {
LOG.info("ToolTipManager.show method not found")
return
}
showMethod.isAccessible = true
showMethod.invoke(ToolTipManager.sharedInstance(), null)
}
catch (e: Throwable) {
LOG.info("Failed to hide tooltip", e)
}
}
private fun clearCglibStopBacktrace() {
val field = ReflectionUtil.getDeclaredField(ClassNameReader::class.java, "EARLY_EXIT")
if (field != null) {
try {
val clearMethod = Window::class.java.declaredMethods.find { it.name == "setTemporaryLostComponent" }
if (clearMethod == null) {
LOG.info("setTemporaryLostComponent method not found")
return
}
clearMethod.isAccessible = true
loop@ for (frame in WindowManager.getInstance().allProjectFrames) {
val window = when (frame) {
is ProjectFrameHelper -> frame.frame
is Window -> frame
else -> continue@loop
}
clearMethod.invoke(window, null)
}
ThrowableInterner.clearBacktrace((field[null] as Throwable))
}
catch (e: Throwable) {
LOG.info("Failed to clear Window.temporaryLostComponent", e)
LOG.info(e)
}
}
}
private fun hideTooltip() {
private fun clearNewFocusOwner() {
val field = ReflectionUtil.getDeclaredField(KeyboardFocusManager::class.java, "newFocusOwner")
if (field != null) {
try {
val showMethod = ToolTipManager::class.java.declaredMethods.find { it.name == "show" }
if (showMethod == null) {
LOG.info("ToolTipManager.show method not found")
return
}
showMethod.isAccessible = true
showMethod.invoke(ToolTipManager.sharedInstance(), null)
field.set(null, null)
}
catch (e: Throwable) {
LOG.info("Failed to hide tooltip", e)
LOG.info(e)
}
}
}
private fun saveMemorySnapshot(pluginId: PluginId): Boolean {
val snapshotDate = SimpleDateFormat("dd.MM.yyyy_HH.mm.ss").format(Date())
val snapshotFileName = "unload-$pluginId-$snapshotDate.hprof"
val snapshotPath = System.getProperty("memory.snapshots.path", SystemProperties.getUserHome()) + "/" + snapshotFileName
private fun clearCglibStopBacktrace() {
val field = ReflectionUtil.getDeclaredField(ClassNameReader::class.java, "EARLY_EXIT")
if (field != null) {
try {
ThrowableInterner.clearBacktrace((field[null] as Throwable))
}
catch (e: Throwable) {
LOG.info(e)
}
}
MemoryDumpHelper.captureMemoryDump(snapshotPath)
if (classloadersFromUnloadedPlugins[pluginId]?.isEmpty() != false) {
LOG.info("Successfully unloaded plugin $pluginId (classloader collected during memory snapshot generation)")
return true
}
private fun clearNewFocusOwner() {
val field = ReflectionUtil.getDeclaredField(KeyboardFocusManager::class.java, "newFocusOwner")
if (field != null) {
try {
field.set(null, null)
}
catch (e: Throwable) {
LOG.info(e)
}
}
}
private fun saveMemorySnapshot(pluginId: PluginId): Boolean {
val snapshotDate = SimpleDateFormat("dd.MM.yyyy_HH.mm.ss").format(Date())
val snapshotFileName = "unload-$pluginId-$snapshotDate.hprof"
val snapshotPath = System.getProperty("memory.snapshots.path", SystemProperties.getUserHome()) + "/" + snapshotFileName
MemoryDumpHelper.captureMemoryDump(snapshotPath)
if (classloadersFromUnloadedPlugins[pluginId]?.isEmpty() != false) {
LOG.info("Successfully unloaded plugin $pluginId (classloader collected during memory snapshot generation)")
if (Registry.`is`("ide.plugins.analyze.snapshot")) {
val analysisResult = analyzeSnapshot(snapshotPath, pluginId)
@Suppress("ReplaceSizeZeroCheckWithIsEmpty")
if (analysisResult.length == 0) {
LOG.info("Successfully unloaded plugin $pluginId (no strong references to classloader in .hprof file)")
classloadersFromUnloadedPlugins.remove(pluginId)
return true
}
if (Registry.`is`("ide.plugins.analyze.snapshot")) {
val analysisResult = analyzeSnapshot(snapshotPath, pluginId)
@Suppress("ReplaceSizeZeroCheckWithIsEmpty")
if (analysisResult.length == 0) {
LOG.info("Successfully unloaded plugin $pluginId (no strong references to classloader in .hprof file)")
classloadersFromUnloadedPlugins.remove(pluginId)
return true
}
else {
LOG.info("Snapshot analysis result: $analysisResult")
}
else {
LOG.info("Snapshot analysis result: $analysisResult")
}
notify(
IdeBundle.message("memory.snapshot.captured.text", snapshotPath, snapshotFileName),
NotificationType.WARNING,
object : AnAction(IdeBundle.message("ide.restart.action")), DumbAware {
override fun actionPerformed(e: AnActionEvent) = ApplicationManager.getApplication().restart()
},
object : AnAction(
IdeBundle.message("memory.snapshot.captured.action.text", snapshotFileName, RevealFileAction.getFileManagerName())), DumbAware {
override fun actionPerformed(e: AnActionEvent) = RevealFileAction.openFile(Paths.get(snapshotPath))
}
)
LOG.info("Plugin $pluginId is not unload-safe because class loader cannot be unloaded. Memory snapshot created at $snapshotPath")
return false
}
DynamicPlugins.notify(
IdeBundle.message("memory.snapshot.captured.text", snapshotPath, snapshotFileName),
NotificationType.WARNING,
object : AnAction(IdeBundle.message("ide.restart.action")), DumbAware {
override fun actionPerformed(e: AnActionEvent) = ApplicationManager.getApplication().restart()
},
object : AnAction(
IdeBundle.message("memory.snapshot.captured.action.text", snapshotFileName, RevealFileAction.getFileManagerName())), DumbAware {
override fun actionPerformed(e: AnActionEvent) = RevealFileAction.openFile(Paths.get(snapshotPath))
}
)
LOG.info("Plugin $pluginId is not unload-safe because class loader cannot be unloaded. Memory snapshot created at $snapshotPath")
return false
}
private fun processImplementationDetailDependenciesOnPlugin(pluginDescriptor: IdeaPluginDescriptorImpl,
@@ -1003,13 +1009,13 @@ private fun loadOptionalDependenciesOnPlugin(dependencyPlugin: IdeaPluginDescrip
}
// 2. setup classloaders
classLoaderConfigurator.configureDependenciesIfNeeded(mainToModule, dependencyPlugin)
classLoaderConfigurator.configureDependenciesIfNeeded(mainToModule)
val app = ApplicationManager.getApplication() as ComponentManagerImpl
val app = ApplicationManager.getApplication() as ApplicationImpl
// 3. load into service container
for (entry in mainToModule.entries) {
for (subDescriptor in entry.value) {
loadPluginDescriptor(subDescriptor, app, listenerCallbacks)
loadModule(sequenceOf(subDescriptor), app, listenerCallbacks)
}
}
}
@@ -1021,25 +1027,35 @@ private fun clearPluginClassLoaderParentListCache(pluginSet: PluginSet) {
}
}
private fun loadPluginDescriptor(pluginDescriptor: IdeaPluginDescriptorImpl,
app: ComponentManagerImpl,
listenerCallbacks: MutableList<Runnable>) {
val list = listOf(pluginDescriptor)
app.registerComponents(plugins = list,
app = ApplicationManager.getApplication(),
precomputedExtensionModel = null,
listenerCallbacks = listenerCallbacks)
private fun loadModule(modules: Sequence<IdeaPluginDescriptorImpl>,
app: ApplicationImpl,
listenerCallbacks: MutableList<Runnable>) {
app.registerComponents(modules = modules,
app = app,
precomputedExtensionModel = null,
listenerCallbacks = listenerCallbacks)
for (openProject in ProjectUtil.getOpenProjects()) {
(openProject as ComponentManagerImpl).registerComponents(list, ApplicationManager.getApplication(), null, listenerCallbacks)
(openProject as ComponentManagerImpl).registerComponents(modules = modules,
app = app,
precomputedExtensionModel = null,
listenerCallbacks = listenerCallbacks)
for (module in ModuleManager.getInstance(openProject).modules) {
(module as ComponentManagerImpl).registerComponents(list, ApplicationManager.getApplication(), null, listenerCallbacks)
(module as ComponentManagerImpl).registerComponents(modules = modules,
app = app,
precomputedExtensionModel = null,
listenerCallbacks = listenerCallbacks)
}
}
(ActionManager.getInstance() as ActionManagerImpl).registerActions(list)
(ActionManager.getInstance() as ActionManagerImpl).registerActions(modules)
}
private fun analyzeSnapshot(hprofPath: String, pluginId: PluginId): String {
private fun getPluginWithContentModules(pluginDescriptor: IdeaPluginDescriptorImpl,
pluginSet: PluginSet): Sequence<IdeaPluginDescriptorImpl> {
return pluginSet.getEnabledModules().filter { it.pluginId == pluginDescriptor.pluginId }
}
private fun analyzeSnapshot(hprofPath: String, pluginId: PluginId): String {
FileChannel.open(Paths.get(hprofPath), StandardOpenOption.READ).use { channel ->
val analysis = HProfAnalysis(channel, SystemTempFilenameSupplier()) { analysisContext, progressIndicator ->
AnalyzeClassloaderReferencesGraph(analysisContext, pluginId.idString).analyze(progressIndicator)
@@ -1058,7 +1074,7 @@ private fun createDisposeTreePredicate(pluginDescriptor: IdeaPluginDescriptorImp
it.classLoaderId == classLoader.instanceId
}
else {
it::class.java.classLoader == classLoader
it::class.java.classLoader === classLoader
}
}
}
@@ -1086,7 +1102,7 @@ private fun processDependenciesOnPlugin(
processor: (pluginDescriptor: IdeaPluginDescriptorImpl, moduleDescriptor: IdeaPluginDescriptorImpl) -> Boolean,
) {
val wantedIds = HashSet<String>(1 + dependencyPlugin.content.modules.size)
wantedIds.add(dependencyPlugin.id.idString)
wantedIds.add(dependencyPlugin.pluginId.idString)
for (module in dependencyPlugin.content.modules) {
wantedIds.add(module.name)
}
@@ -1096,7 +1112,7 @@ private fun processDependenciesOnPlugin(
continue
}
if (!processOptionalDependenciesInOldFormatOnPlugin(dependencyPluginId = dependencyPlugin.id,
if (!processOptionalDependenciesInOldFormatOnPlugin(dependencyPluginId = dependencyPlugin.pluginId,
mainDescriptor = plugin,
loadStateFilter = loadStateFilter,
onlyOptional = onlyOptional,
@@ -1120,7 +1136,7 @@ private fun processDependenciesOnPlugin(
}
}
for (item in module.dependencies.plugins) {
if (dependencyPlugin.id == item.id && !processor(plugin, module)) {
if (dependencyPlugin.pluginId == item.id && !processor(plugin, module)) {
return
}
}
@@ -1171,54 +1187,51 @@ private fun processOptionalDependenciesInOldFormatOnPlugin(
return true
}
private fun doCheckExtensionsCanUnloadWithoutRestart(
extensions: Map<String, List<ExtensionDescriptor>>,
descriptor: IdeaPluginDescriptorImpl,
baseDescriptor: IdeaPluginDescriptorImpl?,
app: Application,
optionalDependencyPluginId: PluginId?,
context: List<IdeaPluginDescriptorImpl>,
pluginSet: PluginSet,
): String? {
val firstProject = ProjectUtil.getOpenProjects().firstOrNull()
val anyProject = firstProject ?: ProjectManager.getInstance().defaultProject
val anyModule = firstProject?.let { ModuleManager.getInstance(it).modules.firstOrNull() }
private fun doCheckExtensionsCanUnloadWithoutRestart(
extensions: Map<String, List<ExtensionDescriptor>>,
descriptor: IdeaPluginDescriptorImpl,
baseDescriptor: IdeaPluginDescriptorImpl?,
app: Application,
optionalDependencyPluginId: PluginId?,
context: List<IdeaPluginDescriptorImpl>,
pluginSet: PluginSet,
): String? {
val firstProject = ProjectUtil.getOpenProjects().firstOrNull()
val anyProject = firstProject ?: ProjectManager.getInstance().defaultProject
val anyModule = firstProject?.let { ModuleManager.getInstance(it).modules.firstOrNull() }
val seenPlugins: MutableSet<IdeaPluginDescriptorImpl> = Collections.newSetFromMap(IdentityHashMap())
epLoop@ for (epName in extensions.keys) {
seenPlugins.clear()
val seenPlugins: MutableSet<IdeaPluginDescriptorImpl> = Collections.newSetFromMap(IdentityHashMap())
epLoop@ for (epName in extensions.keys) {
seenPlugins.clear()
fun getNonDynamicUnloadError(optionalDependencyPluginId: PluginId?): String =
optionalDependencyPluginId?.let {
"Plugin ${baseDescriptor?.pluginId} is not unload-safe because of use of non-dynamic EP $epName in plugin $it that optionally depends on it"
} ?: "Plugin ${descriptor.id} is not unload-safe because of extension to non-dynamic EP $epName"
fun getNonDynamicUnloadError(optionalDependencyPluginId: PluginId?): String = optionalDependencyPluginId?.let {
"Plugin ${baseDescriptor?.pluginId} is not unload-safe because of use of non-dynamic EP $epName in plugin $it that optionally depends on it"
} ?: "Plugin ${descriptor.pluginId} is not unload-safe because of extension to non-dynamic EP $epName"
val result = findLoadedPluginExtensionPointRecursive(
pluginDescriptor = baseDescriptor ?: descriptor,
epName = epName,
pluginSet = pluginSet,
context = context,
seenPlugins = seenPlugins,
)
if (result != null) {
val (pluginExtensionPoint, foundInDependencies) = result
// descriptor.pluginId is null when we check the optional dependencies of the plugin which is being loaded
// if an optional dependency of a plugin extends a non-dynamic EP of that plugin, it shouldn't prevent plugin loading
if (!pluginExtensionPoint.isDynamic) {
if (baseDescriptor == null || foundInDependencies) {
return getNonDynamicUnloadError(null)
}
else if (descriptor === baseDescriptor) {
return getNonDynamicUnloadError(descriptor.pluginId)
}
}
continue
}
val result = findLoadedPluginExtensionPointRecursive(
pluginDescriptor = baseDescriptor ?: descriptor,
epName = epName,
pluginSet = pluginSet,
context = context,
seenPlugins = seenPlugins,
)
if (result != null) {
val (pluginExtensionPoint, foundInDependencies) = result // descriptor.pluginId is null when we check the optional dependencies of the plugin which is being loaded
// if an optional dependency of a plugin extends a non-dynamic EP of that plugin, it shouldn't prevent plugin loading
if (!pluginExtensionPoint.isDynamic) {
if (baseDescriptor == null || foundInDependencies) {
return getNonDynamicUnloadError(null)
}
else if (descriptor === baseDescriptor) {
return getNonDynamicUnloadError(descriptor.pluginId)
}
}
continue
}
@Suppress("RemoveExplicitTypeArguments")
val ep = app.extensionArea.getExtensionPointIfRegistered<Any>(epName)
?: anyProject.extensionArea.getExtensionPointIfRegistered<Any>(epName)
?: anyModule?.extensionArea?.getExtensionPointIfRegistered<Any>(epName)
@Suppress("RemoveExplicitTypeArguments") val ep = app.extensionArea.getExtensionPointIfRegistered<Any>(epName)
?: anyProject.extensionArea.getExtensionPointIfRegistered<Any>(epName)
?: anyModule?.extensionArea?.getExtensionPointIfRegistered<Any>(epName)
if (ep != null) {
if (!ep.isDynamic) {
return getNonDynamicUnloadError(optionalDependencyPluginId)
@@ -1248,11 +1261,11 @@ private fun processOptionalDependenciesInOldFormatOnPlugin(
}
// special case Kotlin EPs registered via code in Kotlin compiler
if (epName.startsWith("org.jetbrains.kotlin") && descriptor.id.idString == "org.jetbrains.kotlin") {
if (epName.startsWith("org.jetbrains.kotlin") && descriptor.pluginId.idString == "org.jetbrains.kotlin") {
continue
}
return "Plugin ${descriptor.id} is not unload-safe because of unresolved extension $epName"
return "Plugin ${descriptor.pluginId} is not unload-safe because of unresolved extension $epName"
}
return null
}
@@ -1278,7 +1291,7 @@ private fun findLoadedPluginExtensionPointRecursive(pluginDescriptor: IdeaPlugin
findPluginExtensionPoint(pluginDescriptor, epName)?.let { return it to false }
for (dependency in pluginDescriptor.pluginDependencies) {
if (pluginSet.isPluginEnabled(dependency.pluginId) || context.any { it.id == dependency.pluginId }) {
if (pluginSet.isPluginEnabled(dependency.pluginId) || context.any { it.pluginId == dependency.pluginId }) {
dependency.subDescriptor?.let { subDescriptor ->
findLoadedPluginExtensionPointRecursive(subDescriptor, epName, pluginSet, context, seenPlugins)?.let { return it }
}
@@ -1294,6 +1307,23 @@ private fun findLoadedPluginExtensionPointRecursive(pluginDescriptor: IdeaPlugin
return null
}
private inline fun processDirectDependencies(module: IdeaPluginDescriptorImpl,
pluginSet: PluginSet,
processor: (IdeaPluginDescriptorImpl) -> Unit) {
for (item in module.dependencies.modules) {
val descriptor = pluginSet.findEnabledModule(item.name)
if (descriptor != null) {
processor(descriptor)
}
}
for (item in module.dependencies.plugins) {
val descriptor = pluginSet.findEnabledPlugin(item.id)
if (descriptor != null) {
processor(descriptor)
}
}
}
private fun unloadClassLoader(pluginDescriptor: IdeaPluginDescriptorImpl, timeoutMs: Int): Boolean {
if (timeoutMs == 0) {
pluginDescriptor.classLoader = null
@@ -102,7 +102,7 @@ public final class PluginInstaller {
.withWaitForClassloaderUnload(true);
uninstalledWithoutRestart = parentComponent != null ?
DynamicPlugins.unloadPluginWithProgress(null, parentComponent, pluginDescriptor, options) :
DynamicPlugins.INSTANCE.unloadPluginWithProgress(null, parentComponent, pluginDescriptor, options) :
DynamicPlugins.INSTANCE.unloadPlugin(pluginDescriptor, options);
}
@@ -840,7 +840,7 @@ public class MyPluginModel extends InstalledPluginsTableModel implements PluginE
boolean requiresRestart(@NotNull IdeaPluginDescriptor descriptor) {
return myRequiresRestart
.computeIfAbsent(descriptor instanceof IdeaPluginDescriptorImpl ? (IdeaPluginDescriptorImpl)descriptor : null,
it -> it == null || DynamicPlugins.checkCanUnloadWithoutRestart(it) != null);
it -> it == null || DynamicPlugins.INSTANCE.checkCanUnloadWithoutRestart(it) != null);
}
boolean isUninstalled(@NotNull IdeaPluginDescriptor descriptor) {
@@ -10,6 +10,7 @@ import com.intellij.icons.AllIcons
import com.intellij.ide.*
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.ide.plugins.PluginSet
import com.intellij.ide.plugins.StartupAbortedException
import com.intellij.ide.ui.laf.darcula.DarculaLaf
import com.intellij.openapi.application.*
@@ -72,17 +73,20 @@ fun initApplication(rawArgs: List<String>, prepareUiFuture: CompletionStage<*>)
}
loadAndInitPluginFuture
.thenAccept { plugins ->
.thenAccept { pluginSet ->
runActivity("app component registration") {
app.registerComponents(plugins, app, null, null)
app.registerComponents(modules = pluginSet.getEnabledModules(),
app = app,
precomputedExtensionModel = null,
listenerCallbacks = null)
}
if (args.isEmpty()) {
startApp(app, IdeStarter(), initAppActivity, plugins, args)
startApp(app, IdeStarter(), initAppActivity, pluginSet, args)
}
else {
// `ApplicationStarter` is an extension, so to find a starter, extensions must be registered first
findCustomAppStarterAndStart(plugins, args, app, initAppActivity)
findCustomAppStarterAndStart(pluginSet, args, app, initAppActivity)
}
if (!Main.isHeadless()) {
@@ -114,12 +118,12 @@ fun initApplication(rawArgs: List<String>, prepareUiFuture: CompletionStage<*>)
private fun startApp(app: ApplicationImpl,
starter: ApplicationStarter,
initAppActivity: Activity,
plugins: List<IdeaPluginDescriptorImpl>,
pluginSet: PluginSet,
args: List<String>) {
// initSystemProperties or RegistryKeyBean.addKeysFromPlugins maybe not yet performed,
// but it is OK, because registry is not and should not be used.
initConfigurationStore(app)
val preloadSyncServiceFuture = preloadServices(plugins, app, activityPrefix = "")
val preloadSyncServiceFuture = preloadServices(pluginSet.getEnabledModules(), app, activityPrefix = "")
val placeOnEventQueueActivity = initAppActivity.startChild(Activities.PLACE_ON_EVENT_QUEUE)
val loadComponentInEdtFuture = CompletableFuture.runAsync({
@@ -201,7 +205,7 @@ private fun startApp(app: ApplicationImpl,
}
}
private fun findCustomAppStarterAndStart(plugins: List<IdeaPluginDescriptorImpl>,
private fun findCustomAppStarterAndStart(pluginSet: PluginSet,
args: List<String>,
app: ApplicationImpl,
initAppActivity: Activity) {
@@ -226,7 +230,7 @@ private fun findCustomAppStarterAndStart(plugins: List<IdeaPluginDescriptorImpl>
}
starter.premain(args)
startApp(app, starter, initAppActivity, plugins, args)
startApp(app, starter, initAppActivity, pluginSet, args)
}
@VisibleForTesting
@@ -241,12 +245,11 @@ internal fun createAppLocatorFile() {
}
}
@JvmOverloads
fun preloadServices(plugins: List<IdeaPluginDescriptorImpl>,
fun preloadServices(modules: Sequence<IdeaPluginDescriptorImpl>,
container: ComponentManagerImpl,
activityPrefix: String,
onlyIfAwait: Boolean = false): CompletableFuture<Void?> {
val result = container.preloadServices(plugins, activityPrefix, onlyIfAwait)
val result = container.preloadServices(modules, activityPrefix, onlyIfAwait)
fun logError(future: CompletableFuture<Void?>): CompletableFuture<Void?> {
return future
@@ -1,8 +1,8 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.idea;
import com.intellij.ide.RecentProjectsManagerBase;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl;
import org.jetbrains.annotations.NotNull;
public final class CommandLineArgs {
@@ -15,7 +15,7 @@ public final class CommandLineArgs {
public static void parse(String[] args) {
for (String arg : args) {
if (arg.equalsIgnoreCase(DISABLE_NON_BUNDLED_PLUGINS)) {
PluginManagerCore.ourDisableNonBundledPlugins = true;
IdeaPluginDescriptorImpl.disableNonBundledPlugins = true;
break;
}
else if (arg.equalsIgnoreCase(DONT_REOPEN_PROJECTS)) {
@@ -980,13 +980,8 @@ public final class StartupUtil {
}
@Override
public <E> @NotNull List<E> copyOf(List<? extends E> collection) {
public <E> @NotNull List<E> copyOfCollection(Collection<? extends E> collection) {
return List.copyOf(collection);
}
@Override
public @NotNull <E> List<E> listOf(E[] collection) {
return List.of(collection);
}
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.idea;
import com.intellij.util.lang.ClassLoadingLocks;
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -55,9 +56,9 @@ public final class ZipFilePoolImpl extends ZipFilePool {
}
@Override
public byte @Nullable [] loadZipEntry(@NotNull String path) throws IOException {
public @Nullable InputStream loadZipEntry(@NotNull String path) throws IOException {
ImmutableZipEntry entry = zipFile.getEntry(path.charAt(0) == '/' ? path.substring(1) : path);
return entry == null ? null : entry.getData(zipFile);
return entry == null ? null : entry.getInputStream(zipFile);
}
@Override
@@ -64,6 +64,7 @@ import com.intellij.util.ui.UIUtil;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import kotlin.Unit;
import kotlin.sequences.Sequence;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -140,7 +141,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
}
registerActions(PluginManagerCore.getLoadedPlugins(null));
registerActions(PluginManagerCore.getPluginSet().getEnabledModules());
EP.forEachExtensionSafe(customizer -> customizer.customize(this));
DYNAMIC_EP_NAME.forEachExtensionSafe(customizer -> customizer.registerActions(this));
@@ -164,12 +165,16 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
@ApiStatus.Internal
public void registerActions(@NotNull List<IdeaPluginDescriptorImpl> plugins) {
public void registerActions(@NotNull Sequence<IdeaPluginDescriptorImpl> modules) {
KeymapManagerEx keymapManager = Objects.requireNonNull(KeymapManagerEx.getInstanceEx());
PrecomputedExtensionModelKt.executeRegisterTask(plugins, it -> {
registerPluginActions(it, keymapManager);
return Unit.INSTANCE;
});
for (Iterator<IdeaPluginDescriptorImpl> iter = modules.iterator(); iter.hasNext(); ) {
IdeaPluginDescriptorImpl module = iter.next();
registerPluginActions(module, keymapManager);
PrecomputedExtensionModelKt.executeRegisterTaskForOldContent(module, it -> {
registerPluginActions(it, keymapManager);
return Unit.INSTANCE;
});
}
}
private static @NotNull AnActionListener publisher() {
@@ -244,13 +249,13 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
private static void setIconFromClass(@Nullable Class<?> actionClass,
@NotNull PluginDescriptor pluginDescriptor,
@NotNull PluginDescriptor module,
@NotNull String iconPath,
@NotNull Presentation presentation) {
long start = StartUpMeasurer.getCurrentTimeIfEnabled();
Icon icon = IconLoader.findIcon(iconPath, actionClass, pluginDescriptor.getPluginClassLoader(), null, true);
Icon icon = IconLoader.findIcon(iconPath, actionClass, module.getPluginClassLoader(), null, true);
if (icon == null) {
reportActionError(pluginDescriptor.getPluginId(), "Icon cannot be found in '" + iconPath + "', action '" + actionClass + "'");
reportActionError(module, "Icon cannot be found in '" + iconPath + "', action '" + actionClass + "'");
icon = AllIcons.Nodes.Unknown;
}
IconLoadMeasurer.actionIcon.end(start);
@@ -282,19 +287,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
return bundle == null ? defaultValue : AbstractBundle.messageOrDefault(bundle, elementType + "." + id + "." + TEXT_ATTR_NAME, defaultValue);
}
private static boolean checkRelativeToAction(String relativeToActionId,
@NotNull Anchor anchor,
@NotNull String actionName,
@Nullable PluginId pluginId) {
if ((Anchor.BEFORE == anchor || Anchor.AFTER == anchor) && relativeToActionId == null) {
reportActionError(pluginId, actionName + ": \"relative-to-action\" cannot be null if anchor is \"after\" or \"before\"");
return false;
}
return true;
}
@Nullable
private static Anchor parseAnchor(String anchorStr, @Nullable String actionName, @Nullable PluginId pluginId) {
private static @Nullable Anchor parseAnchor(String anchorStr, @Nullable String actionName, @NotNull IdeaPluginDescriptor module) {
if (anchorStr == null) {
return Anchor.LAST;
}
@@ -312,15 +305,19 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
return Anchor.AFTER;
}
else {
reportActionError(pluginId, actionName + ": anchor should be one of the following constants: \"first\", \"last\", \"before\" or \"after\"");
reportActionError(module,
actionName + ": anchor should be one of the following constants: \"first\", \"last\", \"before\" or \"after\"");
return null;
}
}
private static void processMouseShortcutNode(XmlElement element, String actionId, PluginId pluginId, @NotNull KeymapManager keymapManager) {
private static void processMouseShortcutNode(@NotNull XmlElement element,
String actionId,
@NotNull IdeaPluginDescriptor module,
@NotNull KeymapManager keymapManager) {
String keystrokeString = element.attributes.get("keystroke");
if (keystrokeString == null || keystrokeString.trim().isEmpty()) {
reportActionError(pluginId, "\"keystroke\" attribute must be specified for action with id=" + actionId);
reportActionError(module, "\"keystroke\" attribute must be specified for action with id=" + actionId);
return;
}
MouseShortcut shortcut;
@@ -328,45 +325,35 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
shortcut = KeymapUtil.parseMouseShortcut(keystrokeString);
}
catch (Exception ex) {
reportActionError(pluginId, "\"keystroke\" attribute has invalid value for action with id=" + actionId);
reportActionError(module, "\"keystroke\" attribute has invalid value for action with id=" + actionId);
return;
}
String keymapName = element.attributes.get(KEYMAP_ATTR_NAME);
if (keymapName == null || keymapName.isEmpty()) {
reportActionError(pluginId, "attribute \"keymap\" should be defined");
reportActionError(module, "attribute \"keymap\" should be defined");
return;
}
Keymap keymap = keymapManager.getKeymap(keymapName);
if (keymap == null) {
reportKeymapNotFoundWarning(pluginId, keymapName);
reportKeymapNotFoundWarning(module, keymapName);
return;
}
processRemoveAndReplace(element, actionId, keymap, shortcut);
}
private static void reportActionError(@Nullable PluginId pluginId, @NotNull String message) {
reportActionError(pluginId, message, null);
private static void reportActionError(@NotNull PluginDescriptor module, @NotNull String message) {
reportActionError(module, message, null);
}
private static void reportActionError(@Nullable PluginId pluginId, @NotNull String message, @Nullable Throwable cause) {
if (pluginId != null) {
LOG.error(new PluginException(message, cause, pluginId));
}
else if (cause != null) {
LOG.error(message, cause);
}
else {
LOG.error(message);
}
private static void reportActionError(@NotNull PluginDescriptor module, @NotNull String message, @Nullable Throwable cause) {
LOG.error(new PluginException(message + " (module=" + module + ")", cause, module.getPluginId()));
}
private static void reportKeymapNotFoundWarning(@Nullable PluginId pluginId, @NotNull String keymapName) {
if (DefaultKeymap.Companion.isBundledKeymapHidden(keymapName)) {
return;
private static void reportKeymapNotFoundWarning(@NotNull PluginDescriptor module, @NotNull String keymapName) {
if (!DefaultKeymap.Companion.isBundledKeymapHidden(keymapName)) {
LOG.warn("keymap \"" + keymapName + "\" not found" + " " + module);
}
String message = "keymap \"" + keymapName + "\" not found";
LOG.warn(pluginId == null ? message : new PluginException(message, null, pluginId).getMessage());
}
private static String getPluginInfo(@Nullable PluginId id) {
@@ -397,7 +384,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
@Override
public void addTimerListener(@NotNull final TimerListener listener) {
public void addTimerListener(final @NotNull TimerListener listener) {
if (ApplicationManager.getApplication().isUnitTestMode()) return;
if (myTimer == null) {
myTimer = new MyTimer();
@@ -415,32 +402,28 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
}
@NotNull
public ActionPopupMenu createActionPopupMenu(@NotNull String place, @NotNull ActionGroup group, @Nullable PresentationFactory presentationFactory) {
public @NotNull ActionPopupMenu createActionPopupMenu(@NotNull String place, @NotNull ActionGroup group, @Nullable PresentationFactory presentationFactory) {
return new ActionPopupMenuImpl(place, group, this, presentationFactory);
}
@NotNull
@Override
public ActionPopupMenu createActionPopupMenu(@NotNull String place, @NotNull ActionGroup group) {
public @NotNull ActionPopupMenu createActionPopupMenu(@NotNull String place, @NotNull ActionGroup group) {
return new ActionPopupMenuImpl(place, group, this, null);
}
@NotNull
@Override
public ActionToolbar createActionToolbar(@NotNull final String place, @NotNull final ActionGroup group, final boolean horizontal) {
public @NotNull ActionToolbar createActionToolbar(final @NotNull String place, final @NotNull ActionGroup group, final boolean horizontal) {
return createActionToolbar(place, group, horizontal, false);
}
@NotNull
@Override
public ActionToolbar createActionToolbar(@NotNull String place, @NotNull ActionGroup group, boolean horizontal, boolean decorateButtons) {
public @NotNull ActionToolbar createActionToolbar(@NotNull String place, @NotNull ActionGroup group, boolean horizontal, boolean decorateButtons) {
return new ActionToolbarImpl(place, group, horizontal, decorateButtons);
}
private void registerPluginActions(@NotNull IdeaPluginDescriptorImpl pluginDescriptor, @NotNull KeymapManagerEx keymapManager) {
List<RawPluginDescriptor.ActionDescriptor> elements = pluginDescriptor.actions;
if (elements == null) {
private void registerPluginActions(@NotNull IdeaPluginDescriptorImpl module, @NotNull KeymapManagerEx keymapManager) {
List<RawPluginDescriptor.ActionDescriptor> elements = module.actions;
if (elements.isEmpty()) {
return;
}
@@ -451,7 +434,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
for (RawPluginDescriptor.ActionDescriptor descriptor : elements) {
String bundleName = descriptor.resourceBundle;
if (bundleName == null) {
bundleName = PluginManagerCore.CORE_ID.equals(pluginDescriptor.getPluginId()) ? "messages.ActionsBundle" : pluginDescriptor.getResourceBundleBaseName();
bundleName = PluginManagerCore.CORE_ID.equals(module.getPluginId()) ? "messages.ActionsBundle" : module.getResourceBundleBaseName();
}
XmlElement element = descriptor.element;
@@ -465,41 +448,41 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
else {
try {
bundle = DynamicBundle.INSTANCE.getResourceBundle(bundleName, pluginDescriptor.getPluginClassLoader());
bundle = DynamicBundle.INSTANCE.getResourceBundle(bundleName, module.getPluginClassLoader());
lastBundle = bundle;
lastBundleName = bundleName;
}
catch (MissingResourceException e) {
LOG.error(new PluginException("Cannot resolve resource bundle " + bundleName + " for action " + element, e, pluginDescriptor.getPluginId()));
LOG.error(new PluginException("Cannot resolve resource bundle " + bundleName + " for action " + element, e, module.getPluginId()));
bundle = null;
}
}
switch (descriptor.name) {
case ACTION_ELEMENT_NAME:
processActionElement(element, pluginDescriptor, bundle, keymapManager, pluginDescriptor.getPluginClassLoader());
processActionElement(element, module, bundle, keymapManager, module.getPluginClassLoader());
break;
case GROUP_ELEMENT_NAME:
processGroupElement(element, pluginDescriptor, bundle, keymapManager, pluginDescriptor.getPluginClassLoader());
processGroupElement(element, module, bundle, keymapManager, module.getPluginClassLoader());
break;
case SEPARATOR_ELEMENT_NAME:
processSeparatorNode(null, element, pluginDescriptor.getPluginId(), bundle);
processSeparatorNode(null, element, module, bundle);
break;
case REFERENCE_ELEMENT_NAME:
processReferenceNode(element, pluginDescriptor.getPluginId(), bundle);
processReferenceNode(element, module, bundle);
break;
case "unregister":
processUnregisterNode(element, pluginDescriptor.getPluginId());
processUnregisterNode(element, module);
break;
case "prohibit":
processProhibitNode(element, pluginDescriptor.getPluginId());
processProhibitNode(element, module);
break;
default:
LOG.error(new PluginException("Unexpected name of element" + descriptor.name, pluginDescriptor.getPluginId()));
LOG.error(new PluginException("Unexpected name of element" + descriptor.name, module.getPluginId()));
break;
}
}
StartUpMeasurer.addPluginCost(pluginDescriptor.getPluginId().getIdString(), "Actions", StartUpMeasurer.getCurrentTime() - startTime);
StartUpMeasurer.addPluginCost(module.getPluginId().getIdString(), "Actions", StartUpMeasurer.getCurrentTime() - startTime);
}
@Override
@@ -585,9 +568,8 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
return getActionImpl(actionId, true) instanceof ActionGroup;
}
@NotNull
@Override
public JComponent createButtonToolbar(@NotNull final String actionPlace, @NotNull final ActionGroup messageActionGroup) {
public @NotNull JComponent createButtonToolbar(final @NotNull String actionPlace, final @NotNull ActionGroup messageActionGroup) {
//noinspection deprecation
return new ButtonToolbarImpl(actionPlace, messageActionGroup);
}
@@ -601,13 +583,13 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
* @return instance of ActionGroup or ActionStub. The method never returns real subclasses of {@code AnAction}.
*/
private @Nullable AnAction processActionElement(@NotNull XmlElement element,
@NotNull IdeaPluginDescriptorImpl plugin,
@NotNull IdeaPluginDescriptorImpl module,
@Nullable ResourceBundle bundle,
@NotNull KeymapManager keymapManager,
@NotNull ClassLoader classLoader) {
String className = element.attributes.get(CLASS_ATTR_NAME);
if (className == null || className.isEmpty()) {
reportActionError(plugin.getPluginId(), "action element should have specified \"class\" attribute");
reportActionError(module, "action element should have specified \"class\" attribute");
return null;
}
@@ -631,11 +613,11 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
//noinspection HardCodedStringLiteral
String descriptionValue = element.attributes.get(DESCRIPTION);
ActionStub stub = new ActionStub(className, id, plugin, iconPath, ProjectType.create(projectType), () -> {
ActionStub stub = new ActionStub(className, id, module, iconPath, ProjectType.create(projectType), () -> {
Supplier<String> text = () -> computeActionText(bundle, id, ACTION_ELEMENT_NAME, textValue, classLoader);
if (text.get() == null) {
reportActionError(plugin.getPluginId(), "'text' attribute is mandatory (actionId=" + id +
", plugin=" + plugin + ")");
LOG.error(new PluginException("'text' attribute is mandatory (actionId=" + id + ", module=" + " " + module + ")",
module.getPluginId()));
}
Presentation presentation = new Presentation();
@@ -653,25 +635,25 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
for (XmlElement e : element.children) {
switch (e.name) {
case ADD_TO_GROUP_ELEMENT_NAME:
processAddToGroupNode(stub, e, plugin.getPluginId(), isSecondary(e));
processAddToGroupNode(stub, e, module, isSecondary(e));
break;
case "keyboard-shortcut":
processKeyboardShortcutNode(e, id, plugin.getPluginId(), keymapManager);
processKeyboardShortcutNode(e, id, module, keymapManager);
break;
case "mouse-shortcut":
processMouseShortcutNode(e, id, plugin.getPluginId(), keymapManager);
processMouseShortcutNode(e, id, module, keymapManager);
break;
case "abbreviation":
processAbbreviationNode(e, id);
break;
case OVERRIDE_TEXT_ELEMENT_NAME:
processOverrideTextNode(stub, stub.getId(), e, plugin.getPluginId(), bundle);
processOverrideTextNode(stub, stub.getId(), e, module, bundle);
break;
case SYNONYM_ELEMENT_NAME:
processSynonymNode(stub, e, plugin.getPluginId(), bundle);
processSynonymNode(stub, e, module, bundle);
break;
default:
reportActionError(plugin.getPluginId(), "unexpected name of element \"" + e.name + "\"");
reportActionError(module, "unexpected name of element \"" + e.name + "\"");
return null;
}
}
@@ -681,7 +663,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
keymapManager.bindShortcuts(shortcutOfActionId, id);
}
registerOrReplaceActionInner(element, id, stub, plugin);
registerOrReplaceActionInner(element, id, stub, module);
return stub;
}
@@ -718,7 +700,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
private AnAction processGroupElement(@NotNull XmlElement element,
@NotNull IdeaPluginDescriptorImpl plugin,
@NotNull IdeaPluginDescriptorImpl module,
@Nullable ResourceBundle bundle,
@NotNull KeymapManagerEx keymapManager,
@NotNull ClassLoader classLoader) {
@@ -733,7 +715,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
try {
String id = element.attributes.get(ID_ATTR_NAME);
if (id != null && id.isEmpty()) {
reportActionError(plugin.getPluginId(), "ID of the group cannot be an empty string");
reportActionError(module, "ID of the group cannot be an empty string");
return null;
}
synchronized (myLock) {
@@ -751,14 +733,14 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
group = new DefaultCompactActionGroup();
}
else if (id == null) {
Object obj = ApplicationManager.getApplication().instantiateClass(className, plugin);
Object obj = ApplicationManager.getApplication().instantiateClass(className, module);
if (!(obj instanceof ActionGroup)) {
reportActionError(plugin.getPluginId(), "class with name \"" + className + "\" should be instance of " + ActionGroup.class.getName());
reportActionError(module, "class with name \"" + className + "\" should be instance of " + ActionGroup.class.getName());
return null;
}
if (element.children.size() != element.count(ADD_TO_GROUP_ELEMENT_NAME)) { //
if (!(obj instanceof DefaultActionGroup)) {
reportActionError(plugin.getPluginId(), "class with name \"" + className + "\" should be instance of " + DefaultActionGroup.class.getName() +
reportActionError(module, "class with name \"" + className + "\" should be instance of " + DefaultActionGroup.class.getName() +
" because there are children specified");
return null;
}
@@ -767,7 +749,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
group = (ActionGroup)obj;
}
else {
group = new ActionGroupStub(id, className, plugin);
group = new ActionGroupStub(id, className, module);
customClass = true;
}
// read ID and register loaded group
@@ -780,7 +762,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
id = "<anonymous-group-" + myAnonymousGroupIdCounter++ + ">";
}
registerOrReplaceActionInner(element, id, group, plugin);
registerOrReplaceActionInner(element, id, group, module);
Presentation presentation = group.getTemplatePresentation();
String finalId = id;
@@ -813,7 +795,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
((ActionGroupStub)group).setIconPath(iconPath);
}
else if (iconPath != null) {
setIconFromClass(null, plugin, iconPath, presentation);
setIconFromClass(null, module, iconPath, presentation);
}
// popup
@@ -839,37 +821,37 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
for (XmlElement child : element.children) {
switch (child.name) {
case ACTION_ELEMENT_NAME: {
AnAction action = processActionElement(child, plugin, bundle, keymapManager, classLoader);
AnAction action = processActionElement(child, module, bundle, keymapManager, classLoader);
if (action != null) {
addToGroupInner(group, action, Constraints.LAST, isSecondary(child));
}
break;
}
case SEPARATOR_ELEMENT_NAME:
processSeparatorNode((DefaultActionGroup)group, child, plugin.getPluginId(), bundle);
processSeparatorNode((DefaultActionGroup)group, child, module, bundle);
break;
case GROUP_ELEMENT_NAME: {
AnAction action = processGroupElement(child, plugin, bundle, keymapManager, classLoader);
AnAction action = processGroupElement(child, module, bundle, keymapManager, classLoader);
if (action != null) {
addToGroupInner(group, action, Constraints.LAST, false);
}
break;
}
case ADD_TO_GROUP_ELEMENT_NAME:
processAddToGroupNode(group, child, plugin.getPluginId(), isSecondary(child));
processAddToGroupNode(group, child, module, isSecondary(child));
break;
case REFERENCE_ELEMENT_NAME: {
AnAction action = processReferenceElement(child, plugin.getPluginId());
AnAction action = processReferenceElement(child, module);
if (action != null) {
addToGroupInner(group, action, Constraints.LAST, isSecondary(child));
}
break;
}
case OVERRIDE_TEXT_ELEMENT_NAME:
processOverrideTextNode(group, id, child, plugin.getPluginId(), bundle);
processOverrideTextNode(group, id, child, module, bundle);
break;
default:
reportActionError(plugin.getPluginId(), "unexpected name of element \"" + child.name + "\n");
reportActionError(module, "unexpected name of element \"" + child.name + "\n");
return null;
}
}
@@ -877,23 +859,23 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
catch (Exception e) {
String message = "cannot create class \"" + className + "\"";
reportActionError(plugin.getPluginId(), message, e);
reportActionError(module, message, e);
return null;
}
}
private void processReferenceNode(@NotNull XmlElement element, @Nullable PluginId pluginId, @Nullable ResourceBundle bundle) {
AnAction action = processReferenceElement(element, pluginId);
private void processReferenceNode(@NotNull XmlElement element, @NotNull IdeaPluginDescriptor module, @Nullable ResourceBundle bundle) {
AnAction action = processReferenceElement(element, module);
if (action == null) {
return;
}
for (XmlElement child : element.children) {
if (ADD_TO_GROUP_ELEMENT_NAME.equals(child.name)) {
processAddToGroupNode(action, child, pluginId, isSecondary(child));
processAddToGroupNode(action, child, module, isSecondary(child));
}
else if (SYNONYM_ELEMENT_NAME.equals(child.name)) {
processSynonymNode(action, child, pluginId, bundle);
processSynonymNode(action, child, module, bundle);
}
}
}
@@ -901,25 +883,26 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
/**
* @param element description of link
*/
private void processAddToGroupNode(AnAction action, XmlElement element, PluginId pluginId, boolean secondary) {
private void processAddToGroupNode(AnAction action, XmlElement element, @NotNull IdeaPluginDescriptor module, boolean secondary) {
String name = action instanceof ActionStub ? ((ActionStub)action).getClassName() : action.getClass().getName();
String id = action instanceof ActionStub ? ((ActionStub)action).getId() : actionToId.get(action);
String actionName = name + " (" + id + ")";
// parent group
final AnAction parentGroup = getParentGroup(element.attributes.get(GROUP_ID_ATTR_NAME), actionName, pluginId);
final AnAction parentGroup = getParentGroup(element.attributes.get(GROUP_ID_ATTR_NAME), actionName, module);
if (parentGroup == null) {
return;
}
// anchor attribute
final Anchor anchor = parseAnchor(element.attributes.get("anchor"), actionName, pluginId);
final Anchor anchor = parseAnchor(element.attributes.get("anchor"), actionName, module);
if (anchor == null) {
return;
}
final String relativeToActionId = element.attributes.get("relative-to-action");
if (!checkRelativeToAction(relativeToActionId, anchor, actionName, pluginId)) {
if ((Anchor.BEFORE == anchor || Anchor.AFTER == anchor) && relativeToActionId == null) {
reportActionError(module, actionName + ": \"relative-to-action\" cannot be null if anchor is \"after\" or \"before\"");
return;
}
addToGroupInner(parentGroup, action, new Constraints(anchor, relativeToActionId), secondary);
@@ -931,32 +914,34 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
idToGroupId.putValue(actionId, actionToId.get(group));
}
@Nullable
public DefaultActionGroup getParentGroup(final String groupId,
@Nullable final String actionName,
@Nullable final PluginId pluginId) {
public @Nullable DefaultActionGroup getParentGroup(String groupId,
@Nullable String actionName,
@NotNull IdeaPluginDescriptor module) {
if (groupId == null || groupId.isEmpty()) {
reportActionError(pluginId, actionName + ": attribute \"group-id\" should be defined");
reportActionError(module, actionName + ": attribute \"group-id\" should be defined");
return null;
}
AnAction parentGroup = getActionImpl(groupId, true);
if (parentGroup == null) {
reportActionError(pluginId, actionName + ": group with id \"" + groupId + "\" isn't registered; action will be added to the \"Other\" group", null);
reportActionError(module, actionName + ": group with id \"" + groupId + "\" isn't registered; action will be added to the \"Other\" group", null);
parentGroup = getActionImpl(IdeActions.GROUP_OTHER_MENU, true);
}
if (!(parentGroup instanceof DefaultActionGroup)) {
reportActionError(pluginId, actionName + ": group with id \"" + groupId + "\" should be instance of " + DefaultActionGroup.class.getName() +
reportActionError(module, actionName + ": group with id \"" + groupId + "\" should be instance of " + DefaultActionGroup.class.getName() +
" but was " + (parentGroup != null ? parentGroup.getClass() : "[null]"));
return null;
}
return (DefaultActionGroup)parentGroup;
}
private static void processOverrideTextNode(AnAction action, String id, XmlElement element, PluginId pluginId,
private static void processOverrideTextNode(AnAction action,
String id,
XmlElement element,
@NotNull IdeaPluginDescriptor module,
@Nullable ResourceBundle bundle) {
String place = element.attributes.get("place");
if (place == null) {
reportActionError(pluginId, id + ": override-text specified without place");
reportActionError(module, id + ": override-text specified without place");
return;
}
String useTextOfPlace = element.attributes.get("use-text-of-place");
@@ -976,7 +961,10 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
}
private static void processSynonymNode(AnAction action, XmlElement element, PluginId pluginId, @Nullable ResourceBundle bundle) {
private static void processSynonymNode(AnAction action,
XmlElement element,
@NotNull IdeaPluginDescriptor module,
@Nullable ResourceBundle bundle) {
@SuppressWarnings("HardCodedStringLiteral")
String text = element.attributes.get(TEXT_ATTR_NAME);
if (text != null && !text.isEmpty()) {
@@ -988,7 +976,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
action.addSynonym(() -> BundleBase.message(bundle, key));
}
else {
reportActionError(pluginId, "Can't process synonym: neither text nor resource bundle key is specified");
reportActionError(module, "Can't process synonym: neither text nor resource bundle key is specified");
}
}
}
@@ -998,7 +986,10 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
* case separator will be added to group described in the <add-to-group ....> sub element.
* @param element XML element which represent separator.
*/
private void processSeparatorNode(@Nullable DefaultActionGroup parentGroup, @NotNull XmlElement element, PluginId pluginId, @Nullable ResourceBundle bundle) {
private void processSeparatorNode(@Nullable DefaultActionGroup parentGroup,
@NotNull XmlElement element,
@NotNull IdeaPluginDescriptor module,
@Nullable ResourceBundle bundle) {
//noinspection HardCodedStringLiteral
String text = element.attributes.get(TEXT_ATTR_NAME);
String key = element.attributes.get(KEY_ATTR_NAME);
@@ -1010,36 +1001,35 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
// try to find inner <add-to-parent...> tag
for (XmlElement child : element.children) {
if (ADD_TO_GROUP_ELEMENT_NAME.equals(child.name)) {
processAddToGroupNode(separator, child, pluginId, isSecondary(child));
processAddToGroupNode(separator, child, module, isSecondary(child));
}
}
}
@NotNull
private static Separator createSeparator(@Nullable ResourceBundle bundle, @NotNull String key) {
private static @NotNull Separator createSeparator(@Nullable ResourceBundle bundle, @NotNull String key) {
String text = bundle != null ? AbstractBundle.messageOrNull(bundle, key) : null;
return text != null ? new Separator(text) : Separator.getInstance();
}
private void processProhibitNode(XmlElement element, PluginId pluginId) {
private void processProhibitNode(XmlElement element, @NotNull IdeaPluginDescriptor module) {
String id = element.attributes.get(ID_ATTR_NAME);
if (id == null) {
reportActionError(pluginId, "'id' attribute is required for 'unregister' elements");
reportActionError(module, "'id' attribute is required for 'unregister' elements");
return;
}
prohibitAction(id);
}
private void processUnregisterNode(XmlElement element, PluginId pluginId) {
private void processUnregisterNode(XmlElement element, @NotNull IdeaPluginDescriptor module) {
String id = element.attributes.get(ID_ATTR_NAME);
if (id == null) {
reportActionError(pluginId, "'id' attribute is required for 'unregister' elements");
reportActionError(module, "'id' attribute is required for 'unregister' elements");
return;
}
AnAction action = getAction(id);
if (action == null) {
reportActionError(pluginId, "Trying to unregister non-existing action " + id);
reportActionError(module, "Trying to unregister non-existing action " + id);
return;
}
@@ -1049,16 +1039,16 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
private static void processKeyboardShortcutNode(XmlElement element,
String actionId,
PluginId pluginId,
@NotNull PluginDescriptor module,
@NotNull KeymapManager keymapManager) {
String firstStrokeString = element.attributes.get("first-keystroke");
if (firstStrokeString == null) {
reportActionError(pluginId, "\"first-keystroke\" attribute must be specified for action with id=" + actionId);
reportActionError(module, "\"first-keystroke\" attribute must be specified for action with id=" + actionId);
return;
}
KeyStroke firstKeyStroke = getKeyStroke(firstStrokeString);
if (firstKeyStroke == null) {
reportActionError(pluginId, "\"first-keystroke\" attribute has invalid value for action with id=" + actionId);
reportActionError(module, "\"first-keystroke\" attribute has invalid value for action with id=" + actionId);
return;
}
@@ -1067,19 +1057,19 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
if (secondStrokeString != null) {
secondKeyStroke = getKeyStroke(secondStrokeString);
if (secondKeyStroke == null) {
reportActionError(pluginId, "\"second-keystroke\" attribute has invalid value for action with id=" + actionId);
reportActionError(module, "\"second-keystroke\" attribute has invalid value for action with id=" + actionId);
return;
}
}
String keymapName = element.attributes.get(KEYMAP_ATTR_NAME);
if (keymapName == null || keymapName.trim().isEmpty()) {
reportActionError(pluginId, "attribute \"keymap\" should be defined");
reportActionError(module, "attribute \"keymap\" should be defined");
return;
}
Keymap keymap = keymapManager.getKeymap(keymapName);
if (keymap == null) {
reportKeymapNotFoundWarning(pluginId, keymapName);
reportKeymapNotFoundWarning(module, keymapName);
return;
}
processRemoveAndReplace(element, actionId, keymap, new KeyboardShortcut(firstKeyStroke, secondKeyStroke));
@@ -1099,10 +1089,10 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
}
private @Nullable AnAction processReferenceElement(XmlElement element, PluginId pluginId) {
private @Nullable AnAction processReferenceElement(@NotNull XmlElement element, @NotNull IdeaPluginDescriptor module) {
String ref = getReferenceActionId(element);
if (ref == null || ref.isEmpty()) {
reportActionError(pluginId, "ID of reference element should be defined", null);
reportActionError(module, "ID of reference element should be defined", null);
return null;
}
@@ -1115,7 +1105,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
AnAction action = getActionImpl(ref, true);
if (action == null) {
if (!myNotRegisteredInternalActionIds.contains(ref)) {
reportActionError(pluginId, "action specified by reference isn't registered (ID=" + ref + ")", null);
reportActionError(module, "action specified by reference isn't registered (ID=" + ref + ")", null);
}
return null;
}
@@ -1132,19 +1122,15 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
@ApiStatus.Internal
public static @Nullable String checkUnloadActions(PluginId pluginId, @NotNull IdeaPluginDescriptorImpl pluginDescriptor) {
List<RawPluginDescriptor.ActionDescriptor> descriptors = pluginDescriptor.actions;
if (descriptors == null) {
return null;
}
public static @Nullable String checkUnloadActions(@NotNull IdeaPluginDescriptorImpl module) {
List<RawPluginDescriptor.ActionDescriptor> descriptors = module.actions;
for (RawPluginDescriptor.ActionDescriptor descriptor : descriptors) {
XmlElement element = descriptor.element;
String elementName = descriptor.name;
if (!elementName.equals(ACTION_ELEMENT_NAME) &&
!(elementName.equals(GROUP_ELEMENT_NAME) && canUnloadGroup(element)) &&
!elementName.equals(REFERENCE_ELEMENT_NAME)) {
return "Plugin " + pluginId + " is not unload-safe because of action element " + elementName;
return "Plugin " + module + " is not unload-safe because of action element " + elementName;
}
}
return null;
@@ -1162,12 +1148,8 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
return true;
}
public void unloadActions(@NotNull IdeaPluginDescriptorImpl pluginDescriptor) {
List<RawPluginDescriptor.ActionDescriptor> descriptors = pluginDescriptor.actions;
if (descriptors == null) {
return;
}
public void unloadActions(@NotNull IdeaPluginDescriptorImpl module) {
List<RawPluginDescriptor.ActionDescriptor> descriptors = module.actions;
for (int i = descriptors.size() - 1; i >= 0; i--) {
RawPluginDescriptor.ActionDescriptor descriptor = descriptors.get(i);
XmlElement element = descriptor.element;
@@ -1179,9 +1161,11 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
unloadGroupElement(element);
break;
case REFERENCE_ELEMENT_NAME:
PluginId pluginId = pluginDescriptor.getPluginId();
AnAction action = processReferenceElement(element, pluginId);
if (action == null) return;
AnAction action = processReferenceElement(element, module);
if (action == null) {
return;
}
String actionId = getReferenceActionId(element);
for (XmlElement child : element.children) {
@@ -1190,8 +1174,10 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
String groupId = child.attributes.get(GROUP_ID_ATTR_NAME);
final DefaultActionGroup parentGroup = getParentGroup(groupId, actionId, pluginId);
if (parentGroup == null) return;
DefaultActionGroup parentGroup = getParentGroup(groupId, actionId, module);
if (parentGroup == null) {
return;
}
parentGroup.remove(action);
idToGroupId.remove(actionId, groupId);
}
@@ -1235,16 +1221,25 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
if (myProhibitedActionIds.contains(actionId)) {
return;
}
if (addToMap(actionId, action, ProjectType.create(projectType)) == null) {
reportActionIdCollision(actionId, action, pluginId);
return;
}
if (actionToId.containsKey(action)) {
reportActionError(pluginId,
"ID \"" + actionToId.get(action) + "\" is already taken by action \"" + action + "\"" + getPluginInfo(pluginId) +
". ID \"" + actionId + "\" cannot be registered for the same action");
IdeaPluginDescriptorImpl module = pluginId == null ? null : PluginManagerCore.getPluginSet().findEnabledPlugin(pluginId);
String message = "ID \"" + actionToId.get(action) + "\" is already taken by action \"" + action + "\". " +
"ID \"" + actionId + "\" cannot be registered for the same action";
if (module == null) {
LOG.error(new PluginException(message + " " + pluginId, null, pluginId));
}
else {
reportActionError(module, message);
}
return;
}
action.registerCustomShortcutSet(new ProxyShortcutSet(actionId), null);
idToIndex.put(actionId, myRegisteredActionsCount++);
actionToId.put(action, actionId);
@@ -1267,9 +1262,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
}
private @Nullable AnAction addToMap(@NotNull String actionId,
@NotNull AnAction action,
@Nullable ProjectType projectType) {
private @Nullable AnAction addToMap(@NotNull String actionId, @NotNull AnAction action, @Nullable ProjectType projectType) {
AnAction chameleonAction = idToAction.computeIfPresent(actionId, (__, old) -> {
return old instanceof ChameleonAction ? old : new ChameleonAction(old, projectType);
});
@@ -1283,9 +1276,7 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
}
private void reportActionIdCollision(@NotNull String actionId,
@NotNull AnAction action,
@Nullable PluginId pluginId) {
private void reportActionIdCollision(@NotNull String actionId, @NotNull AnAction action, @Nullable PluginId pluginId) {
String oldPluginInfo = pluginToId.entrySet()
.stream()
.filter(entry -> entry.getValue().contains(actionId))
@@ -1293,9 +1284,14 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
.map(ActionManagerImpl::getPluginInfo)
.collect(Collectors.joining(","));
reportActionError(pluginId,
"ID \"" + actionId + "\" is already taken by action \"" + idToAction.get(actionId) + "\"" + oldPluginInfo +
". Action \"" + action + "\"" + getPluginInfo(pluginId) + " cannot use the same ID");
String message = ("ID \"" + actionId + "\" is already taken by action \"" + idToAction.get(actionId) + "\"" + oldPluginInfo +
". Action \"" + action + "\"" + " cannot use the same ID") + " " + pluginId;
if (pluginId == null) {
LOG.error(message);
}
else {
LOG.error(new PluginException(message, null, pluginId));
}
}
@Override
@@ -1385,9 +1381,8 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
}
@NotNull
@Override
public Comparator<String> getRegistrationOrderComparator() {
public @NotNull Comparator<String> getRegistrationOrderComparator() {
return Comparator.comparingInt(idToIndex::getInt);
}
@@ -1592,13 +1587,12 @@ public class ActionManagerImpl extends ActionManagerEx implements Disposable {
}
}
@NotNull
@Override
public ActionCallback tryToExecute(@NotNull AnAction action,
@Nullable InputEvent inputEvent,
@Nullable Component contextComponent,
@Nullable String place,
boolean now) {
public @NotNull ActionCallback tryToExecute(@NotNull AnAction action,
@Nullable InputEvent inputEvent,
@Nullable Component contextComponent,
@Nullable String place,
boolean now) {
ApplicationManager.getApplication().assertIsDispatchThread();
ActionCallback result = new ActionCallback();
@@ -48,13 +48,13 @@ import com.intellij.util.containers.Stack;
import com.intellij.util.messages.Topic;
import com.intellij.util.ui.EDT;
import com.intellij.util.ui.EdtInvocationManager;
import kotlin.sequences.Sequence;
import org.jetbrains.annotations.*;
import sun.awt.AWTAccessor;
import sun.awt.AWTAutoShutdown;
import javax.swing.*;
import java.awt.*;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -352,10 +352,13 @@ public class ApplicationImpl extends ClientAwareComponentManager implements Appl
@Override
public final void load() {
List<IdeaPluginDescriptorImpl> plugins = PluginManagerCore.getLoadedPlugins(null);
registerComponents(plugins, this, null, null);
PluginManagerCore.scheduleDescriptorLoading();
Sequence<IdeaPluginDescriptorImpl> modules =
PluginManagerCore.initPlugins(ApplicationImpl.class.getClassLoader()).join().getEnabledModules();
registerComponents(modules, this, null, null);
ApplicationLoader.initConfigurationStore(this);
preloadServices(plugins, "", false).getSecond().join();
preloadServices(modules, "", false).getSecond().join();
loadComponents(null);
ForkJoinTask.invokeAll(ApplicationLoader.callAppInitialized(this));
}
@@ -1022,6 +1025,7 @@ public class ApplicationImpl extends ClientAwareComponentManager implements Appl
new Attachment("threadDump.txt", ThreadDumper.dumpThreadsToString()));
}
@Override
public void assertIsWriteThread() {
if (isWriteThread()) return;
if (ShutDownTracker.isShutdownHookRunning()) return;
@@ -1,12 +1,12 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.client
import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl
import com.intellij.openapi.application.Application
import com.intellij.openapi.components.ServiceDescriptor
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.serviceContainer.ComponentManagerImpl
import com.intellij.serviceContainer.PrecomputedExtensionModel
import com.intellij.serviceContainer.throwAlreadyDisposedError
@@ -56,15 +56,15 @@ abstract class ClientAwareComponentManager @JvmOverloads constructor(
return session?.doGetService(serviceClass, createIfNeeded, false)
}
override fun registerComponents(plugins: List<IdeaPluginDescriptorImpl>,
override fun registerComponents(modules: Sequence<IdeaPluginDescriptorImpl>,
app: Application?,
precomputedExtensionModel: PrecomputedExtensionModel?,
listenerCallbacks: List<Runnable>?) {
super.registerComponents(plugins, app, precomputedExtensionModel, listenerCallbacks)
super.registerComponents(modules, app, precomputedExtensionModel, listenerCallbacks)
val sessionsManager = super.getService(ClientSessionsManager::class.java)!!
for (session in sessionsManager.getSessions(true)) {
(session as? ClientSessionImpl)?.registerComponents(plugins, app, precomputedExtensionModel, listenerCallbacks)
(session as? ClientSessionImpl)?.registerComponents(modules, app, precomputedExtensionModel, listenerCallbacks)
}
}
@@ -77,17 +77,17 @@ abstract class ClientAwareComponentManager @JvmOverloads constructor(
}
}
override fun preloadServices(plugins: List<IdeaPluginDescriptorImpl>,
override fun preloadServices(modules: Sequence<IdeaPluginDescriptorImpl>,
activityPrefix: String,
onlyIfAwait: Boolean): Pair<CompletableFuture<Void?>, CompletableFuture<Void?>> {
val (asyncPreloadFuture, syncPreloadFuture) = super.preloadServices(plugins, activityPrefix, onlyIfAwait)
val (asyncPreloadFuture, syncPreloadFuture) = super.preloadServices(modules, activityPrefix, onlyIfAwait)
val sessionsManager = super.getService(ClientSessionsManager::class.java)!!
val asyncPreloadFutures = mutableListOf<CompletableFuture<Void?>>()
val syncPreloadFutures = mutableListOf<CompletableFuture<Void?>>()
for (session in sessionsManager.getSessions(true)) {
session as? ClientSessionImpl ?: continue
val (sessionAsyncPreloadFuture, sessionSyncPreloadFuture) = session.preloadServices(plugins, activityPrefix, onlyIfAwait)
val (sessionAsyncPreloadFuture, sessionSyncPreloadFuture) = session.preloadServices(modules, activityPrefix, onlyIfAwait)
asyncPreloadFutures.add(sessionAsyncPreloadFuture)
syncPreloadFutures.add(sessionSyncPreloadFuture)
}
@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.client
import com.intellij.codeWithMe.ClientId
@@ -9,7 +9,6 @@ import com.intellij.ide.plugins.IdeaPluginDescriptorImpl
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.idea.preloadServices
import com.intellij.openapi.application.Application
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.impl.ApplicationImpl
import com.intellij.openapi.components.ComponentConfig
import com.intellij.openapi.components.PersistentStateComponent
@@ -21,7 +20,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.project.impl.ProjectExImpl
import com.intellij.serviceContainer.ComponentManagerImpl
import com.intellij.serviceContainer.PrecomputedExtensionModel
import com.intellij.serviceContainer.executeRegisterTask
import com.intellij.serviceContainer.executeRegisterTaskForOldContent
import com.intellij.util.messages.MessageBus
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Path
@@ -34,7 +33,6 @@ abstract class ClientSessionImpl(
final override val clientId: ClientId,
protected val sharedComponentManager: ClientAwareComponentManager
) : ComponentManagerImpl(null, false), ClientSession {
override val isLocal = clientId.isLocal
override val isLightServiceSupported = false
@@ -45,18 +43,13 @@ abstract class ClientSessionImpl(
}
fun registerServices() {
registerComponents(
PluginManagerCore.getLoadedPlugins(null),
ApplicationManager.getApplication(),
null,
null
)
registerComponents()
}
fun preloadServices() {
assert(containerState.get() == ContainerState.PRE_INIT)
preloadServices(
PluginManagerCore.getLoadedPlugins(null),
PluginManagerCore.getPluginSet().getEnabledModules(),
container = this,
activityPrefix = "client ",
onlyIfAwait = false
@@ -79,14 +72,14 @@ abstract class ClientSessionImpl(
/**
* only per-client services are supported (no components, extensions, listeners)
*/
override fun registerComponents(plugins: List<IdeaPluginDescriptorImpl>,
override fun registerComponents(modules: Sequence<IdeaPluginDescriptorImpl>,
app: Application?,
precomputedExtensionModel: PrecomputedExtensionModel?,
listenerCallbacks: List<Runnable>?) {
for (mainPlugin in plugins) {
executeRegisterTask(mainPlugin) { pluginDescriptor ->
val containerDescriptor = getContainerDescriptor(pluginDescriptor)
registerServices(containerDescriptor.services, pluginDescriptor)
for (rootModule in modules) {
registerServices(getContainerDescriptor(rootModule).services, rootModule)
executeRegisterTaskForOldContent(rootModule) { module ->
registerServices(getContainerDescriptor(module).services, module)
}
}
}
@@ -108,7 +101,7 @@ abstract class ClientSessionImpl(
val sessionsManager = sharedComponentManager.getService(ClientSessionsManager::class.java)
val localSession = sessionsManager?.getSession(ClientId.localId) as? ClientSessionImpl
if (localSession?.doGetService(serviceClass, createIfNeeded, fallbackToShared = false) != null) {
if (localSession?.doGetService(serviceClass, createIfNeeded = true, fallbackToShared = false) != null) {
LOG.error("$serviceClass is registered only for client=\"local\", " +
"please provide a guest-specific implementation, or change to client=\"all\"")
return null
@@ -200,7 +193,6 @@ open class ClientProjectSessionImpl(
clientId: ClientId,
final override val project: ProjectExImpl,
) : ClientSessionImpl(clientId, project), ClientProjectSession {
override fun getContainerDescriptor(pluginDescriptor: IdeaPluginDescriptorImpl): ContainerDescriptor {
return pluginDescriptor.projectContainerDescriptor
}
@@ -5,7 +5,6 @@ import com.intellij.configurationStore.StoreUtil;
import com.intellij.diagnostic.ActivityCategory;
import com.intellij.ide.plugins.ContainerDescriptor;
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.components.BaseComponent;
@@ -27,7 +26,6 @@ import com.intellij.util.messages.MessageBus;
import org.jetbrains.annotations.*;
import org.picocontainer.PicoContainer;
import java.util.List;
import java.util.Map;
/**
@@ -312,9 +310,7 @@ final class DefaultProjectImpl extends ComponentManagerImpl implements Project {
public void init() {
// do not leak internal delegate, use DefaultProject everywhere instead
registerServiceInstance(Project.class, actualContainerInstance, ComponentManagerImpl.fakeCorePluginDescriptor);
//noinspection unchecked
registerComponents((List<IdeaPluginDescriptorImpl>)PluginManagerCore.getLoadedPlugins(), ApplicationManager.getApplication(), null, null);
registerComponents();
createComponents(null);
Disposer.register(actualContainerInstance, this);
}
@@ -172,7 +172,7 @@ open class ProjectExImpl(filePath: Path, projectName: String?) : ProjectImpl(App
// for light projects, preload only services that are essential
// ("await" means "project component loading activity is completed only when all such services are completed")
val servicePreloadingFuture = if (preloadServices) {
preloadServices(PluginManagerCore.getLoadedPlugins(null), container = this, activityPrefix = "project ", onlyIfAwait = isLight)
preloadServices(PluginManagerCore.getPluginSet().getEnabledModules(), container = this, activityPrefix = "project ", onlyIfAwait = isLight)
}
else {
null
@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:JvmName("ProjectLoadHelper")
@file:ApiStatus.Internal
package com.intellij.openapi.project.impl
@@ -22,10 +22,7 @@ import org.jetbrains.annotations.ApiStatus
internal fun registerComponents(project: ProjectImpl) {
var activity = createActivity(project) { "project ${Activities.REGISTER_COMPONENTS_SUFFIX}" }
// at this point of time plugins are already loaded by application - no need to pass indicator to getLoadedPlugins call
project.registerComponents(plugins = PluginManagerCore.getLoadedPlugins(null),
app = ApplicationManager.getApplication(),
precomputedExtensionModel = null,
listenerCallbacks = null)
project.registerComponents()
activity = activity?.endAndStart("projectComponentRegistered")
runOnlyCorePluginExtensions(
@@ -0,0 +1,4 @@
+ com.intellij
+ com.jetbrains.restClient
+ com.intellij.microservices.ui
+ com.jetbrains.restClient | intellij.restClient/microservicesUI
@@ -0,0 +1,35 @@
<root>
<idea-plugin package="com.intellij.core">
<id>com.intellij</id>
<module value="com.intellij.modules.microservices"/>
</idea-plugin>
<idea-plugin package="com.intellij.microservices.ui">
<id>com.intellij.microservices.ui</id>
<name>Endpoints</name>
<vendor>JetBrains</vendor>
<category>Microservices</category>
<dependencies>
<plugin id="com.intellij.modules.microservices"/>
</dependencies>
</idea-plugin>
<idea-plugin package="com.intellij.httpClient">
<id>com.jetbrains.restClient</id>
<name>HTTP Client</name>
<category>Other Tools</category>
<vendor>JetBrains</vendor>
<dependencies>
<plugin id="com.intellij.modules.microservices"/>
</dependencies>
<content>
<module name="intellij.restClient/microservicesUI"/>
</content>
</idea-plugin>
<idea-plugin package="com.intellij.httpClient.microservices" moduleFile="intellij.restClient.microservicesUI.xml">
<dependencies>
<plugin id="com.intellij.microservices.ui"/>
</dependencies>
</idea-plugin>
</root>
@@ -1,40 +1,30 @@
+ com.intellij
cycle3
cycle1
cycle2
cycle0
cycle4
cycle5
+ duplicate
+ duplicate0
+ incompatible_with_unknown_module
missing1
missing2
+ incompatible_with_disabled_module
missing3
+ optional_config_cycle0
+ optional_config_cycle1
+ optional_config_cycle2
optional_cycle0
optional_cycle1
optional_cycle2
+ plugin3
+ optional_config_cycle1
+ missing0
+ incompatible_with_unknown_module
+ incompatible_with_disabled_module
+ duplicate0
+ duplicate
+ plugin0
incompatible_with_enabled_module
+ plugin1
+ plugin2
+ plugin1
Module com.intellij.modules.duplicate is declared by multiple plugins:
PluginDescriptor(name=duplicate0, id=duplicate0, path=file:/duplicate0.xml, version=2042.42)
PluginDescriptor(name=duplicate, id=duplicate, path=file:/duplicate1.xml, version=2042.42)
Plugins &#39;optional_cycle1&#39;, &#39;optional_cycle2&#39;, &#39;optional_cycle0&#39; cannot be loaded because they form a dependency cycle
Plugins &#39;cycle5&#39;, &#39;cycle4&#39; cannot be loaded because they form a dependency cycle
Plugins &#39;cycle2&#39;, &#39;cycle3&#39;, &#39;cycle1&#39; cannot be loaded because they form a dependency cycle
Plugin &#39;cycle0&#39; requires plugin &#39;cycle1&#39; to be enabled
Plugin &#39;incompatible0&#39; (version &#39;null&#39;) is not compatible with the current version of the IDE, because it requires build 1.111111 or older but the current build is 2042.42
Plugin &#39;incompatible0-alt&#39; (version &#39;null&#39;) is not compatible with the current version of the IDE, because it requires build 1.111 or older but the current build is 2042.42
Plugin &#39;incompatible1&#39; (version &#39;null&#39;) is not compatible with the current version of the IDE, because it requires build 9999.0 or newer but the current build is 2042.42
Plugin &#39;incompatible2&#39; (version &#39;null&#39;) is not compatible with the current version of the IDE, because it requires build 1.111111 or older but the current build is 2042.42
PluginDescriptor(name=duplicate0, id=duplicate0, descriptorPath=plugin.xml, path=duplicate0.xml, version=2042.42, package=null, isBundled=false)
PluginDescriptor(name=duplicate, id=duplicate, descriptorPath=plugin.xml, path=duplicate1.xml, version=2042.42, package=null, isBundled=false)
Plugins &#39;optional_cycle0&#39;, &#39;optional_cycle2&#39;, &#39;optional_cycle1&#39; cannot be loaded because they form a dependency cycle
Plugins &#39;optional_config_cycle0&#39;, &#39;optional_config_cycle2&#39; cannot be loaded because they form a dependency cycle
Plugins &#39;cycle4&#39;, &#39;cycle5&#39; cannot be loaded because they form a dependency cycle
Plugins &#39;cycle1&#39;, &#39;cycle3&#39;, &#39;cycle2&#39; cannot be loaded because they form a dependency cycle
Plugin &#39;cycle0&#39; requires plugin &#39;
cycle1
&#39; to be installed
Plugin &#39;incompatible0&#39; (version &#39;2042.42&#39;) is not compatible with the current version of the IDE, because it requires build 1.111111 or older but the current build is 2042.42
Plugin &#39;incompatible0-alt&#39; (version &#39;2042.42&#39;) is not compatible with the current version of the IDE, because it requires build 1.111 or older but the current build is 2042.42
Plugin &#39;incompatible1&#39; (version &#39;2042.42&#39;) is not compatible with the current version of the IDE, because it requires build 9999.0 or newer but the current build is 2042.42
Plugin &#39;incompatible2&#39; (version &#39;2042.42&#39;) is not compatible with the current version of the IDE, because it requires build 1.111111 or older but the current build is 2042.42
Plugin &#39;incompatible_with_enabled_module&#39; is not compatible with the IDE because the IDE contains module &#39;com.intellij.modules.plugin0&#39; which conflicts with the plugin
Plugin &#39;missing1&#39; requires plugin &#39;com.intellij.modules.missing&#39; to be installed
Plugin &#39;missing2&#39; requires plugin &#39;com.intellij.missing&#39; to be installed
@@ -173,11 +173,11 @@
Plugin &#39;Jupyter Notebook&#39; requires plugin &#39;com.intellij.modules.pycharm&#39; to be installed
Plugin &#39;Python pytest-bdd support&#39; requires plugin &#39;Python BDD support for engines, based on Gherkin (cucumber, behave)&#39; to be enabled
Plugin &#39;Python pytest-bdd support&#39; requires plugin &#39;com.intellij.modules.pycharm.professional&#39; to be installed
Plugin &#39;Python Docker&#39; requires plugin &#39;com.intellij.modules.pycharm.professional&#39; to be installed
Plugin &#39;Python BDD support for engines, based on Gherkin (cucumber, behave)&#39; requires plugin &#39;com.intellij.modules.pycharm.professional&#39; to be installed
Plugin &#39;Python SSH Interpreter&#39; requires plugin &#39;com.intellij.modules.pycharm.professional&#39; to be installed
Plugin &#39;Python WSL Support&#39; requires plugin &#39;Python SSH Interpreter&#39; to be enabled
Plugin &#39;Python WSL Support&#39; requires plugin &#39;com.intellij.modules.pycharm.professional&#39; to be installed
Plugin &#39;Concurrency Visualiser&#39; requires plugin &#39;com.intellij.modules.pycharm.professional&#39; to be installed
<a href="disable">Disable not loaded plugins</a>
<a href="edit">Open plugin manager</a>
@@ -1,4 +1,5 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:Suppress("ReplaceGetOrSet")
package com.intellij.ide.plugins
import com.intellij.ide.plugins.cl.PluginAwareClassLoader
@@ -80,14 +81,14 @@ internal class ClassLoaderConfiguratorTest {
val plugins = loadDescriptors(rootDir).getEnabledPlugins()
assertThat(plugins).hasSize(2)
val barPlugin = plugins[1]
assertThat(barPlugin.id.idString).isEqualTo("2-bar")
val barPlugin = plugins.get(1)
assertThat(barPlugin.pluginId.idString).isEqualTo("2-bar")
val classLoaderConfigurator = ClassLoaderConfigurator(PluginSet.createPluginSet(plugins, plugins))
plugins.forEach(classLoaderConfigurator::configure)
val classLoaderConfigurator = ClassLoaderConfigurator(PluginSetBuilder(plugins).computeEnabledModuleMap().createPluginSet())
classLoaderConfigurator.configure()
assertThat((barPlugin.classLoader as PluginClassLoader)._getParents().map { it.descriptorPath })
.containsExactly("com.example.sub.xml", null)
.containsExactly(null, "com.example.sub.xml")
}
@Suppress("PluginXmlValidity")
@@ -128,8 +129,8 @@ internal class ClassLoaderConfiguratorTest {
val plugins = loadResult.getEnabledPlugins()
assertThat(plugins).hasSize(2)
val classLoaderConfigurator = ClassLoaderConfigurator(PluginSet.createPluginSet(plugins, plugins))
plugins.forEach(classLoaderConfigurator::configure)
val classLoaderConfigurator = ClassLoaderConfigurator(PluginSetBuilder(plugins).computeEnabledModuleMap().createPluginSet())
classLoaderConfigurator.configure()
return loadResult
}
}
@@ -142,7 +143,7 @@ private fun loadDescriptors(dir: Path): PluginLoadingResult {
val paths = dir.directoryStreamIfExists { it.sorted() }!!
context.use {
for (file in paths) {
result.add(loadDescriptor(file, false, context) ?: continue, false)
result.add(loadDescriptor(file, context) ?: continue, false)
}
}
result.finishLoading()
@@ -111,7 +111,7 @@ class DynamicPluginsTest {
DisabledPluginsState.saveDisabledPlugins(PathManager.getConfigDir())
val newDescriptor = loadDescriptorInTest(path)
ClassLoaderConfigurator(PluginManagerCore.getPluginSet().enablePlugin(newDescriptor)).configure(newDescriptor)
ClassLoaderConfigurator(PluginManagerCore.getPluginSet().enablePlugin(newDescriptor)).configureModule(newDescriptor)
DynamicPlugins.loadPlugin(newDescriptor)
try {
assertThat(PluginManagerCore.getPlugin(descriptor.pluginId)?.pluginClassLoader as? PluginClassLoader).isNotNull()
@@ -201,7 +201,7 @@ class DynamicPluginsTest {
Files.createTempDirectory(inMemoryFs.fs.getPath("/"), null),
)
assertThat(DynamicPlugins.checkCanUnloadWithoutRestart(descriptor))
.isEqualTo("Plugin ${descriptor.id} is not unload-safe because of extension to non-dynamic EP $epName")
.isEqualTo("Plugin ${descriptor.pluginId} is not unload-safe because of extension to non-dynamic EP $epName")
}
@Test
@@ -298,9 +298,9 @@ class DynamicPluginsTest {
"""<extensionPoint qualifiedName="foo.barExtension" beanClass="com.intellij.util.KeyedLazyInstanceEP" dynamic="true"/>""")
.module("intellij.foo.sub",
PluginBuilder()
.extensions("""<barExtension key="foo" implementationClass="y"/>""", "foo")
.packagePrefix("foo1")
.pluginDependency(barBuilder.id)
.extensions("""<barExtension key="foo" implementationClass="y"/>""", "foo")
.packagePrefix("foo1")
.pluginDependency(barBuilder.id)
)
loadPluginWithText(fooBuilder).use {
val ep = ApplicationManager.getApplication().extensionArea.getExtensionPointIfRegistered<KeyedLazyInstanceEP<*>>("foo.barExtension")
@@ -15,19 +15,21 @@ import java.nio.file.Files
import java.nio.file.Path
import java.util.function.Supplier
internal fun loadDescriptorInTest(dir: Path, disabledPlugins: Set<PluginId> = emptySet(), isBundled: Boolean = false): IdeaPluginDescriptorImpl {
internal fun loadDescriptorInTest(dir: Path,
disabledPlugins: Set<PluginId> = emptySet(),
isBundled: Boolean = false): IdeaPluginDescriptorImpl {
assertThat(dir).exists()
PluginManagerCore.getAndClearPluginLoadingErrors()
val buildNumber = BuildNumber.fromString("2042.42")!!
val parentContext = DescriptorListLoadingContext(disabledPlugins = disabledPlugins,
result = PluginLoadingResult(emptyMap(), Supplier { buildNumber }))
val result = loadDescriptorFromFileOrDir(file = dir,
pathName = PluginManagerCore.PLUGIN_XML,
context = parentContext,
pathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER,
isBundled = isBundled,
isEssential = true,
isDirectory = Files.isDirectory(dir))
isDirectory = Files.isDirectory(dir),
useCoreClassLoader = false)
if (result == null) {
@Suppress("USELESS_CAST")
assertThat(PluginManagerCore.getAndClearPluginLoadingErrors()).isNotEmpty()
@@ -1,5 +1,5 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:Suppress("UsePropertyAccessSyntax")
@file:Suppress("UsePropertyAccessSyntax", "ReplaceGetOrSet")
package com.intellij.ide.plugins
import com.intellij.ide.plugins.cl.PluginClassLoader
@@ -41,7 +41,7 @@ private fun loadDescriptors(dir: Path, buildNumber: BuildNumber, disabledPlugins
val paths: List<Path> = dir.directoryStreamIfExists { it.sorted() }!!
context.use {
for (file in paths) {
val descriptor = loadDescriptor(file, false, context) ?: continue
val descriptor = loadDescriptor(file, context) ?: continue
context.result.add(descriptor, false)
}
}
@@ -201,7 +201,7 @@ class PluginDescriptorTest {
assertThat(foo.version).isEqualTo("2.0")
assertThat(foo.pluginId.idString).isEqualTo("foo")
assertThat(pluginSet.allPlugins.toList()).map(Function { it.id }).containsOnly(foo.pluginId)
assertThat(pluginSet.allPlugins.toList()).map(Function { it.pluginId }).containsOnly(foo.pluginId)
assertThat(pluginSet.findEnabledPlugin(foo.pluginId)).isSameAs(foo)
}
@@ -291,7 +291,7 @@ class PluginDescriptorTest {
assertThat(foo.version).isEqualTo("2.0")
assertThat(foo.pluginId.idString).isEqualTo("foo")
assertThat(pluginSet.allPlugins.toList()).map(Function { it.id }).containsOnly(foo.pluginId)
assertThat(pluginSet.allPlugins.toList()).map(Function { it.pluginId }).containsOnly(foo.pluginId)
assertThat(pluginSet.findEnabledPlugin(foo.pluginId)).isSameAs(foo)
}
@@ -309,7 +309,7 @@ class PluginDescriptorTest {
assertThat(foo.version).isEqualTo("1.0")
assertThat(foo.pluginId.idString).isEqualTo("foo")
assertThat(pluginSet.allPlugins.toList()).map(Function { it.id }).containsOnly(foo.pluginId)
assertThat(pluginSet.allPlugins.toList()).map(Function { it.pluginId }).containsOnly(foo.pluginId)
assertThat(pluginSet.findEnabledPlugin(foo.pluginId)).isSameAs(foo)
}
@@ -519,7 +519,7 @@ fun readDescriptorForTest(path: Path, isBundled: Boolean, input: ByteArray, id:
if (id != null) {
raw.id = id.idString
}
val result = IdeaPluginDescriptorImpl(raw = raw, path = path, isBundled = isBundled, id = id)
val result = IdeaPluginDescriptorImpl(raw = raw, path = path, isBundled = isBundled, id = id, moduleName = null)
result.readExternal(
raw = raw,
isSub = false,
@@ -543,7 +543,7 @@ fun createFromDescriptor(path: Path,
includeBase = null,
readInto = null,
locationSource = path.toString())
val result = IdeaPluginDescriptorImpl(raw = raw, path = path, isBundled = isBundled, id = null)
val result = IdeaPluginDescriptorImpl(raw = raw, path = path, isBundled = isBundled, id = null, moduleName = null)
result.readExternal(raw = raw,
pathResolver = pathResolver,
context = context,
@@ -1,12 +1,13 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.plugins;
import com.intellij.idea.Bombed;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.IoTestUtil;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.Strings;
import com.intellij.platform.util.plugins.DataLoader;
import com.intellij.platform.util.plugins.LocalFsDataLoader;
import com.intellij.testFramework.PlatformTestUtil;
@@ -26,17 +27,18 @@ import javax.xml.stream.XMLStreamWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import static com.intellij.ide.plugins.DynamicPluginsTestUtil.loadDescriptorInTest;
import static com.intellij.openapi.util.io.IoTestUtil.assumeSymLinkCreationIsSupported;
import static com.intellij.testFramework.assertions.Assertions.assertThat;
import static java.util.Objects.requireNonNull;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.junit.Assert.*;
@@ -129,11 +131,34 @@ public class PluginManagerTest {
}
@Test
@Bombed(user = "Vladimir Krivosheev / Andrew Kozlow", day = 1, month = Calendar.SEPTEMBER, year = 2021)
public void testSimplePluginSort() throws Exception {
doPluginSortTest("simplePluginSort", false);
}
/**
Actual result:
HTTP Client (main)
Endpoints (main)
HTTP Client (intellij.restClient.microservicesUI, depends on Endpoints)
Expected:
Endpoints (main)
HTTP Client (main)
HTTP Client (intellij.restClient.microservicesUI, depends on Endpoints)
But graph is correct - HTTP Client (main) it is node that doesn't depend on Endpoints (main),
so, no reason for DFSTBuilder to put it after.
See CachingSemiGraph.getSortedPlugins for solution
*/
@Test
public void moduleSort() throws Exception {
doPluginSortTest("moduleSort", true);
}
@Test
public void testUltimatePlugins() throws Exception {
doPluginSortTest("ultimatePlugins", true);
@@ -143,10 +168,12 @@ public class PluginManagerTest {
public void testModulePluginIdContract() {
Path pluginsPath = Path.of(PlatformTestUtil.getPlatformTestDataPath(), "plugins", "withModules");
IdeaPluginDescriptorImpl descriptorBundled = loadDescriptorInTest(pluginsPath, Collections.emptySet(), true);
Map<PluginId, IdeaPluginDescriptorImpl> idMap = PluginManagerCore.buildPluginIdMap(Collections.singletonList(descriptorBundled));
PluginSet pluginSet = new PluginSetBuilder(Collections.singletonList(descriptorBundled))
.computeEnabledModuleMap(null)
.createPluginSet(Collections.emptyList());
PluginId moduleId = PluginId.getId("foo.bar");
PluginId corePlugin = PluginId.getId("my.plugin");
assertEquals(corePlugin, idMap.get(moduleId).getPluginId());
assertThat(pluginSet.findEnabledPlugin(moduleId).getPluginId()).isEqualTo(corePlugin);
}
@Test
@@ -197,8 +224,12 @@ public class PluginManagerTest {
PluginManagerCore.getAndClearPluginLoadingErrors();
PluginManagerState loadPluginResult = loadAndInitializeDescriptors(testDataName + ".xml", isBundled);
StringBuilder text = new StringBuilder();
for (IdeaPluginDescriptorImpl descriptor : loadPluginResult.pluginSet.enabledPlugins) {
text.append(descriptor.isEnabled() ? "+ " : " ").append(descriptor.getPluginId().getIdString()).append('\n');
for (IdeaPluginDescriptorImpl descriptor : loadPluginResult.pluginSet.getRawListOfEnabledModules()) {
text.append(descriptor.isEnabled() ? "+ " : " ").append(descriptor.getPluginId().getIdString());
if (descriptor.moduleName != null) {
text.append(" | ").append(descriptor.moduleName);
}
text.append('\n');
}
text.append("\n\n");
for (HtmlChunk html : PluginManagerCore.getAndClearPluginLoadingErrors()) {
@@ -221,7 +252,7 @@ public class PluginManagerTest {
expect(mock.getUntilBuild()).andReturn(untilBuild).anyTimes();
replay(mock);
PluginLoadingError error =
PluginManagerCore.checkBuildNumberCompatibility(mock, requireNonNull(BuildNumber.fromString(ideVersion)));
PluginManagerCore.checkBuildNumberCompatibility(mock, Objects.requireNonNull(BuildNumber.fromString(ideVersion)));
return error != null ? error.getDetailedMessage() : null;
}
@@ -235,6 +266,8 @@ public class PluginManagerTest {
new DescriptorListLoadingContext(Collections.emptySet(), createPluginLoadingResult(true), false, false, false, false);
XmlElement root = XmlDomReader.readXmlAsModel(Files.newInputStream(file));
Ref<Boolean> autoGenerateModuleDescriptor = new Ref<>(false);
Map<String, XmlElement> moduleMap = new HashMap<>();
PathResolver pathResolver = new PathResolver() {
@Override
public boolean isFlat() {
@@ -257,7 +290,7 @@ public class PluginManagerTest {
@Nullable RawPluginDescriptor readInto) {
for (XmlElement child : root.children) {
if (child.name.equals("config-file-idea-plugin")) {
String url = requireNonNull(child.getAttributeValue("url"));
String url = Objects.requireNonNull(child.getAttributeValue("url"));
if (url.endsWith("/" + relativePath)) {
try {
return XmlReader.readModuleDescriptor(elementAsBytes(child), readContext, this, dataLoader, null, readInto, null);
@@ -276,16 +309,53 @@ public class PluginManagerTest {
@NotNull DataLoader dataLoader,
@NotNull String path,
@Nullable RawPluginDescriptor readInto) {
if (autoGenerateModuleDescriptor.get() && path.startsWith("intellij.")) {
XmlElement element = moduleMap.get(path);
if (element != null) {
try {
return XmlReader.readModuleDescriptorForTest(elementAsBytes(element));
}
catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}
assert readInto == null;
// auto-generate empty descriptor
return XmlReader.readModuleDescriptorForTest(("<idea-plugin package=\"" + path + "\"></idea-plugin>")
.getBytes(StandardCharsets.UTF_8));
}
return resolvePath(readContext, dataLoader, path, readInto);
}
};
for (XmlElement element : root.children) {
String moduleFile = element.attributes.get("moduleFile");
if (moduleFile != null) {
moduleMap.put(moduleFile, element);
}
}
for (XmlElement element : root.children) {
if (!element.name.equals("idea-plugin")) {
continue;
}
Path pluginPath = Path.of(StringUtil.trimStart(requireNonNull(element.getAttributeValue("url")), "file://"));
String url = element.getAttributeValue("url");
Path pluginPath;
if (url == null) {
XmlElement id = element.getChild("id");
if (id == null) {
assert element.attributes.containsKey("moduleFile");
continue;
}
pluginPath = Path.of(id.content.replace('.', '_') + ".xml");
autoGenerateModuleDescriptor.set(true);
}
else {
pluginPath = Path.of(Strings.trimStart(Objects.requireNonNull(url), "file://"));
}
IdeaPluginDescriptorImpl descriptor = PluginDescriptorTestKt.createFromDescriptor(
pluginPath, isBundled, elementAsBytes(element), parentContext, pathResolver, new LocalFsDataLoader(pluginPath));
parentContext.result.add(descriptor, /* overrideUseIfCompatible = */ false);
@@ -1,4 +1,4 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.graph;
import com.intellij.util.Chunk;
@@ -7,7 +7,7 @@ import org.junit.Test;
import java.util.*;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
/**
* @author Eugene Zhuravlev
@@ -27,17 +27,17 @@ public class ChunkGraphTest extends GraphTestCase {
final List<Chunk<String>> expectedNodes = new ArrayList<>();
Chunk<String> A = new Chunk<>("a");
expectedNodes.add(A);
Chunk<String> BC = new Chunk<>(ContainerUtil.newHashSet("b", "c"));
Chunk<String> BC = new Chunk<>(Set.of("b", "c"));
expectedNodes.add(BC);
Chunk<String> DE = new Chunk<>(ContainerUtil.newHashSet("d", "e"));
Chunk<String> DE = new Chunk<>(Set.of("d", "e"));
expectedNodes.add(DE);
checkVertices(expectedNodes, graph.getNodes());
final Map<Chunk<String>, Set<Chunk<String>>> expectedArcs = new HashMap<>();
expectedArcs.put(A, new HashSet<>());
expectedArcs.put(BC, ContainerUtil.newHashSet(A));
expectedArcs.put(DE, ContainerUtil.newHashSet(BC));
expectedArcs.put(BC, Set.of(A));
expectedArcs.put(DE, Set.of(BC));
checkArcs(expectedArcs, graph);
}
@@ -53,7 +53,7 @@ public class ChunkGraphTest extends GraphTestCase {
final Graph<Chunk<String>> graph = getAlgorithmsInstance().computeSCCGraph(initGraph(arcs));
final List<Chunk<String>> expectedNodes = new ArrayList<>();
Chunk<String> ABC = new Chunk<>(ContainerUtil.newHashSet("a", "b", "c"));
Chunk<String> ABC = new Chunk<>(Set.of("a", "b", "c"));
expectedNodes.add(ABC);
Chunk<String> D = new Chunk<>("d");
expectedNodes.add(D);
@@ -62,7 +62,7 @@ public class ChunkGraphTest extends GraphTestCase {
final Map<Chunk<String>, Set<Chunk<String>>> expectedArcs = new HashMap<>();
expectedArcs.put(ABC, new HashSet<>());
expectedArcs.put(D, ContainerUtil.newHashSet(ABC));
expectedArcs.put(D, Set.of(ABC));
checkArcs(expectedArcs, graph);
}
@@ -71,14 +71,14 @@ public class ChunkGraphTest extends GraphTestCase {
for (Chunk<String> chunk : graph.getNodes()) {
List<Chunk<String>> ins = ContainerUtil.newArrayList(() -> graph.getIn(chunk));
Set<Chunk<String>> expectedIns = expectedArcs.get(chunk);
assertTrue(expectedIns.size() == ins.size());
assertTrue(expectedIns.equals(new HashSet<>(ins)));
assertEquals(expectedIns.size(), ins.size());
assertEquals(expectedIns, new HashSet<>(ins));
}
}
private static void checkVertices(List<Chunk<String>> expected, Iterable<Chunk<String>> nodes) {
List<Chunk<String>> realNodes = ContainerUtil.newArrayList(nodes);
assertTrue(expected.size() == realNodes.size());
assertTrue(new HashSet<>(expected).equals(new HashSet<>(realNodes)));
assertEquals(expected.size(), realNodes.size());
assertEquals(new HashSet<>(expected), new HashSet<>(realNodes));
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.workspaceModel.ide.impl.legacyBridge.module
import com.intellij.ProjectTopics
@@ -85,8 +85,7 @@ abstract class ModuleManagerBridgeImpl(private val project: Project) : ModuleMan
val (unloadedEntities, loadedEntities) = entities.partition { it.name in unloadedModuleNames }
LOG.debug { "Loading modules for ${loadedEntities.size} entities" }
val plugins = PluginManagerCore.getLoadedPlugins(null)
val precomputedExtensionModel = precomputeExtensionModel(plugins)
val precomputedExtensionModel = precomputeExtensionModel()
val tasks = loadedEntities.map { moduleEntity ->
ForkJoinTask.adapt(Callable {
@@ -278,7 +277,7 @@ abstract class ModuleManagerBridgeImpl(private val project: Project) : ModuleMan
fun createModuleInstance(moduleEntity: ModuleEntity, versionedStorage: VersionedEntityStorage,
diff: WorkspaceEntityStorageDiffBuilder?, isNew: Boolean,
precomputedExtensionModel: PrecomputedExtensionModel?): ModuleBridge {
val plugins = PluginManagerCore.getLoadedPlugins(null)
val plugins = PluginManagerCore.getPluginSet().getEnabledModules()
val corePlugin = plugins.find { it.pluginId == PluginManagerCore.CORE_ID }
val moduleFileUrl = getModuleVirtualFileUrl(moduleEntity)
@@ -289,7 +288,7 @@ abstract class ModuleManagerBridgeImpl(private val project: Project) : ModuleMan
diff = diff)
module.registerComponents(corePlugin = corePlugin,
plugins = plugins,
modules = plugins,
app = ApplicationManager.getApplication(),
precomputedExtensionModel = precomputedExtensionModel,
listenerCallbacks = null)
@@ -1,3 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.workspaceModel.ide.legacyBridge
import com.intellij.ide.plugins.IdeaPluginDescriptor
@@ -9,7 +10,9 @@ import com.intellij.workspaceModel.storage.VersionedEntityStorage
import com.intellij.workspaceModel.storage.WorkspaceEntityStorageDiffBuilder
import com.intellij.workspaceModel.storage.bridgeEntities.ModuleId
import com.intellij.workspaceModel.storage.url.VirtualFileUrl
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
interface ModuleBridge : ModuleEx {
val moduleEntityId: ModuleId
@@ -28,8 +31,11 @@ interface ModuleBridge : ModuleEx {
fun rename(newName: String, newModuleFileUrl: VirtualFileUrl?, notifyStorage: Boolean)
fun registerComponents(corePlugin: IdeaPluginDescriptor?, plugins: List<IdeaPluginDescriptorImpl>,
precomputedExtensionModel: PrecomputedExtensionModel?, app: Application?, listenerCallbacks: List<Runnable>?)
fun registerComponents(corePlugin: IdeaPluginDescriptor?,
modules: Sequence<IdeaPluginDescriptorImpl>,
precomputedExtensionModel: PrecomputedExtensionModel?,
app: Application?,
listenerCallbacks: List<Runnable>?)
fun callCreateComponents()
}
@@ -213,20 +213,27 @@ abstract class ComponentManagerImpl @JvmOverloads constructor(
indicator.fraction = getPercentageOfComponentsLoaded()
}
@Internal
fun getPercentageOfComponentsLoaded(): Double {
return instantiatedComponentCount.toDouble() / componentConfigCount
}
override fun getExtensionArea(): ExtensionsAreaImpl {
final override fun getExtensionArea(): ExtensionsAreaImpl {
if (!isExtensionSupported) {
error("Extensions aren't supported")
}
return _extensionArea
}
// convenient method - use only in a not important code
fun registerComponents() {
registerComponents(modules = PluginManagerCore.getPluginSet().getEnabledModules(),
app = getApplication(),
precomputedExtensionModel = null,
listenerCallbacks = null)
}
@Internal
open fun registerComponents(plugins: List<IdeaPluginDescriptorImpl>,
open fun registerComponents(modules: Sequence<IdeaPluginDescriptorImpl>,
app: Application?,
precomputedExtensionModel: PrecomputedExtensionModel?,
listenerCallbacks: List<Runnable>?) {
@@ -242,20 +249,12 @@ abstract class ComponentManagerImpl @JvmOverloads constructor(
// register services before registering extensions because plugins can access services in their
// extensions which can be invoked right away if the plugin is loaded dynamically
val extensionPoints = if (precomputedExtensionModel == null) HashMap(extensionArea.extensionPoints) else null
for (mainPlugin in plugins) {
for (rootModule in modules) {
executeRegisterTask(rootModule) { module ->
val containerDescriptor = getContainerDescriptor(module)
registerServices(containerDescriptor.services, module)
newComponentConfigCount += registerComponents(module, containerDescriptor, isHeadless)
executeRegisterTask(mainPlugin) { pluginDescriptor ->
val containerDescriptor = getContainerDescriptor(pluginDescriptor)
registerServices(containerDescriptor.services, pluginDescriptor)
}
executeRegisterTask(mainPlugin) { pluginDescriptor ->
val containerDescriptor = getContainerDescriptor(pluginDescriptor)
newComponentConfigCount += registerComponents(pluginDescriptor, containerDescriptor, isHeadless)
}
executeRegisterTask(mainPlugin) { pluginDescriptor ->
val containerDescriptor = getContainerDescriptor(pluginDescriptor)
containerDescriptor.listeners?.let { listeners ->
var m = map
if (m == null) {
@@ -271,17 +270,14 @@ abstract class ComponentManagerImpl @JvmOverloads constructor(
continue
}
listener.pluginDescriptor = pluginDescriptor
listener.pluginDescriptor = module
m.computeIfAbsent(listener.topicClassName) { ArrayList() }.add(listener)
}
}
}
if (extensionPoints != null) {
executeRegisterTask(mainPlugin) { pluginDescriptor ->
val containerDescriptor = getContainerDescriptor(pluginDescriptor)
if (extensionPoints != null) {
containerDescriptor.extensionPoints?.let {
ExtensionsAreaImpl.createExtensionPoints(it, this, extensionPoints, pluginDescriptor)
ExtensionsAreaImpl.createExtensionPoints(it, this, extensionPoints, module)
}
}
}
@@ -295,10 +291,9 @@ abstract class ComponentManagerImpl @JvmOverloads constructor(
val immutableExtensionPoints = if (extensionPoints!!.isEmpty()) Collections.emptyMap() else java.util.Map.copyOf(extensionPoints)
extensionArea.setPoints(immutableExtensionPoints)
for (mainPlugin in plugins) {
executeRegisterTask(mainPlugin) { pluginDescriptor ->
val containerDescriptor = getContainerDescriptor(pluginDescriptor)
pluginDescriptor.registerExtensions(immutableExtensionPoints, containerDescriptor, listenerCallbacks)
for (rootModule in modules) {
executeRegisterTask(rootModule) { module ->
module.registerExtensions(immutableExtensionPoints, getContainerDescriptor(module), listenerCallbacks)
}
}
}
@@ -979,12 +974,12 @@ abstract class ComponentManagerImpl @JvmOverloads constructor(
open fun activityNamePrefix(): String? = null
@ApiStatus.Internal
open fun preloadServices(plugins: List<IdeaPluginDescriptorImpl>,
activityPrefix: String,
onlyIfAwait: Boolean = false): Pair<CompletableFuture<Void?>, CompletableFuture<Void?>> {
open fun preloadServices(modules: Sequence<IdeaPluginDescriptorImpl>,
activityPrefix: String,
onlyIfAwait: Boolean = false): Pair<CompletableFuture<Void?>, CompletableFuture<Void?>> {
val asyncPreloadedServices = mutableListOf<ForkJoinTask<*>>()
val syncPreloadedServices = mutableListOf<ForkJoinTask<*>>()
for (plugin in plugins) {
for (plugin in modules) {
serviceLoop@ for (service in getContainerDescriptor(plugin).services) {
if (!isServiceSuitable(service) || service.os != null && !isSuitableForOs(service.os)) {
continue@serviceLoop
@@ -1469,4 +1464,10 @@ private class LightServiceComponentAdapter(private val initializedInstance: Any)
override fun getComponentInstance(container: PicoContainer?) = initializedInstance
override fun toString() = componentKey
}
private inline fun executeRegisterTask(mainPluginDescriptor: IdeaPluginDescriptorImpl,
crossinline task: (IdeaPluginDescriptorImpl) -> Unit) {
task(mainPluginDescriptor)
executeRegisterTaskForOldContent(mainPluginDescriptor, task)
}
@@ -2,7 +2,6 @@
@file:ApiStatus.Internal
package com.intellij.serviceContainer
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.components.Service
import com.intellij.openapi.extensions.PluginDescriptor
@@ -25,14 +24,6 @@ internal fun checkCanceledIfNotInClassInit() {
}
}
inline fun executeRegisterTask(mainPluginDescriptor: IdeaPluginDescriptorImpl,
crossinline task: (IdeaPluginDescriptorImpl) -> Unit) {
task(mainPluginDescriptor)
executeRegisterTaskForContent(mainPluginDescriptor) {
task(it)
}
}
internal fun isGettingServiceAllowedDuringPluginUnloading(descriptor: PluginDescriptor): Boolean {
return descriptor.isRequireRestart ||
descriptor.pluginId == PluginManagerCore.CORE_ID || descriptor.pluginId == PluginManagerCore.JAVA_PLUGIN_ID
@@ -4,8 +4,8 @@ package com.intellij
import com.intellij.concurrency.IdeaForkJoinWorkerThreadFactory
import com.intellij.diagnostic.ThreadDumper
import com.intellij.ide.IdeEventQueue
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.ide.plugins.PluginSet
import com.intellij.idea.Main
import com.intellij.idea.callAppInitialized
import com.intellij.idea.initConfigurationStore
@@ -19,8 +19,10 @@ import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl
import com.intellij.util.SystemProperties
import java.awt.EventQueue
import java.util.concurrent.*
import java.util.function.Supplier
import java.util.concurrent.ExecutionException
import java.util.concurrent.ForkJoinTask
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
fun loadHeadlessAppInUnitTestMode() {
doLoadApp {
@@ -43,9 +45,8 @@ internal fun doLoadApp(setupEventQueue: () -> Unit) {
PluginManagerCore.isUnitTestMode = true
IdeaForkJoinWorkerThreadFactory.setupForkJoinCommonPool(true)
val loadedPluginFuture = CompletableFuture.supplyAsync(Supplier {
PluginManagerCore.getLoadedPlugins(PathManager::class.java.classLoader)
}, ForkJoinPool.commonPool())
PluginManagerCore.scheduleDescriptorLoading()
val loadedModuleFuture = PluginManagerCore.initPlugins(PathManager::class.java.classLoader)
setupEventQueue()
@@ -55,15 +56,15 @@ internal fun doLoadApp(setupEventQueue: () -> Unit) {
RecursionManager.assertOnMissedCache(app)
}
val plugins: List<IdeaPluginDescriptorImpl>
val pluginSet: PluginSet
try {
// 40 seconds - tests maybe executed on cloud agents where IO speed is a very slow
plugins = loadedPluginFuture.get(40, TimeUnit.SECONDS)
app.registerComponents(plugins, app, null, null)
pluginSet = loadedModuleFuture.get(40, TimeUnit.SECONDS)
app.registerComponents(modules = pluginSet.getEnabledModules(), app = app, precomputedExtensionModel = null, listenerCallbacks = null)
initConfigurationStore(app)
RegistryKeyBean.addKeysFromPlugins()
Registry.markAsLoaded()
val preloadServiceFuture = preloadServices(plugins, app, activityPrefix = "")
val preloadServiceFuture = preloadServices(pluginSet.getEnabledModules(), app, activityPrefix = "")
app.loadComponents(null)
preloadServiceFuture.get(40, TimeUnit.SECONDS)
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.testFramework;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
@@ -291,10 +291,14 @@ public abstract class LightPlatformCodeInsightTestCase extends LightPlatformTest
@Override
protected void tearDown() throws Exception {
try {
getIndexingMode().tearDownTest(getProject());
FileEditorManager editorManager = FileEditorManager.getInstance(getProject());
for (VirtualFile openFile : editorManager.getOpenFiles()) {
editorManager.closeFile(openFile);
Project project = getProject();
if (myIndexingMode != null && project != null) {
myIndexingMode.tearDownTest(project);
FileEditorManager editorManager = FileEditorManager.getInstance(project);
for (VirtualFile openFile : editorManager.getOpenFiles()) {
editorManager.closeFile(openFile);
}
}
deleteVFile();
myEditor = null;
@@ -24,21 +24,14 @@ public abstract class Java11Shim {
}
@Override
public <E> @NotNull List<E> copyOf(List<? extends E> collection) {
public <E> @NotNull List<E> copyOfCollection(Collection<? extends E> collection) {
return Collections.unmodifiableList(new ArrayList<>(collection));
}
@Override
public @NotNull <E> List<E> listOf(E[] collection) {
return Arrays.asList(collection);
}
};
public abstract <@NotNull K, @NotNull V> Map<K, V> copyOf(Map<? extends K, ? extends V> map);
public abstract <@NotNull E> @NotNull Set<E> copyOf(Set<? extends E> collection);
public abstract <@NotNull E> @NotNull List<E> copyOf(List<? extends E> collection);
public abstract <@NotNull E> @NotNull List<E> listOf(E[] collection);
public abstract <@NotNull E> @NotNull List<E> copyOfCollection(Collection<? extends E> collection);
}
@@ -33,6 +33,7 @@ import java.util.function.Predicate;
*/
public class UrlClassLoader extends ClassLoader implements ClassPath.ClassDataConsumer {
private static final boolean isParallelCapable = registerAsParallelCapable();
private static final ClassLoader appClassLoader = UrlClassLoader.class.getClassLoader();
private static final ThreadLocal<Boolean> skipFindingResource = new ThreadLocal<>();
@@ -216,6 +217,10 @@ public class UrlClassLoader extends ClassLoader implements ClassPath.ClassDataCo
@Override
protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
if (name.startsWith("com.intellij.util.lang.")) {
return appClassLoader.loadClass(name);
}
Class<?> clazz;
try {
clazz = classPath.findClass(name, classDataConsumer);
@@ -361,7 +366,6 @@ public class UrlClassLoader extends ClassLoader implements ClassPath.ClassDataCo
}
@ApiStatus.Internal
@ApiStatus.Experimental
public @Nullable BiPredicate<String, Boolean> resolveScopeManager;
public @Nullable Class<?> loadClassInsideSelf(@NotNull String name, boolean forceLoadFromSubPluginClassloader) throws IOException {
@@ -425,7 +429,7 @@ public class UrlClassLoader extends ClassLoader implements ClassPath.ClassDataCo
}
}
// trying to speedup the common case when there are no "//" or "/."
// trying to speed up the common case when there are no "//" or "/."
int index = -1;
do {
index = path.indexOf('/', index + 1);
@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.lang;
import org.jetbrains.annotations.ApiStatus;
@@ -6,6 +6,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
// API and placement of classes are complicated due to java 8 requirement
@@ -19,6 +20,6 @@ public abstract class ZipFilePool {
public abstract @NotNull Object loadZipFile(@NotNull Path file) throws IOException;
public interface EntryResolver {
byte @Nullable [] loadZipEntry(@NotNull String path) throws IOException;
@Nullable InputStream loadZipEntry(@NotNull String path) throws IOException;
}
}
@@ -1,9 +1,9 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.platform.util.plugins
import com.intellij.util.lang.ZipFilePool
import org.jetbrains.annotations.ApiStatus
import java.io.IOException
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.NoSuchFileException
import java.nio.file.Path
@@ -14,8 +14,7 @@ interface DataLoader {
fun isExcludedFromSubSearch(jarFile: Path): Boolean = false
@Throws(IOException::class)
fun load(path: String): ByteArray?
fun load(path: String): InputStream?
override fun toString(): String
}
@@ -25,9 +24,9 @@ class LocalFsDataLoader(val basePath: Path) : DataLoader {
override val pool: ZipFilePool?
get() = ZipFilePool.POOL
override fun load(path: String): ByteArray? {
override fun load(path: String): InputStream? {
return try {
Files.readAllBytes(basePath.resolve(path))
Files.newInputStream(basePath.resolve(path))
}
catch (e: NoSuchFileException) {
null
@@ -1,33 +1,42 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.graph;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntStack;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.ObjIntConsumer;
import java.util.function.ToIntFunction;
/**
* @author dsl, ven
*/
public final class DFSTBuilder<Node> {
private final OutboundSemiGraph<Node> myGraph;
private final Object2IntMap<Node> myNodeToNNumber; // node -> node number in topological order [0..size). Independent nodes are in reversed loading order (loading order is the graph.getNodes() order)
private final DFSTBuilderAwareGraph<Node> graphAdapter;
@SuppressWarnings("GrazieInspection")
private final ToIntFunction<Node> myNodeToNNumber; // node -> node number in topological order [0..size). Independent nodes are in reversed loading order (loading order is the graph.getNodes() order)
@SuppressWarnings("GrazieInspection")
private final Node[] myInvN; // node number in topological order [0..size) -> node
private Map.Entry<Node, Node> myBackEdge;
private final Node[] allNodes;
private Comparator<Node> myNComparator;
private Comparator<Node> myTComparator;
private final IntList mySCCs = new IntArrayList(); // strongly connected component sizes
private final Object2IntMap<Node> myNodeToTNumber = new Object2IntOpenHashMap<>(); // node -> number in scc topological order. Independent scc are in reversed loading order
private final ToIntFunction<Node> myNodeToTNumber; // node -> number in scc topological order. Independent scc are in reversed loading order
private final Node[] myInvT; // number in (enumerate all nodes scc by scc) order -> node
private final Node[] myAllNodes;
/**
* @see DFSTBuilder#DFSTBuilder(OutboundSemiGraph, Object)
@@ -47,31 +56,93 @@ public final class DFSTBuilder<Node> {
* @see DFSTBuilder#DFSTBuilder(OutboundSemiGraph, Object)
*/
public DFSTBuilder(@NotNull OutboundSemiGraph<Node> graph) {
this(graph, null);
this(graph, null, false);
}
/**
* @param entryNode is a first node for Tarjan's algorithm. Different entry nodes produce different node numbers in topological ordering.
* if all nodes of the graph is reachable from the entry node and the entry node doesn't have incoming edges then
* passing the entry node could be used for finding "natural" back edges (like a loop back edge)
*/
public DFSTBuilder(@NotNull OutboundSemiGraph<Node> graph, @Nullable Node entryNode) {
this(graph, entryNode, false);
}
@ApiStatus.Internal
public DFSTBuilder(@NotNull OutboundSemiGraph<Node> graph, @Nullable Node entryNode, boolean useIdentityStrategy) {
//noinspection unchecked
myAllNodes = (Node[])graph.getNodes().toArray();
this((Node[])graph.getNodes().toArray(), entryNode, useIdentityStrategy, new DFSTBuilderAwareGraph<Node>() {
@Override
public int[] buildOuts(@NotNull ToIntFunction<? super Node> nodeIndex, @NotNull Node node) {
IntList list = new IntArrayList();
Iterator<Node> out = graph.getOut(node);
while (out.hasNext()) {
list.add(nodeIndex.applyAsInt(out.next()));
}
return list.isEmpty() ? ArrayUtilRt.EMPTY_INT_ARRAY : list.toIntArray();
}
});
}
@ApiStatus.Internal
public DFSTBuilder(Node[] allNodes,
@Nullable Node entryNode,
boolean useIdentityStrategy,
@Nullable DFSTBuilderAwareGraph<Node> graphAdapter) {
this.allNodes = allNodes;
if (entryNode != null) {
int index = ArrayUtil.indexOf(myAllNodes, entryNode);
int index = useIdentityStrategy ? ArrayUtil.indexOfIdentity(allNodes, entryNode) : ArrayUtil.indexOf(allNodes, entryNode);
if (index != -1) {
ArrayUtil.swap(myAllNodes, 0, index);
ArrayUtil.swap(allNodes, 0, index);
}
}
myGraph = graph;
int size = graph.getNodes().size();
myNodeToNNumber = new Object2IntOpenHashMap<>(size * 2, 0.5f);
int size = allNodes.length;
//noinspection unchecked
myInvN = (Node[])new Object[size];
//noinspection unchecked
myInvT = (Node[])new Object[size];
new Tarjan().build();
this.graphAdapter = graphAdapter;
if (useIdentityStrategy) {
Reference2IntOpenHashMap<Node> nMap = new Reference2IntOpenHashMap<>(size * 2, 0.5f);
Reference2IntOpenHashMap<Node> tMap = new Reference2IntOpenHashMap<>();
myNodeToNNumber = nMap;
myNodeToTNumber = tMap;
new Tarjan(tMap::put, nMap::put, allNodes, true);
}
else {
//noinspection SSBasedInspection
Object2IntOpenHashMap<Node> nMap = new Object2IntOpenHashMap<>(size * 2, 0.5f);
//noinspection SSBasedInspection
Object2IntOpenHashMap<Node> tMap = new Object2IntOpenHashMap<>();
myNodeToNNumber = nMap;
myNodeToTNumber = tMap;
new Tarjan(tMap::put, nMap::put, allNodes, false);
}
}
@ApiStatus.Internal
public interface DFSTBuilderAwareGraph<Node> {
int[] buildOuts(@NotNull ToIntFunction<? super Node> nodeIndex, @NotNull Node node);
}
private static final class TarjanFrame<Node> {
private final int nodeI;
private final Node[] allNodes;
private final int[] out;
int nextUnexploredIndex;
TarjanFrame(int nodeI, Node[] allNodes, int[] out) {
this.nodeI = nodeI;
this.allNodes = allNodes;
this.out = out;
}
@Override
public String toString() {
StringBuilder o = new StringBuilder();
o.append(allNodes[nodeI]).append(" -> [");
for (int id : out) {
o.append(allNodes[id]).append(", ");
}
return o.append(']').toString();
}
}
/**
@@ -90,54 +161,43 @@ public final class DFSTBuilder<Node> {
private final IntStack nodesOnStack = new IntArrayList();
private final boolean[] isOnStack = new boolean[index.length];
private final class Frame {
Frame(int nodeI) {
this.nodeI = nodeI;
Iterator<Node> outNodes = myGraph.getOut(myAllNodes[nodeI]);
IntList list = new IntArrayList();
while (outNodes.hasNext()) {
Node node = outNodes.next();
list.add(nodeIndex.getInt(node));
}
out = list.toIntArray();
}
private final int nodeI;
private final int[] out;
private int nextUnexploredIndex;
@Override
public String toString() {
StringBuilder o = new StringBuilder();
o.append(myAllNodes[nodeI]).append(" -> [");
for (int id : out) {
o.append(myAllNodes[id]).append(", ");
}
return o.append(']').toString();
}
}
private final Deque<Frame> frames = new ArrayDeque<>(); // recursion stack
private final Object2IntMap<Node> nodeIndex = new Object2IntOpenHashMap<>();
private final Deque<TarjanFrame<Node>> frames = new ArrayDeque<>(); // recursion stack
private int dfsIndex;
private int sccsSizeCombined;
private final IntList topo = new IntArrayList(index.length); // nodes in reverse topological order
private ToIntFunction<? super Node> getNodeIndex;
private void build() {
private Tarjan(ObjIntConsumer<Node> putTNumber, ObjIntConsumer<Node> putNNumber, Node[] allNodes, boolean useIdentityStrategy) {
if (useIdentityStrategy) {
Reference2IntOpenHashMap<Node> nodeIndex = new Reference2IntOpenHashMap<>(allNodes.length);
build(putTNumber, putNNumber, allNodes, nodeIndex::put, nodeIndex);
}
else {
Object2IntMap<Node> nodeIndex = new Object2IntOpenHashMap<>(allNodes.length);
build(putTNumber, putNNumber, allNodes, nodeIndex::put, nodeIndex);
}
}
private void build(ObjIntConsumer<Node> putTNumber,
ObjIntConsumer<Node> putNNumber,
Node[] allNodes,
@NotNull ObjIntConsumer<Node> putNodeIndex,
@NotNull ToIntFunction<? super Node> getNodeIndex) {
this.getNodeIndex = getNodeIndex;
Arrays.fill(index, -1);
for (int i = 0; i < myAllNodes.length; i++) {
Node node = myAllNodes[i];
nodeIndex.put(node, i);
for (int i = 0; i < allNodes.length; i++) {
Node node = allNodes[i];
putNodeIndex.accept(node, i);
}
for (int i = 0; i < index.length; i++) {
if (index[i] != -1) {
continue;
}
frames.addLast(new Frame(i));
frames.addLast(new TarjanFrame<>(i, allNodes, graphAdapter.buildOuts(getNodeIndex, allNodes[i])));
List<List<Node>> sccs = new ArrayList<>();
strongConnect(sccs);
strongConnect(sccs, allNodes);
for (List<Node> scc : sccs) {
int sccSize = scc.size();
@@ -146,7 +206,7 @@ public final class DFSTBuilder<Node> {
int sccBase = index.length - sccsSizeCombined - sccSize;
// root node should be first in scc for some reason
Node rootNode = myAllNodes[i];
Node rootNode = allNodes[i];
int rIndex = scc.indexOf(rootNode);
if (rIndex != -1) {
Node e1 = scc.get(rIndex);
@@ -159,7 +219,7 @@ public final class DFSTBuilder<Node> {
Node sccNode = scc.get(j);
int tIndex = sccBase + j;
myInvT[tIndex] = sccNode;
myNodeToTNumber.put(sccNode, tIndex);
putTNumber.accept(sccNode, tIndex);
}
sccsSizeCombined += sccSize;
}
@@ -167,9 +227,9 @@ public final class DFSTBuilder<Node> {
for (int i = 0; i < topo.size(); i++) {
int nodeI = topo.getInt(i);
Node node = myAllNodes[nodeI];
Node node = allNodes[nodeI];
myNodeToNNumber.put(node, index.length - 1 - i);
putNNumber.accept(node, index.length - 1 - i);
myInvN[index.length - 1 - i] = node;
}
@@ -181,11 +241,11 @@ public final class DFSTBuilder<Node> {
}
}
private void strongConnect(@NotNull List<? super List<Node>> sccs) {
private void strongConnect(@NotNull List<? super List<Node>> sccs, Node[] allNodes) {
int successor = -1;
nextNode:
while (!frames.isEmpty()) {
Frame pair = frames.peekLast();
TarjanFrame<Node> pair = frames.peekLast();
int i = pair.nodeI;
// we have returned to the node
@@ -206,14 +266,14 @@ public final class DFSTBuilder<Node> {
while (pair.nextUnexploredIndex < pair.out.length) {
int nextI = pair.out[pair.nextUnexploredIndex++];
if (index[nextI] == -1) {
frames.addLast(new Frame(nextI));
frames.addLast(new TarjanFrame<>(nextI, allNodes, graphAdapter.buildOuts(getNodeIndex, allNodes[nextI])));
continue nextNode;
}
if (isOnStack[nextI]) {
lowLink[i] = Math.min(lowLink[i], index[nextI]);
if (myBackEdge == null) {
myBackEdge = new AbstractMap.SimpleImmutableEntry<>(myAllNodes[nextI], myAllNodes[i]);
myBackEdge = new AbstractMap.SimpleImmutableEntry<>(allNodes[nextI], allNodes[i]);
}
}
}
@@ -226,7 +286,7 @@ public final class DFSTBuilder<Node> {
int pushedI;
do {
pushedI = nodesOnStack.popInt();
Node pushed = myAllNodes[pushedI];
Node pushed = allNodes[pushedI];
isOnStack[pushedI] = false;
scc.add(pushed);
}
@@ -250,13 +310,13 @@ public final class DFSTBuilder<Node> {
public Comparator<Node> comparator(boolean useNNumber) {
if (useNNumber) {
if (myNComparator == null) {
myNComparator = Comparator.comparingInt(myNodeToNNumber::getInt);
myNComparator = Comparator.comparingInt(myNodeToNNumber);
}
return myNComparator;
}
else {
if (myTComparator == null) {
myTComparator = Comparator.comparingInt(myNodeToTNumber::getInt);
myTComparator = Comparator.comparingInt(myNodeToTNumber);
}
return myTComparator;
}
@@ -373,10 +433,9 @@ public final class DFSTBuilder<Node> {
}
}
@NotNull
public List<Node> getSortedNodes() {
List<Node> result = new ArrayList<>(myGraph.getNodes());
result.sort(comparator());
return result;
public @NotNull List<Node> getSortedNodes() {
Node[] result = allNodes.clone();
Arrays.sort(result, comparator());
return Arrays.asList(result);
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-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.
*/
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.graph;
import org.jetbrains.annotations.NotNull;
@@ -21,9 +7,7 @@ import java.util.Collection;
import java.util.Iterator;
public interface OutboundSemiGraph<Node> {
@NotNull
Collection<Node> getNodes();
@NotNull Collection<Node> getNodes();
@NotNull
Iterator<Node> getOut(Node n);
@NotNull Iterator<Node> getOut(Node n);
}
@@ -36,8 +36,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
public class TailRecursionInspection extends BaseInspection {
public final class TailRecursionInspection extends BaseInspection {
@Override
@NotNull
protected String buildErrorString(Object... infos) {
@@ -67,8 +66,7 @@ public class TailRecursionInspection extends BaseInspection {
return true;
}
private static class RemoveTailRecursionFix extends InspectionGadgetsFix {
private static final class RemoveTailRecursionFix extends InspectionGadgetsFix {
@Override
@NotNull
public String getFamilyName() {
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.lang.properties.editor;
import com.intellij.lang.properties.IProperty;
@@ -186,16 +186,16 @@ public final class ResourceBundlePropertiesUpdateManager {
}
}));
DFSTBuilder<String> dfstBuilder = new DFSTBuilder<>(generator);
final boolean acyclic = dfstBuilder.isAcyclic();
boolean acyclic = dfstBuilder.isAcyclic();
if (acyclic) {
List<String> sortedNodes = new ArrayList<>(generator.getNodes());
if (isAlphaSorted[0]) {
final List<String> sortedNodes = new ArrayList<>(generator.getNodes());
sortedNodes.sort(String.CASE_INSENSITIVE_ORDER);
return Pair.create(sortedNodes, true);
} else {
final List<String> dfsNodes = dfstBuilder.getSortedNodes();
Collections.reverse(dfsNodes);
return Pair.create(dfsNodes, false);
return new Pair<>(sortedNodes, true);
}
else {
sortedNodes.sort(dfstBuilder.comparator().reversed());
return new Pair<>(sortedNodes, false);
}
}
else {