blob: 071a37e5bdbe82bdd60287780f0a193e8546b34c [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.
*/
/*
* @author max
*/
package com.intellij.openapi.util;
import com.intellij.diagnostic.PluginException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.*;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.KeyedLazyInstance;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ConcurrentHashMap;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
public class KeyedExtensionCollector<T, KeyT> {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.KeyedExtensionCollector");
private final Map<String, List<T>> myExplicitExtensions = new THashMap<String, List<T>>();
private final ConcurrentMap<String, List<T>> myCache = new ConcurrentHashMap<String, List<T>>();
@NonNls private final String lock;
private ExtensionPoint<KeyedLazyInstance<T>> myPoint;
private final String myEpName;
private ExtensionPointAndAreaListener<KeyedLazyInstance<T>> myListener;
private final List<ExtensionPointListener<T>> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
public KeyedExtensionCollector(@NonNls @NotNull String epName) {
myEpName = epName;
lock = "lock for KeyedExtensionCollector " + epName;
resetAreaListener();
}
private void resetAreaListener() {
synchronized (lock) {
myCache.clear();
if (myPoint != null) {
myPoint.removeExtensionPointListener(myListener);
myPoint = null;
myListener = null;
}
}
}
public void addExplicitExtension(@NotNull KeyT key, @NotNull T t) {
synchronized (lock) {
final String skey = keyToString(key);
List<T> list = myExplicitExtensions.get(skey);
if (list == null) {
list = new ArrayList<T>();
myExplicitExtensions.put(skey, list);
}
list.add(t);
myCache.remove(skey);
for (ExtensionPointListener<T> listener : myListeners) {
listener.extensionAdded(t, null);
}
}
}
public void removeExplicitExtension(@NotNull KeyT key, @NotNull T t) {
synchronized (lock) {
final String skey = keyToString(key);
List<T> list = myExplicitExtensions.get(skey);
if (list != null) {
list.remove(t);
myCache.remove(skey);
}
for (ExtensionPointListener<T> listener : myListeners) {
listener.extensionRemoved(t, null);
}
}
}
@NotNull
protected String keyToString(@NotNull KeyT key) {
return key.toString();
}
/**
* @see #findSingle(Object)
*/
@NotNull
public List<T> forKey(@NotNull KeyT key) {
final String stringKey = keyToString(key);
boolean rebuild = myPoint == null && Extensions.getRootArea().hasExtensionPoint(myEpName);
List<T> cached = rebuild ? null : myCache.get(stringKey);
if (cached != null) return cached;
cached = buildExtensions(stringKey, key);
cached = ConcurrencyUtil.cacheOrGet(myCache, stringKey, cached);
return cached;
}
public T findSingle(@NotNull KeyT key) {
List<T> list = forKey(key);
return list.isEmpty() ? null : list.get(0);
}
@NotNull
protected List<T> buildExtensions(@NotNull String stringKey, @NotNull KeyT key) {
return buildExtensions(Collections.singleton(stringKey));
}
@NotNull
protected final List<T> buildExtensions(@NotNull Set<String> keys) {
synchronized (lock) {
List<T> result = null;
for (Map.Entry<String, List<T>> entry : myExplicitExtensions.entrySet()) {
String key = entry.getKey();
if (keys.contains(key)) {
List<T> list = entry.getValue();
if (result == null) {
result = new ArrayList<T>(list);
}
else {
result.addAll(list);
}
}
}
final ExtensionPoint<KeyedLazyInstance<T>> point = getPoint();
if (point != null) {
final KeyedLazyInstance<T>[] beans = point.getExtensions();
for (KeyedLazyInstance<T> bean : beans) {
if (keys.contains(bean.getKey())) {
final T instance;
try {
instance = bean.getInstance();
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Exception e) {
LOG.error(e);
continue;
}
catch (NoClassDefFoundError e) {
LOG.error(e);
continue;
}
catch (UnsupportedClassVersionError e) {
LOG.error(e);
continue;
}
if (result == null) result = new SmartList<T>();
result.add(instance);
}
}
}
return result == null ? Collections.<T>emptyList() : result;
}
}
@Nullable
private ExtensionPoint<KeyedLazyInstance<T>> getPoint() {
ExtensionPoint<KeyedLazyInstance<T>> point = myPoint;
if (point == null && Extensions.getRootArea().hasExtensionPoint(myEpName)) {
ExtensionPointName<KeyedLazyInstance<T>> typesafe = ExtensionPointName.create(myEpName);
myPoint = point = Extensions.getRootArea().getExtensionPoint(typesafe);
myListener = new ExtensionPointAndAreaListener<KeyedLazyInstance<T>>() {
@Override
public void extensionAdded(@NotNull final KeyedLazyInstance<T> bean, @Nullable final PluginDescriptor pluginDescriptor) {
synchronized (lock) {
if (bean.getKey() == null) {
if (pluginDescriptor != null) {
throw new PluginException("No key specified for extension of class " + bean.getInstance().getClass(),
pluginDescriptor.getPluginId());
}
LOG.error("No key specified for extension of class " + bean.getInstance().getClass());
return;
}
myCache.remove(bean.getKey());
for (ExtensionPointListener<T> listener : myListeners) {
listener.extensionAdded(bean.getInstance(), null);
}
}
}
@Override
public void extensionRemoved(@NotNull final KeyedLazyInstance<T> bean, @Nullable final PluginDescriptor pluginDescriptor) {
synchronized (lock) {
myCache.remove(bean.getKey());
for (ExtensionPointListener<T> listener : myListeners) {
listener.extensionRemoved(bean.getInstance(), null);
}
}
}
@Override
public void areaReplaced(final ExtensionsArea area) {
resetAreaListener();
}
};
point.addExtensionPointListener(myListener);
}
return point;
}
public boolean hasAnyExtensions() {
synchronized (lock) {
if (!myExplicitExtensions.isEmpty()) return true;
final ExtensionPoint<KeyedLazyInstance<T>> point = getPoint();
return point != null && point.hasAnyExtensions();
}
}
public void addListener(@NotNull ExtensionPointListener<T> listener) {
myListeners.add(listener);
}
public void removeListener(@NotNull ExtensionPointListener<T> listener) {
myListeners.remove(listener);
}
}