Implement DynamicSet<T>, DynamicMap<T> to provide bindings in Guice

The core server can now declare that it needs a DynamicSet supplied by
Guice at runtime:

  DynamicSet.setOf(binder(), SomeBaseType.class);

and then receive this as an injection:

  DynamicSet<SomeBaseType> theThings

Core server code can register static implementations into this set:

  DynamicSet.bind(binder(), SomeBaseType.class).to(AnImpl.class);

Plugins may use the same syntax in their own Guice modules to register
their own implementations. When a plugin starts, its registrations
will be added to the DynamicSet, and when it stops, the references get
cleaned up automatically. During a hot reload of a plugin references
from the old plugin and the new plugin are matched up by collection
member type and Guice annotation information, and atomically swapped.

Plugins can use automatic registration if interfaces are annotated
with @ExtensionPoint and the plugin implementation is annoated with
@Listen and also implements the interface, directly or indirectly
through its base classes or interfaces:

  (gerrit-extension-api)
  @ExtensionPoint
  public interface NewChangeListener {

  (gerrit-server)
  DynamicSet.setOf(binder(), NewChangeListener.class);

  (plugin or extension code)
  @Listen
  class OnNewChange implements NewChangeListener {

Automatic registration binds the listeners into the system module,
which may prevent plugins or extensions from automatically connecting
with extension points inside of the HTTP or SSH servers. This
shouldn't generally be a problem as the majority of interfaces plugins
or extensions care about will be defined in the core server, and thus
be in the system module.

Change-Id: Ic8f371d97f8f0ddb6cad97fef3b58e1c3d32381f
This commit is contained in:
Shawn O. Pearce
2012-05-10 15:21:05 -07:00
parent fac27716d8
commit 61090db9e5
11 changed files with 1129 additions and 9 deletions

View File

@@ -16,8 +16,16 @@ package com.google.gerrit.server.plugins;
import static com.google.gerrit.server.plugins.PluginGuiceEnvironment.is;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.annotations.Export;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.extensions.annotations.Listen;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.UniqueAnnotations;
import org.eclipse.jgit.util.IO;
import org.objectweb.asm.AnnotationVisitor;
@@ -31,7 +39,11 @@ import org.objectweb.asm.Type;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -39,11 +51,15 @@ class AutoRegisterModules {
private static final int SKIP_ALL = ClassReader.SKIP_CODE
| ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
private final String pluginName;
private final PluginGuiceEnvironment env;
private final JarFile jarFile;
private final ClassLoader classLoader;
private final ModuleGenerator sshGen;
private final ModuleGenerator httpGen;
private Set<Class<?>> sysSingletons;
private Map<TypeLiteral<?>, Class<?>> sysListen;
Module sysModule;
Module sshModule;
Module httpModule;
@@ -53,6 +69,7 @@ class AutoRegisterModules {
JarFile jarFile,
ClassLoader classLoader) {
this.pluginName = pluginName;
this.env = env;
this.jarFile = jarFile;
this.classLoader = classLoader;
this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : null;
@@ -60,6 +77,9 @@ class AutoRegisterModules {
}
AutoRegisterModules discover() throws InvalidPluginException {
sysSingletons = Sets.newHashSet();
sysListen = Maps.newHashMap();
if (sshGen != null) {
sshGen.setPluginName(pluginName);
}
@@ -69,6 +89,9 @@ class AutoRegisterModules {
scan();
if (!sysSingletons.isEmpty() || !sysListen.isEmpty()) {
sysModule = makeSystemModule();
}
if (sshGen != null) {
sshModule = sshGen.create();
}
@@ -78,6 +101,36 @@ class AutoRegisterModules {
return this;
}
private Module makeSystemModule() {
return new AbstractModule() {
@Override
protected void configure() {
for (Class<?> clazz : sysSingletons) {
bind(clazz).in(Scopes.SINGLETON);
}
for (Map.Entry<TypeLiteral<?>, Class<?>> e : sysListen.entrySet()) {
@SuppressWarnings("unchecked")
TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey();
@SuppressWarnings("unchecked")
Class<Object> impl = (Class<Object>) e.getValue();
Annotation n = impl.getAnnotation(Export.class);
if (n == null) {
n = impl.getAnnotation(javax.inject.Named.class);
}
if (n == null) {
n = impl.getAnnotation(com.google.inject.name.Named.class);
}
if (n == null) {
n = UniqueAnnotations.create();
}
bind(type).annotatedWith(n).to(impl);
}
}
};
}
private void scan() throws InvalidPluginException {
Enumeration<JarEntry> e = jarFile.entries();
while (e.hasMoreElements()) {
@@ -103,7 +156,15 @@ class AutoRegisterModules {
export(def);
} else {
PluginLoader.log.warn(String.format(
"Plugin %s tries to export abstract class %s",
"Plugin %s tries to @Export(\"%s\") abstract class %s",
pluginName, def.exportedAsName, def.className));
}
} else if (def.listen) {
if (def.isConcrete()) {
listen(def);
} else {
PluginLoader.log.warn(String.format(
"Plugin %s tries to @Listen abstract class %s",
pluginName, def.className));
}
}
@@ -135,11 +196,81 @@ class AutoRegisterModules {
} else if (is("javax.servlet.http.HttpServlet", clazz)) {
if (httpGen != null) {
httpGen.export(export, clazz);
listen(clazz, clazz);
}
} else {
int cnt = sysListen.size();
listen(clazz, clazz);
if (cnt == sysListen.size()) {
// If no bindings were recorded, the extension isn't recognized.
throw new InvalidPluginException(String.format(
"Class %s with @Export(\"%s\") not supported",
clazz.getName(), export.value()));
}
}
}
private void listen(ClassData def) throws InvalidPluginException {
Class<?> clazz;
try {
clazz = Class.forName(def.className, false, classLoader);
} catch (ClassNotFoundException err) {
throw new InvalidPluginException(String.format(
"Class %s with @Export(\"%s\") not supported",
clazz.getName(), export.value()));
"Cannot load %s with @Listen",
def.className), err);
}
Listen listen = clazz.getAnnotation(Listen.class);
if (listen != null) {
listen(clazz, clazz);
} else {
PluginLoader.log.warn(String.format(
"In plugin %s asm incorrectly parsed %s with @Listen",
pluginName, clazz.getName()));
}
}
private void listen(java.lang.reflect.Type type, Class<?> clazz)
throws InvalidPluginException {
while (type != null) {
Class<?> rawType;
if (type instanceof ParameterizedType) {
rawType = (Class<?>) ((ParameterizedType) type).getRawType();
} else if (type instanceof Class) {
rawType = (Class<?>) type;
} else {
return;
}
if (rawType.getAnnotation(ExtensionPoint.class) != null) {
TypeLiteral<?> tl = TypeLiteral.get(type);
if (env.hasDynamicSet(tl)) {
sysSingletons.add(clazz);
sysListen.put(tl, clazz);
} else if (env.hasDynamicMap(tl)) {
if (clazz.getAnnotation(Export.class) == null) {
throw new InvalidPluginException(String.format(
"Class %s requires @Export(\"name\") annotation for %s",
clazz.getName(), rawType.getName()));
}
sysSingletons.add(clazz);
sysListen.put(tl, clazz);
} else {
throw new InvalidPluginException(String.format(
"Cannot register %s, server does not accept %s",
clazz.getName(), rawType.getName()));
}
return;
}
java.lang.reflect.Type[] interfaces = rawType.getGenericInterfaces();
if (interfaces != null) {
for (java.lang.reflect.Type i : interfaces) {
listen(i, clazz);
}
}
type = rawType.getGenericSuperclass();
}
}
@@ -169,9 +300,12 @@ class AutoRegisterModules {
private static class ClassData implements ClassVisitor {
private static final String EXPORT = Type.getType(Export.class).getDescriptor();
private static final String LISTEN = Type.getType(Listen.class).getDescriptor();
String className;
int access;
String exportedAsName;
boolean listen;
boolean isConcrete() {
return (access & Opcodes.ACC_ABSTRACT) == 0
@@ -195,6 +329,10 @@ class AutoRegisterModules {
}
};
}
if (visible && LISTEN.equals(desc)) {
listen = true;
return null;
}
return null;
}

View File

@@ -15,8 +15,10 @@
package com.google.gerrit.server.plugins;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.inject.AbstractModule;
@@ -27,6 +29,8 @@ import com.google.inject.Module;
import org.eclipse.jgit.storage.file.FileSnapshot;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
@@ -55,6 +59,7 @@ public class Plugin {
private Injector sshInjector;
private Injector httpInjector;
private LifecycleManager manager;
private List<ReloadableRegistrationHandle<?>> reloadableHandles;
public Plugin(String name,
File srcJar,
@@ -186,6 +191,10 @@ public class Plugin {
return jarFile;
}
public Injector getSysInjector() {
return sysInjector;
}
@Nullable
public Injector getSshInjector() {
return sshInjector;
@@ -197,6 +206,13 @@ public class Plugin {
}
public void add(final RegistrationHandle handle) {
if (handle instanceof ReloadableRegistrationHandle) {
if (reloadableHandles == null) {
reloadableHandles = Lists.newArrayList();
}
reloadableHandles.add((ReloadableRegistrationHandle<?>) handle);
}
add(new LifecycleListener() {
@Override
public void start() {
@@ -213,6 +229,13 @@ public class Plugin {
manager.add(listener);
}
List<ReloadableRegistrationHandle<?>> getReloadableHandles() {
if (reloadableHandles != null) {
return reloadableHandles;
}
return Collections.emptyList();
}
@Override
public String toString() {
return "Plugin [" + name + "]";

View File

@@ -14,8 +14,16 @@
package com.google.gerrit.server.plugins;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
@@ -25,11 +33,17 @@ import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.UniqueAnnotations;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import javax.inject.Inject;
/**
@@ -45,6 +59,7 @@ public class PluginGuiceEnvironment {
private final CopyConfigModule copyConfigModule;
private final List<StartPluginListener> onStart;
private final List<ReloadPluginListener> onReload;
private Module sysModule;
private Module sshModule;
private Module httpModule;
@@ -52,6 +67,14 @@ public class PluginGuiceEnvironment {
private Provider<ModuleGenerator> sshGen;
private Provider<ModuleGenerator> httpGen;
private Map<TypeLiteral<?>, DynamicSet<?>> sysSets;
private Map<TypeLiteral<?>, DynamicSet<?>> sshSets;
private Map<TypeLiteral<?>, DynamicSet<?>> httpSets;
private Map<TypeLiteral<?>, DynamicMap<?>> sysMaps;
private Map<TypeLiteral<?>, DynamicMap<?>> sshMaps;
private Map<TypeLiteral<?>, DynamicMap<?>> httpMaps;
@Inject
PluginGuiceEnvironment(Injector sysInjector, CopyConfigModule ccm) {
this.sysInjector = sysInjector;
@@ -62,6 +85,21 @@ public class PluginGuiceEnvironment {
onReload = new CopyOnWriteArrayList<ReloadPluginListener>();
onReload.addAll(listeners(sysInjector, ReloadPluginListener.class));
sysSets = dynamicSetsOf(sysInjector);
sysMaps = dynamicMapsOf(sysInjector);
}
boolean hasDynamicSet(TypeLiteral<?> type) {
return sysSets.containsKey(type)
|| (sshSets != null && sshSets.containsKey(type))
|| (httpSets != null && httpSets.containsKey(type));
}
boolean hasDynamicMap(TypeLiteral<?> type) {
return sysMaps.containsKey(type)
|| (sshMaps != null && sshMaps.containsKey(type))
|| (httpMaps != null && httpMaps.containsKey(type));
}
Module getSysModule() {
@@ -84,6 +122,8 @@ public class PluginGuiceEnvironment {
public void setSshInjector(Injector injector) {
sshModule = copy(injector);
sshGen = injector.getProvider(ModuleGenerator.class);
sshSets = dynamicSetsOf(injector);
sshMaps = dynamicMapsOf(injector);
onStart.addAll(listeners(injector, StartPluginListener.class));
onReload.addAll(listeners(injector, ReloadPluginListener.class));
}
@@ -103,6 +143,8 @@ public class PluginGuiceEnvironment {
public void setHttpInjector(Injector injector) {
httpModule = copy(injector);
httpGen = injector.getProvider(ModuleGenerator.class);
httpSets = dynamicSetsOf(injector);
httpMaps = dynamicMapsOf(injector);
onStart.addAll(listeners(injector, StartPluginListener.class));
onReload.addAll(listeners(injector, ReloadPluginListener.class));
}
@@ -123,27 +165,257 @@ public class PluginGuiceEnvironment {
for (StartPluginListener l : onStart) {
l.onStartPlugin(plugin);
}
attachSet(sysSets, plugin.getSysInjector(), plugin);
attachSet(sshSets, plugin.getSshInjector(), plugin);
attachSet(httpSets, plugin.getHttpInjector(), plugin);
attachMap(sysMaps, plugin.getSysInjector(), plugin);
attachMap(sshMaps, plugin.getSshInjector(), plugin);
attachMap(httpMaps, plugin.getHttpInjector(), plugin);
}
private void attachSet(Map<TypeLiteral<?>, DynamicSet<?>> sets,
@Nullable Injector src,
Plugin plugin) {
if (src != null && sets != null && !sets.isEmpty()) {
for (Map.Entry<TypeLiteral<?>, DynamicSet<?>> e : sets.entrySet()) {
@SuppressWarnings("unchecked")
TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey();
@SuppressWarnings("unchecked")
DynamicSet<Object> set = (DynamicSet<Object>) e.getValue();
for (Binding<Object> b : bindings(src, type)) {
plugin.add(set.add(b.getKey(), b.getProvider().get()));
}
}
}
}
private void attachMap(Map<TypeLiteral<?>, DynamicMap<?>> maps,
@Nullable Injector src,
Plugin plugin) {
if (src != null && maps != null && !maps.isEmpty()) {
for (Map.Entry<TypeLiteral<?>, DynamicMap<?>> e : maps.entrySet()) {
@SuppressWarnings("unchecked")
TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey();
@SuppressWarnings("unchecked")
PrivateInternals_DynamicMapImpl<Object> set =
(PrivateInternals_DynamicMapImpl<Object>) e.getValue();
for (Binding<Object> b : bindings(src, type)) {
plugin.add(set.put(
plugin.getName(),
b.getKey(),
b.getProvider().get()));
}
}
}
}
void onReloadPlugin(Plugin oldPlugin, Plugin newPlugin) {
for (ReloadPluginListener l : onReload) {
l.onReloadPlugin(oldPlugin, newPlugin);
}
// Index all old registrations by the raw type. These may be replaced
// during the reattach calls below. Any that are not replaced will be
// removed when the old plugin does its stop routine.
ListMultimap<TypeLiteral<?>, ReloadableRegistrationHandle<?>> old =
LinkedListMultimap.create();
for (ReloadableRegistrationHandle<?> h : oldPlugin.getReloadableHandles()) {
old.put(h.getKey().getTypeLiteral(), h);
}
reattachMap(old, sysMaps, newPlugin.getSysInjector(), newPlugin);
reattachMap(old, sshMaps, newPlugin.getSshInjector(), newPlugin);
reattachMap(old, httpMaps, newPlugin.getHttpInjector(), newPlugin);
reattachSet(old, sysSets, newPlugin.getSysInjector(), newPlugin);
reattachSet(old, sshSets, newPlugin.getSshInjector(), newPlugin);
reattachSet(old, httpSets, newPlugin.getHttpInjector(), newPlugin);
}
private static <T> List<T> listeners(Injector src, Class<T> type) {
List<Binding<T>> bindings = src.findBindingsByType(TypeLiteral.get(type));
List<T> found = Lists.newArrayListWithCapacity(bindings.size());
for (Binding<T> b : bindings) {
found.add(b.getProvider().get());
private void reattachMap(
ListMultimap<TypeLiteral<?>, ReloadableRegistrationHandle<?>> oldHandles,
Map<TypeLiteral<?>, DynamicMap<?>> maps,
@Nullable Injector src,
Plugin newPlugin) {
if (src == null || maps == null || maps.isEmpty()) {
return;
}
for (Map.Entry<TypeLiteral<?>, DynamicMap<?>> e : maps.entrySet()) {
@SuppressWarnings("unchecked")
TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey();
@SuppressWarnings("unchecked")
PrivateInternals_DynamicMapImpl<Object> map =
(PrivateInternals_DynamicMapImpl<Object>) e.getValue();
Map<Annotation, ReloadableRegistrationHandle<?>> am = Maps.newHashMap();
for (ReloadableRegistrationHandle<?> h : oldHandles.get(type)) {
Annotation a = h.getKey().getAnnotation();
if (a != null && !UNIQUE_ANNOTATION.isInstance(a)) {
am.put(a, h);
}
}
for (Binding<?> binding : bindings(src, e.getKey())) {
@SuppressWarnings("unchecked")
Binding<Object> b = (Binding<Object>) binding;
Key<Object> key = b.getKey();
@SuppressWarnings("unchecked")
ReloadableRegistrationHandle<Object> h =
(ReloadableRegistrationHandle<Object>) am.remove(key.getAnnotation());
if (h != null) {
replace(newPlugin, h, b);
oldHandles.remove(type, h);
} else {
newPlugin.add(map.put(
newPlugin.getName(),
b.getKey(),
b.getProvider().get()));
}
}
}
}
/** Type used to declare unique annotations. Guice hides this, so extract it. */
private static final Class<?> UNIQUE_ANNOTATION =
UniqueAnnotations.create().getClass();
private void reattachSet(
ListMultimap<TypeLiteral<?>, ReloadableRegistrationHandle<?>> oldHandles,
Map<TypeLiteral<?>, DynamicSet<?>> sets,
@Nullable Injector src,
Plugin newPlugin) {
if (src == null || sets == null || sets.isEmpty()) {
return;
}
for (Map.Entry<TypeLiteral<?>, DynamicSet<?>> e : sets.entrySet()) {
@SuppressWarnings("unchecked")
TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey();
@SuppressWarnings("unchecked")
DynamicSet<Object> set = (DynamicSet<Object>) e.getValue();
// Index all old handles that match this DynamicSet<T> keyed by
// annotations. Ignore the unique annotations, thereby favoring
// the @Named annotations or some other non-unique naming.
Map<Annotation, ReloadableRegistrationHandle<?>> am = Maps.newHashMap();
List<ReloadableRegistrationHandle<?>> old = oldHandles.get(type);
Iterator<ReloadableRegistrationHandle<?>> oi = old.iterator();
while (oi.hasNext()) {
ReloadableRegistrationHandle<?> h = oi.next();
Annotation a = h.getKey().getAnnotation();
if (a != null && !UNIQUE_ANNOTATION.isInstance(a)) {
am.put(a, h);
oi.remove();
}
}
// Replace old handles with new bindings, favoring cases where there
// is an exact match on an @Named annotation. If there is no match
// pick any handle and replace it. We generally expect only one
// handle of each DynamicSet type when using unique annotations, but
// possibly multiple ones if @Named was used. Plugin authors that want
// atomic replacement across reloads should use @Named annotations with
// stable names that do not change across plugin versions to ensure the
// handles are swapped correctly.
oi = old.iterator();
for (Binding<?> binding : bindings(src, type)) {
@SuppressWarnings("unchecked")
Binding<Object> b = (Binding<Object>) binding;
Key<Object> key = b.getKey();
@SuppressWarnings("unchecked")
ReloadableRegistrationHandle<Object> h1 =
(ReloadableRegistrationHandle<Object>) am.remove(key.getAnnotation());
if (h1 != null) {
replace(newPlugin, h1, b);
} else if (oi.hasNext()) {
@SuppressWarnings("unchecked")
ReloadableRegistrationHandle<Object> h2 =
(ReloadableRegistrationHandle<Object>) oi.next();
oi.remove();
replace(newPlugin, h2, b);
} else {
newPlugin.add(set.add(b.getKey(), b.getProvider().get()));
}
}
}
}
private static <T> void replace(Plugin newPlugin,
ReloadableRegistrationHandle<T> h, Binding<T> b) {
RegistrationHandle n = h.replace(b.getKey(), b.getProvider().get());
if (n != null){
newPlugin.add(n);
}
}
static <T> List<T> listeners(Injector src, Class<T> type) {
List<Binding<T>> bindings = bindings(src, TypeLiteral.get(type));
int cnt = bindings != null ? bindings.size() : 0;
List<T> found = Lists.newArrayListWithCapacity(cnt);
if (bindings != null) {
for (Binding<T> b : bindings) {
found.add(b.getProvider().get());
}
}
return found;
}
private static <T> List<Binding<T>> bindings(Injector src, TypeLiteral<T> type) {
return src.findBindingsByType(type);
}
private static Map<TypeLiteral<?>, DynamicSet<?>> dynamicSetsOf(Injector src) {
Map<TypeLiteral<?>, DynamicSet<?>> m = Maps.newHashMap();
for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
TypeLiteral<?> type = e.getKey().getTypeLiteral();
if (type.getRawType() == DynamicSet.class) {
ParameterizedType p = (ParameterizedType) type.getType();
m.put(TypeLiteral.get(p.getActualTypeArguments()[0]),
(DynamicSet<?>) e.getValue().getProvider().get());
}
}
return m;
}
private static Map<TypeLiteral<?>, DynamicMap<?>> dynamicMapsOf(Injector src) {
Map<TypeLiteral<?>, DynamicMap<?>> m = Maps.newHashMap();
for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
TypeLiteral<?> type = e.getKey().getTypeLiteral();
if (type.getRawType() == DynamicMap.class) {
ParameterizedType p = (ParameterizedType) type.getType();
m.put(TypeLiteral.get(p.getActualTypeArguments()[0]),
(DynamicMap<?>) e.getValue().getProvider().get());
}
}
return m;
}
private static Module copy(Injector src) {
Set<TypeLiteral<?>> dynamicTypes = Sets.newHashSet();
for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
TypeLiteral<?> type = e.getKey().getTypeLiteral();
if (type.getRawType() == DynamicSet.class
|| type.getRawType() == DynamicMap.class) {
ParameterizedType t = (ParameterizedType) type.getType();
dynamicTypes.add(TypeLiteral.get(t.getActualTypeArguments()[0]));
}
}
final Map<Key<?>, Binding<?>> bindings = Maps.newLinkedHashMap();
for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
if (shouldCopy(e.getKey())) {
if (!dynamicTypes.contains(e.getKey().getTypeLiteral())
&& shouldCopy(e.getKey())) {
bindings.put(e.getKey(), e.getValue());
}
}