org.apache.cassandra.locator.AbstractReplicaCollection Maven / Gradle / Ivy
Show all versions of cassandra-all Show documentation
/*
* 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> implements ReplicaCollection
{
protected static final ReplicaList EMPTY_LIST = new ReplicaList(); // since immutable, can safely return this to avoid megamorphic callsites
public static , B extends Builder> Collector collector(Set characteristics, Supplier supplier)
{
return new Collector()
{
private final BiConsumer accumulator = Builder::add;
private final BinaryOperator combiner = (a, b) -> { a.addAll(b); return a; };
private final Function finisher = Builder::build;
public Supplier supplier() { return supplier; }
public BiConsumer accumulator() { return accumulator; }
public BinaryOperator combiner() { return combiner; }
public Function finisher() { return finisher; }
public Set 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
{
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 comparator)
{
Replica[] copy = Arrays.copyOfRange(contents, begin, begin + size);
Arrays.sort(copy, comparator);
return new ReplicaList(copy, 0, copy.length);
}
public Stream 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 iterator()
{
return new Iterator()
{
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 Iterator transformIterator(Function function)
{
return new Iterator()
{
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 filterIterator(Predicate predicate, int limit)
{
return new Iterator()
{
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 extends AbstractMap
{
private final Function 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 map;
private Set keySet;
private Set> entrySet;
abstract class AbstractImmutableSet extends AbstractSet
{
@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
{
@Override
public boolean contains(Object o) { return containsKey(o); }
@Override
public Iterator iterator() { return list.transformIterator(toKey); }
}
class EntrySet extends AbstractImmutableSet>
{
@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> iterator()
{
return list.transformIterator(r -> new SimpleImmutableEntry<>(toKey.apply(r), r));
}
}
public ReplicaMap(ReplicaList list, Function 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 toKey, ObjectIntHashMap 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 keySet()
{
Set ks = keySet;
if (ks == null)
keySet = ks = new KeySet();
return ks;
}
@Override
public Set> entrySet()
{
Set> es = entrySet;
if (es == null)
entrySet = es = new EntrySet();
return es;
}
public int size()
{
return list.size();
}
ReplicaMap 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 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 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 predicate)
{
return filter(predicate, Integer.MAX_VALUE);
}
/** see {@link ReplicaCollection#filter(Predicate, int)}*/
public final C filter(Predicate 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 filterLazily(Predicate predicate)
{
return filterLazily(predicate, Integer.MAX_VALUE);
}
/** see {@link ReplicaCollection#filterLazily(Predicate,int)}*/
public final Iterable filterLazily(Predicate predicate, int limit)
{
return () -> list.filterIterator(predicate, limit);
}
/** see {@link ReplicaCollection#sorted(Comparator)}*/
public final C sorted(Comparator 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 iterator()
{
return list.iterator();
}
public final Stream stream() { return list.stream(); }
/**
*
* 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}.
*
*
* 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.
*
*/
public final boolean equals(Object o)
{
throw new UnsupportedOperationException("AbstractReplicaCollection equals unsupported");
}
/**
*
* 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}.
*
*
* Don't implement this and pick one behavior over the other.
*
*/
public final int hashCode()
{
throw new UnsupportedOperationException("AbstractReplicaCollection hashCode unsupported");
}
@Override
public final String toString()
{
return Iterables.toString(list);
}
static > C concat(C replicas, C extraReplicas, Builder.Conflict ignoreConflicts)
{
if (extraReplicas.isEmpty())
return replicas;
if (replicas.isEmpty())
return extraReplicas;
Builder builder = replicas.newBuilder(replicas.size() + extraReplicas.size());
builder.addAll(replicas, Builder.Conflict.NONE);
builder.addAll(extraReplicas, ignoreConflicts);
return builder.build();
}
}