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 static com.google.common.collect.ImmutableSet.toImmutableSet; 18 : import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet; 19 : import static java.util.Comparator.naturalOrder; 20 : 21 : import com.google.common.collect.ImmutableSet; 22 : import com.google.common.collect.ImmutableSortedSet; 23 : import com.google.gerrit.common.Nullable; 24 : import com.google.inject.Binder; 25 : import com.google.inject.Key; 26 : import com.google.inject.Provider; 27 : import com.google.inject.Scopes; 28 : import com.google.inject.TypeLiteral; 29 : import com.google.inject.binder.LinkedBindingBuilder; 30 : import com.google.inject.internal.UniqueAnnotations; 31 : import com.google.inject.name.Named; 32 : import com.google.inject.util.Providers; 33 : import com.google.inject.util.Types; 34 : import java.util.Collection; 35 : import java.util.Collections; 36 : import java.util.Iterator; 37 : import java.util.NoSuchElementException; 38 : import java.util.concurrent.CopyOnWriteArrayList; 39 : import java.util.concurrent.atomic.AtomicReference; 40 : import java.util.stream.Stream; 41 : import java.util.stream.StreamSupport; 42 : 43 : /** 44 : * A set of members that can be modified as plugins reload. 45 : * 46 : * <p>DynamicSets are always mapped as singletons in Guice. Sets store Providers internally, and 47 : * resolve the provider to an instance on demand. This enables registrations to decide between 48 : * singleton and non-singleton members. 49 : */ 50 : public class DynamicSet<T> implements Iterable<T> { 51 : /** 52 : * Declare a singleton {@code DynamicSet<T>} with a binder. 53 : * 54 : * <p>Sets must be defined in a Guice module before they can be bound: 55 : * 56 : * <pre> 57 : * DynamicSet.setOf(binder(), Interface.class); 58 : * DynamicSet.bind(binder(), Interface.class).to(Impl.class); 59 : * </pre> 60 : * 61 : * @param binder a new binder created in the module. 62 : * @param member type of entry in the set. 63 : */ 64 : public static <T> void setOf(Binder binder, Class<T> member) { 65 153 : binder.disableCircularProxies(); 66 153 : setOf(binder, TypeLiteral.get(member)); 67 153 : } 68 : 69 : /** 70 : * Declare a singleton {@code DynamicSet<T>} with a binder. 71 : * 72 : * <p>Sets must be defined in a Guice module before they can be bound: 73 : * 74 : * <pre> 75 : * DynamicSet.setOf(binder(), new TypeLiteral<Thing<Foo>>() {}); 76 : * </pre> 77 : * 78 : * @param binder a new binder created in the module. 79 : * @param member type of entry in the set. 80 : */ 81 : public static <T> void setOf(Binder binder, TypeLiteral<T> member) { 82 : @SuppressWarnings("unchecked") 83 153 : Key<DynamicSet<T>> key = 84 : (Key<DynamicSet<T>>) 85 153 : Key.get(Types.newParameterizedType(DynamicSet.class, member.getType())); 86 153 : binder.disableCircularProxies(); 87 153 : binder.bind(key).toProvider(new DynamicSetProvider<>(member)).in(Scopes.SINGLETON); 88 153 : } 89 : 90 : /** 91 : * Bind one implementation into the set using a unique annotation. 92 : * 93 : * @param binder a new binder created in the module. 94 : * @param type type of entries in the set. 95 : * @return a binder to continue configuring the new set member. 96 : */ 97 : public static <T> LinkedBindingBuilder<T> bind(Binder binder, Class<T> type) { 98 152 : binder.disableCircularProxies(); 99 152 : return bind(binder, TypeLiteral.get(type)); 100 : } 101 : 102 : /** 103 : * Bind one implementation into the set using a unique annotation. 104 : * 105 : * @param binder a new binder created in the module. 106 : * @param type type of entries in the set. 107 : * @return a binder to continue configuring the new set member. 108 : */ 109 : public static <T> LinkedBindingBuilder<T> bind(Binder binder, TypeLiteral<T> type) { 110 152 : binder.disableCircularProxies(); 111 152 : return binder.bind(type).annotatedWith(UniqueAnnotations.create()); 112 : } 113 : 114 : /** 115 : * Bind a named implementation into the set. 116 : * 117 : * @param binder a new binder created in the module. 118 : * @param type type of entries in the set. 119 : * @param name {@code @Named} annotation to apply instead of a unique annotation. 120 : * @return a binder to continue configuring the new set member. 121 : */ 122 : public static <T> LinkedBindingBuilder<T> bind(Binder binder, Class<T> type, Named name) { 123 0 : binder.disableCircularProxies(); 124 0 : return bind(binder, TypeLiteral.get(type)); 125 : } 126 : 127 : /** 128 : * Bind a named implementation into the set. 129 : * 130 : * @param binder a new binder created in the module. 131 : * @param type type of entries in the set. 132 : * @param name {@code @Named} annotation to apply instead of a unique annotation. 133 : * @return a binder to continue configuring the new set member. 134 : */ 135 : public static <T> LinkedBindingBuilder<T> bind(Binder binder, TypeLiteral<T> type, Named name) { 136 0 : binder.disableCircularProxies(); 137 0 : return binder.bind(type).annotatedWith(name); 138 : } 139 : 140 : public static <T> DynamicSet<T> emptySet() { 141 15 : return new DynamicSet<>(Collections.emptySet()); 142 : } 143 : 144 : private final CopyOnWriteArrayList<AtomicReference<Extension<T>>> items; 145 : 146 154 : DynamicSet(Collection<AtomicReference<Extension<T>>> base) { 147 154 : items = new CopyOnWriteArrayList<>(base); 148 154 : } 149 : 150 : public DynamicSet() { 151 151 : this(Collections.emptySet()); 152 151 : } 153 : 154 : @Override 155 : public Iterator<T> iterator() { 156 153 : Iterator<Extension<T>> entryIterator = entries().iterator(); 157 153 : return new Iterator<>() { 158 : @Override 159 : public boolean hasNext() { 160 153 : return entryIterator.hasNext(); 161 : } 162 : 163 : @Override 164 : public T next() { 165 114 : Extension<T> next = entryIterator.next(); 166 114 : return next != null ? next.getProvider().get() : null; 167 : } 168 : }; 169 : } 170 : 171 : public Iterable<Extension<T>> entries() { 172 153 : final Iterator<AtomicReference<Extension<T>>> itr = items.iterator(); 173 153 : return () -> 174 153 : new Iterator<>() { 175 : private Extension<T> next; 176 : 177 : @Override 178 : public boolean hasNext() { 179 153 : while (next == null && itr.hasNext()) { 180 153 : Extension<T> p = itr.next().get(); 181 153 : if (p != null) { 182 153 : next = p; 183 : } 184 153 : } 185 153 : return next != null; 186 : } 187 : 188 : @Override 189 : public Extension<T> next() { 190 153 : if (hasNext()) { 191 153 : Extension<T> result = next; 192 153 : next = null; 193 153 : return result; 194 : } 195 0 : throw new NoSuchElementException(); 196 : } 197 : 198 : @Override 199 : public void remove() { 200 0 : throw new UnsupportedOperationException(); 201 : } 202 : }; 203 : } 204 : 205 : /** 206 : * Returns {@code true} if this set contains the given item. 207 : * 208 : * @param item item to check whether or not it is contained. 209 : * @return {@code true} if this set contains the given item. 210 : */ 211 : public boolean contains(T item) { 212 101 : Iterator<T> iterator = iterator(); 213 101 : while (iterator.hasNext()) { 214 101 : T candidate = iterator.next(); 215 101 : if (candidate == item) { 216 101 : return true; 217 : } 218 2 : } 219 101 : return false; 220 : } 221 : 222 : /** 223 : * Get the names of all running plugins supplying this type. 224 : * 225 : * @return sorted set of active plugins that supply at least one item. 226 : */ 227 : public ImmutableSortedSet<String> plugins() { 228 1 : return items.stream() 229 1 : .map(i -> i.get().getPluginName()) 230 1 : .collect(toImmutableSortedSet(naturalOrder())); 231 : } 232 : 233 : /** 234 : * Get the items exported by a single plugin. 235 : * 236 : * @param pluginName name of the plugin. 237 : * @return items exported by a plugin. 238 : */ 239 : public ImmutableSet<Provider<T>> byPlugin(String pluginName) { 240 1 : return items.stream() 241 1 : .filter(i -> i.get().getPluginName().equals(pluginName)) 242 1 : .map(i -> i.get().getProvider()) 243 1 : .collect(toImmutableSet()); 244 : } 245 : 246 : /** 247 : * Add one new element to the set. 248 : * 249 : * @param item the item to add to the collection. Must not be null. 250 : * @return handle to remove the item at a later point in time. 251 : */ 252 : public RegistrationHandle add(String pluginName, T item) { 253 146 : return add(pluginName, Providers.of(item)); 254 : } 255 : 256 : /** 257 : * Add one new element to the set. 258 : * 259 : * @param item the item to add to the collection. Must not be null. 260 : * @return handle to remove the item at a later point in time. 261 : */ 262 : public RegistrationHandle add(String pluginName, Provider<T> item) { 263 146 : final AtomicReference<Extension<T>> ref = 264 : new AtomicReference<>(new Extension<>(pluginName, item)); 265 146 : items.add(ref); 266 146 : return () -> { 267 137 : if (ref.compareAndSet(ref.get(), null)) { 268 137 : items.remove(ref); 269 : } 270 137 : }; 271 : } 272 : 273 : /** 274 : * Add one new element that may be hot-replaceable in the future. 275 : * 276 : * @param pluginName unique name of the plugin providing the item. 277 : * @param key unique description from the item's Guice binding. This can be later obtained from 278 : * the registration handle to facilitate matching with the new equivalent instance during a 279 : * hot reload. 280 : * @param item the item to add to the collection right now. Must not be null. 281 : * @return a handle that can remove this item later, or hot-swap the item without it ever leaving 282 : * the collection. 283 : */ 284 : public ReloadableRegistrationHandle<T> add(String pluginName, Key<T> key, Provider<T> item) { 285 107 : AtomicReference<Extension<T>> ref = new AtomicReference<>(new Extension<>(pluginName, item)); 286 107 : items.add(ref); 287 107 : return new ReloadableHandle(ref, key, ref.get()); 288 : } 289 : 290 : public Stream<T> stream() { 291 110 : return StreamSupport.stream(spliterator(), false); 292 : } 293 : 294 : private class ReloadableHandle implements ReloadableRegistrationHandle<T> { 295 : private final AtomicReference<Extension<T>> ref; 296 : private final Key<T> key; 297 : private final Extension<T> item; 298 : 299 107 : ReloadableHandle(AtomicReference<Extension<T>> ref, Key<T> key, Extension<T> item) { 300 107 : this.ref = ref; 301 107 : this.key = key; 302 107 : this.item = item; 303 107 : } 304 : 305 : @Override 306 : public void remove() { 307 107 : if (ref.compareAndSet(item, null)) { 308 107 : items.remove(ref); 309 : } 310 107 : } 311 : 312 : @Override 313 : public Key<T> getKey() { 314 1 : return key; 315 : } 316 : 317 : @Nullable 318 : @Override 319 : public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) { 320 1 : Extension<T> n = new Extension<>(item.getPluginName(), newItem); 321 1 : if (ref.compareAndSet(item, n)) { 322 1 : return new ReloadableHandle(ref, newKey, n); 323 : } 324 0 : return null; 325 : } 326 : } 327 : }