summaryrefslogtreecommitdiffstats
path: root/gerrit-extension-api/src
diff options
context:
space:
mode:
authorShawn O. Pearce <sop@google.com>2012-05-10 15:21:05 -0700
committerShawn O. Pearce <sop@google.com>2012-05-10 16:05:46 -0700
commit61090db9e5877e152058c08c969fc05b31324847 (patch)
tree89a76d8d3dfcae47295bcc55a2c8d6257ec4e7c1 /gerrit-extension-api/src
parentfac27716d8e1883c78c2c7313c31f9393c25a1a7 (diff)
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
Diffstat (limited to 'gerrit-extension-api/src')
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/ExtensionPoint.java41
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Listen.java39
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java155
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java46
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java231
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java56
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java96
-rw-r--r--gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java23
8 files changed, 687 insertions, 0 deletions
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/ExtensionPoint.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/ExtensionPoint.java
new file mode 100644
index 0000000000..4799f5e1ff
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/ExtensionPoint.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.extensions.annotations;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for interfaces that accept auto-registered implementations.
+ * <p>
+ * Interfaces that accept automatically registered implementations into their
+ * {@link DynamicSet} must be tagged with this annotation.
+ * <p>
+ * Plugins or extensions that implement an {@code @ExtensionPoint} interface
+ * should use the {@link Listen} annotation to automatically register.
+ *
+ * @see Listen
+ */
+@Target({ElementType.TYPE})
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface ExtensionPoint {
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Listen.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Listen.java
new file mode 100644
index 0000000000..e4ba9316c4
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Listen.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.extensions.annotations;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for auto-registered extension point implementations.
+ * <p>
+ * Plugins or extensions using auto-registration should apply this annotation to
+ * any non-abstract class that implements an unnamed extension point, such as a
+ * notification listener. Gerrit will automatically determine which extension
+ * points to apply based on the interfaces the type implements.
+ *
+ * @see Export
+ */
+@Target({ElementType.TYPE})
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface Listen {
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
new file mode 100644
index 0000000000..f114afd98d
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
@@ -0,0 +1,155 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.extensions.registration;
+
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Scopes;
+import com.google.inject.TypeLiteral;
+import com.google.inject.util.Types;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A map of members that can be modified as plugins reload.
+ * <p>
+ * Maps index their members by plugin name and export name.
+ * <p>
+ * DynamicMaps are always mapped as singletons in Guice, and only may contain
+ * singletons, as providers are resolved to an instance before the member is
+ * added to the map.
+ */
+public abstract class DynamicMap<T> {
+ /**
+ * Declare a singleton {@code DynamicMap<T>} with a binder.
+ * <p>
+ * Maps must be defined in a Guice module before they can be bound:
+ *
+ * <pre>
+ * DynamicMap.mapOf(binder(), Interface.class);
+ * bind(Interface.class)
+ * .annotatedWith(Exports.named(&quot;foo&quot;))
+ * .to(Impl.class);
+ * </pre>
+ *
+ * @param binder a new binder created in the module.
+ * @param member type of value in the map.
+ */
+ public static <T> void mapOf(Binder binder, Class<T> member) {
+ mapOf(binder, TypeLiteral.get(member));
+ }
+
+ /**
+ * Declare a singleton {@code DynamicMap<T>} with a binder.
+ * <p>
+ * Maps must be defined in a Guice module before they can be bound:
+ *
+ * <pre>
+ * DynamicMap.mapOf(binder(), new TypeLiteral<Thing<Bar>>(){});
+ * bind(new TypeLiteral<Thing<Bar>>() {})
+ * .annotatedWith(Exports.named(&quot;foo&quot;))
+ * .to(Impl.class);
+ * </pre>
+ *
+ * @param binder a new binder created in the module.
+ * @param member type of value in the map.
+ */
+ public static <T> void mapOf(Binder binder, TypeLiteral<T> member) {
+ @SuppressWarnings("unchecked")
+ Key<DynamicMap<T>> key = (Key<DynamicMap<T>>) Key.get(
+ Types.newParameterizedType(DynamicMap.class, member.getType()));
+ binder.bind(key)
+ .toProvider(new DynamicMapProvider<T>(member))
+ .in(Scopes.SINGLETON);
+ }
+
+ final ConcurrentMap<NamePair, T> items;
+
+ DynamicMap() {
+ items = new ConcurrentHashMap<NamePair, T>(16, 0.75f, 1);
+ }
+
+ /**
+ * Lookup an implementation by name.
+ *
+ * @param pluginName local name of the plugin providing the item.
+ * @param exportName name the plugin exports the item as.
+ * @return the implementation. Null if the plugin is not running, or if the
+ * plugin does not export this name.
+ */
+ public T get(String pluginName, String exportName) {
+ return items.get(new NamePair(pluginName, exportName));
+ }
+
+ /**
+ * Get the names of all running plugins supplying this type.
+ *
+ * @return sorted set of active plugins that supply at least one item.
+ */
+ public SortedSet<String> plugins() {
+ SortedSet<String> r = new TreeSet<String>();
+ for (NamePair p : items.keySet()) {
+ r.add(p.pluginName);
+ }
+ return Collections.unmodifiableSortedSet(r);
+ }
+
+ /**
+ * Get the items exported by a single plugin.
+ *
+ * @param pluginName name of the plugin.
+ * @return items exported by a plugin, keyed by the export name.
+ */
+ public SortedMap<String, T> byPlugin(String pluginName) {
+ SortedMap<String, T> r = new TreeMap<String, T>();
+ for (Map.Entry<NamePair, T> e : items.entrySet()) {
+ if (e.getKey().pluginName.equals(pluginName)) {
+ r.put(e.getKey().exportName, e.getValue());
+ }
+ }
+ return Collections.unmodifiableSortedMap(r);
+ }
+
+ static class NamePair {
+ private final String pluginName;
+ private final String exportName;
+
+ NamePair(String pn, String en) {
+ this.pluginName = pn;
+ this.exportName = en;
+ }
+
+ @Override
+ public int hashCode() {
+ return pluginName.hashCode() * 31 + exportName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof NamePair) {
+ NamePair np = (NamePair) other;
+ return pluginName.equals(np) && exportName.equals(np);
+ }
+ return false;
+ }
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java
new file mode 100644
index 0000000000..d771d135e9
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.extensions.registration;
+
+import com.google.inject.Binding;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+
+import java.util.List;
+
+class DynamicMapProvider<T> implements Provider<DynamicMap<T>> {
+ private final TypeLiteral<T> type;
+
+ @Inject
+ private Injector injector;
+
+ DynamicMapProvider(TypeLiteral<T> type) {
+ this.type = type;
+ }
+
+ public DynamicMap<T> get() {
+ PrivateInternals_DynamicMapImpl<T> m =
+ new PrivateInternals_DynamicMapImpl<T>();
+ List<Binding<T>> bindings = injector.findBindingsByType(type);
+ if (bindings != null) {
+ for (Binding<T> b : bindings) {
+ m.put("gerrit", b.getKey(), b.getProvider().get());
+ }
+ }
+ return m;
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
new file mode 100644
index 0000000000..7f46ad42cc
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
@@ -0,0 +1,231 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.extensions.registration;
+
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Scopes;
+import com.google.inject.TypeLiteral;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.internal.UniqueAnnotations;
+import com.google.inject.name.Named;
+import com.google.inject.util.Types;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A set of members that can be modified as plugins reload.
+ * <p>
+ * DynamicSets are always mapped as singletons in Guice, and only may contain
+ * singletons, as providers are resolved to an instance before the member is
+ * added to the set.
+ */
+public class DynamicSet<T> implements Iterable<T> {
+ /**
+ * Declare a singleton {@code DynamicSet<T>} with a binder.
+ * <p>
+ * Sets must be defined in a Guice module before they can be bound:
+ * <pre>
+ * DynamicSet.setOf(binder(), Interface.class);
+ * DynamicSet.bind(binder(), Interface.class).to(Impl.class);
+ * </pre>
+ *
+ * @param binder a new binder created in the module.
+ * @param member type of entry in the set.
+ */
+ public static <T> void setOf(Binder binder, Class<T> member) {
+ setOf(binder, TypeLiteral.get(member));
+ }
+
+ /**
+ * Declare a singleton {@code DynamicSet<T>} with a binder.
+ * <p>
+ * Sets must be defined in a Guice module before they can be bound:
+ * <pre>
+ * DynamicSet.setOf(binder(), new TypeLiteral<Thing<Foo>>() {});
+ * </pre>
+ *
+ * @param binder a new binder created in the module.
+ * @param member type of entry in the set.
+ */
+ public static <T> void setOf(Binder binder, TypeLiteral<T> member) {
+ @SuppressWarnings("unchecked")
+ Key<DynamicSet<T>> key = (Key<DynamicSet<T>>) Key.get(
+ Types.newParameterizedType(DynamicSet.class, member.getType()));
+ binder.bind(key)
+ .toProvider(new DynamicSetProvider<T>(member))
+ .in(Scopes.SINGLETON);
+ }
+
+ /**
+ * Bind one implementation into the set using a unique annotation.
+ *
+ * @param binder a new binder created in the module.
+ * @param type type of entries in the set.
+ * @return a binder to continue configuring the new set member.
+ */
+ public static <T> LinkedBindingBuilder<T> bind(Binder binder, Class<T> type) {
+ return bind(binder, TypeLiteral.get(type));
+ }
+
+ /**
+ * Bind one implementation into the set using a unique annotation.
+ *
+ * @param binder a new binder created in the module.
+ * @param type type of entries in the set.
+ * @return a binder to continue configuring the new set member.
+ */
+ public static <T> LinkedBindingBuilder<T> bind(Binder binder, TypeLiteral<T> type) {
+ return binder.bind(type).annotatedWith(UniqueAnnotations.create());
+ }
+
+ /**
+ * Bind a named implementation into the set.
+ *
+ * @param binder a new binder created in the module.
+ * @param type type of entries in the set.
+ * @param name {@code @Named} annotation to apply instead of a unique
+ * annotation.
+ * @return a binder to continue configuring the new set member.
+ */
+ public static <T> LinkedBindingBuilder<T> bind(Binder binder,
+ Class<T> type,
+ Named name) {
+ return bind(binder, TypeLiteral.get(type));
+ }
+
+ /**
+ * Bind a named implementation into the set.
+ *
+ * @param binder a new binder created in the module.
+ * @param type type of entries in the set.
+ * @param name {@code @Named} annotation to apply instead of a unique
+ * annotation.
+ * @return a binder to continue configuring the new set member.
+ */
+ public static <T> LinkedBindingBuilder<T> bind(Binder binder,
+ TypeLiteral<T> type,
+ Named name) {
+ return binder.bind(type).annotatedWith(name);
+ }
+
+ private final CopyOnWriteArrayList<AtomicReference<T>> items;
+
+ DynamicSet(Collection<AtomicReference<T>> base) {
+ items = new CopyOnWriteArrayList<AtomicReference<T>>(base);
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ final Iterator<AtomicReference<T>> itr = items.iterator();
+ return new Iterator<T>() {
+ private T next;
+
+ @Override
+ public boolean hasNext() {
+ while (next == null && itr.hasNext()) {
+ next = itr.next().get();
+ }
+ return next != null;
+ }
+
+ @Override
+ public T next() {
+ if (hasNext()) {
+ T result = next;
+ next = null;
+ return result;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /**
+ * Add one new element to the set.
+ *
+ * @param item the item to add to the collection. Must not be null.
+ * @return handle to remove the item at a later point in time.
+ */
+ public RegistrationHandle add(final T item) {
+ final AtomicReference<T> ref = new AtomicReference<T>(item);
+ items.add(ref);
+ return new RegistrationHandle() {
+ @Override
+ public void remove() {
+ if (ref.compareAndSet(item, null)) {
+ items.remove(ref);
+ }
+ }
+ };
+ }
+
+ /**
+ * Add one new element that may be hot-replaceable in the future.
+ *
+ * @param key unique description from the item's Guice binding. This can be
+ * later obtained from the registration handle to facilitate matching
+ * with the new equivalent instance during a hot reload.
+ * @param item the item to add to the collection right now. Must not be null.
+ * @return a handle that can remove this item later, or hot-swap the item
+ * without it ever leaving the collection.
+ */
+ public ReloadableRegistrationHandle<T> add(Key<T> key, T item) {
+ AtomicReference<T> ref = new AtomicReference<T>(item);
+ items.add(ref);
+ return new ReloadableHandle(ref, key, item);
+ }
+
+ private class ReloadableHandle implements ReloadableRegistrationHandle<T> {
+ private final AtomicReference<T> ref;
+ private final Key<T> key;
+ private final T item;
+
+ ReloadableHandle(AtomicReference<T> ref, Key<T> key, T item) {
+ this.ref = ref;
+ this.key = key;
+ this.item = item;
+ }
+
+ @Override
+ public void remove() {
+ if (ref.compareAndSet(item, null)) {
+ items.remove(ref);
+ }
+ }
+
+ @Override
+ public Key<T> getKey() {
+ return key;
+ }
+
+ @Override
+ public ReloadableHandle replace(Key<T> newKey, T newItem) {
+ if (ref.compareAndSet(item, newItem)) {
+ return new ReloadableHandle(ref, newKey, newItem);
+ }
+ return null;
+ }
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
new file mode 100644
index 0000000000..694fbd8e66
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.extensions.registration;
+
+import com.google.inject.Binding;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+class DynamicSetProvider<T> implements Provider<DynamicSet<T>> {
+ private final TypeLiteral<T> type;
+
+ @Inject
+ private Injector injector;
+
+ DynamicSetProvider(TypeLiteral<T> type) {
+ this.type = type;
+ }
+
+ public DynamicSet<T> get() {
+ return new DynamicSet<T>(find(injector, type));
+ }
+
+ private static <T> List<AtomicReference<T>> find(
+ Injector src,
+ TypeLiteral<T> type) {
+ List<Binding<T>> bindings = src.findBindingsByType(type);
+ int cnt = bindings != null ? bindings.size() : 0;
+ if (cnt == 0) {
+ return Collections.emptyList();
+ }
+ List<AtomicReference<T>> r = new ArrayList<AtomicReference<T>>(cnt);
+ for (Binding<T> b : bindings) {
+ r.add(new AtomicReference<T>(b.getProvider().get()));
+ }
+ return r;
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
new file mode 100644
index 0000000000..0ce4014535
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
@@ -0,0 +1,96 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.extensions.registration;
+
+import com.google.gerrit.extensions.annotations.Export;
+import com.google.inject.Key;
+
+/** <b>DO NOT USE</b> */
+public class PrivateInternals_DynamicMapImpl<T> extends DynamicMap<T> {
+ PrivateInternals_DynamicMapImpl() {
+ }
+
+ /**
+ * Store one new element into the map.
+ *
+ * @param pluginName unique name of the plugin providing the export.
+ * @param exportName name the plugin has exported the item as.
+ * @param item the item to add to the collection. Must not be null.
+ * @return handle to remove the item at a later point in time.
+ */
+ public RegistrationHandle put(
+ String pluginName, String exportName,
+ final T item) {
+ final NamePair key = new NamePair(pluginName, exportName);
+ items.put(key, item);
+ return new RegistrationHandle() {
+ @Override
+ public void remove() {
+ items.remove(key, item);
+ }
+ };
+ }
+
+ /**
+ * Store one new element that may be hot-replaceable in the future.
+ *
+ * @param pluginName unique name of the plugin providing the export.
+ * @param key unique description from the item's Guice binding. This can be
+ * later obtained from the registration handle to facilitate matching
+ * with the new equivalent instance during a hot reload. The key must
+ * use an {@link @Export} annotation.
+ * @param item the item to add to the collection right now. Must not be null.
+ * @return a handle that can remove this item later, or hot-swap the item
+ * without it ever leaving the collection.
+ */
+ public ReloadableRegistrationHandle<T> put(
+ String pluginName, Key<T> key,
+ T item) {
+ String exportName = ((Export) key.getAnnotation()).value();
+ NamePair np = new NamePair(pluginName, exportName);
+ items.put(np, item);
+ return new ReloadableHandle(np, key, item);
+ }
+
+ private class ReloadableHandle implements ReloadableRegistrationHandle<T> {
+ private final NamePair np;
+ private final Key<T> key;
+ private final T item;
+
+ ReloadableHandle(NamePair np, Key<T> key, T item) {
+ this.np = np;
+ this.key = key;
+ this.item = item;
+ }
+
+ @Override
+ public void remove() {
+ items.remove(np, item);
+ }
+
+ @Override
+ public Key<T> getKey() {
+ return key;
+ }
+
+ @Override
+ public ReloadableHandle replace(Key<T> newKey, T newItem) {
+ if (items.replace(np, item, newItem)) {
+ return new ReloadableHandle(np, newKey, newItem);
+ }
+ return null;
+ }
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java
new file mode 100644
index 0000000000..b7d78c9f15
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.extensions.registration;
+
+import com.google.inject.Key;
+
+public interface ReloadableRegistrationHandle<T> extends RegistrationHandle {
+ public Key<T> getKey();
+
+ public RegistrationHandle replace(Key<T> key, T item);
+}