GuiceUtils.java
package org.matsim.episim;
import com.google.inject.Module;
import com.google.inject.*;
import com.google.inject.internal.AbstractBindingBuilder;
import com.google.inject.internal.BindingBuilder;
import com.google.inject.internal.BindingImpl;
import com.google.inject.internal.SingletonScope;
import com.google.inject.spi.BindingScopingVisitor;
import com.google.inject.util.Modules;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* By design Guice does not allow child injectors to override bindings with a different scope:
* "The reason overriding a binding in a child injector isn't supported is
* because it can lead a developer towards writing code that can work in either a parent & child injector, but have different behavior in each."
* (https://groups.google.com/g/google-guice/c/naqT-fOrOTw)
* <p>
* Unfortunately, that is exactly what is needed.
* This class provides util method to create a copy of an injector to provide similar functionality.
*/
@SuppressWarnings("unchecked, rawtypes")
public class GuiceUtils {
private static final Logger log = LogManager.getLogger(GuiceUtils.class);
/**
* Create a injector similar to a "child injector".
* This new injector has the same singletons as the parent injectors, unless they have been overwritten.
* <p>
* Note: Accessing types in the child before they are bound in the parent may lead to unexpected behaviour.
*
* @param parent the parent injector to copy
* @param modules modules with overriding binding
* @param localScope binding that are not copied into this injector, but instead recreated in the local scope.
*/
public static Injector createCopiedInjector(Injector parent, Iterable<Module> modules, Class<?>... localScope) {
return Guice.createInjector(Modules.override(new CopyModule(parent, localScope)).with(modules));
}
private static class CopyModule extends AbstractModule {
private final Injector parent;
private final List<String> localScope;
private CopyModule(Injector parent, Class<?>[] localScope) {
this.parent = parent;
this.localScope = Arrays.stream(localScope).map(Class::getCanonicalName).collect(Collectors.toList());
}
@Override
protected void configure() {
binder().requireExplicitBindings();
for (Map.Entry<Key<?>, Binding<?>> e : parent.getAllBindings().entrySet()) {
Key key = e.getKey();
// internal guice types are not bound
String type = key.getTypeLiteral().toString();
if (type.contains("com.google.inject") || type.contains("java.util.logging"))
continue;
Binding<?> binding = e.getValue();
Boolean singleton = binding.acceptScopingVisitor(new IsSingleTonVisitor());
if (singleton && !localScope.contains(type)) {
Object instance = parent.getInstance(key);
bind(key).toInstance(instance);
} else {
BindingBuilder binder = (BindingBuilder) bind(key);
try {
GuiceUtils.bind(binder, binding);
} catch (ReflectiveOperationException exc) {
throw new RuntimeException(exc);
}
}
}
}
}
/**
* Internal method to copy a binding.
*/
private static void bind(BindingBuilder binder, Binding binding) throws ReflectiveOperationException {
Method method = AbstractBindingBuilder.class.getDeclaredMethod("setBinding", BindingImpl.class);
method.setAccessible(true);
method.invoke(binder, binding);
}
/**
* Returns true if scope is singleton.
*/
private static class IsSingleTonVisitor implements BindingScopingVisitor<Boolean> {
@Override
public Boolean visitEagerSingleton() {
return true;
}
@Override
public Boolean visitScope(Scope scope) {
return scope instanceof SingletonScope;
}
@Override
public Boolean visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
return scopeAnnotation == Singleton.class;
}
@Override
public Boolean visitNoScoping() {
return false;
}
}
;
}