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

io.permazen.util.AbstractMultiNavigableSet Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.util;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.Objects;

/**
 * Support superclass for {@link NavigableSet} implementations that join together multiple other {@link NavigableSet}s
 * having equivalent {@link Comparator}s and for which {@link #size} is an expensive operation.
 *
 * @param  element type
 */
abstract class AbstractMultiNavigableSet extends AbstractNavigableSet {

    protected final ArrayList> list;
    protected final Comparator comparator;

    /**
     * Convenience constructor for the case where there are no lower or upper bounds.
     *
     * @param sets sets to be combined; assumed to not contain any {@link EmptyNavigableSet}s
     * @throws IllegalArgumentException if the {@link NavigableSet}s in {@code sets} do not have equal {@link Comparator}s
     */
    protected AbstractMultiNavigableSet(Collection> sets) {
        this(sets, AbstractMultiNavigableSet.getComparator(sets), new Bounds<>());
    }

    /**
     * Primary constructor.
     *
     * @param sets sets to be combined; assumed to not contain any {@link EmptyNavigableSet}s
     * @param comparator common comparator
     * @param bounds range restriction
     * @throws IllegalArgumentException if the {@link NavigableSet}s in {@code sets} do not have equal {@link Comparator}s
     * @throws IllegalArgumentException if {@code sets}, or any {@link NavigableSet} therein, is null
     * @throws IllegalArgumentException if {@code bounds} is null
     */
    protected AbstractMultiNavigableSet(Collection> sets,
      Comparator comparator, Bounds bounds) {
        super(bounds);
        Preconditions.checkArgument(sets != null, "null sets");
        this.list = Lists.newArrayList(sets);
        Preconditions.checkArgument(!this.list.contains(null), "null set");
        this.comparator = comparator;
    }

    @Override
    public Comparator comparator() {
        return this.comparator;
    }

    /**
     * Get and verify a common {@link Comparator} (possibly null).
     */
    private static  Comparator getComparator(Collection> sets) {

        // Empty sets should have already been filtered out
        assert sets.stream().noneMatch(EmptyNavigableSet.class::isInstance);

        // Get the first comparator
        final Iterator> i = sets.iterator();
        if (!i.hasNext())
            return null;
        final Comparator comparator = AbstractMultiNavigableSet.normalize(i.next().comparator());

        // Verify remaining comparators are equal to it
        while (i.hasNext()) {
            final Comparator comparator2 = AbstractMultiNavigableSet.normalize(i.next().comparator());
            if (!Objects.equals(comparator2, comparator)) {
                throw new IllegalArgumentException(String.format(
                  "sets have unequal comparators: %s != %s", comparator, comparator2));
            }
        }

        // Done
        return comparator;
    }

    // Convert comparators that sort naturally to null for comparison purposes.
    private static  Comparator normalize(Comparator comparator) {
        if (comparator instanceof NaturalSortAware && ((NaturalSortAware)comparator).sortsNaturally())
            return null;
        return comparator;
    }

    @Override
    protected final NavigableSet createSubSet(boolean reverse, Bounds newBounds) {

        // Apply bounds to all sets
        final Comparator nonNullComparator = NavigableSets.getComparator(this.comparator(), false);
        final ArrayList> newList = new ArrayList<>(this.list.size());
        for (NavigableSet set : this.list) {

            // Reverse set if needed
            if (reverse)
                set = set.descendingSet();

            // Apply lower bound
            if (newBounds.getLowerBoundType() != BoundType.NONE) {
                try {
                    set = set.tailSet(newBounds.getLowerBound(), newBounds.getLowerBoundType().isInclusive());
                } catch (IllegalArgumentException e) {

                    // Bound is out of range; it must be either too low or too high
                    if (set.isEmpty())
                        set = new EmptyNavigableSet<>(this.comparator());
                    else {
                        final int diff = nonNullComparator.compare(newBounds.getLowerBound(), set.last());
                        if (diff > 0)
                            set = new EmptyNavigableSet<>(this.comparator());
                        else if (diff == 0) {
                            if (newBounds.getLowerBoundType().isInclusive())
                                throw e;                            // this indicates faulty logic in the underlying set
                            set = new EmptyNavigableSet<>(this.comparator());
                        }
                    }
                }
            }

            // Apply upper bound
            if (newBounds.getUpperBoundType() != BoundType.NONE) {
                try {
                    set = set.headSet(newBounds.getUpperBound(), newBounds.getUpperBoundType().isInclusive());
                } catch (IllegalArgumentException e) {

                    // Bound is out of range; it must be either too low or too high
                    if (set.isEmpty())
                        set = new EmptyNavigableSet<>(this.comparator());
                    else {
                        final int diff = nonNullComparator.compare(newBounds.getUpperBound(), set.first());
                        if (diff < 0)
                            set = new EmptyNavigableSet<>(this.comparator());
                        else if (diff == 0) {
                            if (newBounds.getUpperBoundType().isInclusive())
                                throw e;                            // this indicates faulty logic in the underlying set
                            set = new EmptyNavigableSet<>(this.comparator());
                        }
                    }
                }
            }

            // Add restricted set
            newList.add(set);
        }

        // Create sub-set
        return this.createSubSet(reverse, newBounds, newList);
    }

    /**
     * Create a (possibly reversed) view of this instance with (possibly) tighter lower and/or upper bounds.
     * The {@code newBounds} are consistent with the new ordering (i.e., reversed if {@code reverse} is true)
     * and have already been range-checked against {@linkplain #bounds this instance's current bounds}.
     *
     * @param reverse whether the new set's ordering should be reversed relative to this instance's ordering
     * @param newBounds new bounds
     * @param list list of this instance's nested sets, restricted to {@code newBounds}
     * @throws IllegalArgumentException if {@code newBounds} is null
     */
    protected abstract NavigableSet createSubSet(boolean reverse, Bounds newBounds, List> list);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy