blob: 7e85012f68ff8d55929b42cec6f8c7f7011b7f7e [file] [log] [blame]
/*
* 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.impl;
import com.intellij.openapi.extensions.*;
import com.intellij.util.containers.ConcurrentHashMap;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.pico.ConstructorInjectionComponentAdapter;
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.TestOnly;
import org.picocontainer.MutablePicoContainer;
import org.picocontainer.PicoContainer;
import java.util.*;
@SuppressWarnings({"HardCodedStringLiteral"})
public class ExtensionsAreaImpl implements ExtensionsArea {
private final LogProvider myLogger;
public static final String ATTRIBUTE_AREA = "area";
private static final Map<String,String> ourDefaultEPs = new THashMap<String, String>();
static {
ourDefaultEPs.put(EPAvailabilityListenerExtension.EXTENSION_POINT_NAME, EPAvailabilityListenerExtension.class.getName());
}
private static final boolean DEBUG_REGISTRATION = false;
private final AreaPicoContainerImpl myPicoContainer;
private final Throwable myCreationTrace;
private final Map<String,ExtensionPointImpl> myExtensionPoints = new ConcurrentHashMap<String, ExtensionPointImpl>();
private final Map<String,Throwable> myEPTraces = DEBUG_REGISTRATION ? new THashMap<String, Throwable>():null;
private final MultiMap<String, ExtensionPointAvailabilityListener> myAvailabilityListeners = new MultiMap<String, ExtensionPointAvailabilityListener>();
private final List<Runnable> mySuspendedListenerActions = new ArrayList<Runnable>();
private boolean myAvailabilityNotificationsActive = true;
private final AreaInstance myAreaInstance;
private final String myAreaClass;
public ExtensionsAreaImpl(String areaClass, AreaInstance areaInstance, PicoContainer parentPicoContainer, @NotNull LogProvider logger) {
myCreationTrace = DEBUG_REGISTRATION ? new Throwable("Area creation trace") : null;
myAreaClass = areaClass;
myAreaInstance = areaInstance;
myPicoContainer = new AreaPicoContainerImpl(parentPicoContainer, areaInstance);
myLogger = logger;
initialize();
}
@TestOnly
ExtensionsAreaImpl(MutablePicoContainer parentPicoContainer, @NotNull LogProvider logger) {
this(null, null, parentPicoContainer, logger);
}
@TestOnly
public final void notifyAreaReplaced() {
for (final ExtensionPointImpl point : myExtensionPoints.values()) {
point.notifyAreaReplaced(this);
}
}
@NotNull
@Override
public AreaPicoContainer getPicoContainer() {
return myPicoContainer;
}
MutablePicoContainer getMutablePicoContainer() {
return myPicoContainer;
}
@Override
public String getAreaClass() {
return myAreaClass;
}
@Override
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);
}
@Override
public void registerExtension(@NotNull final String pluginName, @NotNull final Element extensionElement) {
registerExtension(new DefaultPluginDescriptor(PluginId.getId(pluginName)), extensionElement);
}
@Override
public void registerExtension(@NotNull final PluginDescriptor pluginDescriptor, @NotNull final Element extensionElement) {
final PluginId pluginId = pluginDescriptor.getPluginId();
String epName = extractEPName(extensionElement);
ExtensionComponentAdapter adapter;
final PicoContainer container = getPluginContainer(pluginId.getIdString());
final ExtensionPointImpl extensionPoint = getExtensionPoint(epName);
if (extensionPoint.getKind() == ExtensionPoint.Kind.INTERFACE) {
String implClass = extensionElement.getAttributeValue("implementation");
if (implClass == null) {
throw new RuntimeException("'implementation' attribute not specified for '" + epName + "' extension in '" + pluginId.getIdString() + "' plugin");
}
adapter = new ExtensionComponentAdapter(implClass, extensionElement, container, pluginDescriptor, shouldDeserializeInstance(extensionElement));
}
else {
adapter = new ExtensionComponentAdapter(extensionPoint.getClassName(), extensionElement, container, pluginDescriptor, true);
}
internalGetPluginContainer().registerComponent(adapter);
extensionPoint.registerExtensionAdapter(adapter);
}
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)) {
return true;
}
}
return false;
}
public static String extractEPName(final Element extensionElement) {
String epName = extensionElement.getAttributeValue("point");
if (epName == null) {
final Element parentElement = extensionElement.getParentElement();
final String ns = parentElement != null ? parentElement.getAttributeValue("defaultExtensionNs"):null;
if (ns != null) {
epName = ns + '.' + extensionElement.getName();
} else {
Namespace namespace = extensionElement.getNamespace();
epName = namespace.getURI() + '.' + extensionElement.getName();
}
}
return epName;
}
@NotNull
@Override
public PicoContainer getPluginContainer(@NotNull String pluginName) {
return internalGetPluginContainer();
}
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
@SuppressWarnings({"unchecked"})
public void extensionRemoved(@NotNull Object extension, final PluginDescriptor pluginDescriptor) {
EPAvailabilityListenerExtension epListenerExtension = (EPAvailabilityListenerExtension) extension;
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;
}
}
myLogger.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) {
ConstructorInjectionComponentAdapter adapter =
new ConstructorInjectionComponentAdapter(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) {
myAvailabilityListeners.putValue(extensionPointName, listener);
if (hasExtensionPoint(extensionPointName)) {
notifyAvailableListener(listener, myExtensionPoints.get(extensionPointName));
}
}
@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);
}
@Override
public void registerExtensionPoint(@NotNull final String extensionPointName, @NotNull String extensionPointBeanClass, @NotNull PluginDescriptor descriptor) {
registerExtensionPoint(extensionPointName, extensionPointBeanClass, descriptor, ExtensionPoint.Kind.INTERFACE);
}
private void registerExtensionPoint(@NotNull String extensionPointName,
@NotNull String extensionPointBeanClass,
@NotNull PluginDescriptor descriptor,
@NotNull ExtensionPoint.Kind kind) {
if (hasExtensionPoint(extensionPointName)) {
if (DEBUG_REGISTRATION) {
final ExtensionPointImpl oldEP = getExtensionPoint(extensionPointName);
myLogger.error("Duplicate registration for EP: " + extensionPointName + ": original plugin " + oldEP.getDescriptor().getPluginId() +
", new plugin " + descriptor.getPluginId(),
myEPTraces.get(extensionPointName));
}
throw new RuntimeException("Duplicate registration for EP: " + extensionPointName);
}
registerExtensionPoint(new ExtensionPointImpl(extensionPointName, extensionPointBeanClass, kind, this, myAreaInstance, myLogger, descriptor));
}
public void registerExtensionPoint(@NotNull ExtensionPointImpl extensionPoint) {
String name = extensionPoint.getName();
myExtensionPoints.put(name, extensionPoint);
notifyEPRegistered(extensionPoint);
if (DEBUG_REGISTRATION) {
//noinspection ThrowableResultOfMethodCallIgnored
myEPTraces.put(name, new Throwable("Original registration for " + name));
}
}
@SuppressWarnings({"unchecked"})
private void notifyEPRegistered(final ExtensionPoint extensionPoint) {
Collection<ExtensionPointAvailabilityListener> listeners = myAvailabilityListeners.get(extensionPoint.getName());
for (final ExtensionPointAvailabilityListener listener : listeners) {
notifyAvailableListener(listener, extensionPoint);
}
}
private void notifyAvailableListener(final ExtensionPointAvailabilityListener listener, final ExtensionPoint extensionPoint) {
queueNotificationAction(new Runnable() {
@Override
public void run() {
listener.extensionPointRegistered(extensionPoint);
}
});
}
private void queueNotificationAction(final Runnable action) {
if (myAvailabilityNotificationsActive) {
action.run();
}
else {
mySuspendedListenerActions.add(action);
}
}
@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
@SuppressWarnings({"unchecked"})
public <T> ExtensionPoint<T> getExtensionPoint(@NotNull ExtensionPointName<T> extensionPointName) {
return getExtensionPoint(extensionPointName.getName());
}
@NotNull
@Override
public ExtensionPoint[] getExtensionPoints() {
return myExtensionPoints.values().toArray(new ExtensionPoint[myExtensionPoints.size()]);
}
@Override
public void unregisterExtensionPoint(@NotNull final String extensionPointName) {
ExtensionPoint extensionPoint = myExtensionPoints.get(extensionPointName);
if (extensionPoint != null) {
extensionPoint.reset();
myExtensionPoints.remove(extensionPointName);
notifyEPRemoved(extensionPoint);
}
}
@SuppressWarnings({"unchecked"})
private void notifyEPRemoved(final ExtensionPoint extensionPoint) {
Collection<ExtensionPointAvailabilityListener> listeners = myAvailabilityListeners.get(extensionPoint.getName());
for (final ExtensionPointAvailabilityListener listener : listeners) {
notifyUnavailableListener(extensionPoint, listener);
}
}
private void notifyUnavailableListener(final ExtensionPoint extensionPoint, final ExtensionPointAvailabilityListener listener) {
queueNotificationAction(new Runnable() {
@Override
public void run() {
listener.extensionPointRemoved(extensionPoint);
}
});
}
@Override
public boolean hasExtensionPoint(@NotNull String extensionPointName) {
return myExtensionPoints.containsKey(extensionPointName);
}
@Override
public void suspendInteractions() {
myAvailabilityNotificationsActive = false;
}
@Override
public void resumeInteractions() {
myAvailabilityNotificationsActive = true;
ExtensionPoint[] extensionPoints = getExtensionPoints();
for (ExtensionPoint extensionPoint : extensionPoints) {
extensionPoint.getExtensions(); // creates extensions from ComponentAdapters
}
for (Runnable action : mySuspendedListenerActions) {
try {
action.run();
}
catch (Exception e) {
myLogger.error(e);
}
}
mySuspendedListenerActions.clear();
}
@Override
public void killPendingInteractions() {
mySuspendedListenerActions.clear();
}
@NotNull
public MutablePicoContainer[] getPluginContainers() {
return new MutablePicoContainer[0];
}
public void removeAllComponents(final Set<ExtensionComponentAdapter> extensionAdapters) {
for (final Object extensionAdapter : extensionAdapters) {
ExtensionComponentAdapter componentAdapter = (ExtensionComponentAdapter)extensionAdapter;
internalGetPluginContainer().unregisterComponent(componentAdapter.getComponentKey());
}
}
}