All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cassandra.locator.AbstractReplicaCollection Maven / Gradle / Ivy

Go to download

The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.

There is a newer version: 5.0.2
Show newest version
/*
 * 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(); } }