blob: cbea4c30227b27d6b62d65b14ccad6e4e7e7e0f6 [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.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.*;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.StringInterner;
import org.jdom.Element;
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 java.lang.reflect.Array;
import java.util.*;
/**
* @author AKireyev
*/
@SuppressWarnings("SynchronizeOnThis")
public class ExtensionPointImpl<T> implements ExtensionPoint<T> {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.extensions.impl.ExtensionPointImpl");
private final LogProvider myLogger;
private final AreaInstance myArea;
private final String myName;
private final String myClassName;
private final Kind myKind;
private final List<T> myExtensions = new ArrayList<T>();
private volatile T[] myExtensionsCache;
private final ExtensionsAreaImpl myOwner;
private final PluginDescriptor myDescriptor;
private final Set<ExtensionComponentAdapter> myExtensionAdapters = new LinkedHashSet<ExtensionComponentAdapter>();
private final List<ExtensionPointListener<T>> myEPListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final List<ExtensionComponentAdapter> myLoadedAdapters = new ArrayList<ExtensionComponentAdapter>();
private Class<T> myExtensionClass;
private static final StringInterner INTERNER = new StringInterner();
public ExtensionPointImpl(@NotNull String name,
@NotNull String className,
@NotNull Kind kind,
@NotNull ExtensionsAreaImpl owner,
AreaInstance area,
@NotNull LogProvider logger,
@NotNull PluginDescriptor descriptor) {
synchronized (INTERNER) {
myName = INTERNER.intern(name);
}
myClassName = className;
myKind = kind;
myOwner = owner;
myArea = area;
myLogger = logger;
myDescriptor = descriptor;
}
@NotNull
@Override
public String getName() {
return myName;
}
@Override
public AreaInstance getArea() {
return myArea;
}
@NotNull
@Override
public String getBeanClassName() {
return myClassName;
}
@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) {
assert myExtensions.size() == myLoadedAdapters.size();
ExtensionComponentAdapter adapter = new ObjectComponentAdapter(extension, order);
if (LoadingOrder.ANY == order) {
int index = myLoadedAdapters.size();
if (index > 0) {
ExtensionComponentAdapter lastAdapter = myLoadedAdapters.get(index - 1);
if (lastAdapter.getOrder() == LoadingOrder.LAST) {
index--;
}
}
registerExtension(extension, adapter, index, true);
}
else {
registerExtensionAdapter(adapter);
processAdapters();
}
}
private void registerExtension(@NotNull T extension, @NotNull ExtensionComponentAdapter adapter, int index, boolean runNotifications) {
if (myExtensions.contains(extension)) {
myLogger.error("Extension was already added: " + extension);
return;
}
Class<T> extensionClass = getExtensionClass();
if (!extensionClass.isInstance(extension)) {
myLogger.error("Extension " + extension.getClass() + " does not implement " + extensionClass);
return;
}
myExtensions.add(index, extension);
myLoadedAdapters.add(index, adapter);
if (runNotifications) {
clearCache();
if (!adapter.isNotificationSent()) {
if (extension instanceof Extension) {
try {
((Extension)extension).extensionAdded(this);
}
catch (Throwable e) {
myLogger.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) {
myLogger.error(e);
}
}
}
@Override
@NotNull
public T[] getExtensions() {
T[] result = myExtensionsCache;
if (result == null) {
synchronized (this) {
result = myExtensionsCache;
if (result == null) {
processAdapters();
Class<T> extensionClass = getExtensionClass();
@SuppressWarnings("unchecked") T[] a = (T[])Array.newInstance(extensionClass, myExtensions.size());
result = myExtensions.toArray(a);
for (int i = result.length - 1; i >= 0; i--) {
T t = result[i];
if (i > 0 && result[i] == result[i - 1]) {
LOG.error("Duplicate extension found: " + t + "; " +
" Result: " + Arrays.toString(result) + ";\n" +
" extensions: " + myExtensions + ";\n" +
" getExtensionClass(): " + extensionClass + ";\n" +
" size:" + myExtensions.size() + ";" + result.length);
}
}
myExtensionsCache = result;
}
}
}
return result;
}
@Override
public boolean hasAnyExtensions() {
final T[] cache = myExtensionsCache;
if (cache != null) {
return cache.length > 0;
}
synchronized (this) {
return myExtensionAdapters.size() + myLoadedAdapters.size() > 0;
}
}
private void processAdapters() {
int totalSize = myExtensionAdapters.size() + myLoadedAdapters.size();
if (totalSize != 0) {
List<ExtensionComponentAdapter> adapters = ContainerUtil.newArrayListWithCapacity(totalSize);
adapters.addAll(myExtensionAdapters);
adapters.addAll(myLoadedAdapters);
LoadingOrder.sort(adapters);
myExtensionAdapters.clear();
myExtensionAdapters.addAll(adapters);
Set<ExtensionComponentAdapter> loaded = ContainerUtil.newHashOrEmptySet(myLoadedAdapters);
myExtensions.clear();
myLoadedAdapters.clear();
for (ExtensionComponentAdapter adapter : adapters) {
try {
@SuppressWarnings("unchecked") T extension = (T)adapter.getExtension();
registerExtension(extension, adapter, myExtensions.size(), !loaded.contains(adapter));
myExtensionAdapters.remove(adapter);
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Exception e) {
LOG.error(e);
myExtensionAdapters.remove(adapter);
}
}
}
}
@Override
@Nullable
public T getExtension() {
T[] extensions = getExtensions();
return extensions.length == 0 ? null : extensions[0];
}
@Override
public synchronized boolean hasExtension(@NotNull T extension) {
processAdapters();
return myExtensions.contains(extension);
}
@Override
public synchronized void unregisterExtension(@NotNull final T extension) {
final int index = getExtensionIndex(extension);
final ExtensionComponentAdapter adapter = myLoadedAdapters.get(index);
Object key = adapter.getComponentKey();
myOwner.getMutablePicoContainer().unregisterComponent(key);
for (MutablePicoContainer pluginContainer : myOwner.getPluginContainers()) {
pluginContainer.unregisterComponent(key);
}
processAdapters();
unregisterExtension(extension, null);
}
private int getExtensionIndex(@NotNull T extension) {
int i = myExtensions.indexOf(extension);
if (i == -1) {
throw new IllegalArgumentException("Extension to be removed not found: " + extension);
}
return i;
}
private void unregisterExtension(@NotNull T extension, PluginDescriptor pluginDescriptor) {
int index = getExtensionIndex(extension);
myExtensions.remove(index);
myLoadedAdapters.remove(index);
clearCache();
notifyListenersOnRemove(extension, pluginDescriptor);
if (extension instanceof Extension) {
try {
((Extension)extension).extensionRemoved(this);
}
catch (Throwable e) {
myLogger.error(e);
}
}
}
private void notifyListenersOnRemove(@NotNull T extensionObject, PluginDescriptor pluginDescriptor) {
for (ExtensionPointListener<T> listener : myEPListeners) {
try {
listener.extensionRemoved(extensionObject, pluginDescriptor);
}
catch (Throwable e) {
myLogger.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 {
myEPListeners.add(listener);
}
Disposer.register(parentDisposable, new Disposable() {
@Override
public void dispose() {
removeExtensionPointListener(listener, invokeForLoadedExtensions);
}
});
}
@Override
public synchronized void addExtensionPointListener(@NotNull ExtensionPointListener<T> listener) {
processAdapters();
if (myEPListeners.add(listener)) {
for (ExtensionComponentAdapter componentAdapter : myLoadedAdapters.toArray(new ExtensionComponentAdapter[myLoadedAdapters.size()])) {
try {
@SuppressWarnings("unchecked") T extension = (T)componentAdapter.getExtension();
listener.extensionAdded(extension, componentAdapter.getPluginDescriptor());
}
catch (Throwable e) {
myLogger.error(e);
}
}
}
}
@Override
public void removeExtensionPointListener(@NotNull ExtensionPointListener<T> listener) {
removeExtensionPointListener(listener, true);
}
private synchronized void removeExtensionPointListener(@NotNull ExtensionPointListener<T> listener, boolean invokeForLoadedExtensions) {
if (myEPListeners.remove(listener) && invokeForLoadedExtensions) {
for (ExtensionComponentAdapter componentAdapter : myLoadedAdapters.toArray(new ExtensionComponentAdapter[myLoadedAdapters.size()])) {
try {
@SuppressWarnings("unchecked") T extension = (T)componentAdapter.getExtension();
listener.extensionRemoved(extension, componentAdapter.getPluginDescriptor());
}
catch (Throwable e) {
myLogger.error(e);
}
}
}
}
@Override
public synchronized void reset() {
myOwner.removeAllComponents(myExtensionAdapters);
myExtensionAdapters.clear();
for (T extension : getExtensions()) {
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 = pluginClassLoader == null
? (Class<T>)Class.forName(myClassName) : (Class<T>)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) {
myExtensionAdapters.add(adapter);
clearCache();
}
private void clearCache() {
myExtensionsCache = null;
}
synchronized boolean unregisterExtensionAdapter(@NotNull ExtensionComponentAdapter adapter) {
try {
if (myExtensionAdapters.remove(adapter)) {
return true;
}
if (myLoadedAdapters.contains(adapter)) {
Object key = adapter.getComponentKey();
myOwner.getMutablePicoContainer().unregisterComponent(key);
for (MutablePicoContainer pluginContainer : myOwner.getPluginContainers()) {
pluginContainer.unregisterComponent(key);
}
@SuppressWarnings("unchecked") T extension = (T)adapter.getExtension();
unregisterExtension(extension, adapter.getPluginDescriptor());
return true;
}
return false;
}
finally {
clearCache();
}
}
@TestOnly
final synchronized void notifyAreaReplaced(final ExtensionsArea area) {
for (final ExtensionPointListener<T> listener : myEPListeners) {
if (listener instanceof ExtensionPointAndAreaListener) {
((ExtensionPointAndAreaListener)listener).areaReplaced(area);
}
}
}
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, false);
myExtension = extension;
myLoadingOrder = loadingOrder;
}
@Override
public Object getExtension() {
return myExtension;
}
@Override
public LoadingOrder getOrder() {
return myLoadingOrder;
}
@Override
@Nullable
public String getOrderId() {
return null;
}
@Override
@NonNls
public Element getDescribingElement() {
return new Element("RuntimeExtension: " + myExtension);
}
}
}