mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
configuration and sorting using modules and not plugins
GitOrigin-RevId: 632049b4db917b88f453d774d8a1e404cb4641e6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
583d835f4a
commit
c0b2f613a4
@@ -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)
|
||||
|
||||
+2
-3
@@ -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,
|
||||
|
||||
+12
-10
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+167
-173
@@ -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();
|
||||
|
||||
+8
-4
@@ -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;
|
||||
|
||||
+8
-8
@@ -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 'optional_cycle1', 'optional_cycle2', 'optional_cycle0' cannot be loaded because they form a dependency cycle
|
||||
Plugins 'cycle5', 'cycle4' cannot be loaded because they form a dependency cycle
|
||||
Plugins 'cycle2', 'cycle3', 'cycle1' cannot be loaded because they form a dependency cycle
|
||||
Plugin 'cycle0' requires plugin 'cycle1' to be enabled
|
||||
Plugin 'incompatible0' (version 'null') 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 'incompatible0-alt' (version 'null') 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 'incompatible1' (version 'null') 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 'incompatible2' (version 'null') 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 'optional_cycle0', 'optional_cycle2', 'optional_cycle1' cannot be loaded because they form a dependency cycle
|
||||
Plugins 'optional_config_cycle0', 'optional_config_cycle2' cannot be loaded because they form a dependency cycle
|
||||
Plugins 'cycle4', 'cycle5' cannot be loaded because they form a dependency cycle
|
||||
Plugins 'cycle1', 'cycle3', 'cycle2' cannot be loaded because they form a dependency cycle
|
||||
Plugin 'cycle0' requires plugin '
|
||||
cycle1
|
||||
' to be installed
|
||||
Plugin 'incompatible0' (version '2042.42') 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 'incompatible0-alt' (version '2042.42') 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 'incompatible1' (version '2042.42') 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 'incompatible2' (version '2042.42') 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 'incompatible_with_enabled_module' is not compatible with the IDE because the IDE contains module 'com.intellij.modules.plugin0' which conflicts with the plugin
|
||||
Plugin 'missing1' requires plugin 'com.intellij.modules.missing' to be installed
|
||||
Plugin 'missing2' requires plugin 'com.intellij.missing' to be installed
|
||||
|
||||
@@ -173,11 +173,11 @@
|
||||
|
||||
|
||||
Plugin 'Jupyter Notebook' requires plugin 'com.intellij.modules.pycharm' to be installed
|
||||
Plugin 'Python pytest-bdd support' requires plugin 'Python BDD support for engines, based on Gherkin (cucumber, behave)' to be enabled
|
||||
Plugin 'Python pytest-bdd support' requires plugin 'com.intellij.modules.pycharm.professional' to be installed
|
||||
Plugin 'Python Docker' requires plugin 'com.intellij.modules.pycharm.professional' to be installed
|
||||
Plugin 'Python BDD support for engines, based on Gherkin (cucumber, behave)' requires plugin 'com.intellij.modules.pycharm.professional' to be installed
|
||||
Plugin 'Python SSH Interpreter' requires plugin 'com.intellij.modules.pycharm.professional' to be installed
|
||||
Plugin 'Python WSL Support' requires plugin 'Python SSH Interpreter' to be enabled
|
||||
Plugin 'Python WSL Support' requires plugin 'com.intellij.modules.pycharm.professional' to be installed
|
||||
Plugin 'Concurrency Visualiser' requires plugin 'com.intellij.modules.pycharm.professional' to be installed
|
||||
<a href="disable">Disable not loaded plugins</a>
|
||||
<a href="edit">Open plugin manager</a>
|
||||
+9
-8
@@ -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));
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -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)
|
||||
|
||||
+8
-2
@@ -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()
|
||||
}
|
||||
|
||||
+31
-30
@@ -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)
|
||||
|
||||
+9
-5
@@ -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);
|
||||
}
|
||||
+2
-4
@@ -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() {
|
||||
|
||||
+8
-8
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user