summaryrefslogtreecommitdiff
path: root/platform/extensions
diff options
context:
space:
mode:
authorAndrej Shadura <andrew.shadura@collabora.co.uk>2019-08-28 14:13:29 +0200
committerAndrej Shadura <andrew.shadura@collabora.co.uk>2019-08-29 17:48:13 +0200
commite19ef5983707e6a5c8d127f1ac8f02754cef82fd (patch)
tree9e3852cb9abc81ed6aa444465928d45fd7763dea /platform/extensions
New upstream version 0~183.5153.4+dfsg
Diffstat (limited to 'platform/extensions')
-rw-r--r--platform/extensions/intellij.platform.extensions.iml170
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/AbstractExtensionPointBean.java71
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/AreaInstance.java22
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/AreaListener.java26
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/AreaPicoContainer.java24
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/BaseExtensionPointName.java22
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/CustomLoadingExtensionPointBean.java49
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/DefaultPluginDescriptor.java53
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/EPAvailabilityListenerExtension.java73
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/Extension.java28
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/ExtensionException.java39
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/ExtensionFactory.java24
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/ExtensionPoint.java65
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointAndAreaListener.java26
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointAvailabilityListener.java26
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointListener.java15
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointName.java86
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/Extensions.java227
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/ExtensionsArea.java50
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/KeyedFactoryEPBean.java34
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/LoadingOrder.java208
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/PluginAware.java28
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/PluginDescriptor.java24
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/PluginId.java75
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/ProjectExtensionPointName.java35
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/SimpleSmartExtensionPoint.java26
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/SmartExtensionPoint.java87
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/SortingException.java23
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionComponentAdapter.java173
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionPointImpl.java546
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionsAreaImpl.java406
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/impl/PicoPluginExtensionInitializationException.java32
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/impl/UndefinedPluginDescriptor.java34
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/impl/package.html4
-rw-r--r--platform/extensions/src/com/intellij/openapi/extensions/package.html4
-rw-r--r--platform/extensions/src/com/intellij/openapi/util/KeyedExtensionFactory.java114
-rw-r--r--platform/extensions/src/com/intellij/util/pico/AssignableToComponentAdapter.java23
-rw-r--r--platform/extensions/src/com/intellij/util/pico/CachingConstructorInjectionComponentAdapter.java200
-rw-r--r--platform/extensions/src/com/intellij/util/pico/DefaultPicoContainer.java407
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectOne.java31
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectThree.java43
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectTwo.java31
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionComponentAdapterTest.java43
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java282
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsAreaTest.java39
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsComplexTest.java122
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java289
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/LoadingOrderTest.java190
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/NonCreatableClass.java31
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/TestExtensionClassOne.java37
-rw-r--r--platform/extensions/testSrc/com/intellij/openapi/extensions/impl/XMLTestBean.java86
51 files changed, 4803 insertions, 0 deletions
diff --git a/platform/extensions/intellij.platform.extensions.iml b/platform/extensions/intellij.platform.extensions.iml
new file mode 100644
index 00000000..246056a0
--- /dev/null
+++ b/platform/extensions/intellij.platform.extensions.iml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="JDOM" level="project" />
+ <orderEntry type="library" name="picocontainer" level="project" />
+ <orderEntry type="module" module-name="intellij.platform.util" />
+ <orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
+ <orderEntry type="library" scope="TEST" name="Xerces" level="project" />
+ <orderEntry type="library" scope="TEST" name="assertJ" level="project" />
+ </component>
+ <component name="copyright">
+ <Base>
+ <setting name="state" value="0" />
+ </Base>
+ <LanguageOptions name="HTML">
+ <option name="templateOptions">
+ <value>
+ <option name="block" value="true" />
+ <option name="separateBefore" value="false" />
+ <option name="separateAfter" value="false" />
+ <option name="prefixLines" value="true" />
+ <option name="lenBefore" value="80" />
+ <option name="lenAfter" value="80" />
+ <option name="box" value="false" />
+ <option name="filler" value=" " />
+ </value>
+ </option>
+ <option name="notice" value="Copyright (c) &amp;#36;today.year, Your Corporation. All Rights Reserved." />
+ <option name="keyword" value="Copyright" />
+ <option name="fileTypeOverride" value="2" />
+ <option name="relativeBefore" value="true" />
+ <option name="addBlankAfter" value="true" />
+ <option name="fileLocation" value="1" />
+ <option name="useAlternate" value="false" />
+ </LanguageOptions>
+ <LanguageOptions name="JAVA">
+ <option name="templateOptions">
+ <value>
+ <option name="block" value="true" />
+ <option name="separateBefore" value="false" />
+ <option name="separateAfter" value="false" />
+ <option name="prefixLines" value="true" />
+ <option name="lenBefore" value="80" />
+ <option name="lenAfter" value="80" />
+ <option name="box" value="false" />
+ <option name="filler" value=" " />
+ </value>
+ </option>
+ <option name="notice" value="/*&#10; * Copyright 2000-2005 JetBrains s.r.o.&#10; * &#10; * Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10; * you may not use this file except in compliance with the License.&#10; * You may obtain a copy of the License at&#10; * &#10; * http://www.apache.org/licenses/LICENSE-2.0&#10; * &#10; * Unless required by applicable law or agreed to in writing, software&#10; * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10; * See the License for the specific language governing permissions and&#10; * limitations under the License.&#10; */" />
+ <option name="keyword" value="Copyright" />
+ <option name="fileTypeOverride" value="2" />
+ <option name="relativeBefore" value="true" />
+ <option name="addBlankAfter" value="true" />
+ <option name="fileLocation" value="1" />
+ <option name="useAlternate" value="false" />
+ </LanguageOptions>
+ <LanguageOptions name="JSP">
+ <option name="templateOptions">
+ <value>
+ <option name="block" value="true" />
+ <option name="separateBefore" value="false" />
+ <option name="separateAfter" value="false" />
+ <option name="prefixLines" value="true" />
+ <option name="lenBefore" value="80" />
+ <option name="lenAfter" value="80" />
+ <option name="box" value="false" />
+ <option name="filler" value=" " />
+ </value>
+ </option>
+ <option name="notice" value="Copyright (c) &amp;#36;today.year, Your Corporation. All Rights Reserved." />
+ <option name="keyword" value="Copyright" />
+ <option name="fileTypeOverride" value="2" />
+ <option name="relativeBefore" value="true" />
+ <option name="addBlankAfter" value="true" />
+ <option name="fileLocation" value="1" />
+ <option name="useAlternate" value="false" />
+ </LanguageOptions>
+ <LanguageOptions name="JavaScript">
+ <option name="templateOptions">
+ <value>
+ <option name="block" value="true" />
+ <option name="separateBefore" value="false" />
+ <option name="separateAfter" value="false" />
+ <option name="prefixLines" value="true" />
+ <option name="lenBefore" value="80" />
+ <option name="lenAfter" value="80" />
+ <option name="box" value="false" />
+ <option name="filler" value=" " />
+ </value>
+ </option>
+ <option name="notice" value="Copyright (c) &amp;#36;today.year, Your Corporation. All Rights Reserved." />
+ <option name="keyword" value="Copyright" />
+ <option name="fileTypeOverride" value="2" />
+ <option name="relativeBefore" value="true" />
+ <option name="addBlankAfter" value="true" />
+ <option name="fileLocation" value="1" />
+ <option name="useAlternate" value="false" />
+ </LanguageOptions>
+ <LanguageOptions name="Properties">
+ <option name="templateOptions">
+ <value>
+ <option name="block" value="true" />
+ <option name="separateBefore" value="false" />
+ <option name="separateAfter" value="false" />
+ <option name="prefixLines" value="true" />
+ <option name="lenBefore" value="80" />
+ <option name="lenAfter" value="80" />
+ <option name="box" value="false" />
+ <option name="filler" value=" " />
+ </value>
+ </option>
+ <option name="notice" value="Copyright (c) &amp;#36;today.year, Your Corporation. All Rights Reserved." />
+ <option name="keyword" value="Copyright" />
+ <option name="fileTypeOverride" value="2" />
+ <option name="relativeBefore" value="true" />
+ <option name="addBlankAfter" value="true" />
+ <option name="fileLocation" value="1" />
+ <option name="useAlternate" value="false" />
+ </LanguageOptions>
+ <LanguageOptions name="XML">
+ <option name="templateOptions">
+ <value>
+ <option name="block" value="true" />
+ <option name="separateBefore" value="false" />
+ <option name="separateAfter" value="false" />
+ <option name="prefixLines" value="true" />
+ <option name="lenBefore" value="80" />
+ <option name="lenAfter" value="80" />
+ <option name="box" value="false" />
+ <option name="filler" value=" " />
+ </value>
+ </option>
+ <option name="notice" value="Copyright (c) &amp;#36;today.year, Your Corporation. All Rights Reserved." />
+ <option name="keyword" value="Copyright" />
+ <option name="fileTypeOverride" value="2" />
+ <option name="relativeBefore" value="true" />
+ <option name="addBlankAfter" value="true" />
+ <option name="fileLocation" value="1" />
+ <option name="useAlternate" value="false" />
+ </LanguageOptions>
+ <LanguageOptions name="__TEMPLATE__">
+ <option name="templateOptions">
+ <value>
+ <option name="block" value="true" />
+ <option name="separateBefore" value="false" />
+ <option name="separateAfter" value="false" />
+ <option name="prefixLines" value="true" />
+ <option name="lenBefore" value="80" />
+ <option name="lenAfter" value="80" />
+ <option name="box" value="false" />
+ <option name="filler" value=" " />
+ </value>
+ </option>
+ <option name="notice" value="Copyright 2000-&amp;#36;{today.year} JetBrains s.r.o.&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
+ <option name="keyword" value="Copyright" />
+ <option name="fileTypeOverride" value="4" />
+ <option name="relativeBefore" value="true" />
+ <option name="addBlankAfter" value="true" />
+ <option name="fileLocation" value="1" />
+ <option name="useAlternate" value="false" />
+ </LanguageOptions>
+ </component>
+</module> \ No newline at end of file
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/AbstractExtensionPointBean.java b/platform/extensions/src/com/intellij/openapi/extensions/AbstractExtensionPointBean.java
new file mode 100644
index 00000000..2511b241
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/AbstractExtensionPointBean.java
@@ -0,0 +1,71 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.pico.CachingConstructorInjectionComponentAdapter;
+import com.intellij.util.xmlb.annotations.Transient;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.picocontainer.PicoContainer;
+
+/**
+ * @author peter
+ */
+public abstract class AbstractExtensionPointBean implements PluginAware {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.extensions.AbstractExtensionPointBean");
+ protected PluginDescriptor myPluginDescriptor;
+
+ @Transient
+ public PluginDescriptor getPluginDescriptor() {
+ return myPluginDescriptor;
+ }
+
+ @Override
+ public final void setPluginDescriptor(PluginDescriptor pluginDescriptor) {
+ myPluginDescriptor = pluginDescriptor;
+ }
+
+ @Nullable
+ public PluginId getPluginId() {
+ return myPluginDescriptor == null ? null : myPluginDescriptor.getPluginId();
+ }
+
+ @NotNull
+ public final <T> Class<T> findClass(@NotNull String className) throws ClassNotFoundException {
+ return (Class<T>)Class.forName(className, true, getLoaderForClass());
+ }
+
+ @Nullable
+ public final <T> Class<T> findClassNoExceptions(final String className) {
+ try {
+ return findClass(className);
+ }
+ catch (ClassNotFoundException e) {
+ LOG.error("Problem loading class " + className + " from plugin " + myPluginDescriptor.getPluginId().getIdString(), e);
+ return null;
+ }
+ }
+
+ @NotNull
+ public ClassLoader getLoaderForClass() {
+ return myPluginDescriptor == null ? getClass().getClassLoader() : myPluginDescriptor.getPluginClassLoader();
+ }
+
+ @NotNull
+ public final <T> T instantiate(final String className, @NotNull final PicoContainer container) throws ClassNotFoundException {
+ return instantiate(this.findClass(className), container);
+ }
+
+ @NotNull
+ public static <T> T instantiate(@NotNull final Class<T> aClass, @NotNull final PicoContainer container) {
+ return instantiate(aClass, container, false);
+ }
+
+ @NotNull
+ public static <T> T instantiate(@NotNull final Class<T> aClass,
+ @NotNull final PicoContainer container,
+ final boolean allowNonPublicClasses) {
+ return (T)new CachingConstructorInjectionComponentAdapter(aClass.getName(), aClass, null, allowNonPublicClasses).getComponentInstance(container);
+ }
+
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/AreaInstance.java b/platform/extensions/src/com/intellij/openapi/extensions/AreaInstance.java
new file mode 100644
index 00000000..c0f8b09e
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/AreaInstance.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+/**
+ * @author akireyev
+ */
+public interface AreaInstance {
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/AreaListener.java b/platform/extensions/src/com/intellij/openapi/extensions/AreaListener.java
new file mode 100644
index 00000000..a2386369
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/AreaListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author akireyev
+ */
+public interface AreaListener {
+ void areaCreated(@NotNull String areaClass, @NotNull AreaInstance areaInstance);
+ void areaDisposing(@NotNull String areaClass, @NotNull AreaInstance areaInstance);
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/AreaPicoContainer.java b/platform/extensions/src/com/intellij/openapi/extensions/AreaPicoContainer.java
new file mode 100644
index 00000000..be60bf58
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/AreaPicoContainer.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+import org.picocontainer.MutablePicoContainer;
+
+/**
+ * @author Alexander Kireyev
+ */
+public interface AreaPicoContainer extends MutablePicoContainer {
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/BaseExtensionPointName.java b/platform/extensions/src/com/intellij/openapi/extensions/BaseExtensionPointName.java
new file mode 100644
index 00000000..d48c08be
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/BaseExtensionPointName.java
@@ -0,0 +1,22 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import org.jetbrains.annotations.NotNull;
+
+public abstract class BaseExtensionPointName {
+ private final String myName;
+
+ public BaseExtensionPointName(@NotNull String name) {
+ myName = name;
+ }
+
+ @NotNull
+ public final String getName() {
+ return myName;
+ }
+
+ @Override
+ public final String toString() {
+ return myName;
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/CustomLoadingExtensionPointBean.java b/platform/extensions/src/com/intellij/openapi/extensions/CustomLoadingExtensionPointBean.java
new file mode 100644
index 00000000..e1354407
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/CustomLoadingExtensionPointBean.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.intellij.openapi.extensions;
+
+import com.intellij.util.xmlb.annotations.Attribute;
+import org.jetbrains.annotations.NotNull;
+import org.picocontainer.PicoContainer;
+
+/**
+ * @author yole
+ */
+public class CustomLoadingExtensionPointBean extends AbstractExtensionPointBean {
+ @Attribute("factoryClass")
+ public String factoryClass;
+
+ @Attribute("factoryArgument")
+ public String factoryArgument;
+
+ @NotNull
+ protected Object instantiateExtension(String implementationClass, @NotNull PicoContainer picoContainer) throws ClassNotFoundException {
+ if (factoryClass != null) {
+ ExtensionFactory factory = instantiate(factoryClass, picoContainer);
+ return factory.createInstance(factoryArgument, implementationClass);
+ }
+ else {
+ if (implementationClass == null) {
+ throw new RuntimeException("implementation class is not specified for unknown language extension point, " +
+ "plugin id: " +
+ (myPluginDescriptor == null ? "<not available>" : myPluginDescriptor.getPluginId()) + ". " +
+ "Check if 'implementationClass' attribute is specified");
+ }
+ return instantiate(findClass(implementationClass), picoContainer, true);
+ }
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/DefaultPluginDescriptor.java b/platform/extensions/src/com/intellij/openapi/extensions/DefaultPluginDescriptor.java
new file mode 100644
index 00000000..e3a0e364
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/DefaultPluginDescriptor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class DefaultPluginDescriptor implements PluginDescriptor {
+ @NotNull
+ private final PluginId myPluginId;
+ private final ClassLoader myPluginClassLoader;
+
+ public DefaultPluginDescriptor(String pluginId) {
+ myPluginId = PluginId.getId(pluginId);
+ myPluginClassLoader = null;
+ }
+
+ public DefaultPluginDescriptor(@NotNull PluginId pluginId) {
+ myPluginId = pluginId;
+ myPluginClassLoader = null;
+ }
+
+ public DefaultPluginDescriptor(@NotNull PluginId pluginId, final ClassLoader pluginClassLoader) {
+ myPluginId = pluginId;
+ myPluginClassLoader = pluginClassLoader;
+ }
+
+ @Override
+ @NotNull
+ public PluginId getPluginId() {
+ return myPluginId;
+ }
+
+ @Override
+ public ClassLoader getPluginClassLoader() {
+ return myPluginClassLoader;
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/EPAvailabilityListenerExtension.java b/platform/extensions/src/com/intellij/openapi/extensions/EPAvailabilityListenerExtension.java
new file mode 100644
index 00000000..21a75c9d
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/EPAvailabilityListenerExtension.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author AKireyev
+ */
+public class EPAvailabilityListenerExtension implements PluginAware {
+ public static final String EXTENSION_POINT_NAME = "com.intellij.openapi.extensions.epAvailabilityListener";
+
+ private String myExtensionPointName;
+ private String myListenerClass;
+ private PluginDescriptor myPluginDescriptor;
+
+ public EPAvailabilityListenerExtension() {
+ }
+
+ public EPAvailabilityListenerExtension(@NotNull String extensionPointName, @NotNull String listenerClass) {
+ myExtensionPointName = extensionPointName;
+ myListenerClass = listenerClass;
+ }
+
+ @NotNull
+ public String getExtensionPointName() {
+ return myExtensionPointName;
+ }
+
+ public void setExtensionPointName(@NotNull String extensionPointName) {
+ myExtensionPointName = extensionPointName;
+ }
+
+ @NotNull
+ public String getListenerClass() {
+ return myListenerClass;
+ }
+
+ public void setListenerClass(@NotNull String listenerClass) {
+ myListenerClass = listenerClass;
+ }
+
+ @Override
+ public void setPluginDescriptor(PluginDescriptor pluginDescriptor) {
+ myPluginDescriptor = pluginDescriptor;
+ }
+
+ public PluginDescriptor getPluginDescriptor() {
+ return myPluginDescriptor;
+ }
+
+ public Class loadListenerClass() throws ClassNotFoundException {
+ if (myPluginDescriptor != null && myPluginDescriptor.getPluginClassLoader() != null) {
+ return Class.forName(getListenerClass(), true, myPluginDescriptor.getPluginClassLoader());
+ }
+ else {
+ return Class.forName(getListenerClass());
+ }
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/Extension.java b/platform/extensions/src/com/intellij/openapi/extensions/Extension.java
new file mode 100644
index 00000000..0f6a62c8
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/Extension.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author kir
+ *
+ * An extension can implement this interface to get notifications when it is added/removed to {@link ExtensionPoint}
+ */
+public interface Extension {
+ void extensionAdded(@NotNull ExtensionPoint extensionPoint);
+ void extensionRemoved(@NotNull ExtensionPoint extensionPoint);
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/ExtensionException.java b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionException.java
new file mode 100644
index 00000000..90eeeae2
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+import org.jetbrains.annotations.NotNull;
+
+public class ExtensionException extends RuntimeException{
+ private final Class<?> myExtensionClass;
+
+ public ExtensionException(@NotNull Class<?> extensionClass) {
+ super(extensionClass.getCanonicalName());
+
+ myExtensionClass = extensionClass;
+ }
+
+ public ExtensionException(@NotNull Class<?> extensionClass, @NotNull Throwable cause) {
+ super(extensionClass.getCanonicalName(), cause);
+
+ myExtensionClass = extensionClass;
+ }
+
+ @NotNull
+ public Class<?> getExtensionClass() {
+ return myExtensionClass;
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/ExtensionFactory.java b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionFactory.java
new file mode 100644
index 00000000..8256e6ed
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionFactory.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.intellij.openapi.extensions;
+
+/**
+ * @author yole
+ */
+public interface ExtensionFactory {
+ Object createInstance(final String factoryArgument, String implementationClass);
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPoint.java b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPoint.java
new file mode 100644
index 00000000..09305bf7
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPoint.java
@@ -0,0 +1,65 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import com.intellij.openapi.Disposable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+public interface ExtensionPoint<T> {
+ @NotNull
+ String getName();
+
+ AreaInstance getArea();
+
+ void registerExtension(@NotNull T extension);
+
+ void registerExtension(@NotNull T extension, @NotNull LoadingOrder order);
+
+ /**
+ * Prefer to use {@link #getExtensionList()}.
+ */
+ @NotNull
+ T[] getExtensions();
+
+ @NotNull
+ List<T> getExtensionList();
+
+ @NotNull
+ Stream<T> extensions();
+
+ boolean hasAnyExtensions();
+
+ @Nullable
+ T getExtension();
+
+ boolean hasExtension(@NotNull T extension);
+
+ void unregisterExtension(@NotNull T extension);
+
+ /**
+ * Unregisters an extension of the specified type.
+ */
+ void unregisterExtension(@NotNull Class<? extends T> extensionClass);
+
+ void addExtensionPointListener(@NotNull ExtensionPointListener<T> listener, @NotNull Disposable parentDisposable);
+
+ void addExtensionPointListener(@NotNull ExtensionPointListener<T> listener);
+
+ void removeExtensionPointListener(@NotNull ExtensionPointListener<T> extensionPointListener);
+
+ void reset();
+
+ @NotNull
+ Class<T> getExtensionClass();
+
+ @NotNull
+ Kind getKind();
+
+ @NotNull
+ String getClassName();
+
+ enum Kind {INTERFACE, BEAN_CLASS}
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointAndAreaListener.java b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointAndAreaListener.java
new file mode 100644
index 00000000..277fa4d9
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointAndAreaListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2009 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.
+ */
+
+/*
+ * @author max
+ */
+package com.intellij.openapi.extensions;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface ExtensionPointAndAreaListener<T> extends ExtensionPointListener<T> {
+ void areaReplaced(@NotNull ExtensionsArea oldArea);
+} \ No newline at end of file
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointAvailabilityListener.java b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointAvailabilityListener.java
new file mode 100644
index 00000000..d2926c54
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointAvailabilityListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author AKireyev
+ */
+public interface ExtensionPointAvailabilityListener {
+ void extensionPointRegistered(@NotNull ExtensionPoint extensionPoint);
+ void extensionPointRemoved(@NotNull ExtensionPoint extensionPoint);
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointListener.java b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointListener.java
new file mode 100644
index 00000000..e1c5ae8a
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointListener.java
@@ -0,0 +1,15 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public interface ExtensionPointListener<T> {
+ ExtensionPointListener[] EMPTY_ARRAY = new ExtensionPointListener[0];
+
+ default void extensionAdded(@NotNull T extension, @Nullable PluginDescriptor pluginDescriptor) {
+ }
+
+ default void extensionRemoved(@NotNull T extension, @Nullable PluginDescriptor pluginDescriptor) {
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointName.java b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointName.java
new file mode 100644
index 00000000..1a000d91
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionPointName.java
@@ -0,0 +1,86 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+public final class ExtensionPointName<T> extends BaseExtensionPointName {
+ public ExtensionPointName(@NotNull String name) {
+ super(name);
+ }
+
+ @NotNull
+ public static <T> ExtensionPointName<T> create(@NotNull @NonNls final String name) {
+ return new ExtensionPointName<>(name);
+ }
+
+ /**
+ * Prefer to use {@link #getExtensionList()}.
+ */
+ @NotNull
+ public T[] getExtensions() {
+ return getPoint(null).getExtensions();
+ }
+
+ @NotNull
+ public List<T> getExtensionList() {
+ return getPoint(null).getExtensionList();
+ }
+
+ @NotNull
+ public Stream<T> extensions() {
+ return getPoint(null).extensions();
+ }
+
+ public boolean hasAnyExtensions() {
+ return getPoint(null).hasAnyExtensions();
+ }
+
+ /**
+ * Consider using {@link ProjectExtensionPointName#getExtensions(AreaInstance)}
+ */
+ @NotNull
+ public List<T> getExtensionList(@Nullable AreaInstance areaInstance) {
+ return getPoint(areaInstance).getExtensionList();
+ }
+
+ /**
+ * Consider using {@link ProjectExtensionPointName#getExtensions(AreaInstance)}
+ */
+ @NotNull
+ public T[] getExtensions(@Nullable AreaInstance areaInstance) {
+ return getPoint(areaInstance).getExtensions();
+ }
+
+ /**
+ * Consider using {@link ProjectExtensionPointName#extensions(AreaInstance)}
+ */
+ @NotNull
+ public Stream<T> extensions(@Nullable AreaInstance areaInstance) {
+ return getPoint(areaInstance).extensions();
+ }
+
+ @NotNull
+ public ExtensionPoint<T> getPoint(@Nullable AreaInstance areaInstance) {
+ return Extensions.getArea(areaInstance).getExtensionPoint(getName());
+ }
+
+ @Nullable
+ public <V extends T> V findExtension(@NotNull Class<V> instanceOf) {
+ return ContainerUtil.findInstance(getExtensionList(), instanceOf);
+ }
+
+ @NotNull
+ public <V extends T> V findExtensionOrFail(@NotNull Class<V> instanceOf) {
+ V result = findExtension(instanceOf);
+ if (result == null) {
+ throw new IllegalArgumentException("could not find extension implementation " + instanceOf);
+ }
+ return result;
+ }
+} \ No newline at end of file
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/Extensions.java b/platform/extensions/src/com/intellij/openapi/extensions/Extensions.java
new file mode 100644
index 00000000..d7770132
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/Extensions.java
@@ -0,0 +1,227 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.extensions.impl.ExtensionsAreaImpl;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.SystemInfoRt;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class Extensions {
+ public static final ExtensionPointName<AreaListener> AREA_LISTENER_EXTENSION_POINT = new ExtensionPointName<>("com.intellij.arealistener");
+ private static final Map<AreaInstance, ExtensionsAreaImpl> ourAreaInstance2area = ContainerUtil.newConcurrentMap();
+ private static final Map<String, AreaClassConfiguration> ourAreaClass2Configuration = ContainerUtil.newConcurrentMap();
+
+ @NotNull
+ private static ExtensionsAreaImpl ourRootArea = createRootArea();
+
+ private Extensions() {
+ }
+
+ @NotNull
+ private static ExtensionsAreaImpl createRootArea() {
+ ExtensionsAreaImpl rootArea = new ExtensionsAreaImpl(null, null, null);
+ rootArea.registerExtensionPoint(AREA_LISTENER_EXTENSION_POINT.getName(), AreaListener.class.getName());
+ return rootArea;
+ }
+
+ /**
+ * @return instance containing application-level extensions
+ */
+ @NotNull
+ public static ExtensionsArea getRootArea() {
+ return ourRootArea;
+ }
+
+ /**
+ * If {@code areaInstance} is a project returns instance containing project-level extensions for that project
+ * if {@code areaInstance} is a module returns instance containing module-level extensions for that module,
+ * if {@code areaInstance} is {@code null} returns instance containing application-level extensions.
+ */
+ @NotNull
+ public static ExtensionsArea getArea(@Nullable("null means root") AreaInstance areaInstance) {
+ if (areaInstance == null) {
+ return ourRootArea;
+ }
+ ExtensionsAreaImpl area = ourAreaInstance2area.get(areaInstance);
+ if (area == null) {
+ throw new IllegalArgumentException("No area instantiated for: " + areaInstance);
+ }
+ return area;
+ }
+
+ @TestOnly
+ public static void cleanRootArea(@NotNull Disposable parentDisposable) {
+ final ExtensionsAreaImpl oldRootArea = (ExtensionsAreaImpl)getRootArea();
+ final ExtensionsAreaImpl newArea = createRootArea();
+ ourRootArea = newArea;
+ oldRootArea.notifyAreaReplaced(newArea);
+ Disposer.register(parentDisposable, () -> {
+ ourRootArea = oldRootArea;
+ newArea.notifyAreaReplaced(oldRootArea);
+ });
+ }
+
+ @NotNull
+ public static Object[] getExtensions(@NonNls @NotNull String extensionPointName) {
+ return getExtensions(extensionPointName, null);
+ }
+
+ /**
+ * @deprecated Use {@link ExtensionPointName#getExtensionList()}
+ */
+ @Deprecated
+ @NotNull
+ public static <T> T[] getExtensions(@NotNull ExtensionPointName<T> extensionPointName) {
+ return extensionPointName.getExtensions();
+ }
+
+ /**
+ * @deprecated Use {@link ProjectExtensionPointName#getExtensions(AreaInstance)}
+ */
+ @Deprecated
+ @NotNull
+ public static <T> T[] getExtensions(@NotNull ExtensionPointName<T> extensionPointName, @Nullable AreaInstance areaInstance) {
+ return extensionPointName.getExtensions(areaInstance);
+ }
+
+ @NotNull
+ public static <T> T[] getExtensions(@NotNull String extensionPointName, @Nullable("null means root") AreaInstance areaInstance) {
+ ExtensionsArea area = getArea(areaInstance);
+ ExtensionPoint<T> extensionPoint = area.getExtensionPoint(extensionPointName);
+ return extensionPoint.getExtensions();
+ }
+
+ /**
+ * @deprecated Use {@link ExtensionPointName#findExtensionOrFail(Class)}
+ */
+ @Deprecated
+ @NotNull
+ public static <T, U extends T> U findExtension(@NotNull ExtensionPointName<T> extensionPointName, @NotNull Class<U> extClass) {
+ return extensionPointName.findExtensionOrFail(extClass);
+ }
+
+ @NotNull
+ public static <T, U extends T> U findExtension(@NotNull ExtensionPointName<T> extensionPointName, AreaInstance areaInstance, @NotNull Class<U> extClass) {
+ for (T t : extensionPointName.getExtensions(areaInstance)) {
+ if (extClass.isInstance(t)) {
+ //noinspection unchecked
+ return (U) t;
+ }
+ }
+ throw new IllegalArgumentException("could not find extension implementation " + extClass);
+ }
+
+ public static void instantiateArea(@NonNls @NotNull String areaClass, @NotNull AreaInstance areaInstance, @Nullable("null means root") AreaInstance parentAreaInstance) {
+ AreaClassConfiguration configuration = ourAreaClass2Configuration.get(areaClass);
+ if (configuration == null) {
+ throw new IllegalArgumentException("Area class is not registered: " + areaClass);
+ }
+ ExtensionsArea parentArea = getArea(parentAreaInstance);
+ if (!Objects.equals(parentArea.getAreaClass(), configuration.getParentClassName())) {
+ throw new IllegalArgumentException("Wrong parent area. Expected class: " + configuration.getParentClassName() + " actual class: " + parentArea.getAreaClass());
+ }
+ ExtensionsAreaImpl area = new ExtensionsAreaImpl(areaClass, areaInstance, parentArea.getPicoContainer());
+ if (ourAreaInstance2area.put(areaInstance, area) != null) {
+ throw new IllegalArgumentException("Area already instantiated for: " + areaInstance);
+ }
+ for (AreaListener listener : getAreaListeners()) {
+ listener.areaCreated(areaClass, areaInstance);
+ }
+ }
+
+ @NotNull
+ private static List<AreaListener> getAreaListeners() {
+ return getRootArea().getExtensionPoint(AREA_LISTENER_EXTENSION_POINT).getExtensionList();
+ }
+
+ public static void registerAreaClass(@NonNls @NotNull String areaClass, @Nullable @NonNls String parentAreaClass) {
+ if (ourAreaClass2Configuration.containsKey(areaClass)) {
+ // allow duplicate area class registrations if they are the same - fixing duplicate registration in tests is much more trouble
+ AreaClassConfiguration configuration = ourAreaClass2Configuration.get(areaClass);
+ if (!Objects.equals(configuration.getParentClassName(), parentAreaClass)) {
+ throw new RuntimeException("Area class already registered: " + areaClass + ", "+ configuration);
+ }
+ else {
+ return;
+ }
+ }
+ AreaClassConfiguration configuration = new AreaClassConfiguration(areaClass, parentAreaClass);
+ ourAreaClass2Configuration.put(areaClass, configuration);
+ }
+
+ public static void disposeArea(@NotNull AreaInstance areaInstance) {
+ assert ourAreaInstance2area.containsKey(areaInstance);
+
+ String areaClass = ourAreaInstance2area.get(areaInstance).getAreaClass();
+ if (areaClass == null) {
+ throw new IllegalArgumentException("Area class is null (area never instantiated?). Instance: " + areaInstance);
+ }
+ try {
+ for (AreaListener listener : getAreaListeners()) {
+ listener.areaDisposing(areaClass, areaInstance);
+ }
+ }
+ finally {
+ ourAreaInstance2area.remove(areaInstance);
+ }
+ }
+
+ private static class AreaClassConfiguration {
+ private final String myClassName;
+ private final String myParentClassName;
+
+ private AreaClassConfiguration(@NotNull String className, String parentClassName) {
+ myClassName = className;
+ myParentClassName = parentClassName;
+ }
+
+ @NotNull
+ public String getClassName() {
+ return myClassName;
+ }
+
+ public String getParentClassName() {
+ return myParentClassName;
+ }
+
+ @Override
+ public String toString() {
+ return "AreaClassConfiguration{myClassName='" + myClassName + '\'' + ", myParentClassName='" + myParentClassName + "'}";
+ }
+ }
+
+ public static boolean isComponentSuitableForOs(@Nullable String os) {
+ if (StringUtil.isEmpty(os)) {
+ return true;
+ }
+
+ if (os.equals("mac")) {
+ return SystemInfoRt.isMac;
+ }
+ else if (os.equals("linux")) {
+ return SystemInfoRt.isLinux;
+ }
+ else if (os.equals("windows")) {
+ return SystemInfoRt.isWindows;
+ }
+ else if (os.equals("unix")) {
+ return SystemInfoRt.isUnix;
+ }
+ else if (os.equals("freebsd")) {
+ return SystemInfoRt.isFreeBSD;
+ }
+ else {
+ throw new IllegalArgumentException("Unknown OS " + os);
+ }
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/ExtensionsArea.java b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionsArea.java
new file mode 100644
index 00000000..af9fe61c
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/ExtensionsArea.java
@@ -0,0 +1,50 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @see Extensions#getArea(AreaInstance)
+ * @see Extensions#getRootArea()
+ */
+public interface ExtensionsArea {
+ void registerExtensionPoint(@NonNls @NotNull String extensionPointName, @NotNull String extensionPointBeanClass);
+ void registerExtensionPoint(@NonNls @NotNull String extensionPointName, @NotNull String extensionPointBeanClass, @NotNull ExtensionPoint.Kind kind);
+ void unregisterExtensionPoint(@NonNls @NotNull String extensionPointName);
+
+ boolean hasExtensionPoint(@NonNls @NotNull String extensionPointName);
+
+ boolean hasExtensionPoint(@NotNull ExtensionPointName<?> extensionPointName);
+
+ @NotNull
+ <T> ExtensionPoint<T> getExtensionPoint(@NonNls @NotNull String extensionPointName);
+
+ @NotNull
+ <T> ExtensionPoint<T> getExtensionPoint(@NotNull ExtensionPointName<T> extensionPointName);
+
+ @NotNull
+ ExtensionPoint[] getExtensionPoints();
+
+ void addAvailabilityListener(@NotNull String extensionPointName, @NotNull ExtensionPointAvailabilityListener listener);
+ void removeAvailabilityListener(@NotNull String extensionPointName, @NotNull ExtensionPointAvailabilityListener listener);
+
+ @NotNull
+ AreaPicoContainer getPicoContainer();
+
+ void registerExtensionPoint(@NotNull PluginDescriptor pluginDescriptor, @NotNull Element extensionPointElement);
+
+ /**
+ * Registers a new extension.
+ * @param pluginDescriptor plugin to which extension belongs
+ * @param extensionElement element from plugin.xml file where extension settings are specified
+ * @param extensionNs extension namespace which is prepended to the tag name from {@code extensionElement} to form the qualified extension name.
+ */
+ void registerExtension(@NotNull PluginDescriptor pluginDescriptor, @NotNull Element extensionElement, @Nullable String extensionNs);
+
+ void registerExtension(@NotNull final ExtensionPoint extensionPoint, @NotNull final PluginDescriptor pluginDescriptor, @NotNull final Element extensionElement);
+
+ String getAreaClass();
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/KeyedFactoryEPBean.java b/platform/extensions/src/com/intellij/openapi/extensions/KeyedFactoryEPBean.java
new file mode 100644
index 00000000..64926534
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/KeyedFactoryEPBean.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.intellij.openapi.extensions;
+
+import com.intellij.util.xmlb.annotations.Attribute;
+
+/**
+ * @author yole
+ */
+public class KeyedFactoryEPBean extends AbstractExtensionPointBean {
+ // these must be public for scrambling compatibility
+ @Attribute("key")
+ public String key;
+
+ @Attribute("implementationClass")
+ public String implementationClass;
+
+ @Attribute("factoryClass")
+ public String factoryClass;
+} \ No newline at end of file
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/LoadingOrder.java b/platform/extensions/src/com/intellij/openapi/extensions/LoadingOrder.java
new file mode 100644
index 00000000..0ce7349b
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/LoadingOrder.java
@@ -0,0 +1,208 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import com.intellij.openapi.util.Couple;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.graph.CachingSemiGraph;
+import com.intellij.util.graph.DFSTBuilder;
+import com.intellij.util.graph.GraphGenerator;
+import com.intellij.util.graph.InboundSemiGraph;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * All extensions can have an "order" attribute in their XML element that will affect the place where this extension will appear in the
+ * {@link ExtensionPoint#getExtensions()}. Possible values are "first", "last", "before ID" and "after ID" where ID
+ * is another same-type extension ID. Values can be combined in a comma-separated way. E.g. if you wish to plug before some extension XXX
+ * that has "first" as its order, you must be "first, before XXX". The same with "last".<p>
+ *
+ * Extension ID can be specified in the "id" attribute of corresponding XML element. When you specify order, it's usually a good practice
+ * to specify also id, to allow other plugin-writers to plug relatively to your extension.<p>
+ *
+ * If some anchor id can't be resolved, the constraint is ignored.
+ *
+ * @author Alexander Kireyev
+ */
+public class LoadingOrder {
+ @NonNls public static final String FIRST_STR = "first";
+ @NonNls public static final String LAST_STR = "last";
+ @NonNls public static final String BEFORE_STR = "before ";
+ @NonNls public static final String BEFORE_STR_OLD = "before:";
+ @NonNls public static final String AFTER_STR = "after ";
+ @NonNls public static final String AFTER_STR_OLD = "after:";
+
+ @NonNls public static final String ORDER_RULE_SEPARATOR = ",";
+
+ public static final LoadingOrder ANY = new LoadingOrder();
+ public static final LoadingOrder FIRST = new LoadingOrder(FIRST_STR);
+ public static final LoadingOrder LAST = new LoadingOrder(LAST_STR);
+
+ @NonNls private final String myName; // for debug only
+ private final boolean myFirst;
+ private final boolean myLast;
+ private final Set<String> myBefore = new LinkedHashSet<>(2);
+ private final Set<String> myAfter = new LinkedHashSet<>(2);
+
+ private LoadingOrder() {
+ myName = "ANY";
+ myFirst = false;
+ myLast = false;
+ }
+
+ private LoadingOrder(@NonNls @NotNull String text) {
+ myName = text;
+ boolean last = false;
+ boolean first = false;
+ for (final String string : StringUtil.split(text, ORDER_RULE_SEPARATOR)) {
+ String trimmed = string.trim();
+ if (trimmed.equalsIgnoreCase(FIRST_STR)) first = true;
+ else if (trimmed.equalsIgnoreCase(LAST_STR)) last = true;
+ else if (StringUtil.startsWithIgnoreCase(trimmed, BEFORE_STR)) myBefore.add(trimmed.substring(BEFORE_STR.length()).trim());
+ else if (StringUtil.startsWithIgnoreCase(trimmed, BEFORE_STR_OLD)) myBefore.add(trimmed.substring(BEFORE_STR_OLD.length()).trim());
+ else if (StringUtil.startsWithIgnoreCase(trimmed, AFTER_STR)) myAfter.add(trimmed.substring(AFTER_STR.length()).trim());
+ else if (StringUtil.startsWithIgnoreCase(trimmed, AFTER_STR_OLD)) myAfter.add(trimmed.substring(AFTER_STR_OLD.length()).trim());
+ else throw new AssertionError("Invalid specification: " + trimmed + "; should be one of FIRST, LAST, BEFORE <id> or AFTER <id>");
+ }
+ myFirst = first;
+ myLast = last;
+ }
+
+ public String toString() {
+ return myName;
+ }
+
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (!(o instanceof LoadingOrder)) return false;
+
+ final LoadingOrder that = (LoadingOrder)o;
+
+ if (myFirst != that.myFirst) return false;
+ if (myLast != that.myLast) return false;
+ if (!myAfter.equals(that.myAfter)) return false;
+ if (!myBefore.equals(that.myBefore)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result = myFirst ? 1 : 0;
+ result = 31 * result + (myLast ? 1 : 0);
+ result = 31 * result + myBefore.hashCode();
+ result = 31 * result + myAfter.hashCode();
+ return result;
+ }
+
+ public static LoadingOrder before(@NonNls final String id) {
+ return new LoadingOrder(BEFORE_STR + id);
+ }
+
+ public static LoadingOrder after(@NonNls final String id) {
+ return new LoadingOrder(AFTER_STR + id);
+ }
+
+ public static void sort(@NotNull Orderable... orderable) {
+ sort(Arrays.asList(orderable));
+ }
+
+ public static void sort(@NotNull final List<? extends Orderable> orderable) {
+ // our graph is pretty sparse so do benefit from the fact
+ final Map<String, Orderable> map = ContainerUtil.newLinkedHashMap();
+ final Map<Orderable, LoadingOrder> cachedMap = ContainerUtil.newLinkedHashMap();
+ final Set<Orderable> first = new LinkedHashSet<>(1);
+ final Set<Orderable> hasBefore = new LinkedHashSet<>(orderable.size());
+ for (Orderable o : orderable) {
+ String id = o.getOrderId();
+ if (StringUtil.isNotEmpty(id)) map.put(id, o);
+ LoadingOrder order = o.getOrder();
+ cachedMap.put(o, order);
+ if (order.myFirst) first.add(o);
+ if (!order.myBefore.isEmpty()) hasBefore.add(o);
+ }
+
+ InboundSemiGraph<Orderable> graph = new InboundSemiGraph<Orderable>() {
+ @NotNull
+ @Override
+ public Collection<Orderable> getNodes() {
+ List<Orderable> list = ContainerUtil.newArrayList(orderable);
+ Collections.reverse(list);
+ return list;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<Orderable> getIn(Orderable n) {
+ LoadingOrder order = cachedMap.get(n);
+
+ Set<Orderable> predecessors = new LinkedHashSet<>();
+ for (String id : order.myAfter) {
+ Orderable o = map.get(id);
+ if (o != null) {
+ predecessors.add(o);
+ }
+ }
+
+ String id = n.getOrderId();
+ if (StringUtil.isNotEmpty(id)) {
+ for (Orderable o : hasBefore) {
+ LoadingOrder hisOrder = cachedMap.get(o);
+ if (hisOrder.myBefore.contains(id)) {
+ predecessors.add(o);
+ }
+ }
+ }
+
+ if (order.myLast) {
+ for (Orderable o : orderable) {
+ LoadingOrder hisOrder = cachedMap.get(o);
+ if (!hisOrder.myLast) {
+ predecessors.add(o);
+ }
+ }
+ }
+
+ if (!order.myFirst) {
+ predecessors.addAll(first);
+ }
+
+ return predecessors.iterator();
+ }
+ };
+
+ DFSTBuilder<Orderable> builder = new DFSTBuilder<>(GraphGenerator.generate(CachingSemiGraph.cache(graph)));
+
+ if (!builder.isAcyclic()) {
+ Couple<Orderable> p = builder.getCircularDependency();
+ throw new SortingException("Could not satisfy sorting requirements", p.first, p.second);
+ }
+
+ orderable.sort(builder.comparator());
+ }
+
+ @NotNull
+ public static LoadingOrder readOrder(@Nullable String orderAttr) {
+ if (orderAttr == null) {
+ return ANY;
+ }
+ else if (orderAttr.equals(FIRST_STR)) {
+ return FIRST;
+ }
+ else if (orderAttr.equals(LAST_STR)) {
+ return LAST;
+ }
+ else {
+ return new LoadingOrder(orderAttr);
+ }
+ }
+
+ public interface Orderable {
+ @Nullable
+ String getOrderId();
+
+ LoadingOrder getOrder();
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/PluginAware.java b/platform/extensions/src/com/intellij/openapi/extensions/PluginAware.java
new file mode 100644
index 00000000..6fa42ce7
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/PluginAware.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+/**
+ * Extensions should implement this interface when it is important to find out what particular plugin has provided this extension.
+ * @author akireyev
+ */
+public interface PluginAware {
+ /**
+ * Called by extensions framework when extension is loaded from plugin.xml descriptor.
+ * @param pluginDescriptor descriptor of the plugin that provided this particular extension.
+ */
+ void setPluginDescriptor(PluginDescriptor pluginDescriptor);
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/PluginDescriptor.java b/platform/extensions/src/com/intellij/openapi/extensions/PluginDescriptor.java
new file mode 100644
index 00000000..601d8fb0
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/PluginDescriptor.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+/**
+ * @author Alexander Kireyev
+ */
+public interface PluginDescriptor {
+ PluginId getPluginId();
+ ClassLoader getPluginClassLoader();
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/PluginId.java b/platform/extensions/src/com/intellij/openapi/extensions/PluginId.java
new file mode 100644
index 00000000..b83036b7
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/PluginId.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions;
+
+import gnu.trove.THashMap;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Map;
+
+/**
+ * @author max
+ */
+public class PluginId implements Comparable<PluginId> {
+ public static final PluginId[] EMPTY_ARRAY = new PluginId[0];
+
+ private static final Map<String, PluginId> ourRegisteredIds = new THashMap<>();
+
+ private final String myIdString;
+
+ private PluginId(@NotNull String idString) {
+ myIdString = idString;
+ }
+
+ @Override
+ public int compareTo(@NotNull PluginId o) {
+ return myIdString.compareTo(o.myIdString);
+ }
+
+ @NotNull
+ public static synchronized PluginId getId(@NotNull String idString) {
+ return ourRegisteredIds.computeIfAbsent(idString, PluginId::new);
+ }
+
+ @Nullable
+ public static synchronized PluginId findId(@NotNull String... idStrings) {
+ for (String idString : idStrings) {
+ PluginId pluginId = ourRegisteredIds.get(idString);
+ if (pluginId != null) {
+ return pluginId;
+ }
+ }
+ return null;
+ }
+
+ @NonNls
+ @NotNull
+ public String getIdString() {
+ return myIdString;
+ }
+
+ @Override
+ public String toString() {
+ return getIdString();
+ }
+
+ @NotNull
+ public static synchronized Map<String, PluginId> getRegisteredIds() {
+ return new THashMap<>(ourRegisteredIds);
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/ProjectExtensionPointName.java b/platform/extensions/src/com/intellij/openapi/extensions/ProjectExtensionPointName.java
new file mode 100644
index 00000000..399c3c82
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/ProjectExtensionPointName.java
@@ -0,0 +1,35 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+public final class ProjectExtensionPointName<T> extends BaseExtensionPointName {
+ public ProjectExtensionPointName(@NotNull String name) {
+ super(name);
+ }
+
+ @NotNull
+ public ExtensionPoint<T> getPoint(@NotNull AreaInstance areaInstance) {
+ return Extensions.getArea(areaInstance).getExtensionPoint(getName());
+ }
+
+ @NotNull
+ public List<T> getExtensions(@NotNull AreaInstance areaInstance) {
+ return getPoint(areaInstance).getExtensionList();
+ }
+
+ @NotNull
+ public Stream<T> extensions(@NotNull AreaInstance areaInstance) {
+ return getPoint(areaInstance).extensions();
+ }
+
+ @Nullable
+ public <V extends T> V findExtension(@NotNull Class<V> instanceOf, @NotNull AreaInstance areaInstance) {
+ return ContainerUtil.findInstance(getExtensions(areaInstance), instanceOf);
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/SimpleSmartExtensionPoint.java b/platform/extensions/src/com/intellij/openapi/extensions/SimpleSmartExtensionPoint.java
new file mode 100644
index 00000000..a6e9f43c
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/SimpleSmartExtensionPoint.java
@@ -0,0 +1,26 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import com.intellij.util.SmartList;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * @author peter
+ */
+public abstract class SimpleSmartExtensionPoint<T> extends SmartExtensionPoint<T,T>{
+ public SimpleSmartExtensionPoint(@NotNull final Collection<T> explicitExtensions) {
+ super(explicitExtensions);
+ }
+
+ public SimpleSmartExtensionPoint() {
+ super(new SmartList<>());
+ }
+
+ @Override
+ @NotNull
+ protected final T getExtension(@NotNull final T t) {
+ return t;
+ }
+} \ No newline at end of file
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/SmartExtensionPoint.java b/platform/extensions/src/com/intellij/openapi/extensions/SmartExtensionPoint.java
new file mode 100644
index 00000000..a466b69a
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/SmartExtensionPoint.java
@@ -0,0 +1,87 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author peter
+ */
+public abstract class SmartExtensionPoint<Extension, V> implements ExtensionPointAndAreaListener<Extension> {
+ private final Collection<V> myExplicitExtensions;
+ private ExtensionPoint<Extension> myExtensionPoint;
+ private List<V> myCache;
+
+ protected SmartExtensionPoint(@NotNull final Collection<V> explicitExtensions) {
+ myExplicitExtensions = explicitExtensions;
+ }
+
+ @NotNull
+ protected abstract ExtensionPoint<Extension> getExtensionPoint();
+
+ public final void addExplicitExtension(@NotNull V extension) {
+ synchronized (myExplicitExtensions) {
+ myExplicitExtensions.add(extension);
+ myCache = null;
+ }
+ }
+
+ public final void removeExplicitExtension(@NotNull V extension) {
+ synchronized (myExplicitExtensions) {
+ myExplicitExtensions.remove(extension);
+ myCache = null;
+ }
+ }
+
+ @Nullable
+ protected abstract V getExtension(@NotNull final Extension extension);
+
+ @NotNull
+ public final List<V> getExtensions() {
+ synchronized (myExplicitExtensions) {
+ List<V> result = myCache;
+ if (result == null) {
+ myExtensionPoint = getExtensionPoint();
+ // EP will not add duplicated listener, so, it is safe to not care about is already added
+ myExtensionPoint.addExtensionPointListener(this);
+
+ List<V> registeredExtensions = ContainerUtil.mapNotNull(myExtensionPoint.getExtensions(), this::getExtension);
+ result = new ArrayList<>(myExplicitExtensions.size() + registeredExtensions.size());
+ result.addAll(myExplicitExtensions);
+ result.addAll(registeredExtensions);
+ myCache = result;
+ }
+ return result;
+ }
+ }
+
+ @Override
+ public final void extensionAdded(@NotNull final Extension extension, @Nullable final PluginDescriptor pluginDescriptor) {
+ dropCache();
+ }
+
+ public final void dropCache() {
+ synchronized (myExplicitExtensions) {
+ if (myCache != null) {
+ myCache = null;
+ myExtensionPoint.removeExtensionPointListener(this);
+ myExtensionPoint = null;
+ }
+ }
+ }
+
+ @Override
+ public final void extensionRemoved(@NotNull final Extension extension, @Nullable final PluginDescriptor pluginDescriptor) {
+ dropCache();
+ }
+
+ @Override
+ public final void areaReplaced(@NotNull final ExtensionsArea area) {
+ dropCache();
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/SortingException.java b/platform/extensions/src/com/intellij/openapi/extensions/SortingException.java
new file mode 100644
index 00000000..548ce14e
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/SortingException.java
@@ -0,0 +1,23 @@
+// Copyright 2000-2018 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.openapi.extensions;
+
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class SortingException extends RuntimeException {
+ private final LoadingOrder.Orderable[] myConflictingElements;
+
+ SortingException(String message, @NotNull LoadingOrder.Orderable... conflictingElements) {
+ super(message + ": " + StringUtil.join(conflictingElements,
+ item -> item.getOrderId() + "(" + item.getOrder() + ")", "; "));
+ myConflictingElements = conflictingElements;
+ }
+
+ @NotNull
+ public LoadingOrder.Orderable[] getConflictingElements() {
+ return myConflictingElements;
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionComponentAdapter.java b/platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionComponentAdapter.java
new file mode 100644
index 00000000..7d231b18
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionComponentAdapter.java
@@ -0,0 +1,173 @@
+// Copyright 2000-2018 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.openapi.extensions.impl;
+
+import com.intellij.openapi.extensions.LoadingOrder;
+import com.intellij.openapi.extensions.PluginAware;
+import com.intellij.openapi.extensions.PluginDescriptor;
+import com.intellij.openapi.extensions.PluginId;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.util.pico.AssignableToComponentAdapter;
+import com.intellij.util.pico.CachingConstructorInjectionComponentAdapter;
+import com.intellij.util.xmlb.XmlSerializer;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.picocontainer.*;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class ExtensionComponentAdapter implements LoadingOrder.Orderable, AssignableToComponentAdapter {
+ public static final ExtensionComponentAdapter[] EMPTY_ARRAY = new ExtensionComponentAdapter[0];
+
+ private Object myComponentInstance;
+ @Nullable
+ private final Element myExtensionElement;
+ private final PicoContainer myContainer;
+ private final PluginDescriptor myPluginDescriptor;
+ @NotNull
+ private Object myImplementationClassOrName; // Class or String
+ private boolean myNotificationSent;
+
+ private final String myOrderId;
+ private final LoadingOrder myOrder;
+
+ public ExtensionComponentAdapter(@NotNull String implementationClassName,
+ @Nullable PicoContainer container,
+ @Nullable PluginDescriptor pluginDescriptor,
+ @Nullable String orderId,
+ @NotNull LoadingOrder order,
+ @Nullable Element extensionElement) {
+ myImplementationClassOrName = implementationClassName;
+ myContainer = container;
+ myPluginDescriptor = pluginDescriptor;
+ myExtensionElement = extensionElement;
+
+ myOrderId = orderId;
+ myOrder = order;
+ }
+
+ public ExtensionComponentAdapter(@NotNull String implementationClassName,
+ @Nullable Element extensionElement,
+ PicoContainer container,
+ PluginDescriptor pluginDescriptor) {
+ this(implementationClassName, container, pluginDescriptor, null, LoadingOrder.ANY, extensionElement);
+ }
+
+ @Override
+ public Object getComponentKey() {
+ return this;
+ }
+
+ @Override
+ public Class getComponentImplementation() {
+ return loadImplementationClass();
+ }
+
+ @Override
+ public Object getComponentInstance(final PicoContainer container) throws PicoException, ProcessCanceledException {
+ if (myComponentInstance == null) {
+ try {
+ Class impl = loadImplementationClass();
+ Object componentInstance = new CachingConstructorInjectionComponentAdapter(getComponentKey(), impl, null, true).getComponentInstance(container);
+
+ if (myExtensionElement != null) {
+ try {
+ XmlSerializer.deserializeInto(componentInstance, myExtensionElement);
+ }
+ catch (Exception e) {
+ throw new PicoInitializationException(e);
+ }
+ }
+
+ myComponentInstance = componentInstance;
+ }
+ catch (ProcessCanceledException e) {
+ throw e;
+ }
+ catch (Throwable t) {
+ PluginId pluginId = myPluginDescriptor != null ? myPluginDescriptor.getPluginId() : null;
+ throw new PicoPluginExtensionInitializationException(t.getMessage(), t, pluginId);
+ }
+
+ if (myComponentInstance instanceof PluginAware) {
+ PluginAware pluginAware = (PluginAware)myComponentInstance;
+ pluginAware.setPluginDescriptor(myPluginDescriptor);
+ }
+ }
+
+ return myComponentInstance;
+ }
+
+ @Override
+ public void verify(PicoContainer container) throws PicoIntrospectionException {
+ throw new UnsupportedOperationException("Method verify is not supported in " + getClass());
+ }
+
+ @Override
+ public void accept(PicoVisitor visitor) {
+ throw new UnsupportedOperationException("Method accept is not supported in " + getClass());
+ }
+
+ public Object getExtension() {
+ return getComponentInstance(myContainer);
+ }
+
+ @Override
+ public LoadingOrder getOrder() {
+ return myOrder;
+ }
+
+ @Override
+ public final String getOrderId() {
+ return myOrderId;
+ }
+
+ public PluginId getPluginName() {
+ return myPluginDescriptor.getPluginId();
+ }
+
+ public PluginDescriptor getPluginDescriptor() {
+ return myPluginDescriptor;
+ }
+
+ @NotNull
+ private Class loadImplementationClass() {
+ Object implementationClassOrName = myImplementationClassOrName;
+ if (implementationClassOrName instanceof String) {
+ try {
+ ClassLoader classLoader = myPluginDescriptor == null ? getClass().getClassLoader() : myPluginDescriptor.getPluginClassLoader();
+ if (classLoader == null) {
+ classLoader = getClass().getClassLoader();
+ }
+ myImplementationClassOrName = implementationClassOrName = Class.forName((String)implementationClassOrName, false, classLoader);
+ }
+ catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return (Class)implementationClassOrName;
+ }
+
+ @Override
+ public String getAssignableToClassName() {
+ Object implementationClassOrName = myImplementationClassOrName;
+ if (implementationClassOrName instanceof String) {
+ return (String)implementationClassOrName;
+ }
+ return ((Class)implementationClassOrName).getName();
+ }
+
+ boolean isNotificationSent() {
+ return myNotificationSent;
+ }
+
+ void setNotificationSent(boolean notificationSent) {
+ myNotificationSent = notificationSent;
+ }
+
+ @Override
+ public String toString() {
+ return "ExtensionComponentAdapter[" + getAssignableToClassName() + "]: plugin=" + myPluginDescriptor;
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionPointImpl.java b/platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionPointImpl.java
new file mode 100644
index 00000000..2abe8974
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionPointImpl.java
@@ -0,0 +1,546 @@
+// Copyright 2000-2018 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.openapi.extensions.impl;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.extensions.*;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.StringInterner;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+import java.lang.reflect.Array;
+import java.util.*;
+import java.util.stream.Stream;
+
+/**
+ * @author AKireyev
+ */
+@SuppressWarnings("SynchronizeOnThis")
+public final class ExtensionPointImpl<T> implements ExtensionPoint<T> {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.extensions.impl.ExtensionPointImpl");
+
+ private final AreaInstance myArea;
+ private final String myName;
+ private final String myClassName;
+ private final Kind myKind;
+
+ private volatile List<T> myExtensionsCache;
+ // Since JDK 9 Arrays.ArrayList.toArray() doesn't return T[] array (https://bugs.openjdk.java.net/browse/JDK-6260652),
+ // but instead returns Object[], so, we cannot use toArray() anymore.
+ // Only array.clone should be used because of performance reasons (https://youtrack.jetbrains.com/issue/IDEA-198172).
+ private volatile T[] myExtensionsCacheAsArray;
+
+ private final ExtensionsAreaImpl myOwner;
+ private final PluginDescriptor myDescriptor;
+
+ @NotNull
+ private Set<ExtensionComponentAdapter> myExtensionAdapters = Collections.emptySet(); // guarded by this
+ @SuppressWarnings("unchecked")
+ @NotNull
+ private ExtensionPointListener<T>[] myEPListeners = ExtensionPointListener.EMPTY_ARRAY; // guarded by this
+ @NotNull
+ private List<ExtensionComponentAdapter> myLoadedAdapters = Collections.emptyList(); // guarded by this
+
+ private Class<T> myExtensionClass;
+
+ private static final StringInterner INTERNER = new StringInterner();
+
+ ExtensionPointImpl(@NotNull String name,
+ @NotNull String className,
+ @NotNull Kind kind,
+ @NotNull ExtensionsAreaImpl owner,
+ AreaInstance area,
+ @NotNull PluginDescriptor descriptor) {
+ synchronized (INTERNER) {
+ myName = INTERNER.intern(name);
+ }
+ myClassName = className;
+ myKind = kind;
+ myOwner = owner;
+ myArea = area;
+ myDescriptor = descriptor;
+ }
+
+ @NotNull
+ @Override
+ public String getName() {
+ return myName;
+ }
+
+ @Override
+ public AreaInstance getArea() {
+ return myArea;
+ }
+
+ @NotNull
+ @Override
+ public String getClassName() {
+ return myClassName;
+ }
+
+ @NotNull
+ @Override
+ public Kind getKind() {
+ return myKind;
+ }
+
+ @Override
+ public void registerExtension(@NotNull T extension) {
+ registerExtension(extension, LoadingOrder.ANY);
+ }
+
+ @NotNull
+ public PluginDescriptor getDescriptor() {
+ return myDescriptor;
+ }
+
+ @Override
+ public synchronized void registerExtension(@NotNull T extension, @NotNull LoadingOrder order) {
+ ExtensionComponentAdapter adapter = new ObjectComponentAdapter(extension, order);
+
+ if (LoadingOrder.ANY == order) {
+ int index = myLoadedAdapters.size();
+ while (index > 0) {
+ ExtensionComponentAdapter lastAdapter = myLoadedAdapters.get(index - 1);
+ if (lastAdapter.getOrder() == LoadingOrder.LAST) {
+ index--;
+ }
+ else {
+ break;
+ }
+ }
+ if (getExtensionIndex(extension) != -1) {
+ LOG.error("Extension was already added: " + extension);
+ return;
+ }
+
+ registerExtension(extension, adapter, index, true);
+ }
+ else {
+ registerExtensionAdapter(adapter);
+ processAdapters();
+ }
+ }
+
+ private void registerExtension(@NotNull T extension, @NotNull ExtensionComponentAdapter adapter, int index, boolean runNotifications) {
+ Class<T> extensionClass = getExtensionClass();
+ if (!extensionClass.isInstance(extension)) {
+ LOG.error("Extension " + extension.getClass() + " does not implement " + extensionClass);
+ return;
+ }
+ if (myLoadedAdapters == Collections.<ExtensionComponentAdapter>emptyList()) myLoadedAdapters = new ArrayList<>();
+ myLoadedAdapters.add(index, adapter);
+
+ if (runNotifications) {
+ clearCache();
+
+ if (!adapter.isNotificationSent()) {
+ if (extension instanceof Extension) {
+ try {
+ ((Extension)extension).extensionAdded(this);
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ }
+
+ notifyListenersOnAdd(extension, adapter.getPluginDescriptor());
+ adapter.setNotificationSent(true);
+ }
+ }
+ }
+
+ private void notifyListenersOnAdd(@NotNull T extension, final PluginDescriptor pluginDescriptor) {
+ for (ExtensionPointListener<T> listener : myEPListeners) {
+ try {
+ listener.extensionAdded(extension, pluginDescriptor);
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ }
+ }
+
+ @NotNull
+ @Override
+ public List<T> getExtensionList() {
+ List<T> result = myExtensionsCache;
+ if (result == null) {
+ synchronized (this) {
+ result = myExtensionsCache;
+ if (result == null) {
+ T[] array = processAdapters();
+ if (array == null) {
+ result = Collections.emptyList();
+ }
+ else {
+ myExtensionsCacheAsArray = array;
+ result = Collections.unmodifiableList(Arrays.asList(array));
+ }
+ myExtensionsCache = result;
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ @NotNull
+ public T[] getExtensions() {
+ List<T> list = getExtensionList();
+ if (list.isEmpty()) {
+ //noinspection unchecked
+ return (T[])Array.newInstance(getExtensionClass(), 0);
+ }
+ else {
+ return myExtensionsCacheAsArray.clone();
+ }
+ }
+
+ @NotNull
+ @Override
+ public Stream<T> extensions() {
+ return getExtensionList().stream();
+ }
+
+ @Override
+ public boolean hasAnyExtensions() {
+ final List<T> cache = myExtensionsCache;
+ if (cache != null) {
+ return !cache.isEmpty();
+ }
+ synchronized (this) {
+ return myExtensionAdapters.size() + myLoadedAdapters.size() > 0;
+ }
+ }
+
+ private boolean processingAdaptersNow; // guarded by this
+ @Nullable("null means empty")
+ private T[] processAdapters() {
+ if (processingAdaptersNow) {
+ throw new IllegalStateException("Recursive processAdapters() detected. You must have called 'getExtensions()' from within your extension constructor - don't. Either pass extension via constructor parameter or call getExtensions() later.");
+ }
+ int totalSize = myExtensionAdapters.size() + myLoadedAdapters.size();
+ if (totalSize == 0) {
+ return null;
+ }
+
+ processingAdaptersNow = true;
+ try {
+ Class<T> extensionClass = getExtensionClass();
+ @SuppressWarnings("unchecked") T[] result = (T[])Array.newInstance(extensionClass, totalSize);
+ List<ExtensionComponentAdapter> adapters = ContainerUtil.newArrayListWithCapacity(totalSize);
+ adapters.addAll(myExtensionAdapters);
+ adapters.addAll(myLoadedAdapters);
+ LoadingOrder.sort(adapters);
+ myExtensionAdapters = new LinkedHashSet<>(adapters);
+
+ Set<ExtensionComponentAdapter> loaded = ContainerUtil.newHashOrEmptySet(myLoadedAdapters);
+
+ myLoadedAdapters = Collections.emptyList();
+ boolean errorHappened = false;
+ for (int i = 0; i < adapters.size(); i++) {
+ ExtensionComponentAdapter adapter = adapters.get(i);
+ try {
+ @SuppressWarnings("unchecked") T extension = (T)adapter.getExtension();
+ if (extension == null) {
+ errorHappened = true;
+ LOG.error("null extension in: " + adapter + ";\ngetExtensionClass(): " + getExtensionClass() + ";\n" );
+ }
+ if (i > 0 && extension == result[i - 1]) {
+ errorHappened = true;
+ LOG.error("Duplicate extension found: " + extension + "; " +
+ " Adapter: " + adapter + ";\n" +
+ " Prev adapter: " + adapters.get(i-1) + ";\n" +
+ " getExtensionClass(): " + getExtensionClass() + ";\n" +
+ " result:" + Arrays.asList(result));
+ }
+ if (!extensionClass.isInstance(extension)) {
+ errorHappened = true;
+ LOG.error("Extension " + (extension == null ? null : extension.getClass()) + " does not implement " + extensionClass + ". It came from " + adapter);
+ continue;
+ }
+ result[i] = extension;
+ registerExtension(extension, adapter, myLoadedAdapters.size(), !loaded.contains(adapter));
+ }
+ catch (ProcessCanceledException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ errorHappened = true;
+ LOG.error(e);
+ }
+ myExtensionAdapters.remove(adapter);
+ }
+ myExtensionAdapters = Collections.emptySet();
+ if (errorHappened) {
+ result = ContainerUtil.findAllAsArray(result, Condition.NOT_NULL);
+ }
+ return result;
+ }
+ finally {
+ processingAdaptersNow = false;
+ }
+ }
+
+ public synchronized void removeUnloadableExtensions() {
+ ExtensionComponentAdapter[] adapters = myExtensionAdapters.toArray(ExtensionComponentAdapter.EMPTY_ARRAY);
+ for (ExtensionComponentAdapter adapter : adapters) {
+ try {
+ adapter.getComponentImplementation();
+ }
+ catch (Throwable e) {
+ unregisterExtensionAdapter(adapter);
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public T getExtension() {
+ List<T> extensions = getExtensionList();
+ return extensions.isEmpty() ? null : extensions.get(0);
+ }
+
+ @Override
+ public synchronized boolean hasExtension(@NotNull T extension) {
+ T[] extensions = processAdapters();
+ return extensions != null && ArrayUtil.contains(extension, extensions);
+ }
+
+ @Override
+ public synchronized void unregisterExtension(@NotNull final T extension) {
+ final int index = getExtensionIndex(extension);
+ if (index == -1) {
+ throw new IllegalArgumentException("Extension to be removed not found: " + extension);
+ }
+ final ExtensionComponentAdapter adapter = myLoadedAdapters.get(index);
+
+ Object key = adapter.getComponentKey();
+ myOwner.getPicoContainer().unregisterComponent(key);
+
+ processAdapters();
+ unregisterExtension(extension, null);
+ }
+
+ @Override
+ public void unregisterExtension(@NotNull Class<? extends T> extensionClass) {
+ for (ExtensionComponentAdapter adapter : ContainerUtil.concat(myExtensionAdapters, myLoadedAdapters)) {
+ if (adapter.getAssignableToClassName().equals(extensionClass.getCanonicalName())) {
+ unregisterExtensionAdapter(adapter);
+ return;
+ }
+ }
+ throw new IllegalArgumentException("Extension to be removed not found: " + extensionClass);
+ }
+
+ private int getExtensionIndex(@NotNull T extension) {
+ for (int i = 0; i < myLoadedAdapters.size(); i++) {
+ ExtensionComponentAdapter adapter = myLoadedAdapters.get(i);
+ if (Comparing.equal(adapter.getExtension(), extension)) return i;
+ }
+ return -1;
+ }
+
+ private void unregisterExtension(@NotNull T extension, PluginDescriptor pluginDescriptor) {
+ int index = getExtensionIndex(extension);
+ if (index == -1) {
+ throw new IllegalArgumentException("Extension to be removed not found: " + extension);
+ }
+
+ myLoadedAdapters.remove(index);
+ clearCache();
+
+ notifyListenersOnRemove(extension, pluginDescriptor);
+
+ if (extension instanceof Extension) {
+ try {
+ ((Extension)extension).extensionRemoved(this);
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ }
+ }
+
+ private void notifyListenersOnRemove(@NotNull T extensionObject, PluginDescriptor pluginDescriptor) {
+ for (ExtensionPointListener<T> listener : myEPListeners) {
+ try {
+ listener.extensionRemoved(extensionObject, pluginDescriptor);
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ }
+ }
+
+ @Override
+ public void addExtensionPointListener(@NotNull final ExtensionPointListener<T> listener, @NotNull Disposable parentDisposable) {
+ addExtensionPointListener(listener, true, parentDisposable);
+ }
+
+ public synchronized void addExtensionPointListener(@NotNull final ExtensionPointListener<T> listener,
+ final boolean invokeForLoadedExtensions,
+ @NotNull Disposable parentDisposable) {
+ if (invokeForLoadedExtensions) {
+ addExtensionPointListener(listener);
+ }
+ else {
+ addListener(listener);
+ }
+ Disposer.register(parentDisposable, () -> removeExtensionPointListener(listener, invokeForLoadedExtensions));
+ }
+
+ // true if added
+ private boolean addListener(@NotNull ExtensionPointListener<T> listener) {
+ if (ArrayUtil.indexOf(myEPListeners, listener) != -1) return false;
+ //noinspection unchecked
+ myEPListeners = ArrayUtil.append(myEPListeners, listener, n->n==0?ExtensionPointListener.EMPTY_ARRAY:new ExtensionPointListener[n]);
+ return true;
+ }
+ private boolean removeListener(@NotNull ExtensionPointListener<T> listener) {
+ if (ArrayUtil.indexOf(myEPListeners, listener) == -1) return false;
+ //noinspection unchecked
+ myEPListeners = ArrayUtil.remove(myEPListeners, listener, n->n==0?ExtensionPointListener.EMPTY_ARRAY:new ExtensionPointListener[n]);
+ return true;
+ }
+
+ @Override
+ public synchronized void addExtensionPointListener(@NotNull ExtensionPointListener<T> listener) {
+ processAdapters();
+ if (addListener(listener)) {
+ ExtensionComponentAdapter[] array = myLoadedAdapters.toArray(ExtensionComponentAdapter.EMPTY_ARRAY);
+ for (ExtensionComponentAdapter componentAdapter : array) {
+ try {
+ @SuppressWarnings("unchecked") T extension = (T)componentAdapter.getExtension();
+ listener.extensionAdded(extension, componentAdapter.getPluginDescriptor());
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void removeExtensionPointListener(@NotNull ExtensionPointListener<T> listener) {
+ removeExtensionPointListener(listener, true);
+ }
+
+ private synchronized void removeExtensionPointListener(@NotNull ExtensionPointListener<T> listener, boolean invokeForLoadedExtensions) {
+ if (removeListener(listener) && invokeForLoadedExtensions) {
+ ExtensionComponentAdapter[] array = myLoadedAdapters.toArray(ExtensionComponentAdapter.EMPTY_ARRAY);
+ for (ExtensionComponentAdapter componentAdapter : array) {
+ try {
+ @SuppressWarnings("unchecked") T extension = (T)componentAdapter.getExtension();
+ listener.extensionRemoved(extension, componentAdapter.getPluginDescriptor());
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public synchronized void reset() {
+ myOwner.removeAllComponents(myExtensionAdapters);
+ myExtensionAdapters = Collections.emptySet();
+ for (T extension : getExtensionList()) {
+ unregisterExtension(extension);
+ }
+ }
+
+ @NotNull
+ @Override
+ public Class<T> getExtensionClass() {
+ // racy single-check: we don't care whether the access to 'myExtensionClass' is thread-safe
+ // but initial store in a local variable is crucial to prevent instruction reordering
+ // see Item 71 in Effective Java or http://jeremymanson.blogspot.com/2008/12/benign-data-races-in-java.html
+ Class<T> extensionClass = myExtensionClass;
+ if (extensionClass == null) {
+ try {
+ ClassLoader pluginClassLoader = myDescriptor.getPluginClassLoader();
+ @SuppressWarnings("unchecked") Class<T> extClass =
+ (Class<T>)(pluginClassLoader == null ? Class.forName(myClassName) : Class.forName(myClassName, true, pluginClassLoader));
+ myExtensionClass = extensionClass = extClass;
+ }
+ catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return extensionClass;
+ }
+
+ public String toString() {
+ return getName();
+ }
+
+ synchronized void registerExtensionAdapter(@NotNull ExtensionComponentAdapter adapter) {
+ if (myExtensionAdapters == Collections.<ExtensionComponentAdapter>emptySet()) {
+ myExtensionAdapters = new LinkedHashSet<>();
+ }
+ myExtensionAdapters.add(adapter);
+ clearCache();
+ }
+
+ private void clearCache() {
+ myExtensionsCache = null;
+ myExtensionsCacheAsArray = null;
+ }
+
+ private void unregisterExtensionAdapter(@NotNull ExtensionComponentAdapter adapter) {
+ try {
+ if (!myExtensionAdapters.isEmpty() && myExtensionAdapters.remove(adapter)) {
+ return;
+ }
+ if (myLoadedAdapters.contains(adapter)) {
+ Object key = adapter.getComponentKey();
+ myOwner.getPicoContainer().unregisterComponent(key);
+
+ @SuppressWarnings("unchecked") T extension = (T)adapter.getExtension();
+ unregisterExtension(extension, adapter.getPluginDescriptor());
+ }
+ }
+ finally {
+ clearCache();
+ }
+ }
+
+ @TestOnly
+ final synchronized void notifyAreaReplaced(@NotNull ExtensionsArea oldArea) {
+ for (final ExtensionPointListener<T> listener : myEPListeners) {
+ if (listener instanceof ExtensionPointAndAreaListener) {
+ ((ExtensionPointAndAreaListener)listener).areaReplaced(oldArea);
+ }
+ }
+ }
+
+ private static class ObjectComponentAdapter extends ExtensionComponentAdapter {
+ private final Object myExtension;
+ private final LoadingOrder myLoadingOrder;
+
+ private ObjectComponentAdapter(@NotNull Object extension, @NotNull LoadingOrder loadingOrder) {
+ super(extension.getClass().getName(), null, null, null);
+ myExtension = extension;
+ myLoadingOrder = loadingOrder;
+ }
+
+ @Override
+ public Object getExtension() {
+ return myExtension;
+ }
+
+ @Override
+ public LoadingOrder getOrder() {
+ return myLoadingOrder;
+ }
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionsAreaImpl.java b/platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionsAreaImpl.java
new file mode 100644
index 00000000..bcee0c07
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/impl/ExtensionsAreaImpl.java
@@ -0,0 +1,406 @@
+// Copyright 2000-2018 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.openapi.extensions.impl;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.extensions.*;
+import com.intellij.openapi.util.JDOMUtil;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.MultiMap;
+import com.intellij.util.pico.CachingConstructorInjectionComponentAdapter;
+import com.intellij.util.pico.DefaultPicoContainer;
+import gnu.trove.THashMap;
+import org.jdom.Attribute;
+import org.jdom.Element;
+import org.jdom.Namespace;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+import org.picocontainer.MutablePicoContainer;
+import org.picocontainer.PicoContainer;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+@SuppressWarnings("HardCodedStringLiteral")
+public class ExtensionsAreaImpl implements ExtensionsArea {
+ private static final Logger LOG = Logger.getInstance(ExtensionsAreaImpl.class);
+ public static final String ATTRIBUTE_AREA = "area";
+
+ private static final Map<String,String> ourDefaultEPs = new THashMap<>();
+
+ static {
+ ourDefaultEPs.put(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME, EPAvailabilityListenerExtension.class.getName());
+ }
+
+ private static final boolean DEBUG_REGISTRATION = Boolean.FALSE.booleanValue(); // not compile-time constant to avoid yellow code
+
+ private final AreaPicoContainer myPicoContainer;
+ private final Throwable myCreationTrace;
+ private final Map<String, ExtensionPointImpl> myExtensionPoints = ContainerUtil.newConcurrentMap();
+ private final Map<String,Throwable> myEPTraces = DEBUG_REGISTRATION ? new THashMap<>() : null;
+ private final MultiMap<String, ExtensionPointAvailabilityListener> myAvailabilityListeners = MultiMap.createSmart(); // guarded by myAvailabilityListeners
+ private final AreaInstance myAreaInstance;
+ private final String myAreaClass;
+
+ public ExtensionsAreaImpl(String areaClass, AreaInstance areaInstance, PicoContainer parentPicoContainer) {
+ myCreationTrace = DEBUG_REGISTRATION ? new Throwable("Area creation trace") : null;
+ myAreaClass = areaClass;
+ myAreaInstance = areaInstance;
+ myPicoContainer = new DefaultPicoContainer(parentPicoContainer);
+ initialize();
+ }
+
+ @TestOnly
+ ExtensionsAreaImpl(MutablePicoContainer parentPicoContainer) {
+ this(null, null, parentPicoContainer);
+ }
+
+ @TestOnly
+ public final void notifyAreaReplaced(@NotNull ExtensionsAreaImpl newArea) {
+ Set<String> processedEPs = ContainerUtil.newTroveSet();
+ for (final ExtensionPointImpl point : myExtensionPoints.values()) {
+ point.notifyAreaReplaced(this);
+ processedEPs.add(point.getName());
+ }
+ //this code is required because we have a lot of static extensions e.g. LanguageExtension that are initialized only once
+ //for the extensions AvailabilityListeners will be broken if the initialization happened in "fake" area which doesn't have required EP
+ if (!myAvailabilityListeners.isEmpty()) {
+ for (Map.Entry<String, Collection<ExtensionPointAvailabilityListener>> entry : myAvailabilityListeners.entrySet()) {
+ String key = entry.getKey();
+ if (!processedEPs.contains(key)) {
+ boolean wasAdded = false;
+ //if listeners are "detached" for any EP we have to transfer them to the new area (otherwise it will affect area searching)
+ for (ExtensionPointAvailabilityListener listener : entry.getValue()) {
+ if (!newArea.hasAvailabilityListener(key, listener)) {
+ newArea.addAvailabilityListener(key, listener);
+ wasAdded = true;
+ }
+ }
+ if (wasAdded) {
+ processedEPs.add(key);
+ }
+ }
+ }
+ }
+
+ for (ExtensionPointImpl point : newArea.myExtensionPoints.values()) {
+ if (!processedEPs.contains(point.getName())) {
+ point.notifyAreaReplaced(this);
+ }
+ }
+ }
+
+
+ @NotNull
+ @Override
+ public AreaPicoContainer getPicoContainer() {
+ return myPicoContainer;
+ }
+
+ @Override
+ public String getAreaClass() {
+ return myAreaClass;
+ }
+
+ public void registerExtensionPoint(@NotNull String pluginName, @NotNull Element extensionPointElement) {
+ registerExtensionPoint(new DefaultPluginDescriptor(PluginId.getId(pluginName)), extensionPointElement);
+ }
+
+ @Override
+ public void registerExtensionPoint(@NotNull PluginDescriptor pluginDescriptor, @NotNull Element extensionPointElement) {
+ assert pluginDescriptor.getPluginId() != null;
+ final String pluginId = pluginDescriptor.getPluginId().getIdString();
+ String epName = extensionPointElement.getAttributeValue("qualifiedName");
+ if (epName == null) {
+ final String name = extensionPointElement.getAttributeValue("name");
+ if (name == null) {
+ throw new RuntimeException("'name' attribute not specified for extension point in '" + pluginId + "' plugin");
+ }
+ epName = pluginId + '.' + name;
+ }
+
+ String beanClassName = extensionPointElement.getAttributeValue("beanClass");
+ String interfaceClassName = extensionPointElement.getAttributeValue("interface");
+ if (beanClassName == null && interfaceClassName == null) {
+ throw new RuntimeException("Neither 'beanClass' nor 'interface' attribute is specified for extension point '" + epName + "' in '" + pluginId + "' plugin");
+ }
+ if (beanClassName != null && interfaceClassName != null) {
+ throw new RuntimeException("Both 'beanClass' and 'interface' attributes are specified for extension point '" + epName + "' in '" + pluginId + "' plugin");
+ }
+
+ ExtensionPoint.Kind kind;
+ String className;
+ if (interfaceClassName != null) {
+ className = interfaceClassName;
+ kind = ExtensionPoint.Kind.INTERFACE;
+ }
+ else {
+ className = beanClassName;
+ kind = ExtensionPoint.Kind.BEAN_CLASS;
+ }
+ registerExtensionPoint(epName, className, pluginDescriptor, kind);
+ }
+
+ public void registerExtension(@NotNull final String pluginName, @NotNull final Element extensionElement) {
+ registerExtension(new DefaultPluginDescriptor(PluginId.getId(pluginName)), extensionElement, null);
+ }
+
+ @Override
+ public void registerExtension(@NotNull final PluginDescriptor pluginDescriptor, @NotNull final Element extensionElement, String extensionNs) {
+ String epName = extractEPName(extensionElement, extensionNs);
+ registerExtension(getExtensionPoint(epName), pluginDescriptor, extensionElement);
+ }
+
+ // Used in Upsource
+ @Override
+ public void registerExtension(@NotNull final ExtensionPoint extensionPoint, @NotNull final PluginDescriptor pluginDescriptor, @NotNull final Element extensionElement) {
+ if (!Extensions.isComponentSuitableForOs(extensionElement.getAttributeValue("os"))) {
+ return;
+ }
+
+ ExtensionComponentAdapter adapter;
+ if (extensionPoint.getKind() == ExtensionPoint.Kind.INTERFACE) {
+ String implClass = extensionElement.getAttributeValue("implementation");
+ if (implClass == null) {
+ throw new RuntimeException("'implementation' attribute not specified for '" + extensionPoint.getName() + "' extension in '"
+ + pluginDescriptor.getPluginId().getIdString() + "' plugin");
+ }
+ adapter = createAdapter(implClass, extensionElement, shouldDeserializeInstance(extensionElement), pluginDescriptor);
+ }
+ else {
+ adapter = createAdapter(extensionPoint.getClassName(), extensionElement, !JDOMUtil.isEmpty(extensionElement), pluginDescriptor);
+ }
+ myPicoContainer.registerComponent(adapter);
+ ((ExtensionPointImpl)extensionPoint).registerExtensionAdapter(adapter);
+ }
+
+ // this method is not ExtensionComponentAdapter constructor because later ExtensionComponentAdapter will not hold element
+ @NotNull
+ private ExtensionComponentAdapter createAdapter(@NotNull String implementationClassName, @NotNull Element extensionElement, boolean isNeedToDeserialize, @NotNull PluginDescriptor pluginDescriptor) {
+ String orderId = extensionElement.getAttributeValue("id");
+ LoadingOrder order = LoadingOrder.readOrder(extensionElement.getAttributeValue("order"));
+ return new ExtensionComponentAdapter(implementationClassName, myPicoContainer, pluginDescriptor, orderId, order, isNeedToDeserialize ? extensionElement : null);
+ }
+
+ private static boolean shouldDeserializeInstance(Element extensionElement) {
+ // has content
+ if (!extensionElement.getContent().isEmpty()) return true;
+ // has custom attributes
+ for (Attribute attribute : extensionElement.getAttributes()) {
+ final String name = attribute.getName();
+ if (!"implementation".equals(name) && !"id".equals(name) && !"order".equals(name) && !"os".equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @NotNull
+ public static String extractEPName(@NotNull Element extensionElement, @Nullable String ns) {
+ String epName = extensionElement.getAttributeValue("point");
+
+ if (epName == null) {
+ if (ns == null) {
+ Namespace namespace = extensionElement.getNamespace();
+ epName = namespace.getURI() + '.' + extensionElement.getName();
+ }
+ else {
+ epName = ns + '.' + extensionElement.getName();
+ }
+ }
+ return epName;
+ }
+
+ private MutablePicoContainer internalGetPluginContainer() {
+ return myPicoContainer;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initialize() {
+ for (Map.Entry<String, String> entry : ourDefaultEPs.entrySet()) {
+ String epName = entry.getKey();
+ registerExtensionPoint(epName, entry.getValue());
+ }
+
+ getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).addExtensionPointListener(new ExtensionPointListener() {
+ @Override
+ public void extensionRemoved(@NotNull Object extension, final PluginDescriptor pluginDescriptor) {
+ EPAvailabilityListenerExtension epListenerExtension = (EPAvailabilityListenerExtension) extension;
+ synchronized (myAvailabilityListeners) {
+ Collection<ExtensionPointAvailabilityListener> listeners = myAvailabilityListeners.get(epListenerExtension.getExtensionPointName());
+ for (Iterator<ExtensionPointAvailabilityListener> iterator = listeners.iterator(); iterator.hasNext();) {
+ ExtensionPointAvailabilityListener listener = iterator.next();
+ if (listener.getClass().getName().equals(epListenerExtension.getListenerClass())) {
+ iterator.remove();
+ return;
+ }
+ }
+ }
+ LOG.warn("Failed to find EP availability listener: " + epListenerExtension.getListenerClass());
+ }
+
+ @Override
+ public void extensionAdded(@NotNull Object extension, final PluginDescriptor pluginDescriptor) {
+ EPAvailabilityListenerExtension epListenerExtension = (EPAvailabilityListenerExtension) extension;
+ try {
+ String epName = epListenerExtension.getExtensionPointName();
+
+ ExtensionPointAvailabilityListener listener = (ExtensionPointAvailabilityListener) instantiate(epListenerExtension.loadListenerClass());
+ addAvailabilityListener(epName, listener);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ private Object instantiate(Class clazz) {
+ CachingConstructorInjectionComponentAdapter adapter =
+ new CachingConstructorInjectionComponentAdapter(Integer.toString(System.identityHashCode(new Object())), clazz);
+
+ return adapter.getComponentInstance(getPicoContainer());
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public Throwable getCreationTrace() {
+ return myCreationTrace;
+ }
+
+ @Override
+ public void addAvailabilityListener(@NotNull String extensionPointName, @NotNull ExtensionPointAvailabilityListener listener) {
+ synchronized (myAvailabilityListeners) {
+ myAvailabilityListeners.putValue(extensionPointName, listener);
+ }
+ ExtensionPointImpl<?> ep = myExtensionPoints.get(extensionPointName);
+ if (ep != null) {
+ listener.extensionPointRegistered(ep);
+ }
+ }
+
+ @Override
+ public void removeAvailabilityListener(@NotNull String extensionPointName, @NotNull ExtensionPointAvailabilityListener listener) {
+ synchronized (myAvailabilityListeners) {
+ myAvailabilityListeners.remove(extensionPointName, listener);
+ }
+ }
+
+ private boolean hasAvailabilityListener(@NotNull String extensionPointName, @NotNull ExtensionPointAvailabilityListener listener) {
+ Collection<ExtensionPointAvailabilityListener> listeners = myAvailabilityListeners.get(extensionPointName);
+ return ContainerUtil.containsIdentity(listeners, listener);
+ }
+
+ @Override
+ public void registerExtensionPoint(@NotNull final String extensionPointName, @NotNull String extensionPointBeanClass) {
+ registerExtensionPoint(extensionPointName, extensionPointBeanClass, ExtensionPoint.Kind.INTERFACE);
+ }
+
+ @Override
+ public void registerExtensionPoint(@NotNull @NonNls String extensionPointName, @NotNull String extensionPointBeanClass, @NotNull ExtensionPoint.Kind kind) {
+ registerExtensionPoint(extensionPointName, extensionPointBeanClass, new UndefinedPluginDescriptor(), kind);
+ }
+
+ private void registerExtensionPoint(@NotNull String extensionPointName,
+ @NotNull String extensionPointBeanClass,
+ @NotNull PluginDescriptor descriptor,
+ @NotNull ExtensionPoint.Kind kind) {
+ if (hasExtensionPoint(extensionPointName)) {
+ final String message =
+ "Duplicate registration for EP: " + extensionPointName + ": original plugin " + getExtensionPoint(extensionPointName).getDescriptor().getPluginId() +
+ ", new plugin " + descriptor.getPluginId();
+ if (DEBUG_REGISTRATION) {
+ LOG.error(message, myEPTraces.get(extensionPointName));
+ }
+ throw new PicoPluginExtensionInitializationException(message, null, descriptor.getPluginId());
+ }
+
+ registerExtensionPoint(new ExtensionPointImpl(extensionPointName, extensionPointBeanClass, kind, this, myAreaInstance, descriptor));
+ }
+
+ public void registerExtensionPoint(@NotNull ExtensionPointImpl extensionPoint) {
+ String name = extensionPoint.getName();
+ myExtensionPoints.put(name, extensionPoint);
+ notifyEPRegistered(extensionPoint);
+ if (DEBUG_REGISTRATION) {
+ myEPTraces.put(name, new Throwable("Original registration for " + name));
+ }
+ }
+
+ private void notifyEPRegistered(@NotNull ExtensionPoint extensionPoint) {
+ Collection<ExtensionPointAvailabilityListener> listeners;
+ synchronized (myAvailabilityListeners) {
+ listeners = myAvailabilityListeners.get(extensionPoint.getName());
+ }
+ for (final ExtensionPointAvailabilityListener listener : listeners) {
+ listener.extensionPointRegistered(extensionPoint);
+ }
+ }
+
+ @Override
+ @NotNull
+ public <T> ExtensionPointImpl<T> getExtensionPoint(@NotNull String extensionPointName) {
+ //noinspection unchecked
+ ExtensionPointImpl<T> extensionPoint = myExtensionPoints.get(extensionPointName);
+ if (extensionPoint == null) {
+ throw new IllegalArgumentException("Missing extension point: " + extensionPointName + " in area " + myAreaInstance);
+ }
+ return extensionPoint;
+ }
+
+ @NotNull
+ @Override
+ public <T> ExtensionPoint<T> getExtensionPoint(@NotNull ExtensionPointName<T> extensionPointName) {
+ return getExtensionPoint(extensionPointName.getName());
+ }
+
+ @NotNull
+ @Override
+ public ExtensionPoint[] getExtensionPoints() {
+ return myExtensionPoints.values().toArray(new ExtensionPoint[0]);
+ }
+
+ @Override
+ public void unregisterExtensionPoint(@NotNull final String extensionPointName) {
+ ExtensionPoint extensionPoint = myExtensionPoints.get(extensionPointName);
+ if (extensionPoint != null) {
+ extensionPoint.reset();
+ myExtensionPoints.remove(extensionPointName);
+ notifyEPRemoved(extensionPoint);
+ }
+ }
+
+ private void notifyEPRemoved(@NotNull ExtensionPoint extensionPoint) {
+ Collection<ExtensionPointAvailabilityListener> listeners;
+ synchronized (myAvailabilityListeners) {
+ listeners = myAvailabilityListeners.get(extensionPoint.getName());
+ }
+ for (final ExtensionPointAvailabilityListener listener : listeners) {
+ listener.extensionPointRemoved(extensionPoint);
+ }
+ }
+
+ @Override
+ public boolean hasExtensionPoint(@NotNull String extensionPointName) {
+ return myExtensionPoints.containsKey(extensionPointName);
+ }
+
+ @Override
+ public boolean hasExtensionPoint(@NotNull ExtensionPointName<?> extensionPointName) {
+ return hasExtensionPoint(extensionPointName.getName());
+ }
+
+ void removeAllComponents(@NotNull Set<ExtensionComponentAdapter> extensionAdapters) {
+ for (final Object extensionAdapter : extensionAdapters) {
+ ExtensionComponentAdapter componentAdapter = (ExtensionComponentAdapter)extensionAdapter;
+ internalGetPluginContainer().unregisterComponent(componentAdapter.getComponentKey());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return (myAreaClass == null ? "Root" : myAreaClass)+" Area";
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/impl/PicoPluginExtensionInitializationException.java b/platform/extensions/src/com/intellij/openapi/extensions/impl/PicoPluginExtensionInitializationException.java
new file mode 100644
index 00000000..4e609132
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/impl/PicoPluginExtensionInitializationException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2013 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions.impl;
+
+import com.intellij.openapi.extensions.PluginId;
+import org.picocontainer.PicoInitializationException;
+
+public class PicoPluginExtensionInitializationException extends PicoInitializationException {
+ private final PluginId myPluginId;
+
+ PicoPluginExtensionInitializationException(String message, Throwable cause, PluginId id) {
+ super(message, cause);
+ myPluginId = id;
+ }
+
+ public PluginId getPluginId() {
+ return myPluginId;
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/impl/UndefinedPluginDescriptor.java b/platform/extensions/src/com/intellij/openapi/extensions/impl/UndefinedPluginDescriptor.java
new file mode 100644
index 00000000..4ab748c1
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/impl/UndefinedPluginDescriptor.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions.impl;
+
+import com.intellij.openapi.extensions.PluginDescriptor;
+import com.intellij.openapi.extensions.PluginId;
+
+/**
+ * @author Alexander Kireyev
+ */
+class UndefinedPluginDescriptor implements PluginDescriptor {
+ @Override
+ public PluginId getPluginId() {
+ throw new UnsupportedOperationException("This method should not be called on this object");
+ }
+
+ @Override
+ public ClassLoader getPluginClassLoader() {
+ return null;
+ }
+}
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/impl/package.html b/platform/extensions/src/com/intellij/openapi/extensions/impl/package.html
new file mode 100644
index 00000000..56fe0777
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/impl/package.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html><body bgcolor="white">
+Provides the implementation of the extension point framework used in IDEA and Fabrique.
+</body></html>
diff --git a/platform/extensions/src/com/intellij/openapi/extensions/package.html b/platform/extensions/src/com/intellij/openapi/extensions/package.html
new file mode 100644
index 00000000..6dccaee7
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/extensions/package.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html><body bgcolor="white">
+Provides interfaces for the extension point framework used in IDEA and Fabrique.
+</body></html>
diff --git a/platform/extensions/src/com/intellij/openapi/util/KeyedExtensionFactory.java b/platform/extensions/src/com/intellij/openapi/util/KeyedExtensionFactory.java
new file mode 100644
index 00000000..cbe19884
--- /dev/null
+++ b/platform/extensions/src/com/intellij/openapi/util/KeyedExtensionFactory.java
@@ -0,0 +1,114 @@
+// Copyright 2000-2018 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.openapi.util;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.extensions.KeyedFactoryEPBean;
+import com.intellij.util.ExceptionUtil;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.picocontainer.PicoContainer;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author yole
+ */
+public abstract class KeyedExtensionFactory<T, KeyT> {
+ private final Class<T> myInterfaceClass;
+ private final ExtensionPointName<KeyedFactoryEPBean> myEpName;
+ private final PicoContainer myPicoContainer;
+
+ public KeyedExtensionFactory(@NotNull final Class<T> interfaceClass, @NonNls @NotNull final ExtensionPointName<KeyedFactoryEPBean> epName,
+ @NotNull PicoContainer picoContainer) {
+ myInterfaceClass = interfaceClass;
+ myEpName = epName;
+ myPicoContainer = picoContainer;
+ }
+
+ @NotNull
+ public T get() {
+ final List<KeyedFactoryEPBean> epBeans = myEpName.getExtensionList();
+ InvocationHandler handler = new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ //noinspection unchecked
+ KeyT keyArg = (KeyT) args [0];
+ String key = getKey(keyArg);
+ Object result = getByKey(epBeans, key, method, args);
+ if (result == null) {
+ result = getByKey(epBeans, null, method, args);
+ }
+ return result;
+ }
+ };
+ //noinspection unchecked
+ return (T)Proxy.newProxyInstance(myInterfaceClass.getClassLoader(), new Class<?>[] { myInterfaceClass }, handler );
+ }
+
+ public T getByKey(@NotNull KeyT key) {
+ final List<KeyedFactoryEPBean> epBeans = myEpName.getExtensionList();
+ for (KeyedFactoryEPBean epBean : epBeans) {
+ if (Comparing.strEqual(getKey(key), epBean.key)) {
+ try {
+ if (epBean.implementationClass != null) {
+ return (T)epBean.instantiate(epBean.implementationClass, myPicoContainer);
+ }
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return null;
+ }
+
+ @NotNull
+ public Set<String> getAllKeys() {
+ List<KeyedFactoryEPBean> list = myEpName.getExtensionList();
+ Set<String> set = new THashSet<>();
+ for (KeyedFactoryEPBean epBean : list) {
+ set.add(epBean.key);
+ }
+ return set;
+ }
+
+ private T getByKey(final List<KeyedFactoryEPBean> epBeans, final String key, final Method method, final Object[] args) {
+ Object result = null;
+ for(KeyedFactoryEPBean epBean: epBeans) {
+ if (Comparing.strEqual(epBean.key, key, true)) {
+ try {
+ if (epBean.implementationClass != null) {
+ result = epBean.instantiate(epBean.implementationClass, myPicoContainer);
+ }
+ else {
+ Object factory = epBean.instantiate(epBean.factoryClass, myPicoContainer);
+ result = method.invoke(factory, args);
+ }
+ if (result != null) {
+ break;
+ }
+ }
+ catch (InvocationTargetException e) {
+ ExceptionUtil.rethrowUnchecked(e.getCause());
+ throw new RuntimeException(e);
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return (T)result;
+ }
+
+ public abstract String getKey(@NotNull KeyT key);
+}
+
diff --git a/platform/extensions/src/com/intellij/util/pico/AssignableToComponentAdapter.java b/platform/extensions/src/com/intellij/util/pico/AssignableToComponentAdapter.java
new file mode 100644
index 00000000..6ca5d286
--- /dev/null
+++ b/platform/extensions/src/com/intellij/util/pico/AssignableToComponentAdapter.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.intellij.util.pico;
+
+import org.picocontainer.ComponentAdapter;
+
+public interface AssignableToComponentAdapter extends ComponentAdapter {
+ String getAssignableToClassName();
+}
diff --git a/platform/extensions/src/com/intellij/util/pico/CachingConstructorInjectionComponentAdapter.java b/platform/extensions/src/com/intellij/util/pico/CachingConstructorInjectionComponentAdapter.java
new file mode 100644
index 00000000..baa2a41c
--- /dev/null
+++ b/platform/extensions/src/com/intellij/util/pico/CachingConstructorInjectionComponentAdapter.java
@@ -0,0 +1,200 @@
+// Copyright 2000-2018 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.util.pico;
+
+import com.intellij.util.ExceptionUtil;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.picocontainer.*;
+import org.picocontainer.defaults.*;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+
+/**
+ * A drop-in replacement of {@link ConstructorInjectionComponentAdapter}
+ * The same code (generified and cleaned up) but without constructor caching (hence taking up less memory).
+ * This class also inlines instance caching (e.g. it doesn't need to be wrapped in a CachingComponentAdapter).
+ */
+public class CachingConstructorInjectionComponentAdapter extends InstantiatingComponentAdapter {
+ @SuppressWarnings("SSBasedInspection")
+ private static final ThreadLocal<Set<CachingConstructorInjectionComponentAdapter>> ourGuard =
+ new ThreadLocal<>();
+ private Object myInstance;
+
+ public CachingConstructorInjectionComponentAdapter(@NotNull Object componentKey, @NotNull Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses) throws AssignabilityRegistrationException, NotConcreteRegistrationException {
+ super(componentKey, componentImplementation, parameters, allowNonPublicClasses, DefaultPicoContainer.DEFAULT_DELEGATING_COMPONENT_MONITOR, DefaultPicoContainer.DEFAULT_LIFECYCLE_STRATEGY);
+ }
+
+ public CachingConstructorInjectionComponentAdapter(@NotNull Object componentKey, @NotNull Class componentImplementation, Parameter[] parameters) {
+ this(componentKey, componentImplementation, parameters, false);
+ }
+
+ public CachingConstructorInjectionComponentAdapter(@NotNull Object componentKey, @NotNull Class componentImplementation) throws AssignabilityRegistrationException, NotConcreteRegistrationException {
+ this(componentKey, componentImplementation, null);
+ }
+
+ @Override
+ public Object getComponentInstance(PicoContainer container) throws PicoInitializationException, PicoIntrospectionException,
+ AssignabilityRegistrationException, NotConcreteRegistrationException {
+ Object instance = myInstance;
+ if (instance == null) {
+ myInstance = instance = instantiateGuarded(container, getComponentImplementation());
+ }
+ return instance;
+ }
+
+ private Object instantiateGuarded(PicoContainer container, Class stackFrame) {
+ Set<CachingConstructorInjectionComponentAdapter> currentStack = ourGuard.get();
+ if (currentStack == null) {
+ ourGuard.set(currentStack = ContainerUtil.newIdentityTroveSet());
+ }
+
+ if (currentStack.contains(this)) {
+ throw new CyclicDependencyException(stackFrame);
+ }
+
+ try {
+ currentStack.add(this);
+ return doGetComponentInstance(container);
+ } catch (final CyclicDependencyException e) {
+ e.push(stackFrame);
+ throw e;
+ } finally {
+ currentStack.remove(this);
+ }
+ }
+
+ private Object doGetComponentInstance(PicoContainer guardedContainer) {
+ final Constructor constructor;
+ try {
+ constructor = getGreediestSatisfiableConstructor(guardedContainer);
+ }
+ catch (AmbiguousComponentResolutionException e) {
+ e.setComponent(getComponentImplementation());
+ throw e;
+ }
+ ComponentMonitor componentMonitor = currentMonitor();
+ try {
+ Object[] parameters = getConstructorArguments(guardedContainer, constructor);
+ componentMonitor.instantiating(constructor);
+ long startTime = System.currentTimeMillis();
+ Object inst = newInstance(constructor, parameters);
+ componentMonitor.instantiated(constructor, System.currentTimeMillis() - startTime);
+ return inst;
+ }
+ catch (InvocationTargetException e) {
+ componentMonitor.instantiationFailed(constructor, e);
+ ExceptionUtil.rethrowUnchecked(e.getTargetException());
+ throw new PicoInvocationTargetInitializationException(e.getTargetException());
+ }
+ catch (InstantiationException e) {
+ componentMonitor.instantiationFailed(constructor, e);
+ throw new PicoInitializationException("Should never get here");
+ }
+ catch (IllegalAccessException e) {
+ componentMonitor.instantiationFailed(constructor, e);
+ throw new PicoInitializationException(e);
+ }
+ }
+
+ @NotNull
+ private Object[] getConstructorArguments(PicoContainer container, Constructor ctor) {
+ Class[] parameterTypes = ctor.getParameterTypes();
+ Object[] result = new Object[parameterTypes.length];
+ Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
+
+ for (int i = 0; i < currentParameters.length; i++) {
+ result[i] = currentParameters[i].resolveInstance(container, this, parameterTypes[i]);
+ }
+ return result;
+ }
+
+ @Override
+ protected Constructor getGreediestSatisfiableConstructor(PicoContainer container) throws
+ PicoIntrospectionException,
+ AssignabilityRegistrationException, NotConcreteRegistrationException {
+ final Set<Constructor> conflicts = new HashSet<>();
+ final Set<List<Class>> unsatisfiableDependencyTypes = new HashSet<>();
+ List<Constructor> sortedMatchingConstructors = getSortedMatchingConstructors();
+ Constructor greediestConstructor = null;
+ int lastSatisfiableConstructorSize = -1;
+ Class unsatisfiedDependencyType = null;
+ for (Constructor constructor : sortedMatchingConstructors) {
+ boolean failedDependency = false;
+ Class[] parameterTypes = constructor.getParameterTypes();
+ Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
+
+ // remember: all constructors with less arguments than the given parameters are filtered out already
+ for (int j = 0; j < currentParameters.length; j++) {
+ // check whether this constructor is satisfiable
+ if (currentParameters[j].isResolvable(container, this, parameterTypes[j])) {
+ continue;
+ }
+ unsatisfiableDependencyTypes.add(Arrays.asList(parameterTypes));
+ unsatisfiedDependencyType = parameterTypes[j];
+ failedDependency = true;
+ break;
+ }
+
+ if (greediestConstructor != null && parameterTypes.length != lastSatisfiableConstructorSize) {
+ if (conflicts.isEmpty()) {
+ // we found our match [aka. greedy and satisfied]
+ return greediestConstructor;
+ }
+ else {
+ // fits although not greedy
+ conflicts.add(constructor);
+ }
+ }
+ else if (!failedDependency && lastSatisfiableConstructorSize == parameterTypes.length) {
+ // satisfied and same size as previous one?
+ conflicts.add(constructor);
+ conflicts.add(greediestConstructor);
+ }
+ else if (!failedDependency) {
+ greediestConstructor = constructor;
+ lastSatisfiableConstructorSize = parameterTypes.length;
+ }
+ }
+ if (!conflicts.isEmpty()) {
+ throw new TooManySatisfiableConstructorsException(getComponentImplementation(), conflicts);
+ } else if (greediestConstructor == null && !unsatisfiableDependencyTypes.isEmpty()) {
+ throw new UnsatisfiableDependenciesException(this, unsatisfiedDependencyType, unsatisfiableDependencyTypes, container);
+ } else if (greediestConstructor == null) {
+ // be nice to the user, show all constructors that were filtered out
+ final Set<Constructor> nonMatching = ContainerUtil.newHashSet(getConstructors());
+ throw new PicoInitializationException("Either do the specified parameters not match any of the following constructors: " + nonMatching.toString() + " or the constructors were not accessible for '" + getComponentImplementation() + "'");
+ }
+ return greediestConstructor;
+ }
+
+ private List<Constructor> getSortedMatchingConstructors() {
+ List<Constructor> matchingConstructors = new ArrayList<>();
+ // filter out all constructors that will definitely not match
+ for (Constructor constructor : getConstructors()) {
+ if ((parameters == null || constructor.getParameterTypes().length == parameters.length) &&
+ (allowNonPublicClasses || (constructor.getModifiers() & Modifier.PUBLIC) != 0)) {
+ matchingConstructors.add(constructor);
+ }
+ }
+ // optimize list of constructors moving the longest at the beginning
+ if (parameters == null) {
+ matchingConstructors.sort((arg0, arg1) -> arg1.getParameterTypes().length - arg0.getParameterTypes().length);
+ }
+ return matchingConstructors;
+ }
+
+ @NotNull
+ private Constructor[] getConstructors() {
+ return (Constructor[]) AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public Object run() {
+ return getComponentImplementation().getDeclaredConstructors();
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/platform/extensions/src/com/intellij/util/pico/DefaultPicoContainer.java b/platform/extensions/src/com/intellij/util/pico/DefaultPicoContainer.java
new file mode 100644
index 00000000..c9e0c548
--- /dev/null
+++ b/platform/extensions/src/com/intellij/util/pico/DefaultPicoContainer.java
@@ -0,0 +1,407 @@
+// Copyright 2000-2018 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.util.pico;
+
+import com.intellij.openapi.extensions.AreaPicoContainer;
+import com.intellij.util.ReflectionUtil;
+import com.intellij.util.SmartList;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.FList;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.picocontainer.*;
+import org.picocontainer.defaults.*;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class DefaultPicoContainer implements AreaPicoContainer {
+ static final DelegatingComponentMonitor DEFAULT_DELEGATING_COMPONENT_MONITOR = new DelegatingComponentMonitor();
+ static final DefaultLifecycleStrategy DEFAULT_LIFECYCLE_STRATEGY = new DefaultLifecycleStrategy(DEFAULT_DELEGATING_COMPONENT_MONITOR);
+ private final PicoContainer parent;
+ private final Set<PicoContainer> children = new THashSet<>();
+
+ private final Map<Object, ComponentAdapter> componentKeyToAdapterCache = ContainerUtil.newConcurrentMap();
+ private final LinkedHashSetWrapper<ComponentAdapter> componentAdapters = new LinkedHashSetWrapper<>();
+ private final Map<String, ComponentAdapter> classNameToAdapter = ContainerUtil.newConcurrentMap();
+ private final AtomicReference<FList<ComponentAdapter>> nonAssignableComponentAdapters = new AtomicReference<>(FList.emptyList());
+
+ public DefaultPicoContainer(@Nullable PicoContainer parent) {
+ this.parent = parent == null ? null : ImmutablePicoContainerProxyFactory.newProxyInstance(parent);
+ }
+
+ public DefaultPicoContainer() {
+ this(null);
+ }
+
+ @Override
+ public Collection<ComponentAdapter> getComponentAdapters() {
+ return componentAdapters.getImmutableSet();
+ }
+
+ private void appendNonAssignableAdaptersOfType(@NotNull Class componentType, @NotNull List<? super ComponentAdapter> result) {
+ List<ComponentAdapter> comp = new ArrayList<>();
+ for (final ComponentAdapter componentAdapter : nonAssignableComponentAdapters.get()) {
+ if (ReflectionUtil.isAssignable(componentType, componentAdapter.getComponentImplementation())) {
+ comp.add(componentAdapter);
+ }
+ }
+ for (int i = comp.size() - 1; i >= 0; i--) {
+ result.add(comp.get(i));
+ }
+ }
+
+ @Override
+ @Nullable
+ public final ComponentAdapter getComponentAdapter(Object componentKey) {
+ ComponentAdapter adapter = getFromCache(componentKey);
+ if (adapter == null && parent != null) {
+ return parent.getComponentAdapter(componentKey);
+ }
+ return adapter;
+ }
+
+ @Nullable
+ private ComponentAdapter getFromCache(final Object componentKey) {
+ ComponentAdapter adapter = componentKeyToAdapterCache.get(componentKey);
+ if (adapter != null) {
+ return adapter;
+ }
+
+ if (componentKey instanceof Class) {
+ return componentKeyToAdapterCache.get(((Class)componentKey).getName());
+ }
+
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public ComponentAdapter getComponentAdapterOfType(@NotNull Class componentType) {
+ // See http://jira.codehaus.org/secure/ViewIssue.jspa?key=PICO-115
+ ComponentAdapter adapterByKey = getComponentAdapter(componentType);
+ if (adapterByKey != null) {
+ return adapterByKey;
+ }
+
+ List<ComponentAdapter> found = getComponentAdaptersOfType(componentType);
+ if (found.size() == 1) {
+ return found.get(0);
+ }
+ if (found.isEmpty()) {
+ return parent == null ? null : parent.getComponentAdapterOfType(componentType);
+ }
+
+ Class[] foundClasses = new Class[found.size()];
+ for (int i = 0; i < foundClasses.length; i++) {
+ foundClasses[i] = found.get(i).getComponentImplementation();
+ }
+ throw new AmbiguousComponentResolutionException(componentType, foundClasses);
+ }
+
+ @Override
+ public List<ComponentAdapter> getComponentAdaptersOfType(final Class componentType) {
+ if (componentType == null || componentType == String.class) {
+ return Collections.emptyList();
+ }
+
+ List<ComponentAdapter> result = new SmartList<>();
+
+ final ComponentAdapter cacheHit = classNameToAdapter.get(componentType.getName());
+ if (cacheHit != null) {
+ result.add(cacheHit);
+ }
+
+ appendNonAssignableAdaptersOfType(componentType, result);
+ return result;
+ }
+
+ @Override
+ public ComponentAdapter registerComponent(@NotNull ComponentAdapter componentAdapter) {
+ Object componentKey = componentAdapter.getComponentKey();
+ if (componentKeyToAdapterCache.containsKey(componentKey)) {
+ throw new DuplicateComponentKeyRegistrationException(componentKey);
+ }
+
+ if (componentAdapter instanceof AssignableToComponentAdapter) {
+ String classKey = ((AssignableToComponentAdapter)componentAdapter).getAssignableToClassName();
+ classNameToAdapter.put(classKey, componentAdapter);
+ }
+ else {
+ do {
+ FList<ComponentAdapter> oldList = nonAssignableComponentAdapters.get();
+ FList<ComponentAdapter> newList = oldList.prepend(componentAdapter);
+ if (nonAssignableComponentAdapters.compareAndSet(oldList, newList)) {
+ break;
+ }
+ }
+ while (true);
+ }
+
+ componentAdapters.add(componentAdapter);
+
+ componentKeyToAdapterCache.put(componentKey, componentAdapter);
+ return componentAdapter;
+ }
+
+ @Override
+ public ComponentAdapter unregisterComponent(@NotNull Object componentKey) {
+ ComponentAdapter adapter = componentKeyToAdapterCache.remove(componentKey);
+ componentAdapters.remove(adapter);
+ if (adapter instanceof AssignableToComponentAdapter) {
+ classNameToAdapter.remove(((AssignableToComponentAdapter)adapter).getAssignableToClassName());
+ }
+ else {
+ do {
+ FList<ComponentAdapter> oldList = nonAssignableComponentAdapters.get();
+ FList<ComponentAdapter> newList = oldList.without(adapter);
+ if (nonAssignableComponentAdapters.compareAndSet(oldList, newList)) {
+ break;
+ }
+ }
+ while (true);
+ }
+ return adapter;
+ }
+
+ @Override
+ public List getComponentInstances() {
+ return getComponentInstancesOfType(Object.class);
+ }
+
+ @Override
+ public List<Object> getComponentInstancesOfType(@Nullable Class componentType) {
+ if (componentType == null) {
+ return Collections.emptyList();
+ }
+
+ List<Object> result = new ArrayList<>();
+ for (ComponentAdapter componentAdapter : getComponentAdapters()) {
+ if (ReflectionUtil.isAssignable(componentType, componentAdapter.getComponentImplementation())) {
+ // may be null in the case of the "implicit" adapter representing "this".
+ ContainerUtil.addIfNotNull(result, getInstance(componentAdapter));
+ }
+ }
+ return result;
+ }
+
+ @FunctionalInterface
+ public interface LazyComponentAdapter {
+ boolean isComponentInstantiated();
+ }
+
+ @Nullable
+ public <T> T getComponentInstanceIfInstantiated(@NotNull String componentKey) {
+ ComponentAdapter adapter = getFromCache(componentKey);
+ if (!(adapter instanceof LazyComponentAdapter)) {
+ //noinspection unchecked
+ return (T)getComponentInstance(componentKey);
+ }
+
+ if (((LazyComponentAdapter)adapter).isComponentInstantiated()) {
+ //noinspection unchecked
+ return (T)getLocalInstance(adapter);
+ }
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public Object getComponentInstance(Object componentKey) {
+ ComponentAdapter adapter = getFromCache(componentKey);
+ if (adapter != null) {
+ return getLocalInstance(adapter);
+ }
+ if (parent != null) {
+ adapter = parent.getComponentAdapter(componentKey);
+ if (adapter != null) {
+ return parent.getComponentInstance(adapter.getComponentKey());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public Object getComponentInstanceOfType(Class componentType) {
+ final ComponentAdapter componentAdapter = getComponentAdapterOfType(componentType);
+ return componentAdapter == null ? null : getInstance(componentAdapter);
+ }
+
+ @Nullable
+ private Object getInstance(@NotNull ComponentAdapter componentAdapter) {
+ if (getComponentAdapters().contains(componentAdapter)) {
+ return getLocalInstance(componentAdapter);
+ }
+ if (parent != null) {
+ return parent.getComponentInstance(componentAdapter.getComponentKey());
+ }
+
+ return null;
+ }
+
+ private Object getLocalInstance(@NotNull ComponentAdapter componentAdapter) {
+ PicoException firstLevelException;
+ try {
+ return componentAdapter.getComponentInstance(this);
+ }
+ catch (PicoInitializationException | PicoIntrospectionException e) {
+ firstLevelException = e;
+ }
+
+ if (parent != null) {
+ Object instance = parent.getComponentInstance(componentAdapter.getComponentKey());
+ if (instance != null) {
+ return instance;
+ }
+ }
+
+ throw firstLevelException;
+ }
+
+ @Override
+ @Nullable
+ public ComponentAdapter unregisterComponentByInstance(@NotNull Object componentInstance) {
+ for (ComponentAdapter adapter : getComponentAdapters()) {
+ Object o = getInstance(adapter);
+ if (componentInstance.equals(o)) {
+ return unregisterComponent(adapter.getComponentKey());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void verify() {
+ new VerifyingVisitor().traverse(this);
+ }
+
+ @Override
+ public void start() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void stop() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void dispose() {
+ throw new UnsupportedOperationException();
+ }
+
+ @NotNull
+ @Override
+ public MutablePicoContainer makeChildContainer() {
+ DefaultPicoContainer pc = new DefaultPicoContainer(this);
+ addChildContainer(pc);
+ return pc;
+ }
+
+ @Override
+ public boolean addChildContainer(@NotNull PicoContainer child) {
+ return children.add(child);
+ }
+
+ @Override
+ public boolean removeChildContainer(@NotNull PicoContainer child) {
+ return children.remove(child);
+ }
+
+ @Override
+ public void accept(PicoVisitor visitor) {
+ visitor.visitContainer(this);
+
+ for (ComponentAdapter adapter : getComponentAdapters()) {
+ adapter.accept(visitor);
+ }
+ for (PicoContainer child : new SmartList<>(children)) {
+ child.accept(visitor);
+ }
+ }
+
+ @Override
+ public ComponentAdapter registerComponentInstance(@NotNull Object component) {
+ return registerComponentInstance(component.getClass(), component);
+ }
+
+ @Override
+ public ComponentAdapter registerComponentInstance(@NotNull Object componentKey, @NotNull Object componentInstance) {
+ return registerComponent(new InstanceComponentAdapter(componentKey, componentInstance, DEFAULT_LIFECYCLE_STRATEGY));
+ }
+
+ @Override
+ public ComponentAdapter registerComponentImplementation(@NotNull Class componentImplementation) {
+ return registerComponentImplementation(componentImplementation, componentImplementation);
+ }
+
+ @Override
+ public ComponentAdapter registerComponentImplementation(@NotNull Object componentKey, @NotNull Class componentImplementation) {
+ return registerComponentImplementation(componentKey, componentImplementation, null);
+ }
+
+ @Override
+ public ComponentAdapter registerComponentImplementation(@NotNull Object componentKey, @NotNull Class componentImplementation, Parameter[] parameters) {
+ ComponentAdapter componentAdapter = new CachingConstructorInjectionComponentAdapter(componentKey, componentImplementation, parameters, true);
+ return registerComponent(componentAdapter);
+ }
+
+ @Override
+ public PicoContainer getParent() {
+ return parent;
+ }
+
+ /**
+ * A linked hash set that's copied on write operations.
+ * @param <T>
+ */
+ private static class LinkedHashSetWrapper<T> {
+ private final Object lock = new Object();
+ private volatile Set<T> immutableSet;
+ private LinkedHashSet<T> synchronizedSet = new LinkedHashSet<>();
+
+ public void add(@NotNull T element) {
+ synchronized (lock) {
+ if (!synchronizedSet.contains(element)) {
+ copySyncSetIfExposedAsImmutable().add(element);
+ }
+ }
+ }
+
+ private LinkedHashSet<T> copySyncSetIfExposedAsImmutable() {
+ if (immutableSet != null) {
+ immutableSet = null;
+ synchronizedSet = new LinkedHashSet<>(synchronizedSet);
+ }
+ return synchronizedSet;
+ }
+
+ public void remove(@Nullable T element) {
+ synchronized (lock) {
+ copySyncSetIfExposedAsImmutable().remove(element);
+ }
+ }
+
+ @NotNull
+ public Set<T> getImmutableSet() {
+ Set<T> res = immutableSet;
+ if (res == null) {
+ synchronized (lock) {
+ res = immutableSet;
+ if (res == null) {
+ // Expose the same set as immutable. It should be never modified again. Next add/remove operations will copy synchronizedSet
+ immutableSet = res = Collections.unmodifiableSet(synchronizedSet);
+ }
+ }
+ }
+
+ return res;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultPicoContainer" + (getParent() == null ? " (root)" : " (parent="+getParent()+")");
+ }
+} \ No newline at end of file
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectOne.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectOne.java
new file mode 100644
index 00000000..489e8f34
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectOne.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions.impl;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class DependentObjectOne {
+ private final XMLTestBean[] myTestBeans;
+
+ public DependentObjectOne(XMLTestBean[] testBeans) {
+ myTestBeans = testBeans;
+ }
+
+ public XMLTestBean[] getTestBeans() {
+ return myTestBeans;
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectThree.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectThree.java
new file mode 100644
index 00000000..f6ccabfb
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectThree.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions.impl;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class DependentObjectThree {
+ private final DependentObjectOne myOne;
+ private final DependentObjectTwo myTwo;
+ private final XMLTestBean[] myTestBeans;
+
+ public DependentObjectThree(DependentObjectOne one, XMLTestBean[] testBeans, DependentObjectTwo two) {
+ myOne = one;
+ myTestBeans = testBeans;
+ myTwo = two;
+ }
+
+ public DependentObjectOne getOne() {
+ return myOne;
+ }
+
+ public XMLTestBean[] getTestBeans() {
+ return myTestBeans;
+ }
+
+ public DependentObjectTwo getTwo() {
+ return myTwo;
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectTwo.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectTwo.java
new file mode 100644
index 00000000..b337a5e8
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/DependentObjectTwo.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions.impl;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class DependentObjectTwo {
+ private final DependentObjectOne myOne;
+
+ public DependentObjectTwo(DependentObjectOne one) {
+ myOne = one;
+ }
+
+ public DependentObjectOne getOne() {
+ return myOne;
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionComponentAdapterTest.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionComponentAdapterTest.java
new file mode 100644
index 00000000..143fb898
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionComponentAdapterTest.java
@@ -0,0 +1,43 @@
+// Copyright 2000-2018 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.openapi.extensions.impl;
+
+import com.intellij.openapi.extensions.DefaultPluginDescriptor;
+import com.intellij.openapi.extensions.LoadingOrder;
+import com.intellij.openapi.util.JDOMUtil;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.picocontainer.defaults.DefaultPicoContainer;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class ExtensionComponentAdapterTest {
+ @Test
+ public void testLoadingOrderReading() {
+ assertEquals(LoadingOrder.ANY, createAdapter(LoadingOrder.ANY).getOrder());
+ assertEquals(LoadingOrder.FIRST, createAdapter(LoadingOrder.FIRST).getOrder());
+ assertEquals(LoadingOrder.LAST, createAdapter(LoadingOrder.LAST).getOrder());
+ assertEquals(LoadingOrder.before("test"), createAdapter(LoadingOrder.readOrder("BEFORE test")).getOrder());
+ assertEquals(LoadingOrder.after("test"), createAdapter(LoadingOrder.readOrder("AFTER test")).getOrder());
+ }
+
+ @Test
+ public void testUnknownAttributes() throws IOException, JDOMException {
+ String name = TestExtensionClassOne.class.getName();
+ Element element = JDOMUtil.load("<bean implementation=\"123\"/>");
+ DefaultPicoContainer container = new DefaultPicoContainer();
+ DefaultPluginDescriptor descriptor = new DefaultPluginDescriptor("test");
+ new ExtensionComponentAdapter(name, element, container, descriptor).getComponentInstance(container);
+ }
+
+ @NotNull
+ private static ExtensionComponentAdapter createAdapter(@NotNull LoadingOrder order) {
+ return new ExtensionComponentAdapter(Object.class.getName(), null, null, null, order, null);
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java
new file mode 100644
index 00000000..f2363240
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java
@@ -0,0 +1,282 @@
+// Copyright 2000-2018 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.openapi.extensions.impl;
+
+import com.intellij.openapi.extensions.*;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.util.SmartList;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+import org.picocontainer.defaults.DefaultPicoContainer;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+/**
+ * @author AKireyev
+ */
+public class ExtensionPointImplTest {
+ @Test
+ public void testCreate() {
+ ExtensionPoint<Integer> extensionPoint = buildExtensionPoint(Integer.class);
+ assertThat(extensionPoint.getName()).isEqualTo(ExtensionsImplTest.EXTENSION_POINT_NAME_1);
+ assertThat(extensionPoint.getClassName()).isEqualTo(Integer.class.getName());
+ }
+
+ @Test
+ public void testUnregisterObject() {
+ ExtensionPoint<Integer> extensionPoint = buildExtensionPoint(Integer.class);
+ extensionPoint.registerExtension(new Integer(123));
+ Object[] extensions = extensionPoint.getExtensions();
+ assertThat(extensions).hasSize(1);
+ extensionPoint.unregisterExtension(new Integer(123));
+ extensions = extensionPoint.getExtensions();
+ assertThat(extensions).isEmpty();
+ }
+
+ @Test
+ public void testRegisterUnregisterExtension() {
+ final AreaInstance area = new AreaInstance() {};
+ final ExtensionPoint<Object> extensionPoint = new ExtensionPointImpl<>(
+ "an.extension.point", Object.class.getName(), ExtensionPoint.Kind.INTERFACE, buildExtensionArea(), area,
+ new UndefinedPluginDescriptor());
+
+ final boolean[] flags = new boolean[2];
+ Extension extension = new Extension() {
+ @Override
+ public void extensionAdded(@NotNull ExtensionPoint extensionPoint1) {
+ assertThat(extensionPoint1).isSameAs(extensionPoint);
+ assertThat(extensionPoint1.getArea()).isSameAs(area);
+ flags[0] = true;
+ }
+
+ @Override
+ public void extensionRemoved(@NotNull ExtensionPoint extensionPoint1) {
+ assertThat(extensionPoint1).isSameAs(extensionPoint);
+ assertThat(extensionPoint1.getArea()).isSameAs(area);
+ flags[1] = true;
+ }
+ };
+
+ extensionPoint.registerExtension(extension);
+ assertThat(flags[0]).describedAs("Register call is missed").isTrue();
+ assertThat(flags[1]).isFalse();
+
+ extensionPoint.unregisterExtension(extension);
+ assertThat(flags[1]).describedAs("Unregister call is missed").isTrue();
+ }
+
+ @Test
+ public void testRegisterObject() {
+ ExtensionPoint<Integer> extensionPoint = buildExtensionPoint(Integer.class);
+ extensionPoint.registerExtension(new Integer(123));
+ Object[] extensions = extensionPoint.getExtensions();
+ assertThat(extensions).describedAs("One extension").hasSize(1);
+ assertThat(extensions).isInstanceOf(Integer[].class);
+ assertThat(extensions[0]).isEqualTo(new Integer(123));
+ }
+
+ @Test
+ public void testRegistrationOrder() {
+ ExtensionPoint<Integer> extensionPoint = buildExtensionPoint(Integer.class);
+ extensionPoint.registerExtension(new Integer(123));
+ extensionPoint.registerExtension(new Integer(321), LoadingOrder.FIRST);
+ Object[] extensions = extensionPoint.getExtensions();
+ assertThat(extensions).hasSize(2);
+ assertThat(extensions[0]).isEqualTo(new Integer(321));
+ }
+
+ @Test
+ public void testListener() {
+ ExtensionPoint<Integer> extensionPoint = buildExtensionPoint(Integer.class);
+ final boolean[] added = new boolean[1];
+ final boolean[] removed = new boolean[1];
+ extensionPoint.addExtensionPointListener(new ExtensionPointListener<Integer>() {
+ @Override
+ public void extensionAdded(@NotNull Integer extension, final PluginDescriptor pluginDescriptor) {
+ added[0] = true;
+ }
+
+ @Override
+ public void extensionRemoved(@NotNull Integer extension, final PluginDescriptor pluginDescriptor) {
+ removed[0] = true;
+ }
+ });
+ assertThat(added[0]).isFalse();
+ assertThat(removed[0]).isFalse();
+ extensionPoint.registerExtension(new Integer(123));
+ assertThat(added[0]).isTrue();
+ assertThat(removed[0]).isFalse();
+ added[0] = false;
+ extensionPoint.unregisterExtension(new Integer(123));
+ assertThat(added[0]).isFalse();
+ assertThat(removed[0]).isTrue();
+ }
+
+ @Test
+ public void testLateListener() {
+ ExtensionPoint<Integer> extensionPoint = buildExtensionPoint(Integer.class);
+ final boolean[] added = new boolean[1];
+ extensionPoint.registerExtension(new Integer(123));
+ assertThat(added[0]).isFalse();
+ extensionPoint.addExtensionPointListener(new ExtensionPointListener<Integer>() {
+ @Override
+ public void extensionAdded(@NotNull Integer extension, final PluginDescriptor pluginDescriptor) {
+ added[0] = true;
+ }
+
+ @Override
+ public void extensionRemoved(@NotNull Integer extension, final PluginDescriptor pluginDescriptor) {
+ }
+ });
+ assertThat(added[0]).isTrue();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testIncompatibleExtension() {
+ ExtensionPoint extensionPoint = buildExtensionPoint(Integer.class);
+
+ try {
+ extensionPoint.registerExtension(new Double(0));
+ fail("must throw");
+ }
+ catch (AssertionError ignored) {
+ }
+
+ assertThat(extensionPoint.getExtensions()).isEmpty();
+
+ extensionPoint.registerExtension(new Integer(0));
+ assertThat(extensionPoint.getExtensions()).hasSize(1);
+ }
+
+ @Test
+ public void testIncompatibleAdapter() {
+ ExtensionPointImpl<Integer> extensionPoint = buildExtensionPoint(Integer.class);
+
+ extensionPoint.registerExtensionAdapter(stringAdapter());
+
+ try {
+ assertThat(extensionPoint.getExtensions()).isEmpty();
+ fail("must throw");
+ }
+ catch (AssertionError ignored) {
+ }
+ }
+
+ @Test
+ public void testCompatibleAdapter() {
+ ExtensionPointImpl<Integer> extensionPoint = buildExtensionPoint(Integer.class);
+ extensionPoint.registerExtension(new Integer(0));
+ assertThat(extensionPoint.getExtensions()).hasSize(1);
+ }
+
+ @Test
+ public void testCancelledRegistration() {
+ ExtensionPoint<String> extensionPoint = buildExtensionPoint(String.class);
+ MyShootingComponentAdapter adapter = stringAdapter();
+
+ extensionPoint.registerExtension("first");
+ assertThat(extensionPoint.getExtensions()).hasSize(1);
+
+ extensionPoint.registerExtension("second", LoadingOrder.FIRST); // registers a wrapping adapter
+ ((ExtensionPointImpl)extensionPoint).registerExtensionAdapter(adapter);
+ adapter.setFire(true);
+ try {
+ extensionPoint.getExtensions();
+ fail("PCE expected");
+ }
+ catch (ProcessCanceledException ignored) { }
+
+ adapter.setFire(false);
+ String[] extensions = extensionPoint.getExtensions();
+ assertThat(extensions[0]).isEqualTo("second");
+ assertThat(new SmartList<>(extensions[1])).containsAnyOf("", "first");
+ assertThat(new SmartList<>(extensions[2])).containsAnyOf("", "first");
+ assertThat(extensions[2]).isNotEqualTo(extensions[1]);
+ }
+
+ @Test
+ public void testListenerNotifications() {
+ ExtensionPoint<String> extensionPoint = buildExtensionPoint(String.class);
+ final List<String> extensions = ContainerUtil.newArrayList();
+ extensionPoint.addExtensionPointListener(new ExtensionPointListener<String>() {
+ @Override
+ public void extensionAdded(@NotNull String extension, @Nullable PluginDescriptor pluginDescriptor) {
+ extensions.add(extension);
+ }
+ });
+ MyShootingComponentAdapter adapter = stringAdapter();
+
+ extensionPoint.registerExtension("first");
+ assertThat(extensions).contains("first");
+
+ extensionPoint.registerExtension("second", LoadingOrder.FIRST);
+ ((ExtensionPointImpl)extensionPoint).registerExtensionAdapter(adapter);
+ adapter.setFire(true);
+ try {
+ extensionPoint.getExtensions();
+ fail("PCE expected");
+ }
+ catch (ProcessCanceledException ignored) { }
+ assertThat(extensions).contains("first", "second");
+
+ adapter.setFire(false);
+ extensionPoint.getExtensions();
+ assertThat(extensions).contains("first", "second", "");
+ }
+
+ @Test
+ public void clientsCannotModifyCachedExtensions() {
+ ExtensionPoint<Integer> extensionPoint = buildExtensionPoint(Integer.class);
+ extensionPoint.registerExtension(4);
+ extensionPoint.registerExtension(2);
+
+ Integer[] extensions = extensionPoint.getExtensions();
+ assertThat(extensions).containsExactly(4, 2);
+ Arrays.sort(extensions);
+ assertThat(extensions).containsExactly(2, 4);
+
+ assertThat(extensionPoint.getExtensions()).containsExactly(4, 2);
+ }
+
+ private static <T> ExtensionPointImpl<T> buildExtensionPoint(Class<T> aClass) {
+ return new ExtensionPointImpl<>(
+ ExtensionsImplTest.EXTENSION_POINT_NAME_1, aClass.getName(), ExtensionPoint.Kind.INTERFACE,
+ buildExtensionArea(), null, new UndefinedPluginDescriptor());
+ }
+
+ private static ExtensionsAreaImpl buildExtensionArea() {
+ return new ExtensionsAreaImpl(new DefaultPicoContainer());
+ }
+
+ private static MyShootingComponentAdapter stringAdapter() {
+ return new MyShootingComponentAdapter(String.class.getName());
+ }
+
+ private static class MyShootingComponentAdapter extends ExtensionComponentAdapter {
+ private boolean myFire;
+
+ MyShootingComponentAdapter(@NotNull String implementationClass) {
+ super(implementationClass, null, new DefaultPicoContainer(), new DefaultPluginDescriptor("test"));
+ }
+
+ public void setFire(boolean fire) {
+ myFire = fire;
+ }
+
+ @Override
+ public Object getExtension() {
+ if (myFire) {
+ throw new ProcessCanceledException();
+ }
+ else {
+ return super.getExtension();
+ }
+ }
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsAreaTest.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsAreaTest.java
new file mode 100644
index 00000000..c23a9580
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsAreaTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions.impl;
+
+import org.junit.Test;
+import org.picocontainer.MutablePicoContainer;
+import org.picocontainer.defaults.DefaultPicoContainer;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author mike
+ */
+public class ExtensionsAreaTest {
+ @Test
+ public void testGetComponentAdapterDoesntDuplicateAdapters() {
+ MutablePicoContainer picoContainer =
+ new ExtensionsAreaImpl("foo", null, new DefaultPicoContainer()).getPicoContainer();
+ picoContainer.registerComponentImplementation("runnable", ExtensionsAreaTest.class);
+
+ List adapters = picoContainer.getComponentAdaptersOfType(ExtensionsAreaTest.class);
+ assertEquals(1, adapters.size());
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsComplexTest.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsComplexTest.java
new file mode 100644
index 00000000..59ffe6c6
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsComplexTest.java
@@ -0,0 +1,122 @@
+// Copyright 2000-2018 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.openapi.extensions.impl;
+
+import com.intellij.openapi.extensions.AreaInstance;
+import com.intellij.openapi.extensions.ExtensionPoint;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.util.JDOMUtil;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jetbrains.annotations.NonNls;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class ExtensionsComplexTest {
+ private static final String PLUGIN_NAME = "the.test.plugin";
+ private static final String PLUGIN_NAME_2 = "another.test.plugin";
+
+ private List<MyAreaInstance> myAreasToDispose;
+
+ @Before
+ public void setUp() {
+ myAreasToDispose = new ArrayList<>();
+ Extensions.registerAreaClass("area", null);
+ Extensions.registerAreaClass("child_area", "area");
+ }
+
+ @After
+ public void tearDown() {
+ for (MyAreaInstance instance : myAreasToDispose) {
+ Extensions.disposeArea(instance);
+ }
+ myAreasToDispose = null;
+ for (ExtensionPoint extensionPoint : Extensions.getRootArea().getExtensionPoints()) {
+ if (extensionPoint.getName().startsWith(PLUGIN_NAME) || extensionPoint.getName().startsWith(PLUGIN_NAME_2)) {
+ Extensions.getRootArea().unregisterExtensionPoint(extensionPoint.getName());
+ }
+ }
+ }
+
+ @Test
+ public void testPluginInit() throws IOException, JDOMException {
+ initExtensionPoints(
+ "<extensionPoints>\n" +
+ " <extensionPoint name=\"extensionPoint\" beanClass=\"com.intellij.openapi.extensions.impl.XMLTestBean\" />\n" +
+ " <extensionPoint name=\"dependentOne\" beanClass=\"com.intellij.openapi.extensions.impl.DependentObjectOne\" />\n" +
+ "</extensionPoints>", null);
+ initExtensions(
+ " <extensions xmlns=\"the.test.plugin\">\n" +
+ " <extensionPoint>\n" +
+ " <prop1>321</prop1>\n" +
+ " </extensionPoint>\n" +
+ " <dependentOne/>\n" +
+ " </extensions>", null);
+
+ assertTrue(Extensions.getRootArea().hasExtensionPoint("the.test.plugin.extensionPoint"));
+ assertEquals(1, Extensions.getExtensions("the.test.plugin.extensionPoint").length);
+ ExtensionPoint<XMLTestBean> ep = Extensions.getRootArea().getExtensionPoint("the.test.plugin.extensionPoint");
+ XMLTestBean bean = ep.getExtension();
+ assertNotNull(bean);
+ assertEquals(321, bean.getProp1());
+ assertEquals("the.test.plugin", bean.getPluginId().getIdString());
+
+ DependentObjectOne dependentObjectOne = (DependentObjectOne)Extensions.getRootArea().getExtensionPoint("the.test.plugin.dependentOne").getExtension();
+ assertNotNull(dependentObjectOne);
+ assertEquals(1, dependentObjectOne.getTestBeans().length);
+
+ AreaInstance areaInstance = new MyAreaInstance();
+ Extensions.instantiateArea("area", areaInstance, null);
+
+ initExtensionPoints(
+ "<extensionPoints>\n" +
+ " <extensionPoint name=\"dependentTwo\" beanClass=\"com.intellij.openapi.extensions.impl.DependentObjectTwo\" area=\"area\"/>\n" +
+ " <extensionPoint name=\"extensionPoint4area\" beanClass=\"com.intellij.openapi.extensions.impl.XMLTestBean\" area=\"area\" />\n" +
+ "</extensionPoints>", areaInstance);
+
+ initExtensions(
+ " <extensions xmlns=\"the.test.plugin\">\n" +
+ " <extensionPoint4area area=\"area\"/>\n" +
+ " <dependentTwo area=\"area\"/>\n" +
+ " </extensions>", areaInstance);
+
+ ExtensionPoint extensionPoint = Extensions.getArea(areaInstance).getExtensionPoint("the.test.plugin.extensionPoint4area");
+ assertNotNull(extensionPoint);
+ assertSame(areaInstance, extensionPoint.getArea());
+ assertNotNull(extensionPoint.getExtension());
+
+ DependentObjectTwo dependentObjectTwo = (DependentObjectTwo)Extensions.getArea(areaInstance).getExtensionPoint("the.test.plugin.dependentTwo").getExtension();
+ assertNotNull(dependentObjectTwo);
+ assertSame(dependentObjectOne, dependentObjectTwo.getOne());
+ }
+
+ private static void initExtensionPoints(@NonNls String data, AreaInstance instance) throws IOException, JDOMException {
+ final Element element = JDOMUtil.load(data);
+ for (final Object o : element.getChildren()) {
+ Element child = (Element)o;
+ ((ExtensionsAreaImpl)Extensions.getArea(instance)).registerExtensionPoint(ExtensionsComplexTest.PLUGIN_NAME, child);
+ }
+ }
+
+ private static void initExtensions(@NonNls String data, AreaInstance instance) throws IOException, JDOMException {
+ final Element element = JDOMUtil.load(data);
+ for (final Element child : element.getChildren()) {
+ ((ExtensionsAreaImpl)Extensions.getArea(instance)).registerExtension(element.getNamespaceURI(), child);
+ }
+ }
+
+ private class MyAreaInstance implements AreaInstance {
+ private MyAreaInstance() {
+ myAreasToDispose.add(this);
+ }
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java
new file mode 100644
index 00000000..795133a5
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java
@@ -0,0 +1,289 @@
+// Copyright 2000-2018 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.openapi.extensions.impl;
+
+import com.intellij.openapi.extensions.*;
+import com.intellij.openapi.util.JDOMUtil;
+import org.jdom.JDOMException;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.picocontainer.MutablePicoContainer;
+import org.picocontainer.defaults.DefaultPicoContainer;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author AKireyev
+ */
+public class ExtensionsImplTest {
+ public static final String EXTENSION_POINT_NAME_1 = "ext.point.one";
+
+ @Test
+ public void testCreateAndAccess() {
+ ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null);
+ int numEP = extensionsArea.getExtensionPoints().length;
+ extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName());
+ assertEquals("Additional EP available", numEP + 1, extensionsArea.getExtensionPoints().length);
+ assertNotNull("EP by name available", extensionsArea.getExtensionPoint(EXTENSION_POINT_NAME_1));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testInvalidActions() {
+ ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null);
+ extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName());
+ extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Boolean.class.getName());
+ fail("Should not allow duplicate registration");
+ }
+
+ @Test
+ public void testUnregisterEP() {
+ ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null);
+ int numEP = extensionsArea.getExtensionPoints().length;
+ extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName());
+
+ final boolean[] removed = {false};
+ extensionsArea.getExtensionPoint(EXTENSION_POINT_NAME_1).addExtensionPointListener(new ExtensionPointListener<Object>() {
+ @Override
+ public void extensionRemoved(@NotNull Object extension, PluginDescriptor pluginDescriptor) {
+ removed[0] = true;
+ }
+ });
+ extensionsArea.getExtensionPoint(EXTENSION_POINT_NAME_1).registerExtension(new Integer(123));
+ extensionsArea.unregisterExtensionPoint(EXTENSION_POINT_NAME_1);
+ assertEquals("Extension point should be removed", numEP, extensionsArea.getExtensionPoints().length);
+ assertTrue("Extension point disposed", removed[0]);
+ }
+
+ @Test
+ public void testAvailabilityListener() {
+ ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null);
+ MyListener.reset();
+ extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension(
+ new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName()));
+ assertEquals(0, MyListener.regCount);
+ assertEquals(0, MyListener.remCount);
+ extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName());
+ assertEquals(1, MyListener.regCount);
+ assertEquals(0, MyListener.remCount);
+ MyListener.reset();
+ extensionsArea.unregisterExtensionPoint(EXTENSION_POINT_NAME_1);
+ assertEquals(1, MyListener.remCount);
+ assertEquals(0, MyListener.regCount);
+ }
+
+ @Test
+ public void testAvailability2Listeners() {
+ ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null);
+ MyListener.reset();
+ extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName());
+ extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension(
+ new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName()));
+ extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension(
+ new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName()));
+ assertEquals(2, MyListener.regCount);
+ assertEquals(0, MyListener.remCount);
+ MyListener.reset();
+ extensionsArea.unregisterExtensionPoint(EXTENSION_POINT_NAME_1);
+ assertEquals(2, MyListener.remCount);
+ assertEquals(0, MyListener.regCount);
+ }
+
+ @Test
+ public void testAvailabilityListenerAfter() {
+ ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(null);
+ extensionsArea.registerExtensionPoint(EXTENSION_POINT_NAME_1, Integer.class.getName());
+ MyListener.reset();
+ extensionsArea.getExtensionPoint(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME).registerExtension(
+ new EPAvailabilityListenerExtension(EXTENSION_POINT_NAME_1, MyListener.class.getName()));
+ assertEquals(1, MyListener.regCount);
+ assertEquals(0, MyListener.remCount);
+ }
+
+ @Test
+ public void testTryPicoContainer() {
+ DefaultPicoContainer rootContainer = new DefaultPicoContainer();
+ rootContainer.registerComponentInstance("plugin1", new DefaultPicoContainer(rootContainer));
+ rootContainer.registerComponentInstance("plugin2", new DefaultPicoContainer(rootContainer));
+ MutablePicoContainer container1 = (MutablePicoContainer)rootContainer.getComponentInstance("plugin1");
+ MutablePicoContainer container2 = (MutablePicoContainer)rootContainer.getComponentInstance("plugin2");
+ container1.registerComponentImplementation("component1", MyComponent1.class);
+ container1.registerComponentImplementation("component1.1", MyComponent1.class);
+ container2.registerComponentImplementation("component2", MyComponent2.class);
+ MyInterface1 testInstance = () -> { };
+ rootContainer.registerComponentInstance(testInstance);
+ MyComponent1 component1 = (MyComponent1)container1.getComponentInstance("component1");
+ assertEquals(testInstance, component1.testObject);
+ rootContainer.registerComponentInstance("component1", component1);
+ MyComponent1 component11 = (MyComponent1)container1.getComponentInstance("component1.1");
+ rootContainer.registerComponentInstance("component11", component11);
+ MyComponent2 component2 = (MyComponent2)container2.getComponentInstance("component2");
+ assertEquals(testInstance, component2.testObject);
+ assertTrue(Arrays.asList(component2.comp1).contains(component1));
+ assertTrue(Arrays.asList(component2.comp1).contains(component11));
+ rootContainer.registerComponentInstance("component2", component2);
+ rootContainer.registerComponentImplementation(MyTestComponent.class);
+ MyTestComponent testComponent = (MyTestComponent)rootContainer.getComponentInstance(MyTestComponent.class);
+ assertTrue(Arrays.asList(testComponent.comp1).contains(component1));
+ assertTrue(Arrays.asList(testComponent.comp1).contains(component11));
+ assertEquals(component2, testComponent.comp2);
+ }
+
+ @Test
+ public void testTryPicoContainer2() {
+ DefaultPicoContainer rootContainer = new DefaultPicoContainer();
+ rootContainer.registerComponentImplementation("component1", MyComponent1.class);
+ rootContainer.registerComponentImplementation("component1.1", MyComponent1.class);
+ rootContainer.registerComponentImplementation("component2", MyComponent2.class);
+ rootContainer.registerComponentImplementation(MyTestComponent.class);
+ MyInterface1 testInstance = () -> { };
+ rootContainer.registerComponentInstance(testInstance);
+ MyTestComponent testComponent = (MyTestComponent)rootContainer.getComponentInstance(MyTestComponent.class);
+ MyComponent2 component2 = (MyComponent2)rootContainer.getComponentInstance("component2");
+ MyComponent1 component11 = (MyComponent1)rootContainer.getComponentInstance("component1.1");
+ MyComponent1 component1 = (MyComponent1)rootContainer.getComponentInstance("component1");
+ assertEquals(testInstance, component1.testObject);
+ assertEquals(testInstance, component2.testObject);
+ assertTrue(Arrays.asList(component2.comp1).contains(component1));
+ assertTrue(Arrays.asList(component2.comp1).contains(component11));
+ assertTrue(Arrays.asList(testComponent.comp1).contains(component1));
+ assertTrue(Arrays.asList(testComponent.comp1).contains(component11));
+ assertEquals(component2, testComponent.comp2);
+ }
+
+ @Test
+ public void testExtensionsNamespaces() throws IOException, JDOMException {
+ ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(new DefaultPicoContainer());
+ extensionsArea.registerExtensionPoint("plugin.ep1", TestExtensionClassOne.class.getName(), ExtensionPoint.Kind.BEAN_CLASS);
+ extensionsArea.registerExtension("plugin", JDOMUtil.load(
+ "<plugin:ep1 xmlns:plugin=\"plugin\" order=\"LAST\"><text>3</text></plugin:ep1>"));
+ extensionsArea.registerExtension("plugin", JDOMUtil.load(
+ "<ep1 xmlns=\"plugin\" order=\"FIRST\"><text>1</text></ep1>"));
+ extensionsArea.registerExtension("plugin", JDOMUtil.load(
+ "<extension point=\"plugin.ep1\"><text>2</text></extension>"));
+ ExtensionPoint extensionPoint = extensionsArea.getExtensionPoint("plugin.ep1");
+ TestExtensionClassOne[] extensions = (TestExtensionClassOne[]) extensionPoint.getExtensions();
+ assertEquals(3, extensions.length);
+ assertEquals("1", extensions[0].getText());
+ assertEquals("2", extensions[1].getText());
+ assertEquals("3", extensions[2].getText());
+ }
+
+ @Test
+ public void testExtensionsWithOrdering() throws IOException, JDOMException {
+ ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(new DefaultPicoContainer());
+ extensionsArea.registerExtensionPoint("ep1", TestExtensionClassOne.class.getName(), ExtensionPoint.Kind.BEAN_CLASS);
+ extensionsArea.registerExtension("", JDOMUtil.load(
+ "<extension point=\"ep1\" order=\"LAST\"><text>3</text></extension>"));
+ extensionsArea.registerExtension("", JDOMUtil.load(
+ "<extension point=\"ep1\" order=\"FIRST\"><text>1</text></extension>"));
+ extensionsArea.registerExtension("", JDOMUtil.load(
+ "<extension point=\"ep1\"><text>2</text></extension>"));
+ ExtensionPoint extensionPoint = extensionsArea.getExtensionPoint("ep1");
+ TestExtensionClassOne[] extensions = (TestExtensionClassOne[]) extensionPoint.getExtensions();
+ assertEquals(3, extensions.length);
+ assertEquals("1", extensions[0].getText());
+ assertEquals("2", extensions[1].getText());
+ assertEquals("3", extensions[2].getText());
+ }
+
+ @Test
+ public void testExtensionsWithOrderingUpdate() throws IOException, JDOMException {
+ ExtensionsAreaImpl extensionsArea = new ExtensionsAreaImpl(new DefaultPicoContainer());
+ extensionsArea.registerExtensionPoint("ep1", TestExtensionClassOne.class.getName(), ExtensionPoint.Kind.BEAN_CLASS);
+ extensionsArea.registerExtension("", JDOMUtil.load("<extension point=\"ep1\" id=\"_7\" order=\"LAST\"><text>7</text></extension>"));
+ extensionsArea.registerExtension("", JDOMUtil.load("<extension point=\"ep1\" id=\"fst\" order=\"FIRST\"><text>1</text></extension>"));
+ extensionsArea.registerExtension("", JDOMUtil.load("<extension point=\"ep1\" id=\"id\"><text>3</text></extension>"));
+ ExtensionPoint<TestExtensionClassOne> extensionPoint = extensionsArea.getExtensionPoint("ep1");
+ TestExtensionClassOne[] extensions = extensionPoint.getExtensions();
+ assertEquals(3, extensions.length);
+ assertEquals("1", extensions[0].getText());
+ assertEquals("3", extensions[1].getText());
+ assertEquals("7", extensions[2].getText());
+ TestExtensionClassOne extension = new TestExtensionClassOne("xxx");
+ extensionPoint.registerExtension(extension);
+ extensionPoint.unregisterExtension(extension);
+ extensionsArea.registerExtension("", JDOMUtil.load("<extension point=\"ep1\" order=\"BEFORE id\"><text>2</text></extension>"));
+ extensionsArea.registerExtension("", JDOMUtil.load("<extension point=\"ep1\" order=\"AFTER id\"><text>4</text></extension>"));
+ extensionsArea.registerExtension("", JDOMUtil.load("<extension point=\"ep1\" order=\"last, after _7\"><text>8</text></extension>"));
+ extensionsArea.registerExtension("", JDOMUtil.load("<extension point=\"ep1\" order=\"after:id, before _7, after fst\"><text>5</text></extension>"));
+ extensionPoint.registerExtension(new TestExtensionClassOne("6"));
+ extensions = extensionPoint.getExtensions();
+ assertEquals(8, extensions.length);
+ assertEquals("1", extensions[0].getText());
+ assertEquals("2", extensions[1].getText());
+ assertEquals("3", extensions[2].getText());
+ assertTrue("4".equals(extensions[3].getText()) || "5".equals(extensions[3].getText()) );
+ assertTrue("4".equals(extensions[4].getText()) || "5".equals(extensions[4].getText()) );
+ assertEquals("6", extensions[5].getText());
+ assertEquals("7", extensions[6].getText());
+ assertEquals("8", extensions[7].getText());
+ }
+
+ public interface MyInterface1 extends Runnable {
+ }
+
+ public interface MyInterface2 {
+ }
+
+ public interface MyInterface3 extends Runnable {
+ }
+
+ public interface MyInterface4 extends MyInterface1, MyInterface2, MyInterface3 {
+ }
+
+ public static class MyClass implements MyInterface4 {
+ @Override
+ public void run() {
+ }
+ }
+
+ public static class MyComponent1 {
+ public MyInterface1 testObject;
+
+ public MyComponent1(MyInterface1 testObject) {
+ this.testObject = testObject;
+ }
+ }
+
+ public static class MyComponent2 {
+ public MyInterface1 testObject;
+ public MyComponent1[] comp1;
+
+ public MyComponent2(MyComponent1[] comp1, MyInterface1 testObject) {
+ this.comp1 = comp1;
+ this.testObject = testObject;
+ }
+ }
+
+ public static class MyTestComponent {
+ public MyComponent1[] comp1;
+ public MyComponent2 comp2;
+
+ public MyTestComponent(MyComponent1[] comp1, MyComponent2 comp2) {
+ this.comp1 = comp1;
+ this.comp2 = comp2;
+ }
+ }
+
+ public static class MyListener implements ExtensionPointAvailabilityListener {
+ public static int regCount;
+ public static int remCount;
+
+ @Override
+ public void extensionPointRegistered(@NotNull ExtensionPoint extensionPoint) {
+ regCount++;
+ }
+
+ @Override
+ public void extensionPointRemoved(@NotNull ExtensionPoint extensionPoint) {
+ remCount++;
+ }
+
+ public static void reset() {
+ regCount = 0;
+ remCount = 0;
+ }
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/LoadingOrderTest.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/LoadingOrderTest.java
new file mode 100644
index 00000000..4bb62e68
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/LoadingOrderTest.java
@@ -0,0 +1,190 @@
+// Copyright 2000-2018 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.openapi.extensions.impl;
+
+import com.intellij.openapi.extensions.LoadingOrder;
+import com.intellij.openapi.extensions.SortingException;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class LoadingOrderTest {
+ @Test
+ public void testSimpleSorting() {
+ List<LoadingOrder.Orderable> target = new ArrayList<>();
+ target.add(createElement(LoadingOrder.ANY, null, "Any"));
+ target.add(createElement(LoadingOrder.FIRST, null, "1"));
+ target.add(createElement(LoadingOrder.LAST, null, "2"));
+ target.add(createElement(LoadingOrder.ANY, null, "Any"));
+ LoadingOrder.Orderable[] array = target.toArray(new LoadingOrder.Orderable[0]);
+ assertSequence(array, "1AnyAny2");
+ }
+
+ @Test
+ public void testStability() {
+ List<LoadingOrder.Orderable> target = new ArrayList<>();
+ target.add(createElement(LoadingOrder.ANY, null, "1"));
+ target.add(createElement(LoadingOrder.ANY, null, "2"));
+ target.add(createElement(LoadingOrder.ANY, null, "3"));
+ target.add(createElement(LoadingOrder.ANY, null, "4"));
+ LoadingOrder.Orderable[] array = target.toArray(new LoadingOrder.Orderable[0]);
+ assertSequence(array, "1234");
+ }
+
+ @Test
+ public void testComplexSorting() {
+ List<LoadingOrder.Orderable> target = new ArrayList<>();
+ String idOne = "idOne";
+ String idTwo = "idTwo";
+ target.add(createElement(LoadingOrder.before(idTwo), idOne, "2"));
+ target.add(createElement(LoadingOrder.FIRST, null, "0"));
+ target.add(createElement(LoadingOrder.LAST, null, "5"));
+ target.add(createElement(LoadingOrder.after(idTwo), null, "4"));
+ target.add(createElement(LoadingOrder.ANY, idTwo, "3"));
+ target.add(createElement(LoadingOrder.before(idOne), null, "1"));
+ LoadingOrder.Orderable[] array = target.toArray(new LoadingOrder.Orderable[0]);
+ assertSequence(array, "012345");
+ }
+
+ @Test
+ public void testComplexSorting2() {
+ List<LoadingOrder.Orderable> target = new ArrayList<>();
+ String idOne = "idOne";
+ target.add(createElement(LoadingOrder.before(idOne), null, "2"));
+ target.add(createElement(LoadingOrder.after(idOne), null, "4"));
+ target.add(createElement(LoadingOrder.FIRST, null, "1"));
+ target.add(createElement(LoadingOrder.ANY, idOne, "3"));
+ target.add(createElement(LoadingOrder.ANY, null, "5"));
+ target.add(createElement(LoadingOrder.LAST, null, "6"));
+ LoadingOrder.Orderable[] array = target.toArray(new LoadingOrder.Orderable[0]);
+ assertSequence(array, "123456");
+ }
+
+ @Test
+ public void testComplexSortingBeforeLast() {
+ List<LoadingOrder.Orderable> target = new ArrayList<>();
+ target.add(createElement(LoadingOrder.LAST, "1", "1"));
+ target.add(createElement(LoadingOrder.readOrder("last,before 1"), null, "2"));
+ target.add(createElement(LoadingOrder.ANY, null, "3"));
+ target.add(createElement(LoadingOrder.before("1'"), null, "4"));
+ LoadingOrder.Orderable[] array = target.toArray(new LoadingOrder.Orderable[0]);
+ assertSequence(array, "3421");
+ }
+
+ private static void assertSequence(LoadingOrder.Orderable[] array, String expected) {
+ LoadingOrder.sort(array);
+ StringBuffer sequence = buildSequence(array);
+ assertEquals(expected, sequence.toString());
+ }
+
+ private static StringBuffer buildSequence(LoadingOrder.Orderable[] array) {
+ StringBuffer sequence = new StringBuffer();
+ for (LoadingOrder.Orderable adapter : array) {
+ sequence.append(((MyOrderable)adapter).getID());
+ }
+ return sequence;
+ }
+
+ @Test
+ public void testFailingSortingBeforeFirst() {
+ List<LoadingOrder.Orderable> target = new ArrayList<>();
+ target.add(createElement(LoadingOrder.ANY, null, "good"));
+ target.add(createElement(LoadingOrder.FIRST, "first", "bad"));
+ target.add(createElement(LoadingOrder.LAST, null, "good"));
+ target.add(createElement(LoadingOrder.before("first"), null, "bad"));
+ LoadingOrder.Orderable[] array = target.toArray(new LoadingOrder.Orderable[0]);
+ checkSortingFailure(array);
+ }
+
+ @Test
+ public void testFailingSortingFirst() {
+ List<LoadingOrder.Orderable> target = new ArrayList<>();
+ target.add(createElement(LoadingOrder.ANY, null, "2"));
+ target.add(createElement(LoadingOrder.FIRST, "first", "1"));
+ target.add(createElement(LoadingOrder.LAST, null, "3"));
+ target.add(createElement(LoadingOrder.FIRST, null, "1"));
+ LoadingOrder.Orderable[] array = target.toArray(new LoadingOrder.Orderable[0]);
+ assertSequence(array, "1123");
+ }
+
+ private static void checkSortingFailure(LoadingOrder.Orderable[] array) {
+ try {
+ LoadingOrder.sort(array);
+ fail("Should have failed");
+ }
+ catch (SortingException e) {
+ LoadingOrder.Orderable[] conflictingElements = e.getConflictingElements();
+ assertEquals(2, conflictingElements.length);
+ assertEquals("bad", ((MyOrderable)conflictingElements[0]).getID());
+ assertEquals("bad", ((MyOrderable)conflictingElements[1]).getID());
+ }
+ }
+
+ @Test
+ public void testFailingSortingAfterLast() {
+ List<LoadingOrder.Orderable> target = new ArrayList<>();
+ target.add(createElement(LoadingOrder.after("last"), null, "bad"));
+ target.add(createElement(LoadingOrder.FIRST, null, "good"));
+ target.add(createElement(LoadingOrder.LAST, "last", "bad"));
+ target.add(createElement(LoadingOrder.ANY, null, "good"));
+ LoadingOrder.Orderable[] array = target.toArray(new LoadingOrder.Orderable[0]);
+ checkSortingFailure(array);
+ }
+
+ @Test
+ public void testFailingSortingLast() {
+ List<LoadingOrder.Orderable> target = new ArrayList<>();
+ target.add(createElement(LoadingOrder.LAST, null, "3"));
+ target.add(createElement(LoadingOrder.FIRST, null, "1"));
+ target.add(createElement(LoadingOrder.LAST, "last", "3"));
+ target.add(createElement(LoadingOrder.ANY, null, "2"));
+ LoadingOrder.Orderable[] array = target.toArray(new LoadingOrder.Orderable[0]);
+ assertSequence(array, "1233");
+ }
+
+ @Test
+ public void testFailingSortingComplex() {
+ List<LoadingOrder.Orderable> target = new ArrayList<>();
+ target.add(createElement(LoadingOrder.after("2"), "1", "bad"));
+ target.add(createElement(LoadingOrder.after("3"), "2", "bad"));
+ target.add(createElement(LoadingOrder.after("1"), "3", "bad"));
+ LoadingOrder.Orderable[] array = target.toArray(new LoadingOrder.Orderable[0]);
+ checkSortingFailure(array);
+ }
+
+ private static LoadingOrder.Orderable createElement(final LoadingOrder order, final String idString, final String elementId) {
+ return new MyOrderable(order, idString, elementId);
+ }
+
+ private static class MyOrderable implements LoadingOrder.Orderable {
+ private final LoadingOrder myOrder;
+ private final String myOrderId;
+ private final String myId;
+
+ MyOrderable(LoadingOrder order, String orderId, String id) {
+ myOrder = order;
+ myOrderId = orderId;
+ myId = id;
+ }
+
+ @Override
+ public String getOrderId() {
+ return myOrderId;
+ }
+
+ @Override
+ public LoadingOrder getOrder() {
+ return myOrder;
+ }
+
+ public String getID() {
+ return myId;
+ }
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/NonCreatableClass.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/NonCreatableClass.java
new file mode 100644
index 00000000..7094143b
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/NonCreatableClass.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions.impl;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class NonCreatableClass {
+ static {
+ if (true) {
+ throw new RuntimeException("Cannot be created");
+ }
+ }
+
+ public NonCreatableClass() {
+ throw new RuntimeException("Cannot be created");
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/TestExtensionClassOne.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/TestExtensionClassOne.java
new file mode 100644
index 00000000..74e60447
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/TestExtensionClassOne.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions.impl;
+
+import com.intellij.util.xmlb.annotations.Tag;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class TestExtensionClassOne {
+ @Tag("text")
+ public String myText;
+
+ public TestExtensionClassOne() {
+ }
+
+ public TestExtensionClassOne(String text) {
+ myText = text;
+ }
+
+ public String getText() {
+ return myText;
+ }
+}
diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/XMLTestBean.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/XMLTestBean.java
new file mode 100644
index 00000000..5c800877
--- /dev/null
+++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/XMLTestBean.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.extensions.impl;
+
+import com.intellij.openapi.extensions.PluginAware;
+import com.intellij.openapi.extensions.PluginDescriptor;
+import com.intellij.openapi.extensions.PluginId;
+import com.intellij.util.xmlb.annotations.Tag;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author Alexander Kireyev
+ */
+public class XMLTestBean implements PluginAware {
+ private boolean otherProperty;
+ private int prop1;
+ private Object prop2;
+ private Collection<String> collectionProperty = new ArrayList<>();
+ private PluginId pluginId;
+
+ public XMLTestBean() {
+ }
+
+ public XMLTestBean(Collection aCollectionProperty, boolean aOtherProperty, int aProp1) {
+ collectionProperty = aCollectionProperty;
+ otherProperty = aOtherProperty;
+ prop1 = aProp1;
+ }
+
+ public boolean isOtherProperty() {
+ return otherProperty;
+ }
+
+ public void setOtherProperty(boolean otherProperty) {
+ this.otherProperty = otherProperty;
+ }
+
+ @Tag("prop1")
+ public int getProp1() {
+ return prop1;
+ }
+
+ public void setProp1(int prop1) {
+ this.prop1 = prop1;
+ }
+
+ public Object getProp2() {
+ return prop2;
+ }
+
+ public void setProp2(Object prop2) {
+ this.prop2 = prop2;
+ }
+
+ public Collection<String> getCollectionProperty() {
+ return collectionProperty;
+ }
+
+ public void setCollectionProperty(Collection<String> collectionProperty) {
+ this.collectionProperty = collectionProperty;
+ }
+
+ @Override
+ public void setPluginDescriptor(PluginDescriptor pluginDescriptor) {
+ pluginId = pluginDescriptor.getPluginId();
+ }
+
+ public PluginId getPluginId() {
+ return pluginId;
+ }
+}