Have common class for entries in DynamicMap/DynamicSet/DynamicItem

At the moment we have several classes to represent plugin extensions:
DynamicMap.Entry, DynamicSet.Entry, NamedProvider

They are all very similar and can be merged into one class. This has the
advantage that we are now able to offer methods that deal with plugin
extensions regardless from which source they come.

Change-Id: I4293795800771ef06eae6ec6d64be4b0a8fd72f4
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/java/com/google/gerrit/extensions/registration/DynamicItem.java b/java/com/google/gerrit/extensions/registration/DynamicItem.java
index 4f36ab4..67982d9 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicItem.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicItem.java
@@ -111,17 +111,22 @@
   }
 
   private final Key<DynamicItem<T>> key;
-  private final AtomicReference<NamedProvider<T>> ref;
+  private final AtomicReference<Extension<T>> ref;
 
   DynamicItem(Key<DynamicItem<T>> key, Provider<T> provider, String pluginName) {
-    NamedProvider<T> in = null;
+    Extension<T> in = null;
     if (provider != null) {
-      in = new NamedProvider<>(provider, pluginName);
+      in = new Extension<>(pluginName, provider);
     }
     this.key = key;
     this.ref = new AtomicReference<>(in);
   }
 
+  @Nullable
+  public Extension<T> getEntry() {
+    return ref.get();
+  }
+
   /**
    * Get the configured item, or null.
    *
@@ -130,8 +135,8 @@
    */
   @Nullable
   public T get() {
-    NamedProvider<T> item = ref.get();
-    return item != null ? item.impl.get() : null;
+    Extension<T> item = ref.get();
+    return item != null ? item.get() : null;
   }
 
   /**
@@ -143,8 +148,8 @@
    */
   @Nullable
   public String getPluginName() {
-    NamedProvider<T> item = ref.get();
-    return item != null ? item.pluginName : null;
+    Extension<T> item = ref.get();
+    return item != null ? item.getPluginName() : null;
   }
 
   /**
@@ -166,19 +171,19 @@
    * @return handle to remove the item at a later point in time.
    */
   public RegistrationHandle set(Provider<T> impl, String pluginName) {
-    final NamedProvider<T> item = new NamedProvider<>(impl, pluginName);
-    NamedProvider<T> old = null;
+    final Extension<T> item = new Extension<>(pluginName, impl);
+    Extension<T> old = null;
     while (!ref.compareAndSet(old, item)) {
       old = ref.get();
-      if (old != null && !PluginName.GERRIT.equals(old.pluginName)) {
+      if (old != null && !PluginName.GERRIT.equals(old.getPluginName())) {
         throw new ProvisionException(
             String.format(
                 "%s already provided by %s, ignoring plugin %s",
-                key.getTypeLiteral(), old.pluginName, pluginName));
+                key.getTypeLiteral(), old.getPluginName(), pluginName));
       }
     }
 
-    final NamedProvider<T> defaultItem = old;
+    final Extension<T> defaultItem = old;
     return new RegistrationHandle() {
       @Override
       public void remove() {
@@ -198,13 +203,13 @@
    * @return a handle that can remove this item later, or hot-swap the item.
    */
   public ReloadableRegistrationHandle<T> set(Key<T> key, Provider<T> impl, String pluginName) {
-    final NamedProvider<T> item = new NamedProvider<>(impl, pluginName);
-    NamedProvider<T> old = null;
+    final Extension<T> item = new Extension<>(pluginName, impl);
+    Extension<T> old = null;
     while (!ref.compareAndSet(old, item)) {
       old = ref.get();
       if (old != null
-          && !PluginName.GERRIT.equals(old.pluginName)
-          && !pluginName.equals(old.pluginName)) {
+          && !PluginName.GERRIT.equals(old.getPluginName())
+          && !pluginName.equals(old.getPluginName())) {
         // We allow to replace:
         // 1. Gerrit core items, e.g. websession cache
         //    can be replaced by plugin implementation
@@ -212,7 +217,7 @@
         throw new ProvisionException(
             String.format(
                 "%s already provided by %s, ignoring plugin %s",
-                this.key.getTypeLiteral(), old.pluginName, pluginName));
+                this.key.getTypeLiteral(), old.getPluginName(), pluginName));
       }
     }
     return new ReloadableHandle(key, item, old);
@@ -220,10 +225,10 @@
 
   private class ReloadableHandle implements ReloadableRegistrationHandle<T> {
     private final Key<T> handleKey;
-    private final NamedProvider<T> item;
-    private final NamedProvider<T> defaultItem;
+    private final Extension<T> item;
+    private final Extension<T> defaultItem;
 
-    ReloadableHandle(Key<T> handleKey, NamedProvider<T> item, NamedProvider<T> defaultItem) {
+    ReloadableHandle(Key<T> handleKey, Extension<T> item, Extension<T> defaultItem) {
       this.handleKey = handleKey;
       this.item = item;
       this.defaultItem = defaultItem;
@@ -242,7 +247,7 @@
     @Override
     @Nullable
     public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) {
-      NamedProvider<T> n = new NamedProvider<>(newItem, item.pluginName);
+      Extension<T> n = new Extension<>(item.getPluginName(), newItem);
       if (ref.compareAndSet(item, n)) {
         return new ReloadableHandle(newKey, n, defaultItem);
       }
diff --git a/java/com/google/gerrit/extensions/registration/DynamicMap.java b/java/com/google/gerrit/extensions/registration/DynamicMap.java
index 96d19b2..48b1279 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicMap.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicMap.java
@@ -40,29 +40,7 @@
  * resolve the provider to an instance on demand. This enables registrations to decide between
  * singleton and non-singleton members.
  */
-public abstract class DynamicMap<T> implements Iterable<DynamicMap.Entry<T>> {
-  public static class Entry<T> {
-    private final NamePair namePair;
-    private final Provider<T> provider;
-
-    private Entry(NamePair namePair, Provider<T> provider) {
-      this.namePair = namePair;
-      this.provider = provider;
-    }
-
-    public String getPluginName() {
-      return namePair.pluginName;
-    }
-
-    public String getExportName() {
-      return namePair.exportName;
-    }
-
-    public Provider<T> getProvider() {
-      return provider;
-    }
-  }
-
+public abstract class DynamicMap<T> implements Iterable<Extension<T>> {
   /**
    * Declare a singleton {@code DynamicMap<T>} with a binder.
    *
@@ -166,18 +144,18 @@
 
   /** Iterate through all entries in an undefined order. */
   @Override
-  public Iterator<Entry<T>> iterator() {
+  public Iterator<Extension<T>> iterator() {
     final Iterator<Map.Entry<NamePair, Provider<T>>> i = items.entrySet().iterator();
-    return new Iterator<Entry<T>>() {
+    return new Iterator<Extension<T>>() {
       @Override
       public boolean hasNext() {
         return i.hasNext();
       }
 
       @Override
-      public Entry<T> next() {
+      public Extension<T> next() {
         Map.Entry<NamePair, Provider<T>> e = i.next();
-        return new Entry<>(e.getKey(), e.getValue());
+        return new Extension<>(e.getKey().pluginName, e.getKey().exportName, e.getValue());
       }
 
       @Override
diff --git a/java/com/google/gerrit/extensions/registration/DynamicSet.java b/java/com/google/gerrit/extensions/registration/DynamicSet.java
index 6b3a49b..dcd0d8f 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicSet.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicSet.java
@@ -45,24 +45,6 @@
  * singleton and non-singleton members.
  */
 public class DynamicSet<T> implements Iterable<T> {
-  public static class Entry<T> {
-    private final String pluginName;
-    private final Provider<T> provider;
-
-    private Entry(String pluginName, Provider<T> provider) {
-      this.pluginName = pluginName;
-      this.provider = provider;
-    }
-
-    public String getPluginName() {
-      return pluginName;
-    }
-
-    public Provider<T> getProvider() {
-      return provider;
-    }
-  }
-
   /**
    * Declare a singleton {@code DynamicSet<T>} with a binder.
    *
@@ -153,12 +135,12 @@
   }
 
   public static <T> DynamicSet<T> emptySet() {
-    return new DynamicSet<>(Collections.<AtomicReference<NamedProvider<T>>>emptySet());
+    return new DynamicSet<>(Collections.<AtomicReference<Extension<T>>>emptySet());
   }
 
-  private final CopyOnWriteArrayList<AtomicReference<NamedProvider<T>>> items;
+  private final CopyOnWriteArrayList<AtomicReference<Extension<T>>> items;
 
-  DynamicSet(Collection<AtomicReference<NamedProvider<T>>> base) {
+  DynamicSet(Collection<AtomicReference<Extension<T>>> base) {
     items = new CopyOnWriteArrayList<>(base);
   }
 
@@ -168,7 +150,7 @@
 
   @Override
   public Iterator<T> iterator() {
-    Iterator<Entry<T>> entryIterator = entries().iterator();
+    Iterator<Extension<T>> entryIterator = entries().iterator();
     return new Iterator<T>() {
       @Override
       public boolean hasNext() {
@@ -177,39 +159,35 @@
 
       @Override
       public T next() {
-        Entry<T> next = entryIterator.next();
+        Extension<T> next = entryIterator.next();
         return next != null ? next.getProvider().get() : null;
       }
     };
   }
 
-  public Iterable<Entry<T>> entries() {
-    final Iterator<AtomicReference<NamedProvider<T>>> itr = items.iterator();
-    return new Iterable<Entry<T>>() {
+  public Iterable<Extension<T>> entries() {
+    final Iterator<AtomicReference<Extension<T>>> itr = items.iterator();
+    return new Iterable<Extension<T>>() {
       @Override
-      public Iterator<Entry<T>> iterator() {
-        return new Iterator<Entry<T>>() {
-          private Entry<T> next;
+      public Iterator<Extension<T>> iterator() {
+        return new Iterator<Extension<T>>() {
+          private Extension<T> next;
 
           @Override
           public boolean hasNext() {
             while (next == null && itr.hasNext()) {
-              NamedProvider<T> p = itr.next().get();
+              Extension<T> p = itr.next().get();
               if (p != null) {
-                try {
-                  next = new Entry<>(p.pluginName, p.impl);
-                } catch (RuntimeException e) {
-                  // TODO Log failed member of DynamicSet.
-                }
+                next = p;
               }
             }
             return next != null;
           }
 
           @Override
-          public Entry<T> next() {
+          public Extension<T> next() {
             if (hasNext()) {
-              Entry<T> result = next;
+              Extension<T> result = next;
               next = null;
               return result;
             }
@@ -250,7 +228,7 @@
   public ImmutableSortedSet<String> plugins() {
     return items
         .stream()
-        .map(i -> i.get().pluginName)
+        .map(i -> i.get().getPluginName())
         .collect(toImmutableSortedSet(naturalOrder()));
   }
 
@@ -263,8 +241,8 @@
   public ImmutableSet<Provider<T>> byPlugin(String pluginName) {
     return items
         .stream()
-        .filter(i -> i.get().pluginName.equals(pluginName))
-        .map(i -> i.get().impl)
+        .filter(i -> i.get().getPluginName().equals(pluginName))
+        .map(i -> i.get().getProvider())
         .collect(toImmutableSet());
   }
 
@@ -285,8 +263,8 @@
    * @return handle to remove the item at a later point in time.
    */
   public RegistrationHandle add(String pluginName, Provider<T> item) {
-    final AtomicReference<NamedProvider<T>> ref =
-        new AtomicReference<>(new NamedProvider<>(item, pluginName));
+    final AtomicReference<Extension<T>> ref =
+        new AtomicReference<>(new Extension<>(pluginName, item));
     items.add(ref);
     return new RegistrationHandle() {
       @Override
@@ -310,18 +288,17 @@
    *     the collection.
    */
   public ReloadableRegistrationHandle<T> add(String pluginName, Key<T> key, Provider<T> item) {
-    AtomicReference<NamedProvider<T>> ref =
-        new AtomicReference<>(new NamedProvider<>(item, pluginName));
+    AtomicReference<Extension<T>> ref = new AtomicReference<>(new Extension<>(pluginName, item));
     items.add(ref);
     return new ReloadableHandle(ref, key, ref.get());
   }
 
   private class ReloadableHandle implements ReloadableRegistrationHandle<T> {
-    private final AtomicReference<NamedProvider<T>> ref;
+    private final AtomicReference<Extension<T>> ref;
     private final Key<T> key;
-    private final NamedProvider<T> item;
+    private final Extension<T> item;
 
-    ReloadableHandle(AtomicReference<NamedProvider<T>> ref, Key<T> key, NamedProvider<T> item) {
+    ReloadableHandle(AtomicReference<Extension<T>> ref, Key<T> key, Extension<T> item) {
       this.ref = ref;
       this.key = key;
       this.item = item;
@@ -341,7 +318,7 @@
 
     @Override
     public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) {
-      NamedProvider<T> n = new NamedProvider<>(newItem, item.pluginName);
+      Extension<T> n = new Extension<>(item.getPluginName(), newItem);
       if (ref.compareAndSet(item, n)) {
         return new ReloadableHandle(ref, newKey, n);
       }
diff --git a/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java b/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
index 6d36f54..832933b 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java
@@ -38,17 +38,16 @@
     return new DynamicSet<>(find(injector, type));
   }
 
-  private static <T> List<AtomicReference<NamedProvider<T>>> find(
-      Injector src, TypeLiteral<T> type) {
+  private static <T> List<AtomicReference<Extension<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<NamedProvider<T>>> r = new ArrayList<>(cnt);
+    List<AtomicReference<Extension<T>>> r = new ArrayList<>(cnt);
     for (Binding<T> b : bindings) {
       if (b.getKey().getAnnotation() != null) {
-        r.add(new AtomicReference<>(new NamedProvider<>(b.getProvider(), PluginName.GERRIT)));
+        r.add(new AtomicReference<>(new Extension<>(PluginName.GERRIT, b.getProvider())));
       }
     }
     return r;
diff --git a/java/com/google/gerrit/extensions/registration/Extension.java b/java/com/google/gerrit/extensions/registration/Extension.java
new file mode 100644
index 0000000..aaec201
--- /dev/null
+++ b/java/com/google/gerrit/extensions/registration/Extension.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2018 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.common.Nullable;
+import com.google.inject.Provider;
+
+/**
+ * An extension that is provided by a plugin.
+ *
+ * <p>Contains the name of the plugin that provides the extension, the extension point
+ * implementation and optionally the export name under which the extension was exported.
+ *
+ * <p>An export name is only available if this extension is an entry in a {@link DynamicMap}.
+ *
+ * @param <T> Type of extension point that this extension implements
+ */
+public class Extension<T> {
+  private final String pluginName;
+  private final @Nullable String exportName;
+  private final Provider<T> provider;
+
+  protected Extension(String pluginName, Provider<T> provider) {
+    this(pluginName, null, provider);
+  }
+
+  protected Extension(String pluginName, @Nullable String exportName, Provider<T> provider) {
+    this.pluginName = pluginName;
+    this.exportName = exportName;
+    this.provider = provider;
+  }
+
+  public String getPluginName() {
+    return pluginName;
+  }
+
+  @Nullable
+  public String getExportName() {
+    return exportName;
+  }
+
+  public Provider<T> getProvider() {
+    return provider;
+  }
+
+  public T get() {
+    return provider.get();
+  }
+}
diff --git a/java/com/google/gerrit/extensions/registration/NamedProvider.java b/java/com/google/gerrit/extensions/registration/NamedProvider.java
deleted file mode 100644
index aca651b..0000000
--- a/java/com/google/gerrit/extensions/registration/NamedProvider.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2018 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.Provider;
-
-/** Pair of provider implementation and plugin providing it. */
-class NamedProvider<T> {
-  final Provider<T> impl;
-  final String pluginName;
-
-  NamedProvider(Provider<T> provider, String pluginName) {
-    this.impl = provider;
-    this.pluginName = pluginName;
-  }
-}
diff --git a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
index 5e234d2e..30ebe6e 100644
--- a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
@@ -25,7 +25,7 @@
 import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.DynamicMap.Entry;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.AccessPath;
 import com.google.gerrit.server.account.AccountCache;
@@ -191,7 +191,7 @@
    */
   private void pickOnlyProvider() throws ServletException {
     try {
-      Entry<OAuthLoginProvider> loginProvider = Iterables.getOnlyElement(loginProviders);
+      Extension<OAuthLoginProvider> loginProvider = Iterables.getOnlyElement(loginProviders);
       defaultAuthPlugin = loginProvider.getPluginName();
       defaultAuthProvider = loginProvider.getExportName();
     } catch (NoSuchElementException e) {
diff --git a/java/com/google/gerrit/server/cache/CacheMetrics.java b/java/com/google/gerrit/server/cache/CacheMetrics.java
index 3435652..c652d50 100644
--- a/java/com/google/gerrit/server/cache/CacheMetrics.java
+++ b/java/com/google/gerrit/server/cache/CacheMetrics.java
@@ -18,6 +18,7 @@
 import com.google.common.cache.CacheStats;
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.registration.PluginName;
 import com.google.gerrit.metrics.CallbackMetric;
 import com.google.gerrit.metrics.CallbackMetric1;
@@ -71,7 +72,7 @@
     metrics.newTrigger(
         cacheMetrics,
         () -> {
-          for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
+          for (Extension<Cache<?, ?>> e : cacheMap) {
             Cache<?, ?> c = e.getProvider().get();
             String name = metricNameOf(e);
             CacheStats cstats = c.stats();
@@ -95,7 +96,7 @@
     return ((double) d.hitCount() / d.requestCount() * 100);
   }
 
-  private static String metricNameOf(DynamicMap.Entry<Cache<?, ?>> e) {
+  private static String metricNameOf(Extension<Cache<?, ?>> e) {
     if (PluginName.GERRIT.equals(e.getPluginName())) {
       return e.getExportName();
     }
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 43f7b2f..e1a0aa79 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -88,6 +88,7 @@
 import com.google.gerrit.extensions.config.DownloadCommand;
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.index.query.QueryResult;
@@ -1471,7 +1472,7 @@
   private Map<String, FetchInfo> makeFetchMap(ChangeData cd, PatchSet in)
       throws PermissionBackendException, OrmException, IOException {
     Map<String, FetchInfo> r = new LinkedHashMap<>();
-    for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
+    for (Extension<DownloadScheme> e : downloadSchemes) {
       String schemeName = e.getExportName();
       DownloadScheme scheme = e.getProvider().get();
       if (!scheme.isEnabled()
@@ -1502,7 +1503,7 @@
       String projectName,
       String refName,
       FetchInfo fetchInfo) {
-    for (DynamicMap.Entry<DownloadCommand> e2 : commands) {
+    for (Extension<DownloadCommand> e2 : commands) {
       String commandName = e2.getExportName();
       DownloadCommand command = e2.getProvider().get();
       String c = command.getCommand(scheme, projectName, refName);
diff --git a/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/java/com/google/gerrit/server/config/ProjectConfigEntry.java
index d30e080..5515f0e 100644
--- a/java/com/google/gerrit/server/config/ProjectConfigEntry.java
+++ b/java/com/google/gerrit/server/config/ProjectConfigEntry.java
@@ -22,7 +22,7 @@
 import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.DynamicMap.Entry;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -321,7 +321,7 @@
         ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
         ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
         if (oldCfg != null && newCfg != null) {
-          for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
+          for (Extension<ProjectConfigEntry> e : pluginConfigEntries) {
             ProjectConfigEntry configEntry = e.getProvider().get();
             String newValue = getValue(newCfg, e);
             String oldValue = getValue(oldCfg, e);
@@ -367,7 +367,7 @@
       }
     }
 
-    private static String getValue(ProjectConfig cfg, Entry<ProjectConfigEntry> e) {
+    private static String getValue(ProjectConfig cfg, Extension<ProjectConfigEntry> e) {
       String value = cfg.getPluginConfig(e.getPluginName()).getString(e.getExportName());
       if (value == null) {
         value = e.getProvider().get().getDefaultValue();
diff --git a/java/com/google/gerrit/server/edit/ChangeEditJson.java b/java/com/google/gerrit/server/edit/ChangeEditJson.java
index 78baef7..bd9c3a6 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditJson.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditJson.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.config.DownloadCommand;
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.server.CommonConverters;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.change.ChangeJson;
@@ -78,7 +79,7 @@
 
   private Map<String, FetchInfo> fillFetchMap(ChangeEdit edit) {
     Map<String, FetchInfo> r = new LinkedHashMap<>();
-    for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
+    for (Extension<DownloadScheme> e : downloadSchemes) {
       String schemeName = e.getExportName();
       DownloadScheme scheme = e.getProvider().get();
       if (!scheme.isEnabled()
diff --git a/java/com/google/gerrit/server/extensions/webui/UiActions.java b/java/com/google/gerrit/server/extensions/webui/UiActions.java
index af28bed3..3ca2bdb 100644
--- a/java/com/google/gerrit/server/extensions/webui/UiActions.java
+++ b/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
 import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.registration.PluginName;
 import com.google.gerrit.extensions.restapi.RestCollection;
 import com.google.gerrit.extensions.restapi.RestResource;
@@ -121,7 +122,7 @@
 
   @Nullable
   private <R extends RestResource> UiAction.Description describe(
-      DynamicMap.Entry<RestView<R>> e, R resource) {
+      Extension<RestView<R>> e, R resource) {
     int d = e.getExportName().indexOf('.');
     if (d < 0) {
       return null;
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index ee751d7..325002b 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -70,8 +70,8 @@
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.DynamicMap.Entry;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -1131,7 +1131,7 @@
    * fails.
    */
   private void validatePluginConfig(ReceiveCommand cmd, ProjectConfig cfg) {
-    for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
+    for (Extension<ProjectConfigEntry> e : pluginConfigEntries) {
       PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
       ProjectConfigEntry configEntry = e.getProvider().get();
       String value = pluginCfg.getString(e.getExportName());
diff --git a/java/com/google/gerrit/server/git/validators/MergeValidators.java b/java/com/google/gerrit/server/git/validators/MergeValidators.java
index c4df4dd..f755aab 100644
--- a/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -19,8 +19,8 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.DynamicMap.Entry;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -207,7 +207,7 @@
             }
           }
 
-          for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
+          for (Extension<ProjectConfigEntry> e : pluginConfigEntries) {
             PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
             ProjectConfigEntry configEntry = e.getProvider().get();
 
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index ec0c1f2..4cf9d7b 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.mail.HtmlParser;
@@ -148,7 +149,7 @@
 
   private void processImpl(BatchUpdate.Factory buf, MailMessage message)
       throws OrmException, UpdateException, RestApiException, IOException {
-    for (DynamicMap.Entry<MailFilter> filter : mailFilters) {
+    for (Extension<MailFilter> filter : mailFilters) {
       if (!filter.getProvider().get().shouldProcessMessage(message)) {
         logger.atWarning().log(
             "Message %s filtered by plugin %s %s. Will delete message.",
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 5667869..cc17899 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.errors.NotSignedInException;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.SchemaUtil;
@@ -427,7 +428,7 @@
   }
 
   private void setupDynamicOperators() {
-    for (DynamicMap.Entry<ChangeOperatorFactory> e : args.opFactories) {
+    for (Extension<ChangeOperatorFactory> e : args.opFactories) {
       String name = e.getExportName() + "_" + e.getPluginName();
       opFactories.put(name, e.getProvider().get());
     }
diff --git a/java/com/google/gerrit/server/restapi/account/SetPreferences.java b/java/com/google/gerrit/server/restapi/account/SetPreferences.java
index fccdabe..2471689 100644
--- a/java/com/google/gerrit/server/restapi/account/SetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/account/SetPreferences.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -83,7 +84,7 @@
       return;
     }
 
-    for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
+    for (Extension<DownloadScheme> e : downloadSchemes) {
       if (e.getExportName().equals(downloadScheme) && e.getProvider().get().isEnabled()) {
         return;
       }
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java b/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
index 6b7a708..ae6e5d1 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
@@ -130,7 +131,7 @@
         new ArrayList<>(reviewerSuggestionPluginMap.plugins().size());
     List<Double> weights = new ArrayList<>(reviewerSuggestionPluginMap.plugins().size());
 
-    for (DynamicMap.Entry<ReviewerSuggestion> plugin : reviewerSuggestionPluginMap) {
+    for (Extension<ReviewerSuggestion> plugin : reviewerSuggestionPluginMap) {
       tasks.add(
           () ->
               plugin
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 66e9f90..2ff2d55 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -39,6 +39,7 @@
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.server.EnableSignedPush;
@@ -254,7 +255,7 @@
   private DownloadInfo getDownloadInfo() {
     DownloadInfo info = new DownloadInfo();
     info.schemes = new HashMap<>();
-    for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
+    for (Extension<DownloadScheme> e : downloadSchemes) {
       DownloadScheme scheme = e.getProvider().get();
       if (scheme.isEnabled() && scheme.getUrl("${project}") != null) {
         info.schemes.put(e.getExportName(), getDownloadSchemeInfo(scheme));
@@ -272,7 +273,7 @@
     info.isAuthSupported = toBoolean(scheme.isAuthSupported());
 
     info.commands = new HashMap<>();
-    for (DynamicMap.Entry<DownloadCommand> e : downloadCommands) {
+    for (Extension<DownloadCommand> e : downloadCommands) {
       String commandName = e.getExportName();
       DownloadCommand command = e.getProvider().get();
       String c = command.getCommand(scheme, "${project}", "${ref}");
@@ -282,7 +283,7 @@
     }
 
     info.cloneCommands = new HashMap<>();
-    for (DynamicMap.Entry<CloneCommand> e : cloneCommands) {
+    for (Extension<CloneCommand> e : cloneCommands) {
       String commandName = e.getExportName();
       CloneCommand command = e.getProvider().get();
       String c = command.getCommand(scheme, "${project-path}/${project-base-name}");
diff --git a/java/com/google/gerrit/server/restapi/config/ListCaches.java b/java/com/google/gerrit/server/restapi/config/ListCaches.java
index 38664fb..f310ed7 100644
--- a/java/com/google/gerrit/server/restapi/config/ListCaches.java
+++ b/java/com/google/gerrit/server/restapi/config/ListCaches.java
@@ -26,6 +26,7 @@
 import com.google.common.collect.Streams;
 import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.cache.PersistentCache;
@@ -60,7 +61,7 @@
 
   public Map<String, CacheInfo> getCacheInfos() {
     Map<String, CacheInfo> cacheInfos = new TreeMap<>();
-    for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
+    for (Extension<Cache<?, ?>> e : cacheMap) {
       cacheInfos.put(
           cacheNameOf(e.getPluginName(), e.getExportName()), new CacheInfo(e.getProvider().get()));
     }
diff --git a/java/com/google/gerrit/server/restapi/config/PostCaches.java b/java/com/google/gerrit/server/restapi/config/PostCaches.java
index 57ba097..c633af0 100644
--- a/java/com/google/gerrit/server/restapi/config/PostCaches.java
+++ b/java/com/google/gerrit/server/restapi/config/PostCaches.java
@@ -20,6 +20,7 @@
 import com.google.common.cache.Cache;
 import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.registration.PluginName;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -96,7 +97,7 @@
   }
 
   private void flushAll() throws AuthException, PermissionBackendException {
-    for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
+    for (Extension<Cache<?, ?>> e : cacheMap) {
       CacheResource cacheResource =
           new CacheResource(e.getPluginName(), e.getExportName(), e.getProvider());
       if (FlushCache.WEB_SESSIONS.equals(cacheResource.getName())) {
diff --git a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
index 60b5dee..e179896 100644
--- a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
+++ b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
@@ -22,7 +22,7 @@
 import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
 import com.google.gerrit.extensions.common.ActionInfo;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.DynamicMap.Entry;
+import com.google.gerrit.extensions.registration.Extension;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
@@ -122,7 +122,7 @@
       PluginConfigFactory cfgFactory,
       AllProjectsName allProjects) {
     TreeMap<String, Map<String, ConfigParameterInfo>> pluginConfig = new TreeMap<>();
-    for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
+    for (Extension<ProjectConfigEntry> e : pluginConfigEntries) {
       ProjectConfigEntry configEntry = e.getProvider().get();
       PluginConfig cfg = cfgFactory.getFromProjectConfig(project, e.getPluginName());
       String configuredValue = cfg.getString(e.getExportName());
@@ -165,7 +165,7 @@
   }
 
   private String getInheritedValue(
-      ProjectState project, PluginConfigFactory cfgFactory, Entry<ProjectConfigEntry> e) {
+      ProjectState project, PluginConfigFactory cfgFactory, Extension<ProjectConfigEntry> e) {
     ProjectConfigEntry configEntry = e.getProvider().get();
     ProjectState parent = Iterables.getFirst(project.parents(), null);
     String inheritedValue = configEntry.getDefaultValue();
diff --git a/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java b/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java
index c86160f..0542c35 100644
--- a/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java
+++ b/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java
@@ -123,8 +123,8 @@
     ds.add("bar", 2);
     ds.add("bar", 3);
 
-    Iterator<DynamicSet.Entry<Integer>> entryIterator = ds.entries().iterator();
-    DynamicSet.Entry<Integer> next = entryIterator.next();
+    Iterator<Extension<Integer>> entryIterator = ds.entries().iterator();
+    Extension<Integer> next = entryIterator.next();
     assertThat(next.getPluginName()).isEqualTo("foo");
     assertThat(next.getProvider().get()).isEqualTo(1);