| // 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.Provider; |
| import com.google.inject.ProvisionException; |
| import com.google.inject.Scopes; |
| import com.google.inject.TypeLiteral; |
| import com.google.inject.util.Types; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.NavigableMap; |
| import java.util.NavigableSet; |
| 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. Maps store Providers internally, and |
| * 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<Extension<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("foo")) |
| * .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("foo")) |
| * .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<>(member)).in(Scopes.SINGLETON); |
| } |
| |
| /** Returns an empty DynamicMap instance * */ |
| public static <T> DynamicMap<T> emptyMap() { |
| return new PrivateInternals_DynamicMapImpl<>(); |
| } |
| |
| final ConcurrentMap<NamePair, Provider<T>> items; |
| |
| DynamicMap() { |
| items = |
| new ConcurrentHashMap<>( |
| 16 /* initial size */, |
| 0.75f /* load factor */, |
| 1 /* concurrency level of 1, load/unload is single threaded */); |
| } |
| |
| /** |
| * 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. |
| * @throws ProvisionException if the registered provider is unable to obtain an instance of the |
| * requested implementation. |
| */ |
| public T get(String pluginName, String exportName) throws ProvisionException { |
| Provider<T> p = items.get(new NamePair(pluginName, exportName)); |
| return p != null ? p.get() : null; |
| } |
| |
| /** |
| * Get the names of all running plugins supplying this type. |
| * |
| * @return navigatable set of active plugins that supply at least one item. |
| */ |
| public NavigableSet<String> plugins() { |
| NavigableSet<String> r = new TreeSet<>(); |
| for (NamePair p : items.keySet()) { |
| r.add(p.pluginName); |
| } |
| return Collections.unmodifiableNavigableSet(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 NavigableMap<String, Provider<T>> byPlugin(String pluginName) { |
| NavigableMap<String, Provider<T>> r = new TreeMap<>(); |
| for (Map.Entry<NamePair, Provider<T>> e : items.entrySet()) { |
| if (e.getKey().pluginName.equals(pluginName)) { |
| r.put(e.getKey().exportName, e.getValue()); |
| } |
| } |
| return Collections.unmodifiableNavigableMap(r); |
| } |
| |
| /** Iterate through all entries in an undefined order. */ |
| @Override |
| public Iterator<Extension<T>> iterator() { |
| final Iterator<Map.Entry<NamePair, Provider<T>>> i = items.entrySet().iterator(); |
| return new Iterator<>() { |
| @Override |
| public boolean hasNext() { |
| return i.hasNext(); |
| } |
| |
| @Override |
| public Extension<T> next() { |
| Map.Entry<NamePair, Provider<T>> e = i.next(); |
| return new Extension<>(e.getKey().pluginName, e.getKey().exportName, e.getValue()); |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| |
| static class NamePair { |
| private final String pluginName; |
| private final String exportName; |
| |
| NamePair(String pn, String en) { |
| pluginName = pn; |
| 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.pluginName) && exportName.equals(np.exportName); |
| } |
| return false; |
| } |
| } |
| } |