LCOV - code coverage report
Current view: top level - extensions/registration - DynamicSet.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 73 80 91.2 %
Date: 2022-11-19 15:00:39 Functions: 31 34 91.2 %

          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&lt;Thing&lt;Foo&gt;&gt;() {});
      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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750