| // 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; | |
| } | |
| } | |
| } |