blob: c51b950b67a708c1676d8f9c6b1135c38817397d [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.util.pico;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.picocontainer.*;
import org.picocontainer.defaults.*;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
public class DefaultPicoContainer implements MutablePicoContainer, Serializable {
private final ComponentAdapterFactory componentAdapterFactory;
private final PicoContainer parent;
private final Set<PicoContainer> children = new HashSet<PicoContainer>();
private final Map<Object, ComponentAdapter> componentKeyToAdapterCache = ContainerUtil.newConcurrentMap();
private final LinkedHashSetWrapper<ComponentAdapter> componentAdapters = new LinkedHashSetWrapper<ComponentAdapter>();
private final Map<String, ComponentAdapter> classNameToAdapter = ContainerUtil.newConcurrentMap();
private final AtomicReference<FList<ComponentAdapter>> nonAssignableComponentAdapters = new AtomicReference<FList<ComponentAdapter>>(FList.<ComponentAdapter>emptyList());
public DefaultPicoContainer(@NotNull ComponentAdapterFactory componentAdapterFactory, PicoContainer parent) {
this.componentAdapterFactory = componentAdapterFactory;
this.parent = parent == null ? null : ImmutablePicoContainerProxyFactory.newProxyInstance(parent);
}
protected DefaultPicoContainer() {
this(new DefaultComponentAdapterFactory(), null);
}
@Override
public Collection<ComponentAdapter> getComponentAdapters() {
return componentAdapters.getImmutableSet();
}
public Map<String, ComponentAdapter> getAssignablesCache() {
return Collections.unmodifiableMap(classNameToAdapter);
}
protected LinkedList<ComponentAdapter> getNonAssignableAdaptersOfType(final Class componentType) {
LinkedList<ComponentAdapter> result = new LinkedList<ComponentAdapter>();
for (final ComponentAdapter componentAdapter : nonAssignableComponentAdapters.get()) {
if (ReflectionUtil.isAssignable(componentType, componentAdapter.getComponentImplementation())) {
result.addFirst(componentAdapter);
}
}
return result;
}
@Override
@Nullable
public final ComponentAdapter getComponentAdapter(Object componentKey) {
ComponentAdapter adapter = getFromCache(componentKey);
if (adapter == null && parent != null) {
adapter = 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) {
Class klass = (Class)componentKey;
return componentKeyToAdapterCache.get(klass.getName());
}
return null;
}
@Override
@Nullable
public ComponentAdapter getComponentAdapterOfType(Class componentType) {
// See http://jira.codehaus.org/secure/ViewIssue.jspa?key=PICO-115
ComponentAdapter adapterByKey = getComponentAdapter(componentType);
if (adapterByKey != null) {
return adapterByKey;
}
List found = getComponentAdaptersOfType(componentType);
if (found.size() == 1) {
return (ComponentAdapter)found.get(0);
}
else if (found.isEmpty()) {
if (parent != null) {
return parent.getComponentAdapterOfType(componentType);
}
return null;
}
else {
Class[] foundClasses = new Class[found.size()];
for (int i = 0; i < foundClasses.length; i++) {
foundClasses[i] = ((ComponentAdapter)found.get(i)).getComponentImplementation();
}
throw new AmbiguousComponentResolutionException(componentType, foundClasses);
}
}
@Override
public List getComponentAdaptersOfType(Class componentType) {
if (componentType == null) {
return Collections.emptyList();
}
List<ComponentAdapter> found = new ArrayList<ComponentAdapter>();
for (final Object o : getComponentAdapters()) {
ComponentAdapter componentAdapter = (ComponentAdapter)o;
if (ReflectionUtil.isAssignable(componentType, componentAdapter.getComponentImplementation())) {
found.add(componentAdapter);
}
}
return found;
}
@Override
public ComponentAdapter registerComponent(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(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() throws PicoException {
return getComponentInstancesOfType(Object.class);
}
@Override
public List getComponentInstancesOfType(Class componentType) {
if (componentType == null) {
return Collections.emptyList();
}
List<Object> result = new ArrayList<Object>();
for (final ComponentAdapter componentAdapter : componentAdapters.getImmutableSet()) {
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;
}
@Override
@Nullable
public Object getComponentInstance(Object componentKey) {
ComponentAdapter componentAdapter = getComponentAdapter(componentKey);
return componentAdapter == null ? null : getInstance(componentAdapter);
}
@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 (componentAdapters.getImmutableSet().contains(componentAdapter)) {
return getLocalInstance(componentAdapter);
}
if (parent != null) {
return parent.getComponentInstance(componentAdapter.getComponentKey());
}
return null;
}
private Object getLocalInstance(final ComponentAdapter componentAdapter) {
PicoException firstLevelException = null;
Object instance = null;
try {
instance = componentAdapter.getComponentInstance(this);
}
catch (PicoInitializationException e) {
firstLevelException = e;
}
catch (PicoIntrospectionException e) {
firstLevelException = e;
}
if (firstLevelException != null) {
if (parent != null) {
instance = parent.getComponentInstance(componentAdapter.getComponentKey());
if (instance != null) {
return instance;
}
}
throw firstLevelException;
}
return instance;
}
@Override
@Nullable
public ComponentAdapter unregisterComponentByInstance(Object componentInstance) {
Collection<ComponentAdapter> adapters = getComponentAdapters();
for (final ComponentAdapter adapter : adapters) {
final Object o = getInstance(adapter);
if (o != null && o.equals(componentInstance)) {
return unregisterComponent(adapter.getComponentKey());
}
}
return null;
}
@Override
public void verify() throws PicoVerificationException {
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();
}
@Override
public MutablePicoContainer makeChildContainer() {
DefaultPicoContainer pc = new DefaultPicoContainer(componentAdapterFactory, this);
addChildContainer(pc);
return pc;
}
@Override
public boolean addChildContainer(PicoContainer child) {
return children.add(child);
}
@Override
public boolean removeChildContainer(PicoContainer child) {
return children.remove(child);
}
@Override
public void accept(PicoVisitor visitor) {
visitor.visitContainer(this);
final List<ComponentAdapter> adapters = new ArrayList<ComponentAdapter>(getComponentAdapters());
for (final ComponentAdapter adapter : adapters) {
adapter.accept(visitor);
}
final List<PicoContainer> allChildren = new ArrayList<PicoContainer>(children);
for (PicoContainer child : allChildren) {
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));
}
@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 = componentAdapterFactory.createComponentAdapter(componentKey, componentImplementation, parameters);
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<T>();
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<T>(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;
}
}
}