diff --git a/server/src/main/java/org/elasticsearch/common/collect/CopyOnWriteHashMap.java b/server/src/main/java/org/elasticsearch/common/collect/CopyOnWriteHashMap.java
deleted file mode 100644
index 1ea52783c7762..0000000000000
--- a/server/src/main/java/org/elasticsearch/common/collect/CopyOnWriteHashMap.java
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-package org.elasticsearch.common.collect;
-
-import org.apache.lucene.util.mutable.MutableValueInt;
-
-import java.lang.reflect.Array;
-import java.util.AbstractMap;
-import java.util.AbstractSet;
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.stream.Stream;
-
-/**
- * An immutable map whose writes result in a new copy of the map to be created.
- *
- * This is essentially a hash array mapped trie: inner nodes use a bitmap in
- * order to map hashes to slots by counting ones. In case of a collision (two
- * values having the same 32-bits hash), a leaf node is created which stores
- * and searches for values sequentially.
- *
- * Reads and writes both perform in logarithmic time. Null keys and values are
- * not supported.
- *
- * This structure might need to perform several object creations per write so
- * it is better suited for work-loads that are not too write-intensive.
- *
- * @see the wikipedia page
- */
-public final class CopyOnWriteHashMap extends AbstractMap {
-
- private static final int TOTAL_HASH_BITS = 32;
- private static final Object[] EMPTY_ARRAY = new Object[0];
-
- private static final int HASH_BITS = 6;
- private static final int HASH_MASK = 0x3F;
-
- /**
- * Return a copy of the provided map.
- */
- public static CopyOnWriteHashMap copyOf(Map extends K, ? extends V> map) {
- if (map instanceof CopyOnWriteHashMap) {
- // no need to copy in that case
- @SuppressWarnings("unchecked")
- final CopyOnWriteHashMap cowMap = (CopyOnWriteHashMap) map;
- return cowMap;
- } else {
- return new CopyOnWriteHashMap().copyAndPutAll(map);
- }
- }
-
- /**
- * Abstraction of a node, implemented by both inner and leaf nodes.
- */
- private abstract static class Node {
-
- /**
- * Recursively get the key with the given hash.
- */
- abstract V get(Object key, int hash);
-
- /**
- * Recursively add a new entry to this node. hashBits
is
- * the number of bits that are still set in the hash. When this value
- * reaches a number that is less than or equal to {@code 0}, a leaf
- * node needs to be created since it means that a collision occurred
- * on the 32 bits of the hash.
- */
- abstract Node put(K key, int hash, int hashBits, V value, MutableValueInt newValue);
-
- /**
- * Recursively remove an entry from this node.
- */
- abstract Node remove(Object key, int hash);
-
- /**
- * For the current node only, append entries that are stored on this
- * node to entries
and sub nodes to nodes
.
- */
- abstract void visit(Deque> entries, Deque> nodes);
-
- /**
- * Whether this node stores nothing under it.
- */
- abstract boolean isEmpty();
-
- }
-
- /**
- * A leaf of the tree where all hashes are equal. Values are added and retrieved in linear time.
- */
- private static class Leaf extends Node {
-
- private final K[] keys;
- private final V[] values;
-
- Leaf(K[] keys, V[] values) {
- this.keys = keys;
- this.values = values;
- }
-
- @SuppressWarnings("unchecked")
- Leaf() {
- this((K[]) EMPTY_ARRAY, (V[]) EMPTY_ARRAY);
- }
-
- @Override
- boolean isEmpty() {
- return keys.length == 0;
- }
-
- @Override
- void visit(Deque> entries, Deque> nodes) {
- for (int i = 0; i < keys.length; ++i) {
- entries.add(new AbstractMap.SimpleImmutableEntry<>(keys[i], values[i]));
- }
- }
-
- @Override
- V get(Object key, int hash) {
- for (int i = 0; i < keys.length; i++) {
- if (key.equals(keys[i])) {
- return values[i];
- }
- }
- return null;
-
- }
-
- private static T[] replace(T[] array, int index, T value) {
- final T[] copy = Arrays.copyOf(array, array.length);
- copy[index] = value;
- return copy;
- }
-
- @Override
- Leaf put(K key, int hash, int hashBits, V value, MutableValueInt newValue) {
- assert hashBits <= 0 : hashBits;
- int slot = -1;
- for (int i = 0; i < keys.length; i++) {
- if (key.equals(keys[i])) {
- slot = i;
- break;
- }
- }
-
- final K[] keys2;
- final V[] values2;
-
- if (slot < 0) {
- keys2 = appendElement(keys, key);
- values2 = appendElement(values, value);
- newValue.value = 1;
- } else {
- keys2 = replace(keys, slot, key);
- values2 = replace(values, slot, value);
- }
-
- return new Leaf<>(keys2, values2);
- }
-
- @Override
- Leaf remove(Object key, int hash) {
- int slot = -1;
- for (int i = 0; i < keys.length; i++) {
- if (key.equals(keys[i])) {
- slot = i;
- break;
- }
- }
- if (slot < 0) {
- return this;
- }
- final K[] keys2 = removeArrayElement(keys, slot);
- final V[] values2 = removeArrayElement(values, slot);
- return new Leaf<>(keys2, values2);
- }
- }
-
- private static T[] removeArrayElement(T[] array, int index) {
- @SuppressWarnings("unchecked")
- final T[] result = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length - 1);
- System.arraycopy(array, 0, result, 0, index);
- if (index < array.length - 1) {
- System.arraycopy(array, index + 1, result, index, array.length - index - 1);
- }
-
- return result;
- }
-
- public static T[] appendElement(final T[] array, final T element) {
- final T[] newArray = Arrays.copyOf(array, array.length + 1);
- newArray[newArray.length - 1] = element;
- return newArray;
- }
-
- public static T[] insertElement(final T[] array, final T element, final int index) {
- final T[] result = Arrays.copyOf(array, array.length + 1);
- System.arraycopy(array, 0, result, 0, index);
- result[index] = element;
- if (index < array.length) {
- System.arraycopy(array, index, result, index + 1, array.length - index);
- }
- return result;
- }
-
- /**
- * An inner node in this trie. Inner nodes store up to 64 key-value pairs
- * and use a bitmap in order to associate hashes to them. For example, if
- * an inner node contains 5 values, then 5 bits will be set in the bitmap
- * and the ordinal of the bit set in this bit map will be the slot number.
- *
- * As a consequence, the number of slots in an inner node is equal to the
- * number of one bits in the bitmap.
- */
- private static class InnerNode extends Node {
-
- private final long mask; // the bitmap
- private final K[] keys;
- final Object[] subNodes; // subNodes[slot] is either a value or a sub node in case of a hash collision
-
- InnerNode(long mask, K[] keys, Object[] subNodes) {
- this.mask = mask;
- this.keys = keys;
- this.subNodes = subNodes;
- assert consistent();
- }
-
- // only used in assert
- private boolean consistent() {
- assert Long.bitCount(mask) == keys.length;
- assert Long.bitCount(mask) == subNodes.length;
- for (int i = 0; i < keys.length; ++i) {
- if (subNodes[i] instanceof Node) {
- assert keys[i] == null;
- } else {
- assert keys[i] != null;
- }
- }
- return true;
- }
-
- @Override
- boolean isEmpty() {
- return mask == 0;
- }
-
- @SuppressWarnings("unchecked")
- InnerNode() {
- this(0, (K[]) EMPTY_ARRAY, EMPTY_ARRAY);
- }
-
- @Override
- void visit(Deque> entries, Deque> nodes) {
- for (int i = 0; i < keys.length; ++i) {
- final Object sub = subNodes[i];
- if (sub instanceof Node) {
- @SuppressWarnings("unchecked")
- final Node subNode = (Node) sub;
- assert keys[i] == null;
- nodes.add(subNode);
- } else {
- @SuppressWarnings("unchecked")
- final V value = (V) sub;
- entries.add(new AbstractMap.SimpleImmutableEntry<>(keys[i], value));
- }
- }
- }
-
- /**
- * For a given hash on 6 bits, its value is set if the bitmap has a one
- * at the corresponding index.
- */
- private boolean exists(int hash6) {
- return (mask & (1L << hash6)) != 0;
- }
-
- /**
- * For a given hash on 6 bits, the slot number is the number of one
- * bits on the right of the hash6
-th bit.
- */
- private int slot(int hash6) {
- return Long.bitCount(mask & ((1L << hash6) - 1));
- }
-
- @Override
- V get(Object key, int hash) {
- final int hash6 = hash & HASH_MASK;
- if (exists(hash6) == false) {
- return null;
- }
- final int slot = slot(hash6);
- final Object sub = subNodes[slot];
- assert sub != null;
- if (sub instanceof Node) {
- assert keys[slot] == null; // keys don't make sense on inner nodes
- @SuppressWarnings("unchecked")
- final Node subNode = (Node) sub;
- return subNode.get(key, hash >>> HASH_BITS);
- } else {
- if (keys[slot].equals(key)) {
- @SuppressWarnings("unchecked")
- final V v = (V) sub;
- return v;
- } else {
- // we have an entry for this hash, but the value is different
- return null;
- }
- }
- }
-
- private Node newSubNode(int hashBits) {
- if (hashBits <= 0) {
- return new Leaf();
- } else {
- return new InnerNode();
- }
- }
-
- @SuppressWarnings("unchecked")
- private InnerNode putExisting(K key, int hash, int hashBits, int slot, V value, MutableValueInt newValue) {
- final K[] keys2 = Arrays.copyOf(keys, keys.length);
- final Object[] subNodes2 = Arrays.copyOf(subNodes, subNodes.length);
-
- final Object previousValue = subNodes2[slot];
- if (previousValue instanceof Node) {
- // insert recursively
- assert keys[slot] == null;
- subNodes2[slot] = ((Node) previousValue).put(key, hash, hashBits, value, newValue);
- } else if (keys[slot].equals(key)) {
- // replace the existing entry
- subNodes2[slot] = value;
- } else {
- // hash collision
- final K previousKey = keys[slot];
- final int previousHash = previousKey.hashCode() >>> (TOTAL_HASH_BITS - hashBits);
- Node subNode = newSubNode(hashBits);
- subNode = subNode.put(previousKey, previousHash, hashBits, (V) previousValue, newValue);
- subNode = subNode.put(key, hash, hashBits, value, newValue);
- keys2[slot] = null;
- subNodes2[slot] = subNode;
- }
- return new InnerNode<>(mask, keys2, subNodes2);
- }
-
- private InnerNode putNew(K key, int hash6, int slot, V value) {
- final long mask2 = mask | (1L << hash6);
- final K[] keys2 = insertElement(keys, key, slot);
- final Object[] subNodes2 = insertElement(subNodes, value, slot);
- return new InnerNode<>(mask2, keys2, subNodes2);
- }
-
- @Override
- InnerNode put(K key, int hash, int hashBits, V value, MutableValueInt newValue) {
- final int hash6 = hash & HASH_MASK;
- final int slot = slot(hash6);
-
- if (exists(hash6)) {
- hash >>>= HASH_BITS;
- hashBits -= HASH_BITS;
- return putExisting(key, hash, hashBits, slot, value, newValue);
- } else {
- newValue.value = 1;
- return putNew(key, hash6, slot, value);
- }
- }
-
- private InnerNode removeSlot(int hash6, int slot) {
- final long mask2 = mask & ~(1L << hash6);
- final K[] keys2 = removeArrayElement(keys, slot);
- final Object[] subNodes2 = removeArrayElement(subNodes, slot);
- return new InnerNode<>(mask2, keys2, subNodes2);
- }
-
- @Override
- InnerNode remove(Object key, int hash) {
- final int hash6 = hash & HASH_MASK;
- if (exists(hash6) == false) {
- return this;
- }
- final int slot = slot(hash6);
- final Object previousValue = subNodes[slot];
- if (previousValue instanceof Node) {
- @SuppressWarnings("unchecked")
- final Node subNode = (Node) previousValue;
- final Node removed = subNode.remove(key, hash >>> HASH_BITS);
- if (removed == subNode) {
- // not in sub-nodes
- return this;
- }
- if (removed.isEmpty()) {
- return removeSlot(hash6, slot);
- }
- final K[] keys2 = Arrays.copyOf(keys, keys.length);
- final Object[] subNodes2 = Arrays.copyOf(subNodes, subNodes.length);
- subNodes2[slot] = removed;
- return new InnerNode<>(mask, keys2, subNodes2);
- } else if (keys[slot].equals(key)) {
- // remove entry
- return removeSlot(hash6, slot);
- } else {
- // hash collision, nothing to remove
- return this;
- }
- }
-
- }
-
- private static class EntryIterator implements Iterator> {
-
- private final Deque> entries;
- private final Deque> nodes;
-
- EntryIterator(Node node) {
- entries = new ArrayDeque<>();
- nodes = new ArrayDeque<>();
- node.visit(entries, nodes);
- }
-
- @Override
- public boolean hasNext() {
- return entries.isEmpty() == false || nodes.isEmpty() == false;
- }
-
- @Override
- public Map.Entry next() {
- while (entries.isEmpty()) {
- if (nodes.isEmpty()) {
- throw new NoSuchElementException();
- }
- final Node nextNode = nodes.pop();
- nextNode.visit(entries, nodes);
- }
- return entries.pop();
- }
-
- @Override
- public final void remove() {
- throw new UnsupportedOperationException();
- }
-
- }
-
- private final InnerNode root;
- private final int size;
-
- /**
- * Create a new empty map.
- */
- public CopyOnWriteHashMap() {
- this(new InnerNode(), 0);
- }
-
- private CopyOnWriteHashMap(InnerNode root, int size) {
- this.root = root;
- this.size = size;
- }
-
- @Override
- public boolean containsKey(Object key) {
- // works fine since null values are not supported
- return get(key) != null;
- }
-
- @Override
- public V get(Object key) {
- if (key == null) {
- throw new IllegalArgumentException("null keys are not supported");
- }
- final int hash = key.hashCode();
- return root.get(key, hash);
- }
-
- @Override
- public int size() {
- assert size != 0 || root.isEmpty();
- return size;
- }
-
- /**
- * Associate key
with value
and return a new copy
- * of the hash table. The current hash table is not modified.
- */
- public CopyOnWriteHashMap copyAndPut(K key, V value) {
- if (key == null) {
- throw new IllegalArgumentException("null keys are not supported");
- }
- if (value == null) {
- throw new IllegalArgumentException("null values are not supported");
- }
- final int hash = key.hashCode();
- final MutableValueInt newValue = new MutableValueInt();
- final InnerNode newRoot = root.put(key, hash, TOTAL_HASH_BITS, value, newValue);
- final int newSize = size + newValue.value;
- return new CopyOnWriteHashMap<>(newRoot, newSize);
- }
-
- /**
- * Same as {@link #copyAndPut(Object, Object)} but for an arbitrary number of entries.
- */
- public CopyOnWriteHashMap copyAndPutAll(Map extends K, ? extends V> other) {
- return copyAndPutAll(other.entrySet());
- }
-
- public CopyOnWriteHashMap copyAndPutAll(Iterable> entries) {
- CopyOnWriteHashMap result = this;
- for (Entry entry : entries) {
- result = result.copyAndPut(entry.getKey(), entry.getValue());
- }
- return result;
- }
-
- public CopyOnWriteHashMap copyAndPutAll(Stream> entries) {
- return copyAndPutAll(entries::iterator);
- }
-
- /**
- * Remove the given key from this map. The current hash table is not modified.
- */
- public CopyOnWriteHashMap copyAndRemove(Object key) {
- if (key == null) {
- throw new IllegalArgumentException("null keys are not supported");
- }
- final int hash = key.hashCode();
- final InnerNode newRoot = root.remove(key, hash);
- if (root == newRoot) {
- return this;
- } else {
- return new CopyOnWriteHashMap<>(newRoot, size - 1);
- }
- }
-
- /**
- * Same as {@link #copyAndRemove(Object)} but for an arbitrary number of entries.
- */
- public CopyOnWriteHashMap copyAndRemoveAll(Collection> keys) {
- CopyOnWriteHashMap result = this;
- for (Object key : keys) {
- result = result.copyAndRemove(key);
- }
- return result;
- }
-
- @Override
- public Set> entrySet() {
- return new AbstractSet>() {
-
- @Override
- public Iterator> iterator() {
- return new EntryIterator<>(root);
- }
-
- @Override
- public boolean contains(Object o) {
- if (o == null || (o instanceof Map.Entry) == false) {
- return false;
- }
- Map.Entry, ?> entry = (java.util.Map.Entry, ?>) o;
- return entry.getValue().equals(CopyOnWriteHashMap.this.get(entry.getKey()));
- }
-
- @Override
- public int size() {
- return CopyOnWriteHashMap.this.size();
- }
- };
- }
-
-}
diff --git a/server/src/test/java/org/elasticsearch/common/collect/CopyOnWriteHashMapTests.java b/server/src/test/java/org/elasticsearch/common/collect/CopyOnWriteHashMapTests.java
deleted file mode 100644
index 1a7f2f9608450..0000000000000
--- a/server/src/test/java/org/elasticsearch/common/collect/CopyOnWriteHashMapTests.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-package org.elasticsearch.common.collect;
-
-import org.elasticsearch.test.ESTestCase;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static java.util.Collections.emptyMap;
-
-public class CopyOnWriteHashMapTests extends ESTestCase {
-
- private static class O {
-
- private final int value, hashCode;
-
- O(int value, int hashCode) {
- super();
- this.value = value;
- this.hashCode = hashCode;
- }
-
- @Override
- public int hashCode() {
- return hashCode;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null || (obj instanceof O) == false) {
- return false;
- }
- return value == ((O) obj).value;
- }
- }
-
- public void testDuel() {
- final int iters = scaledRandomIntBetween(2, 5);
- for (int iter = 0; iter < iters; ++iter) {
- final int valueBits = randomIntBetween(1, 30);
- final int hashBits = randomInt(valueBits);
- // we compute the total number of ops based on the bits of the hash
- // since the test is much heavier when few bits are used for the hash
- final int numOps = randomInt(10 + hashBits * 100);
-
- Map ref = new HashMap<>();
- CopyOnWriteHashMap map = new CopyOnWriteHashMap<>();
- assertEquals(ref, map);
- final int hashBase = randomInt();
- for (int i = 0; i < numOps; ++i) {
- final int v = randomInt(1 << valueBits);
- final int h = (v & ((1 << hashBits) - 1)) ^ hashBase;
- O key = new O(v, h);
-
- Map newRef = new HashMap<>(ref);
- final CopyOnWriteHashMap newMap;
-
- if (randomBoolean()) {
- // ADD
- Integer value = v;
- newRef.put(key, value);
- newMap = map.copyAndPut(key, value);
- } else {
- // REMOVE
- final Integer removed = newRef.remove(key);
- newMap = map.copyAndRemove(key);
- if (removed == null) {
- assertSame(map, newMap);
- }
- }
-
- assertEquals(ref, map); // make sure that the old copy has not been modified
- assertEquals(newRef, newMap);
- assertEquals(newMap, newRef);
-
- ref = newRef;
- map = newMap;
- }
- assertEquals(ref, CopyOnWriteHashMap.copyOf(ref));
- assertEquals(emptyMap(), CopyOnWriteHashMap.copyOf(ref).copyAndRemoveAll(ref.keySet()));
- }
- }
-
- public void testCollision() {
- CopyOnWriteHashMap map = new CopyOnWriteHashMap<>();
- map = map.copyAndPut(new O(3, 0), 2);
- assertEquals((Integer) 2, map.get(new O(3, 0)));
- assertNull(map.get(new O(5, 0)));
-
- map = map.copyAndPut(new O(5, 0), 5);
- assertEquals((Integer) 2, map.get(new O(3, 0)));
- assertEquals((Integer) 5, map.get(new O(5, 0)));
-
- map = map.copyAndRemove(new O(3, 0));
- assertNull(map.get(new O(3, 0)));
- assertEquals((Integer) 5, map.get(new O(5, 0)));
-
- map = map.copyAndRemove(new O(5, 0));
- assertNull(map.get(new O(3, 0)));
- assertNull(map.get(new O(5, 0)));
- }
-
- public void testUnsupportedAPIs() {
- try {
- new CopyOnWriteHashMap<>().put("a", "b");
- fail();
- } catch (UnsupportedOperationException e) {
- // expected
- }
-
- try {
- new CopyOnWriteHashMap<>().copyAndPut("a", "b").remove("a");
- fail();
- } catch (UnsupportedOperationException e) {
- // expected
- }
- }
-
- public void testUnsupportedValues() {
- try {
- new CopyOnWriteHashMap<>().copyAndPut("a", null);
- fail();
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- new CopyOnWriteHashMap<>().copyAndPut(null, "b");
- fail();
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
-}
diff --git a/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/AutoFollowCoordinator.java b/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/AutoFollowCoordinator.java
index 2b7c8688ff8b1..0ee1da85eb990 100644
--- a/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/AutoFollowCoordinator.java
+++ b/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/AutoFollowCoordinator.java
@@ -26,7 +26,6 @@
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.routing.IndexRoutingTable;
import org.elasticsearch.cluster.service.ClusterService;
-import org.elasticsearch.common.collect.CopyOnWriteHashMap;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.component.Lifecycle;
import org.elasticsearch.common.settings.Settings;
@@ -258,13 +257,13 @@ void updateAutoFollowers(ClusterState followerClusterState) {
this.patterns = Set.copyOf(autoFollowMetadata.getPatterns().keySet());
- final CopyOnWriteHashMap autoFollowersCopy = CopyOnWriteHashMap.copyOf(this.autoFollowers);
+ final Map currentAutoFollowers = Map.copyOf(this.autoFollowers);
Set newRemoteClusters = autoFollowMetadata.getPatterns()
.values()
.stream()
.filter(AutoFollowPattern::isActive)
.map(AutoFollowPattern::getRemoteCluster)
- .filter(remoteCluster -> autoFollowersCopy.containsKey(remoteCluster) == false)
+ .filter(remoteCluster -> currentAutoFollowers.containsKey(remoteCluster) == false)
.collect(Collectors.toSet());
Map newAutoFollowers = Maps.newMapWithExpectedSize(newRemoteClusters.size());
@@ -344,7 +343,7 @@ public void clusterStateProcessed(ClusterState oldState, ClusterState newState)
}
List removedRemoteClusters = new ArrayList<>();
- for (Map.Entry entry : autoFollowersCopy.entrySet()) {
+ for (Map.Entry entry : currentAutoFollowers.entrySet()) {
String remoteCluster = entry.getKey();
AutoFollower autoFollower = entry.getValue();
boolean exist = autoFollowMetadata.getPatterns()
@@ -365,7 +364,11 @@ public void clusterStateProcessed(ClusterState oldState, ClusterState newState)
}
}
assert assertNoOtherActiveAutoFollower(newAutoFollowers);
- this.autoFollowers = autoFollowersCopy.copyAndPutAll(newAutoFollowers).copyAndRemoveAll(removedRemoteClusters);
+
+ Map updatedFollowers = new HashMap<>(currentAutoFollowers);
+ updatedFollowers.putAll(newAutoFollowers);
+ removedRemoteClusters.forEach(updatedFollowers.keySet()::remove);
+ this.autoFollowers = Collections.unmodifiableMap(updatedFollowers);
}
private boolean assertNoOtherActiveAutoFollower(Map newAutoFollowers) {