Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.extensions.registration; 16 : 17 : import com.google.inject.Binder; 18 : import com.google.inject.Key; 19 : import com.google.inject.Provider; 20 : import com.google.inject.ProvisionException; 21 : import com.google.inject.Scopes; 22 : import com.google.inject.TypeLiteral; 23 : import com.google.inject.util.Types; 24 : import java.util.Collections; 25 : import java.util.Iterator; 26 : import java.util.Map; 27 : import java.util.NavigableMap; 28 : import java.util.NavigableSet; 29 : import java.util.TreeMap; 30 : import java.util.TreeSet; 31 : import java.util.concurrent.ConcurrentHashMap; 32 : import java.util.concurrent.ConcurrentMap; 33 : 34 : /** 35 : * A map of members that can be modified as plugins reload. 36 : * 37 : * <p>Maps index their members by plugin name and export name. 38 : * 39 : * <p>DynamicMaps are always mapped as singletons in Guice. Maps store Providers internally, and 40 : * resolve the provider to an instance on demand. This enables registrations to decide between 41 : * singleton and non-singleton members. 42 : */ 43 : public abstract class DynamicMap<T> implements Iterable<Extension<T>> { 44 : /** 45 : * Declare a singleton {@code DynamicMap<T>} with a binder. 46 : * 47 : * <p>Maps must be defined in a Guice module before they can be bound: 48 : * 49 : * <pre> 50 : * DynamicMap.mapOf(binder(), Interface.class); 51 : * bind(Interface.class) 52 : * .annotatedWith(Exports.named("foo")) 53 : * .to(Impl.class); 54 : * </pre> 55 : * 56 : * @param binder a new binder created in the module. 57 : * @param member type of value in the map. 58 : */ 59 : public static <T> void mapOf(Binder binder, Class<T> member) { 60 152 : mapOf(binder, TypeLiteral.get(member)); 61 152 : } 62 : 63 : /** 64 : * Declare a singleton {@code DynamicMap<T>} with a binder. 65 : * 66 : * <p>Maps must be defined in a Guice module before they can be bound: 67 : * 68 : * <pre> 69 : * DynamicMap.mapOf(binder(), new TypeLiteral<Thing<Bar>>(){}); 70 : * bind(new TypeLiteral<Thing<Bar>>() {}) 71 : * .annotatedWith(Exports.named("foo")) 72 : * .to(Impl.class); 73 : * </pre> 74 : * 75 : * @param binder a new binder created in the module. 76 : * @param member type of value in the map. 77 : */ 78 : public static <T> void mapOf(Binder binder, TypeLiteral<T> member) { 79 : @SuppressWarnings("unchecked") 80 152 : Key<DynamicMap<T>> key = 81 : (Key<DynamicMap<T>>) 82 152 : Key.get(Types.newParameterizedType(DynamicMap.class, member.getType())); 83 152 : binder.bind(key).toProvider(new DynamicMapProvider<>(member)).in(Scopes.SINGLETON); 84 152 : } 85 : 86 : /** Returns an empty DynamicMap instance * */ 87 : public static <T> DynamicMap<T> emptyMap() { 88 151 : return new PrivateInternals_DynamicMapImpl<>(); 89 : } 90 : 91 : final ConcurrentMap<NamePair, Provider<T>> items; 92 : 93 152 : DynamicMap() { 94 152 : items = 95 : new ConcurrentHashMap<>( 96 : 16 /* initial size */, 97 : 0.75f /* load factor */, 98 : 1 /* concurrency level of 1, load/unload is single threaded */); 99 152 : } 100 : 101 : /** 102 : * Lookup an implementation by name. 103 : * 104 : * @param pluginName local name of the plugin providing the item. 105 : * @param exportName name the plugin exports the item as. 106 : * @return the implementation. Null if the plugin is not running, or if the plugin does not export 107 : * this name. 108 : * @throws ProvisionException if the registered provider is unable to obtain an instance of the 109 : * requested implementation. 110 : */ 111 : public T get(String pluginName, String exportName) throws ProvisionException { 112 29 : Provider<T> p = items.get(new NamePair(pluginName, exportName)); 113 29 : return p != null ? p.get() : null; 114 : } 115 : 116 : /** 117 : * Get the names of all running plugins supplying this type. 118 : * 119 : * @return navigatable set of active plugins that supply at least one item. 120 : */ 121 : public NavigableSet<String> plugins() { 122 125 : NavigableSet<String> r = new TreeSet<>(); 123 125 : for (NamePair p : items.keySet()) { 124 8 : r.add(p.pluginName); 125 8 : } 126 125 : return Collections.unmodifiableNavigableSet(r); 127 : } 128 : 129 : /** 130 : * Get the items exported by a single plugin. 131 : * 132 : * @param pluginName name of the plugin. 133 : * @return items exported by a plugin, keyed by the export name. 134 : */ 135 : public NavigableMap<String, Provider<T>> byPlugin(String pluginName) { 136 8 : NavigableMap<String, Provider<T>> r = new TreeMap<>(); 137 8 : for (Map.Entry<NamePair, Provider<T>> e : items.entrySet()) { 138 8 : if (e.getKey().pluginName.equals(pluginName)) { 139 7 : r.put(e.getKey().exportName, e.getValue()); 140 : } 141 8 : } 142 8 : return Collections.unmodifiableNavigableMap(r); 143 : } 144 : 145 : /** Iterate through all entries in an undefined order. */ 146 : @Override 147 : public Iterator<Extension<T>> iterator() { 148 151 : final Iterator<Map.Entry<NamePair, Provider<T>>> i = items.entrySet().iterator(); 149 151 : return new Iterator<>() { 150 : @Override 151 : public boolean hasNext() { 152 151 : return i.hasNext(); 153 : } 154 : 155 : @Override 156 : public Extension<T> next() { 157 82 : Map.Entry<NamePair, Provider<T>> e = i.next(); 158 82 : return new Extension<>(e.getKey().pluginName, e.getKey().exportName, e.getValue()); 159 : } 160 : 161 : @Override 162 : public void remove() { 163 0 : throw new UnsupportedOperationException(); 164 : } 165 : }; 166 : } 167 : 168 : static class NamePair { 169 : private final String pluginName; 170 : private final String exportName; 171 : 172 152 : NamePair(String pn, String en) { 173 152 : pluginName = pn; 174 152 : exportName = en; 175 152 : } 176 : 177 : @Override 178 : public int hashCode() { 179 152 : return pluginName.hashCode() * 31 + exportName.hashCode(); 180 : } 181 : 182 : @Override 183 : public boolean equals(Object other) { 184 29 : if (other instanceof NamePair) { 185 29 : NamePair np = (NamePair) other; 186 29 : return pluginName.equals(np.pluginName) && exportName.equals(np.exportName); 187 : } 188 0 : return false; 189 : } 190 : } 191 : }