// 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.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. 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<DynamicMap.Entry<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); } 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 sorted set of active plugins that supply at least one item. */ public SortedSet<String> plugins() { SortedSet<String> r = new TreeSet<>(); 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, Provider<T>> byPlugin(String pluginName) { SortedMap<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.unmodifiableSortedMap(r); } /** Iterate through all entries in an undefined order. */ public Iterator<Entry<T>> iterator() { final Iterator<Map.Entry<NamePair, Provider<T>>> i = items.entrySet().iterator(); return new Iterator<Entry<T>>() { @Override public boolean hasNext() { return i.hasNext(); } @Override public Entry<T> next() { final Map.Entry<NamePair, Provider<T>> e = i.next(); return new Entry<T>() { @Override public String getPluginName() { return e.getKey().pluginName; } @Override public String getExportName() { return e.getKey().exportName; } @Override public Provider<T> getProvider() { return e.getValue(); } }; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } public interface Entry<T> { String getPluginName(); String getExportName(); Provider<T> getProvider(); } 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.pluginName) && exportName.equals(np.exportName); } return false; } } public static <T> DynamicMap<T> emptyMap() { return new DynamicMap<T>() {}; } }