blob: 6d280a5e11affb1a1b9af5a8431f0e4ce6bba42e [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.components.impl;
import com.intellij.diagnostic.PluginException;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.*;
import com.intellij.openapi.components.ex.ComponentManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ConcurrentHashMap;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusFactory;
import com.intellij.util.pico.ConstructorInjectionComponentAdapter;
import com.intellij.util.pico.IdeaPicoContainer;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.picocontainer.*;
import org.picocontainer.defaults.CachingComponentAdapter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author mike
*/
public abstract class ComponentManagerImpl extends UserDataHolderBase implements ComponentManagerEx, Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.components.ComponentManager");
private final Map<Class, Object> myInitializedComponents = new ConcurrentHashMap<Class, Object>();
private boolean myComponentsCreated = false;
private volatile MutablePicoContainer myPicoContainer;
private volatile boolean myDisposed = false;
private volatile boolean myDisposeCompleted = false;
private MessageBus myMessageBus;
private final ComponentManagerConfigurator myConfigurator = new ComponentManagerConfigurator(this);
private final ComponentManager myParentComponentManager;
private ComponentsRegistry myComponentsRegistry = new ComponentsRegistry();
private final Condition myDisposedCondition = new Condition() {
@Override
public boolean value(final Object o) {
return isDisposed();
}
};
protected ComponentManagerImpl(ComponentManager parentComponentManager) {
myParentComponentManager = parentComponentManager;
bootstrapPicoContainer(toString());
}
protected ComponentManagerImpl(ComponentManager parentComponentManager, @NotNull String name) {
myParentComponentManager = parentComponentManager;
bootstrapPicoContainer(name);
}
public void init() {
createComponents();
getComponents();
}
@NotNull
@Override
public MessageBus getMessageBus() {
if (myDisposeCompleted || myDisposed) {
ProgressManager.checkCanceled();
throw new AssertionError("Already disposed");
}
assert myMessageBus != null : "Not initialized yet";
return myMessageBus;
}
public boolean isComponentsCreated() {
return myComponentsCreated;
}
private void createComponents() {
try {
myComponentsRegistry.loadClasses();
Class[] componentInterfaces = myComponentsRegistry.getComponentInterfaces();
for (Class componentInterface : componentInterfaces) {
ProgressIndicatorProvider.checkCanceled();
createComponent(componentInterface);
}
}
finally {
myComponentsCreated = true;
}
}
protected synchronized Object createComponent(@NotNull Class componentInterface) {
final Object component = getPicoContainer().getComponentInstance(componentInterface.getName());
LOG.assertTrue(component != null, "Can't instantiate component for: " + componentInterface);
return component;
}
protected synchronized void disposeComponents() {
assert !myDisposeCompleted : "Already disposed!";
final List<Object> components = myComponentsRegistry.getRegisteredImplementations();
myDisposed = true;
for (int i = components.size() - 1; i >= 0; i--) {
Object component = components.get(i);
if (component instanceof BaseComponent) {
try {
((BaseComponent)component).disposeComponent();
}
catch (Throwable e) {
LOG.error(e);
}
}
}
myComponentsCreated = false;
}
@SuppressWarnings({"unchecked"})
@Nullable
protected <T> T getComponentFromContainer(@NotNull Class<T> interfaceClass) {
final T initializedComponent = (T)myInitializedComponents.get(interfaceClass);
if (initializedComponent != null) return initializedComponent;
synchronized (this) {
if (myComponentsRegistry == null || !myComponentsRegistry.containsInterface(interfaceClass)) {
return null;
}
Object lock = myComponentsRegistry.getComponentLock(interfaceClass);
synchronized (lock) {
T dcl = (T)myInitializedComponents.get(interfaceClass);
if (dcl != null) return dcl;
T component = (T)getPicoContainer().getComponentInstance(interfaceClass.getName());
if (component == null) {
component = (T)createComponent(interfaceClass);
}
if (component == null) {
throw new IncorrectOperationException("createComponent() returns null for: " + interfaceClass);
}
myInitializedComponents.put(interfaceClass, component);
if (component instanceof com.intellij.openapi.Disposable) {
Disposer.register(this, (com.intellij.openapi.Disposable)component);
}
return component;
}
}
}
@Override
public <T> T getComponent(@NotNull Class<T> interfaceClass) {
if (myDisposeCompleted) {
ProgressManager.checkCanceled();
throw new AssertionError("Already disposed: " + this);
}
return getComponent(interfaceClass, null);
}
@Override
public <T> T getComponent(@NotNull Class<T> interfaceClass, T defaultImplementation) {
final T fromContainer = getComponentFromContainer(interfaceClass);
if (fromContainer != null) return fromContainer;
if (defaultImplementation != null) return defaultImplementation;
return null;
}
@Nullable
protected static ProgressIndicator getProgressIndicator() {
return ProgressIndicatorProvider.getGlobalProgressIndicator();
}
protected float getPercentageOfComponentsLoaded() {
return myComponentsRegistry.getPercentageOfComponentsLoaded();
}
@Override
public void initializeComponent(@NotNull Object component, boolean service) {
}
protected void handleInitComponentError(Throwable ex, String componentClassName, ComponentConfig config) {
LOG.error(ex);
}
@Override
@SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext"})
public synchronized void registerComponent(@NotNull final ComponentConfig config, final PluginDescriptor pluginDescriptor) {
if (!config.prepareClasses(isHeadless())) return;
config.pluginDescriptor = pluginDescriptor;
myComponentsRegistry.registerComponent(config);
}
public synchronized void registerComponentImplementation(@NotNull Class componentKey, @NotNull Class componentImplementation) {
getPicoContainer().registerComponentImplementation(componentKey.getName(), componentImplementation);
myInitializedComponents.remove(componentKey);
}
@TestOnly
public synchronized <T> T registerComponentInstance(@NotNull Class<T> componentKey, @NotNull T componentImplementation) {
getPicoContainer().unregisterComponent(componentKey.getName());
getPicoContainer().registerComponentInstance(componentKey.getName(), componentImplementation);
@SuppressWarnings("unchecked") T t = (T)myInitializedComponents.remove(componentKey);
return t;
}
@Override
public synchronized boolean hasComponent(@NotNull Class interfaceClass) {
return myComponentsRegistry != null && myComponentsRegistry.containsInterface(interfaceClass);
}
@NotNull
protected synchronized Object[] getComponents() {
Class[] componentClasses = myComponentsRegistry.getComponentInterfaces();
List<Object> components = new ArrayList<Object>(componentClasses.length);
for (Class<?> interfaceClass : componentClasses) {
ProgressIndicatorProvider.checkCanceled();
Object component = getComponent(interfaceClass);
if (component != null) components.add(component);
}
return ArrayUtil.toObjectArray(components);
}
@Override
@SuppressWarnings({"unchecked"})
@NotNull
public synchronized <T> T[] getComponents(@NotNull Class<T> baseClass) {
return myComponentsRegistry.getComponentsByType(baseClass);
}
@Override
@NotNull
public MutablePicoContainer getPicoContainer() {
MutablePicoContainer container = myPicoContainer;
if (container == null || myDisposeCompleted) {
ProgressManager.checkCanceled();
throw new AssertionError("Already disposed");
}
return container;
}
@NotNull
protected MutablePicoContainer createPicoContainer() {
MutablePicoContainer result;
if (myParentComponentManager != null) {
result = new IdeaPicoContainer(myParentComponentManager.getPicoContainer());
}
else {
result = new IdeaPicoContainer();
}
return result;
}
@Override
public synchronized BaseComponent getComponent(@NotNull String name) {
return myComponentsRegistry.getComponentByName(name);
}
protected boolean isComponentSuitable(Map<String, String> options) {
return !isTrue(options, "internal") || ApplicationManager.getApplication().isInternal();
}
private static boolean isTrue(Map<String, String> options, @NonNls @NotNull String option) {
return options != null && options.containsKey(option) && Boolean.valueOf(options.get(option)).booleanValue();
}
@Override
public synchronized void dispose() {
ApplicationManager.getApplication().assertIsDispatchThread();
myDisposeCompleted = true;
if (myMessageBus != null) {
myMessageBus.dispose();
myMessageBus = null;
}
myInitializedComponents.clear();
myComponentsRegistry = null;
myPicoContainer = null;
}
@Override
public boolean isDisposed() {
return myDisposed || temporarilyDisposed;
}
protected volatile boolean temporarilyDisposed = false;
@TestOnly
public void setTemporarilyDisposed(boolean disposed) {
temporarilyDisposed = disposed;
}
protected void loadComponentsConfiguration(@NotNull ComponentConfig[] components, @Nullable PluginDescriptor descriptor, boolean defaultProject) {
myConfigurator.loadComponentsConfiguration(components, descriptor, defaultProject);
}
protected void bootstrapPicoContainer(@NotNull String name) {
myPicoContainer = createPicoContainer();
myMessageBus = MessageBusFactory.newMessageBus(name, myParentComponentManager == null ? null : myParentComponentManager.getMessageBus());
final MutablePicoContainer picoContainer = getPicoContainer();
picoContainer.registerComponentInstance(MessageBus.class, myMessageBus);
}
protected ComponentManager getParentComponentManager() {
return myParentComponentManager;
}
private static class HeadlessHolder {
private static final boolean myHeadless = ApplicationManager.getApplication().isHeadlessEnvironment();
}
private boolean isHeadless() {
return HeadlessHolder.myHeadless;
}
@Override
public void registerComponent(@NotNull final ComponentConfig config) {
registerComponent(config, null);
}
@NotNull
public ComponentConfig[] getComponentConfigurations() {
return myComponentsRegistry.getComponentConfigurations();
}
@Nullable
public Object getComponent(final ComponentConfig componentConfig) {
return getPicoContainer().getComponentInstance(componentConfig.getInterfaceClass());
}
public ComponentConfig getConfig(Class componentImplementation) {
return myComponentsRegistry.getConfig(componentImplementation);
}
@Override
@NotNull
public Condition getDisposed() {
return myDisposedCondition;
}
@NotNull
public static String getComponentName(@NotNull final Object component) {
if (component instanceof NamedComponent) {
return ((NamedComponent)component).getComponentName();
}
else {
return component.getClass().getName();
}
}
protected boolean logSlowComponents() {
return LOG.isDebugEnabled();
}
protected class ComponentsRegistry {
private final Map<Class, Object> myInterfaceToLockMap = new THashMap<Class, Object>();
private final Map<Class, Class> myInterfaceToClassMap = new THashMap<Class, Class>();
private final List<Class> myComponentInterfaces = new ArrayList<Class>(); // keeps order of component's registration
private final Map<String, BaseComponent> myNameToComponent = new THashMap<String, BaseComponent>();
private final List<ComponentConfig> myComponentConfigs = new ArrayList<ComponentConfig>();
private final List<Object> myImplementations = new ArrayList<Object>();
private final Map<Class, ComponentConfig> myComponentClassToConfig = new THashMap<Class, ComponentConfig>();
private boolean myClassesLoaded = false;
private void loadClasses() {
assert !myClassesLoaded;
for (ComponentConfig config : myComponentConfigs) {
loadClasses(config);
}
myClassesLoaded = true;
}
private void loadClasses(final ComponentConfig config) {
ClassLoader loader = config.getClassLoader();
try {
final Class<?> interfaceClass = Class.forName(config.getInterfaceClass(), true, loader);
final Class<?> implementationClass = Comparing.equal(config.getInterfaceClass(), config.getImplementationClass()) ?
interfaceClass : Class.forName(config.getImplementationClass(), true, loader);
if (myInterfaceToClassMap.get(interfaceClass) != null) {
throw new RuntimeException("Component already registered: " + interfaceClass.getName());
}
getPicoContainer().registerComponent(new ComponentConfigComponentAdapter(config, implementationClass));
myInterfaceToClassMap.put(interfaceClass, implementationClass);
myComponentClassToConfig.put(implementationClass, config);
myComponentInterfaces.add(interfaceClass);
}
catch (Throwable t) {
handleInitComponentError(t, null, config);
}
}
private Object getComponentLock(final Class componentClass) {
Object lock = myInterfaceToLockMap.get(componentClass);
if (lock == null) {
myInterfaceToLockMap.put(componentClass, lock = new Object());
}
return lock;
}
private Class[] getComponentInterfaces() {
assert myClassesLoaded;
return myComponentInterfaces.toArray(new Class[myComponentInterfaces.size()]);
}
private boolean containsInterface(final Class interfaceClass) {
return myInterfaceToClassMap.containsKey(interfaceClass);
}
public float getPercentageOfComponentsLoaded() {
return ((float)myImplementations.size()) / myComponentConfigs.size();
}
private void registerComponentInstance(final Object component) {
myImplementations.add(component);
if (component instanceof BaseComponent) {
BaseComponent baseComponent = (BaseComponent)component;
final String componentName = baseComponent.getComponentName();
if (myNameToComponent.containsKey(componentName)) {
BaseComponent loadedComponent = myNameToComponent.get(componentName);
// component may have been already loaded by PicoContainer, so fire error only if components are really different
if (!component.equals(loadedComponent)) {
LOG.error("Component name collision: " + componentName + " " + loadedComponent.getClass() + " and " + component.getClass());
}
}
else {
myNameToComponent.put(componentName, baseComponent);
}
}
}
@NotNull
public List<Object> getRegisteredImplementations() {
return myImplementations;
}
private void registerComponent(ComponentConfig config) {
myComponentConfigs.add(config);
if (myClassesLoaded) {
loadClasses(config);
}
}
private BaseComponent getComponentByName(final String name) {
return myNameToComponent.get(name);
}
@SuppressWarnings({"unchecked"})
public <T> T[] getComponentsByType(final Class<T> baseClass) {
ArrayList<T> array = new ArrayList<T>();
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < myComponentInterfaces.size(); i++) {
Class interfaceClass = myComponentInterfaces.get(i);
final Class implClass = myInterfaceToClassMap.get(interfaceClass);
if (ReflectionUtil.isAssignable(baseClass, implClass)) {
array.add((T)getComponent(interfaceClass));
}
}
return array.toArray((T[])Array.newInstance(baseClass, array.size()));
}
public ComponentConfig[] getComponentConfigurations() {
return myComponentConfigs.toArray(new ComponentConfig[myComponentConfigs.size()]);
}
public ComponentConfig getConfig(final Class componentImplementation) {
return myComponentClassToConfig.get(componentImplementation);
}
}
private class ComponentConfigComponentAdapter implements ComponentAdapter {
private final ComponentConfig myConfig;
private final ComponentAdapter myDelegate;
private boolean myInitialized = false;
private boolean myInitializing = false;
public ComponentConfigComponentAdapter(final ComponentConfig config, Class<?> implementationClass) {
myConfig = config;
final String componentKey = config.getInterfaceClass();
myDelegate = new CachingComponentAdapter(new ConstructorInjectionComponentAdapter(componentKey, implementationClass, null, true)) {
@Override
public Object getComponentInstance(PicoContainer picoContainer) throws PicoInitializationException, PicoIntrospectionException, ProcessCanceledException {
ProgressIndicator indicator = getProgressIndicator();
if (indicator != null) {
indicator.checkCanceled();
}
Object componentInstance = null;
try {
long startTime = myInitialized ? 0 : System.nanoTime();
componentInstance = super.getComponentInstance(picoContainer);
if (!myInitialized) {
if (myInitializing) {
if (myConfig.pluginDescriptor != null) {
LOG.error(new PluginException("Cyclic component initialization: " + componentKey, myConfig.pluginDescriptor.getPluginId()));
}
else {
LOG.error(new Throwable("Cyclic component initialization: " + componentKey));
}
}
try {
myInitializing = true;
myComponentsRegistry.registerComponentInstance(componentInstance);
initializeComponent(componentInstance, false);
if (componentInstance instanceof BaseComponent) {
((BaseComponent)componentInstance).initComponent();
}
long ms = (System.nanoTime() - startTime) / 1000000;
if (ms > 10 && logSlowComponents()) {
LOG.info(componentInstance.getClass().getName() + " initialized in " + ms + " ms");
}
}
finally {
myInitializing = false;
}
myInitialized = true;
}
}
catch (ProcessCanceledException e) {
throw e;
}
catch (StateStorageException e) {
throw e;
}
catch (Throwable t) {
handleInitComponentError(t, componentKey, config);
}
return componentInstance;
}
};
}
@Override
public Object getComponentKey() {
return myConfig.getInterfaceClass();
}
@Override
public Class getComponentImplementation() {
return myDelegate.getComponentImplementation();
}
@Override
public Object getComponentInstance(final PicoContainer container) throws PicoInitializationException, PicoIntrospectionException {
return myDelegate.getComponentInstance(container);
}
@Override
public void verify(final PicoContainer container) throws PicoIntrospectionException {
myDelegate.verify(container);
}
@Override
public void accept(final PicoVisitor visitor) {
visitor.visitComponentAdapter(this);
myDelegate.accept(visitor);
}
@Override
public String toString() {
return "ComponentConfigAdapter[" + getComponentKey() + "]: implementation=" + getComponentImplementation() + ", plugin=" + myConfig.getPluginId();
}
}
}