diff options
Diffstat (limited to 'platform/extensions/src/com')
38 files changed, 3409 insertions, 0 deletions
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 |