| // 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 static com.google.common.collect.ImmutableSet.toImmutableSet; | |
| import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet; | |
| import static java.util.Comparator.naturalOrder; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.ImmutableSortedSet; | |
| import com.google.gerrit.common.Nullable; | |
| import com.google.inject.Binder; | |
| import com.google.inject.Key; | |
| import com.google.inject.Provider; | |
| import com.google.inject.Scopes; | |
| import com.google.inject.TypeLiteral; | |
| import com.google.inject.binder.LinkedBindingBuilder; | |
| import com.google.inject.internal.UniqueAnnotations; | |
| import com.google.inject.name.Named; | |
| import com.google.inject.util.Providers; | |
| import com.google.inject.util.Types; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.Iterator; | |
| import java.util.NoSuchElementException; | |
| import java.util.concurrent.CopyOnWriteArrayList; | |
| import java.util.concurrent.atomic.AtomicReference; | |
| import java.util.stream.Stream; | |
| import java.util.stream.StreamSupport; | |
| /** | |
| * A set of members that can be modified as plugins reload. | |
| * | |
| * <p>DynamicSets are always mapped as singletons in Guice. Sets store Providers internally, and | |
| * resolve the provider to an instance on demand. This enables registrations to decide between | |
| * singleton and non-singleton members. | |
| */ | |
| public class DynamicSet<T> implements Iterable<T> { | |
| /** | |
| * Declare a singleton {@code DynamicSet<T>} with a binder. | |
| * | |
| * <p>Sets must be defined in a Guice module before they can be bound: | |
| * | |
| * <pre> | |
| * DynamicSet.setOf(binder(), Interface.class); | |
| * DynamicSet.bind(binder(), Interface.class).to(Impl.class); | |
| * </pre> | |
| * | |
| * @param binder a new binder created in the module. | |
| * @param member type of entry in the set. | |
| */ | |
| public static <T> void setOf(Binder binder, Class<T> member) { | |
| binder.disableCircularProxies(); | |
| setOf(binder, TypeLiteral.get(member)); | |
| } | |
| /** | |
| * Declare a singleton {@code DynamicSet<T>} with a binder. | |
| * | |
| * <p>Sets must be defined in a Guice module before they can be bound: | |
| * | |
| * <pre> | |
| * DynamicSet.setOf(binder(), new TypeLiteral<Thing<Foo>>() {}); | |
| * </pre> | |
| * | |
| * @param binder a new binder created in the module. | |
| * @param member type of entry in the set. | |
| */ | |
| public static <T> void setOf(Binder binder, TypeLiteral<T> member) { | |
| @SuppressWarnings("unchecked") | |
| Key<DynamicSet<T>> key = | |
| (Key<DynamicSet<T>>) | |
| Key.get(Types.newParameterizedType(DynamicSet.class, member.getType())); | |
| binder.disableCircularProxies(); | |
| binder.bind(key).toProvider(new DynamicSetProvider<>(member)).in(Scopes.SINGLETON); | |
| } | |
| /** | |
| * Bind one implementation into the set using a unique annotation. | |
| * | |
| * @param binder a new binder created in the module. | |
| * @param type type of entries in the set. | |
| * @return a binder to continue configuring the new set member. | |
| */ | |
| public static <T> LinkedBindingBuilder<T> bind(Binder binder, Class<T> type) { | |
| binder.disableCircularProxies(); | |
| return bind(binder, TypeLiteral.get(type)); | |
| } | |
| /** | |
| * Bind one implementation into the set using a unique annotation. | |
| * | |
| * @param binder a new binder created in the module. | |
| * @param type type of entries in the set. | |
| * @return a binder to continue configuring the new set member. | |
| */ | |
| public static <T> LinkedBindingBuilder<T> bind(Binder binder, TypeLiteral<T> type) { | |
| binder.disableCircularProxies(); | |
| return binder.bind(type).annotatedWith(UniqueAnnotations.create()); | |
| } | |
| /** | |
| * Bind a named implementation into the set. | |
| * | |
| * @param binder a new binder created in the module. | |
| * @param type type of entries in the set. | |
| * @param name {@code @Named} annotation to apply instead of a unique annotation. | |
| * @return a binder to continue configuring the new set member. | |
| */ | |
| public static <T> LinkedBindingBuilder<T> bind(Binder binder, Class<T> type, Named name) { | |
| binder.disableCircularProxies(); | |
| return bind(binder, TypeLiteral.get(type)); | |
| } | |
| /** | |
| * Bind a named implementation into the set. | |
| * | |
| * @param binder a new binder created in the module. | |
| * @param type type of entries in the set. | |
| * @param name {@code @Named} annotation to apply instead of a unique annotation. | |
| * @return a binder to continue configuring the new set member. | |
| */ | |
| public static <T> LinkedBindingBuilder<T> bind(Binder binder, TypeLiteral<T> type, Named name) { | |
| binder.disableCircularProxies(); | |
| return binder.bind(type).annotatedWith(name); | |
| } | |
| public static <T> DynamicSet<T> emptySet() { | |
| return new DynamicSet<>(Collections.emptySet()); | |
| } | |
| private final CopyOnWriteArrayList<AtomicReference<Extension<T>>> items; | |
| DynamicSet(Collection<AtomicReference<Extension<T>>> base) { | |
| items = new CopyOnWriteArrayList<>(base); | |
| } | |
| public DynamicSet() { | |
| this(Collections.emptySet()); | |
| } | |
| @Override | |
| public Iterator<T> iterator() { | |
| Iterator<Extension<T>> entryIterator = entries().iterator(); | |
| return new Iterator<>() { | |
| @Override | |
| public boolean hasNext() { | |
| return entryIterator.hasNext(); | |
| } | |
| @Override | |
| public T next() { | |
| Extension<T> next = entryIterator.next(); | |
| return next != null ? next.getProvider().get() : null; | |
| } | |
| }; | |
| } | |
| public Iterable<Extension<T>> entries() { | |
| final Iterator<AtomicReference<Extension<T>>> itr = items.iterator(); | |
| return () -> | |
| new Iterator<>() { | |
| private Extension<T> next; | |
| @Override | |
| public boolean hasNext() { | |
| while (next == null && itr.hasNext()) { | |
| Extension<T> p = itr.next().get(); | |
| if (p != null) { | |
| next = p; | |
| } | |
| } | |
| return next != null; | |
| } | |
| @Override | |
| public Extension<T> next() { | |
| if (hasNext()) { | |
| Extension<T> result = next; | |
| next = null; | |
| return result; | |
| } | |
| throw new NoSuchElementException(); | |
| } | |
| @Override | |
| public void remove() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| }; | |
| } | |
| /** | |
| * Returns {@code true} if this set contains the given item. | |
| * | |
| * @param item item to check whether or not it is contained. | |
| * @return {@code true} if this set contains the given item. | |
| */ | |
| public boolean contains(T item) { | |
| Iterator<T> iterator = iterator(); | |
| while (iterator.hasNext()) { | |
| T candidate = iterator.next(); | |
| if (candidate == item) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Get the names of all running plugins supplying this type. | |
| * | |
| * @return sorted set of active plugins that supply at least one item. | |
| */ | |
| public ImmutableSortedSet<String> plugins() { | |
| return items.stream() | |
| .map(i -> i.get().getPluginName()) | |
| .collect(toImmutableSortedSet(naturalOrder())); | |
| } | |
| /** | |
| * Get the items exported by a single plugin. | |
| * | |
| * @param pluginName name of the plugin. | |
| * @return items exported by a plugin. | |
| */ | |
| public ImmutableSet<Provider<T>> byPlugin(String pluginName) { | |
| return items.stream() | |
| .filter(i -> i.get().getPluginName().equals(pluginName)) | |
| .map(i -> i.get().getProvider()) | |
| .collect(toImmutableSet()); | |
| } | |
| /** | |
| * Add one new element to the set. | |
| * | |
| * @param item the item to add to the collection. Must not be null. | |
| * @return handle to remove the item at a later point in time. | |
| */ | |
| public RegistrationHandle add(String pluginName, T item) { | |
| return add(pluginName, Providers.of(item)); | |
| } | |
| /** | |
| * Add one new element to the set. | |
| * | |
| * @param item the item to add to the collection. Must not be null. | |
| * @return handle to remove the item at a later point in time. | |
| */ | |
| public RegistrationHandle add(String pluginName, Provider<T> item) { | |
| final AtomicReference<Extension<T>> ref = | |
| new AtomicReference<>(new Extension<>(pluginName, item)); | |
| items.add(ref); | |
| return () -> { | |
| if (ref.compareAndSet(ref.get(), null)) { | |
| items.remove(ref); | |
| } | |
| }; | |
| } | |
| /** | |
| * Add one new element that may be hot-replaceable in the future. | |
| * | |
| * @param pluginName unique name of the plugin providing the item. | |
| * @param key unique description from the item's Guice binding. This can be later obtained from | |
| * the registration handle to facilitate matching with the new equivalent instance during a | |
| * hot reload. | |
| * @param item the item to add to the collection right now. Must not be null. | |
| * @return a handle that can remove this item later, or hot-swap the item without it ever leaving | |
| * the collection. | |
| */ | |
| public ReloadableRegistrationHandle<T> add(String pluginName, Key<T> key, Provider<T> item) { | |
| AtomicReference<Extension<T>> ref = new AtomicReference<>(new Extension<>(pluginName, item)); | |
| items.add(ref); | |
| return new ReloadableHandle(ref, key, ref.get()); | |
| } | |
| public Stream<T> stream() { | |
| return StreamSupport.stream(spliterator(), false); | |
| } | |
| private class ReloadableHandle implements ReloadableRegistrationHandle<T> { | |
| private final AtomicReference<Extension<T>> ref; | |
| private final Key<T> key; | |
| private final Extension<T> item; | |
| ReloadableHandle(AtomicReference<Extension<T>> ref, Key<T> key, Extension<T> item) { | |
| this.ref = ref; | |
| this.key = key; | |
| this.item = item; | |
| } | |
| @Override | |
| public void remove() { | |
| if (ref.compareAndSet(item, null)) { | |
| items.remove(ref); | |
| } | |
| } | |
| @Override | |
| public Key<T> getKey() { | |
| return key; | |
| } | |
| @Nullable | |
| @Override | |
| public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) { | |
| Extension<T> n = new Extension<>(item.getPluginName(), newItem); | |
| if (ref.compareAndSet(item, n)) { | |
| return new ReloadableHandle(ref, newKey, n); | |
| } | |
| return null; | |
| } | |
| } | |
| } |