/*
* Copyright 2008-2017 the original author or authors.
*
* 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.codehaus.griffon.runtime.injection;
import com.google.inject.AbstractModule;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.binder.ScopedBindingBuilder;
import griffon.core.injection.Binding;
import griffon.core.injection.Injector;
import griffon.core.injection.InstanceBinding;
import griffon.core.injection.ProviderBinding;
import griffon.core.injection.ProviderTypeBinding;
import griffon.core.injection.Qualified;
import griffon.core.injection.TargetBinding;
import griffon.exceptions.ClosedInjectorException;
import griffon.exceptions.InstanceNotFoundException;
import griffon.exceptions.MembersInjectionException;
import griffon.util.AnnotationUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Singleton;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static com.google.inject.util.Providers.guicify;
import static java.util.Objects.requireNonNull;
/**
* @author Andres Almiray
* @since 2.0.0
*/
public class GuiceInjector implements Injector<com.google.inject.Injector> {
private static final String ERROR_TYPE_NULL = "Argument 'type' must not be null";
private static final String ERROR_DELEGATE_NULL = "Argument 'delegate' must not be null";
private static final String ERROR_INSTANCE_TRACKER_NULL = "Argument 'instanceTracker' must not be null";
private static final String ERROR_QUALIFIER_NULL = "Argument 'qualifier' must not be null";
private static final String ERROR_INSTANCE_NULL = "Argument 'instance' must not be null";
private final InstanceTracker instanceTracker;
private final com.google.inject.Injector delegate;
private final Object lock = new Object[0];
@GuardedBy("lock")
private boolean closed;
public GuiceInjector(@Nonnull InstanceTracker instanceTracker) {
this.instanceTracker = requireNonNull(instanceTracker, ERROR_INSTANCE_TRACKER_NULL);
this.delegate = requireNonNull(instanceTracker.getInjector(), ERROR_DELEGATE_NULL);
}
static Module moduleFromBindings(@Nonnull final Iterable<Binding<?>> bindings) {
return new AbstractModule() {
@Override
protected void configure() {
for (Binding<?> binding : bindings) {
if (binding instanceof TargetBinding) {
handleTargetBinding((TargetBinding) binding);
} else if (binding instanceof InstanceBinding) {
handleInstanceBinding((InstanceBinding) binding);
} else if (binding instanceof ProviderTypeBinding) {
handleProviderTypeBinding((ProviderTypeBinding) binding);
} else if (binding instanceof ProviderBinding) {
handleProviderBinding((ProviderBinding) binding);
} else {
throw new IllegalArgumentException("Don't know how to handle " + binding);
}
}
}
@Nonnull
private LinkedBindingBuilder handleBinding(@Nonnull Binding<?> binding) {
AnnotatedBindingBuilder<?> builder = bind(binding.getSource());
if (binding.getClassifier() != null) {
return builder.annotatedWith(binding.getClassifier());
} else if (binding.getClassifierType() != null) {
return builder.annotatedWith(binding.getClassifierType());
}
return builder;
}
@SuppressWarnings("unchecked")
private void handleTargetBinding(@Nonnull TargetBinding<?> binding) {
LinkedBindingBuilder lbuilder = handleBinding(binding);
if (binding.getSource() != binding.getTarget()) {
ScopedBindingBuilder sbuilder = lbuilder.to(binding.getTarget());
if (binding.isSingleton()) {
sbuilder.in(Singleton.class);
}
} else if (binding.isSingleton()) {
lbuilder.in(Singleton.class);
}
}
@SuppressWarnings("unchecked")
private void handleInstanceBinding(@Nonnull InstanceBinding<?> binding) {
handleBinding(binding).toInstance(binding.getInstance());
}
@SuppressWarnings("unchecked")
private void handleProviderTypeBinding(@Nonnull ProviderTypeBinding<?> binding) {
ScopedBindingBuilder builder = handleBinding(binding).toProvider(binding.getProviderType());
if (binding.isSingleton()) {
builder.in(Singleton.class);
}
}
@SuppressWarnings("unchecked")
private void handleProviderBinding(@Nonnull ProviderBinding<?> binding) {
ScopedBindingBuilder builder = handleBinding(binding).toProvider(guicify(binding.getProvider()));
if (binding.isSingleton()) {
builder.in(Singleton.class);
}
}
};
}
@Nonnull
@Override
public <T> T getInstance(@Nonnull Class<T> type) throws InstanceNotFoundException {
requireNonNull(type, ERROR_TYPE_NULL);
if (isClosed()) {
throw new InstanceNotFoundException(type, new ClosedInjectorException(this));
}
try {
return delegate.getInstance(type);
} catch (RuntimeException e) {
throw new InstanceNotFoundException(type, e);
}
}
@Nonnull
@Override
public <T> T getInstance(@Nonnull Class<T> type, @Nonnull Annotation qualifier) throws InstanceNotFoundException {
requireNonNull(type, ERROR_TYPE_NULL);
requireNonNull(qualifier, ERROR_QUALIFIER_NULL);
if (isClosed()) {
throw new InstanceNotFoundException(type, qualifier, new ClosedInjectorException(this));
}
try {
return delegate.getInstance(Key.get(type, qualifier));
} catch (RuntimeException e) {
throw new InstanceNotFoundException(type, qualifier, e);
}
}
@Nonnull
@Override
public <T> Collection<T> getInstances(@Nonnull Class<T> type) throws InstanceNotFoundException {
requireNonNull(type, ERROR_TYPE_NULL);
if (isClosed()) {
throw new InstanceNotFoundException(type, new ClosedInjectorException(this));
}
List<T> instances = new ArrayList<>();
List<com.google.inject.Binding<T>> bindings;
try {
bindings = delegate.findBindingsByType(TypeLiteral.get(type));
} catch (RuntimeException e) {
throw new InstanceNotFoundException(type, e);
}
if (bindings == null) {
throw new InstanceNotFoundException(type);
}
for (com.google.inject.Binding<T> binding : bindings) {
try {
instances.add(delegate.getInstance(binding.getKey()));
} catch (RuntimeException e) {
throw new InstanceNotFoundException(type, e);
}
}
return instances;
}
@Nonnull
@Override
public <T> Collection<Qualified<T>> getQualifiedInstances(@Nonnull Class<T> type) throws InstanceNotFoundException {
requireNonNull(type, ERROR_TYPE_NULL);
if (isClosed()) {
throw new InstanceNotFoundException(type, new ClosedInjectorException(this));
}
List<Qualified<T>> instances = new ArrayList<>();
List<com.google.inject.Binding<T>> bindings;
try {
bindings = delegate.findBindingsByType(TypeLiteral.get(type));
} catch (RuntimeException e) {
throw new InstanceNotFoundException(type, e);
}
if (bindings == null) {
throw new InstanceNotFoundException(type);
}
for (com.google.inject.Binding<T> binding : bindings) {
try {
Key<T> key = binding.getKey();
final T instance = delegate.getInstance(key);
instances.add(new Qualified<>(instance, translate(key.getAnnotation())));
} catch (RuntimeException e) {
throw new InstanceNotFoundException(type, e);
}
}
return instances;
}
@Nullable
private Annotation translate(@Nullable Annotation annotation) {
if (annotation instanceof com.google.inject.name.Named) {
return AnnotationUtils.named(((com.google.inject.name.Named) annotation).value());
}
return annotation;
}
@Override
public void injectMembers(@Nonnull Object instance) throws MembersInjectionException {
requireNonNull(instance, ERROR_INSTANCE_NULL);
if (isClosed()) {
throw new MembersInjectionException(instance, new ClosedInjectorException(this));
}
try {
delegate.injectMembers(instance);
} catch (RuntimeException e) {
throw new MembersInjectionException(instance, e);
}
}
@Nonnull
@Override
public com.google.inject.Injector getDelegateInjector() {
return delegate;
}
@Override
public void release(@Nonnull Object instance) {
instanceTracker.release(instance);
}
@Override
public void close() {
if (isClosed()) {
throw new ClosedInjectorException(this);
}
instanceTracker.releaseAll();
synchronized (lock) {
closed = true;
}
}
private boolean isClosed() {
synchronized (lock) {
return closed;
}
}
}