/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.ioc.client.api.builtin;
import static org.jboss.errai.ioc.client.QualifierUtil.DEFAULT_ANNOTATION;
import static org.jboss.errai.ioc.client.container.IOC.getBeanManager;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.Dependent;
import org.jboss.errai.ioc.client.IOCUtil;
import org.jboss.errai.ioc.client.api.ContextualTypeProvider;
import org.jboss.errai.ioc.client.api.Disposer;
import org.jboss.errai.ioc.client.api.IOCProvider;
import org.jboss.errai.ioc.client.api.ManagedInstance;
import org.jboss.errai.ioc.client.container.SyncBeanDef;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
@IOCProvider
public class ManagedInstanceProvider implements ContextualTypeProvider<ManagedInstance<?>>, Disposer<ManagedInstance<?>> {
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public ManagedInstance<?> provide(final Class<?>[] typeargs, final Annotation[] qualifiers) {
return new ManagedInstanceImpl(typeargs[0], qualifiers.length == 0 ? new Annotation[] { DEFAULT_ANNOTATION } : qualifiers);
}
@Override
public void dispose(final ManagedInstance<?> managedInstance) {
managedInstance.destroyAll();
}
private static final Map<Class<?>, SubTypeNode> subTypeNodes = new HashMap<>();
private static void addSubTypeRelation(final Class<?> superType, final Class<?> subType) {
if (!superType.equals(subType)) {
SubTypeNode superNode = subTypeNodes.get(superType);
if (superNode == null) {
superNode = new SubTypeNode(superType, new LinkedHashSet<>(0));
subTypeNodes.put(superType, superNode);
}
SubTypeNode subNode = subTypeNodes.get(subType);
if (subNode == null) {
subNode = new SubTypeNode(subType, new LinkedHashSet<>(1));
subTypeNodes.put(subType, subNode);
}
subNode.superTypes.add(superNode);
}
}
private static boolean isSubTypeRelation(final Class<?> superType, final Class<?> subType) {
if (superType.equals(subType)) {
return true;
}
else {
final SubTypeNode superNode = subTypeNodes.get(superType);
final SubTypeNode subNode = subTypeNodes.get(subType);
return superNode != null && subNode != null && isSubTypeRelation(superNode, subNode);
}
}
private static boolean isSubTypeRelation(final SubTypeNode superNode, final SubTypeNode subNode) {
if (subNode.superTypes.contains(superNode)) {
return true;
}
else {
for (final SubTypeNode node : subNode.superTypes) {
if (isSubTypeRelation(superNode, node)) {
return true;
}
}
return false;
}
}
private static class SubTypeNode {
private final Class<?> type;
private final Set<SubTypeNode> superTypes;
private SubTypeNode(final Class<?> type, final Set<SubTypeNode> superTypes) {
this.type = type;
this.superTypes = superTypes;
}
@Override
public int hashCode() {
return type.hashCode();
}
@Override
public boolean equals(final Object obj) {
return obj instanceof SubTypeNode && ((SubTypeNode) obj).type.equals(type);
}
}
private static class InstanceKey<T> {
private final Class<T> type;
private final Set<Annotation> qualifiers;
private InstanceKey(final Class<T> type, final Set<Annotation> qualifiers) {
this.type = type;
this.qualifiers = qualifiers;
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof InstanceKey) {
final InstanceKey<?> other = (InstanceKey<?>) obj;
return type.equals(other.type) && qualifiers.equals(other.qualifiers);
}
else {
return false;
}
}
@Override
public int hashCode() {
return type.hashCode() ^ qualifiers.hashCode();
}
}
private static class ManagedInstanceImpl<S, T extends S> implements ManagedInstance<T> {
private final InstanceKey<T> key;
private final Multimap<InstanceKey<? extends S>, ? super T> dependentInstances;
private ManagedInstanceImpl(final Class<T> type, final Annotation[] qualifiers) {
this(type, qualifiers, ArrayListMultimap.create());
}
private ManagedInstanceImpl(final Class<T> type, final Annotation[] qualifiers, final Multimap<InstanceKey<? extends S>, ? super T> dependentInstances) {
this(type, new HashSet<>(Arrays.asList(qualifiers)), dependentInstances);
}
private ManagedInstanceImpl(final Class<T> type, final Set<Annotation> qualifiers, final Multimap<InstanceKey<? extends S>, ? super T> dependentInstances) {
this.key = new InstanceKey<>(type, qualifiers);
this.dependentInstances = dependentInstances;
}
@Override
public T get() {
final SyncBeanDef<T> bean = IOCUtil.getSyncBean(key.type, qualifierArray());
final T instance = bean.getInstance();
if (Dependent.class.equals(bean.getScope())) {
dependentInstances.put(key, instance);
}
return instance;
}
private Annotation[] qualifierArray() {
return key.qualifiers.toArray(new Annotation[key.qualifiers.size()]);
}
@Override
public Iterator<T> iterator() {
return new ManagedInstanceImplIterator<>(getBeanManager().lookupBeans(key.type, qualifierArray()), key, dependentInstances);
}
@Override
public ManagedInstance<T> select(final Annotation... qualifiers) {
return select(key.type, qualifiers);
}
@Override
public <U extends T> ManagedInstance<U> select(final Class<U> subtype, final Annotation... qualifiers) {
final Set<Annotation> combined = new HashSet<>(key.qualifiers);
combined.addAll(Arrays.asList(qualifiers));
addSubTypeRelation(key.type, subtype);
return new ManagedInstanceImpl<>(subtype, combined, dependentInstances);
}
@Override
public boolean isUnsatisfied() {
return IOCUtil.isUnsatisfied(key.type, qualifierArray());
}
@Override
public boolean isAmbiguous() {
return IOCUtil.isAmbiguous(key.type, qualifierArray());
}
@Override
public void destroy(final T instance) {
IOCUtil.destroy(instance);
dependentInstances.remove(key, instance);
}
@Override
public void destroyAll() {
final Iterator<InstanceKey<? extends S>> keysIter = dependentInstances.keySet().iterator();
while (keysIter.hasNext()) {
final InstanceKey<? extends S> key = keysIter.next();
final Set<Annotation> quals = key.qualifiers;
if (quals.containsAll(this.key.qualifiers) && isSubTypeRelation(this.key.type, key.type)) {
final Collection<? super T> instances = dependentInstances.get(key);
for (final Object instance : instances) {
IOCUtil.destroy(instance);
}
keysIter.remove();
}
}
}
private static class ManagedInstanceImplIterator<S, T extends S> implements Iterator<T> {
private final Iterator<SyncBeanDef<T>> delegate;
private final InstanceKey<T> key;
private final Multimap<InstanceKey<? extends S>, ? super T> dependentInstances;
private Object lastCreatedInstance;
private ManagedInstanceImplIterator(final Collection<SyncBeanDef<T>> beanDefs, final InstanceKey<T> key,
final Multimap<InstanceKey<? extends S>, ? super T> dependentInstances) {
this.key = key;
this.delegate = beanDefs.iterator();
this.dependentInstances = dependentInstances;
}
@Override
public boolean hasNext() {
return delegate.hasNext();
}
@Override
public T next() {
final SyncBeanDef<T> bean = delegate.next();
final T instance = bean.getInstance();
if (Dependent.class.equals(bean.getScope())) {
dependentInstances.put(key, instance);
}
lastCreatedInstance = instance;
return instance;
}
@Override
public void remove() {
if (lastCreatedInstance == null) {
throw new IllegalStateException();
}
else if (dependentInstances.remove(key, lastCreatedInstance)) {
IOCUtil.destroy(lastCreatedInstance);
lastCreatedInstance = null;
}
}
}
}
}