blob: 2ec555cc55522b6a95789f5bf2a2e40148b530cb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cassandra.locator;
import com.carrotsearch.hppc.ObjectIntHashMap;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;
/**
* A collection like class for Replica objects. Since the Replica class contains inetaddress, range, and
* transient replication status, basic contains and remove methods can be ambiguous. Replicas forces you
* to be explicit about what you're checking the container for, or removing from it.
*/
public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollection<C>> implements ReplicaCollection<C>
{
protected static final ReplicaList EMPTY_LIST = new ReplicaList(); // since immutable, can safely return this to avoid megamorphic callsites
public static <C extends ReplicaCollection<C>, B extends Builder<C>> Collector<Replica, B, C> collector(Set<Collector.Characteristics> characteristics, Supplier<B> supplier)
{
return new Collector<Replica, B, C>()
{
private final BiConsumer<B, Replica> accumulator = Builder::add;
private final BinaryOperator<B> combiner = (a, b) -> { a.addAll(b); return a; };
private final Function<B, C> finisher = Builder::build;
public Supplier<B> supplier() { return supplier; }
public BiConsumer<B, Replica> accumulator() { return accumulator; }
public BinaryOperator<B> combiner() { return combiner; }
public Function<B, C> finisher() { return finisher; }
public Set<Characteristics> characteristics() { return characteristics; }
};
}
/**
* A simple list with no comodification checks and immutability by default (only append permitted, and only one initial copy)
* this permits us to reduce the amount of garbage generated, by not wrapping iterators or unnecessarily copying
* and reduces the amount of indirection necessary, as well as ensuring monomorphic callsites
*/
protected static class ReplicaList implements Iterable<Replica>
{
private static final Replica[] EMPTY = new Replica[0];
Replica[] contents;
int begin, size;
public ReplicaList() { this(0); }
public ReplicaList(int capacity) { contents = capacity == 0 ? EMPTY : new Replica[capacity]; }
public ReplicaList(Replica[] contents, int begin, int size) { this.contents = contents; this.begin = begin; this.size = size; }
public boolean isSubList(ReplicaList subList)
{
return subList.contents == contents;
}
public Replica get(int index)
{
if (index > size)
throw new IndexOutOfBoundsException();
return contents[begin + index];
}
public void add(Replica replica)
{
// can only add to full array - if we have sliced it, we must be a snapshot
if (begin != 0)
throw new IllegalStateException();
if (size == contents.length)
{
int newSize;
if (size < 3) newSize = 3;
else if (size < 9) newSize = 9;
else newSize = size * 2;
contents = Arrays.copyOf(contents, newSize);
}
contents[size++] = replica;
}
public int size()
{
return size;
}
public boolean isEmpty()
{
return size == 0;
}
public ReplicaList subList(int begin, int end)
{
if (end > size || begin > end) throw new IndexOutOfBoundsException();
return new ReplicaList(contents, this.begin + begin, end - begin);
}
public ReplicaList sorted(Comparator<Replica> comparator)
{
Replica[] copy = Arrays.copyOfRange(contents, begin, begin + size);
Arrays.sort(copy, comparator);
return new ReplicaList(copy, 0, copy.length);
}
public Stream<Replica> stream()
{
return Arrays.stream(contents, begin, begin + size);
}
// we implement our own iterator, because it is trivial to do so, and in monomorphic call sites
// will compile down to almost optimal indexed for loop
@Override
public Iterator<Replica> iterator()
{
return new Iterator<Replica>()
{
final int end = begin + size;
int i = begin;
@Override
public boolean hasNext()
{
return i < end;
}
@Override
public Replica next()
{
if (!hasNext()) throw new IllegalStateException();
return contents[i++];
}
};
}
// we implement our own iterator, because it is trivial to do so, and in monomorphic call sites
// will compile down to almost optimal indexed for loop
public <K> Iterator<K> transformIterator(Function<Replica, K> function)
{
return new Iterator<K>()
{
final int end = begin + size;
int i = begin;
@Override
public boolean hasNext()
{
return i < end;
}
@Override
public K next()
{
return function.apply(contents[i++]);
}
};
}
// we implement our own iterator, because it is trivial to do so, and in monomorphic call sites
// will compile down to almost optimal indexed for loop
// in this case, especially, it is impactful versus Iterables.limit(Iterables.filter())
private Iterator<Replica> filterIterator(Predicate<Replica> predicate, int limit)
{
return new Iterator<Replica>()
{
final int end = begin + size;
int next = begin;
int count = 0;
{ updateNext(); }
void updateNext()
{
if (count == limit) next = end;
while (next < end && !predicate.test(contents[next]))
++next;
++count;
}
@Override
public boolean hasNext()
{
return next < end;
}
@Override
public Replica next()
{
if (!hasNext()) throw new IllegalStateException();
Replica result = contents[next++];
updateNext();
return result;
}
};
}
@VisibleForTesting
public boolean equals(Object to)
{
if (to == null || to.getClass() != ReplicaList.class)
return false;
ReplicaList that = (ReplicaList) to;
if (this.size != that.size) return false;
return Iterables.elementsEqual(this, that);
}
}
/**
* A simple map that ensures the underlying list's iteration order is maintained, and can be shared with
* subLists (either produced via subList, or via filter that naturally produced a subList).
* This permits us to reduce the amount of garbage generated, by not unnecessarily copying,
* reduces the amount of indirection necessary, as well as ensuring monomorphic callsites.
* The underlying map is also more efficient, particularly for such small collections as we typically produce.
*/
protected static class ReplicaMap<K> extends AbstractMap<K, Replica>
{
private final Function<Replica, K> toKey;
private final ReplicaList list;
// we maintain a map of key -> index in our list; this lets us share with subLists (or between Builder and snapshots)
// since we only need to corroborate that the list index we find is within the bounds of our list
// (if not, it's a shared map, and the key only occurs in one of our ancestors)
private final ObjectIntHashMap<K> map;
private Set<K> keySet;
private Set<Entry<K, Replica>> entrySet;
abstract class AbstractImmutableSet<T> extends AbstractSet<T>
{
@Override
public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); }
@Override
public boolean remove(Object o) { throw new UnsupportedOperationException(); }
@Override
public int size() { return list.size(); }
}
class KeySet extends AbstractImmutableSet<K>
{
@Override
public boolean contains(Object o) { return containsKey(o); }
@Override
public Iterator<K> iterator() { return list.transformIterator(toKey); }
}
class EntrySet extends AbstractImmutableSet<Entry<K, Replica>>
{
@Override
public boolean contains(Object o)
{
Preconditions.checkNotNull(o);
if (!(o instanceof Entry<?, ?>)) return false;
return Objects.equals(get(((Entry) o).getKey()), ((Entry) o).getValue());
}
@Override
public Iterator<Entry<K, Replica>> iterator()
{
return list.transformIterator(r -> new SimpleImmutableEntry<>(toKey.apply(r), r));
}
}
public ReplicaMap(ReplicaList list, Function<Replica, K> toKey)
{
// 8*0.65 => RF=5; 16*0.65 ==> RF=10
// use list capacity if empty, otherwise use actual list size
this.toKey = toKey;
this.map = new ObjectIntHashMap<>(list.size == 0 ? list.contents.length : list.size, 0.65f);
this.list = list;
for (int i = list.begin ; i < list.begin + list.size ; ++i)
{
boolean inserted = internalPutIfAbsent(list.contents[i], i);
assert inserted;
}
}
public ReplicaMap(ReplicaList list, Function<Replica, K> toKey, ObjectIntHashMap<K> map)
{
this.toKey = toKey;
this.list = list;
this.map = map;
}
// to be used only by subclasses of AbstractReplicaCollection
boolean internalPutIfAbsent(Replica replica, int index)
{
K key = toKey.apply(replica);
int otherIndex = map.put(key, index + 1);
if (otherIndex == 0)
return true;
map.put(key, otherIndex);
return false;
}
@Override
public boolean containsKey(Object key)
{
Preconditions.checkNotNull(key);
return get((K)key) != null;
}
public Replica get(Object key)
{
Preconditions.checkNotNull(key);
int index = map.get((K)key) - 1;
// since this map can be shared between sublists (or snapshots of mutables)
// we have to first corroborate that the index we've found is actually within our list's bounds
if (index < list.begin || index >= list.begin + list.size)
return null;
return list.contents[index];
}
@Override
public Replica remove(Object key)
{
throw new UnsupportedOperationException();
}
@Override
public Set<K> keySet()
{
Set<K> ks = keySet;
if (ks == null)
keySet = ks = new KeySet();
return ks;
}
@Override
public Set<Entry<K, Replica>> entrySet()
{
Set<Entry<K, Replica>> es = entrySet;
if (es == null)
entrySet = es = new EntrySet();
return es;
}
public int size()
{
return list.size();
}
ReplicaMap<K> forSubList(ReplicaList subList)
{
assert subList.contents == list.contents;
return new ReplicaMap<>(subList, toKey, map);
}
}
protected final ReplicaList list;
AbstractReplicaCollection(ReplicaList list)
{
this.list = list;
}
/**
* construct a new Builder of our own type, so that we can concatenate
* TODO: this isn't terribly pretty, but we need sometimes to select / merge two Endpoints of unknown type;
*/
public abstract Builder<C> newBuilder(int initialCapacity);
// return a new "sub-collection" with some sub-selection of the contents of this collection
abstract C snapshot(ReplicaList newList);
// return this object, if it is an immutable snapshot, otherwise returns a copy with these properties
public abstract C snapshot();
/** see {@link ReplicaCollection#subList(int, int)}*/
public final C subList(int start, int end)
{
if (start == 0 && end == size())
return snapshot();
ReplicaList subList;
if (start == end) subList = EMPTY_LIST;
else subList = list.subList(start, end);
return snapshot(subList);
}
/** see {@link ReplicaCollection#count(Predicate)}*/
public int count(Predicate<Replica> predicate)
{
int count = 0;
for (int i = 0 ; i < list.size() ; ++i)
if (predicate.test(list.get(i)))
++count;
return count;
}
/** see {@link ReplicaCollection#filter(Predicate)}*/
public final C filter(Predicate<Replica> predicate)
{
return filter(predicate, Integer.MAX_VALUE);
}
/** see {@link ReplicaCollection#filter(Predicate, int)}*/
public final C filter(Predicate<Replica> predicate, int limit)
{
if (isEmpty())
return snapshot();
ReplicaList copy = null;
int beginRun = -1, endRun = -1;
int i = 0;
for (; i < list.size() ; ++i)
{
Replica replica = list.get(i);
if (predicate.test(replica))
{
if (copy != null)
copy.add(replica);
else if (beginRun < 0)
beginRun = i;
else if (endRun > 0)
{
copy = new ReplicaList(Math.min(limit, (list.size() - i) + (endRun - beginRun)));
for (int j = beginRun ; j < endRun ; ++j)
copy.add(list.get(j));
copy.add(list.get(i));
}
if (--limit == 0)
{
++i;
break;
}
}
else if (beginRun >= 0 && endRun < 0)
endRun = i;
}
if (beginRun < 0)
beginRun = endRun = 0;
if (endRun < 0)
endRun = i;
if (copy == null)
return subList(beginRun, endRun);
return snapshot(copy);
}
/** see {@link ReplicaCollection#filterLazily(Predicate)}*/
public final Iterable<Replica> filterLazily(Predicate<Replica> predicate)
{
return filterLazily(predicate, Integer.MAX_VALUE);
}
/** see {@link ReplicaCollection#filterLazily(Predicate,int)}*/
public final Iterable<Replica> filterLazily(Predicate<Replica> predicate, int limit)
{
return () -> list.filterIterator(predicate, limit);
}
/** see {@link ReplicaCollection#sorted(Comparator)}*/
public final C sorted(Comparator<Replica> comparator)
{
return snapshot(list.sorted(comparator));
}
public final Replica get(int i)
{
return list.get(i);
}
public final int size()
{
return list.size();
}
public final boolean isEmpty()
{
return list.isEmpty();
}
public final Iterator<Replica> iterator()
{
return list.iterator();
}
public final Stream<Replica> stream() { return list.stream(); }
/**
* <p>
* It's not clear whether {@link AbstractReplicaCollection} should implement the order sensitive {@link Object#equals(Object) equals}
* of {@link java.util.List} or the order oblivious {@link Object#equals(Object) equals} of {@link java.util.Set}. We never rely on equality
* in the database so rather then leave in a potentially surprising implementation we have it throw {@link UnsupportedOperationException}.
* </p>
* <p>
* Don't implement this and pick one behavior over the other. If you want equality you can static import {@link com.google.common.collect.Iterables#elementsEqual(Iterable, Iterable)}
* and use that to get order sensitive equals.
* </p>
*/
public final boolean equals(Object o)
{
throw new UnsupportedOperationException("AbstractReplicaCollection equals unsupported");
}
/**
* <p>
* It's not clear whether {@link AbstractReplicaCollection} should implement the order sensitive {@link Object#hashCode() hashCode}
* of {@link java.util.List} or the order oblivious {@link Object#hashCode() equals} of {@link java.util.Set}. We never rely on hashCode
* in the database so rather then leave in a potentially surprising implementation we have it throw {@link UnsupportedOperationException}.
* </p>
* <p>
* Don't implement this and pick one behavior over the other.
* </p>
*/
public final int hashCode()
{
throw new UnsupportedOperationException("AbstractReplicaCollection hashCode unsupported");
}
@Override
public final String toString()
{
return Iterables.toString(list);
}
static <C extends AbstractReplicaCollection<C>> C concat(C replicas, C extraReplicas, Builder.Conflict ignoreConflicts)
{
if (extraReplicas.isEmpty())
return replicas;
if (replicas.isEmpty())
return extraReplicas;
Builder<C> builder = replicas.newBuilder(replicas.size() + extraReplicas.size());
builder.addAll(replicas, Builder.Conflict.NONE);
builder.addAll(extraReplicas, ignoreConflicts);
return builder.build();
}
}