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

cdc.applic.expressions.content.AbstractRangeSet Maven / Gradle / Ivy

There is a newer version: 0.13.3
Show newest version
package cdc.applic.expressions.content;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;

import cdc.graphs.PartialOrderPosition;
import cdc.util.lang.Checks;
import cdc.util.lang.CollectionUtils;

/**
 * Base abstract class of sets based on {@link Domain}.
 *
 * @author Damien Carbonne
 * @param  The value type.
 * @param  The range type.
 * @param  The set type.
 */
public abstract class AbstractRangeSet,
                                       R extends AbstractRange,
                                       S extends AbstractRangeSet>
        extends AbstractSItemSet implements CheckedSet, Comparable {
    /** Sorted non empty ranges. */
    private final List ranges;

    /** The associated domain. */
    private final Domain domain;

    protected abstract S create(List ranges);

    /**
     * @return The empty set.
     */
    public abstract S empty();

    /**
     * @return This set.
     */
    protected abstract S self();

    /**
     * Converts an {@link SItemSet} as a {@code S} set.
     *
     * @param other The set to convert.
     * @return The conversion of {@code other} as an {@code S} set.
     */
    protected abstract S adapt(SItemSet other);

    protected AbstractRangeSet(Domain domain) {
        this.domain = domain;
        this.ranges = Collections.emptyList();
    }

    protected AbstractRangeSet(Domain domain,
                               Collection ranges) {
        this.domain = domain;
        if (ranges.isEmpty()) {
            this.ranges = Collections.emptyList();
        } else {
            final List list = new ArrayList<>();
            for (final R range : ranges) {
                unionInternal(list, range);
            }
            this.ranges = Collections.unmodifiableList(list);
        }
    }

    // /**
    // * Checks that a list of ranges is sorted.
    // *
    // * @param list The list of ranges to check.
    // */
    // private void check(List list) {
    // if (list.size() > 1) {
    // for (int index = 0; index < list.size() - 1; index++) {
    // final R r1 = list.get(index);
    // final R r2 = list.get(index + 1);
    // Checks.assertTrue(r1.compareTo(r2) < 0, "Implementation error (" + list + ")");
    // }
    // }
    // }

    private int binarySearch(List ranges,
                             V value) {
        return Collections.binarySearch(ranges, domain.create(value));
    }

    /**
     * Adds a range (possibly empty) to a sorted list (possibly empty) of ranges.
     *
     * @param sorted The sorted list of ranges.
     * @param range The range to add.
     */
    private void unionInternal(List sorted,
                               R range) {
        if (!range.isEmpty()) {
            if (sorted.isEmpty()) {
                sorted.add(range);
            } else {
                final int size = sorted.size();
                // Index of the range that contains min
                final int first = binarySearch(sorted, range.getMin());
                if (first < 0
                        && -(first + 1) == size
                        && !domain.adjoint(sorted.get(size - 1).getMax(), range.getMin())) {
                    // 1) No range contains min
                    // 2) [min~min] should be created in last position
                    // 3) min is not joined with last range
                    // Conclusion: New range is strictly after all ranges
                    sorted.add(range);
                } else {
                    // Index of the range that contains max
                    final int last = binarySearch(sorted, range.getMax());
                    if (last < 0
                            && -(last + 1) == 0
                            && !domain.adjoint(sorted.get(0).getMin(), range.getMax())) {
                        // 1) No range contains max
                        // 2) [max~max] should be inserted in first position
                        // 3) max is not joined with first range
                        // Conclusion: New range is strictly before all ranges
                        sorted.add(0, range);
                    } else {
                        // New range is overlapping / joining existing ranges

                        // Index of the first merged range
                        final int efirst;
                        if (first >= 0) {
                            // Range that contains min exists
                            efirst = first;
                        } else {
                            // Range that contains min does not exist
                            final int index = -(first + 1);

                            if (index > 0 && domain.adjoint(sorted.get(index - 1).getMax(), range.getMin())) {
                                efirst = index - 1;
                            } else {
                                efirst = index;
                            }
                        }

                        // Index of the last merged range
                        final int elast;
                        if (last >= 0) {
                            elast = last;
                        } else {
                            final int index = -(last + 1);
                            if (index < size && domain.adjoint(sorted.get(index).getMin(), range.getMax())) {
                                elast = index;
                            } else {
                                elast = index - 1;
                            }
                        }

                        // All existing ranges between efirst (inclusive) and
                        // elast (inclusive) must be merged into one range
                        // and replaced by this merged range
                        R merged = range;
                        for (int index = efirst; index <= elast; index++) {
                            merged = RangeUtils.union(domain, merged, sorted.get(index));
                        }
                        sorted.subList(efirst, elast + 1).clear();
                        sorted.add(efirst, merged);
                    }
                }
            }
            // check(sorted);
        }
    }

    /**
     * Removes a non empty range from a non empty sorted list of ranges.
     *
     * @param sorted The sorted list of ranges.
     * @param range The range to remove.
     */
    private void removeInternal(List sorted,
                                R range) {
        // Checks.assertTrue(!sorted.isEmpty(), "sorted is empty");
        // Checks.assertTrue(!range.isEmpty(), "range is empty");

        final int size = sorted.size();
        // Index of the range that contains min
        final int first = binarySearch(sorted, range.getMin());
        if (first < 0
                && -(first + 1) == size) {
            // 1) No range contains min
            // 2) [min~min] should be created in last position
            // Conclusion: Nothing to remove
        } else {
            // Index of the range that contains max
            final int last = binarySearch(sorted, range.getMax());
            if (last < 0
                    && -(last + 1) == 0) {
                // 1) No range contains max
                // 2) [max~max] should be inserted in first position
                // Conclusion: New range is strictly before all ranges
            } else {
                // range is overlapping / joining existing ranges

                // Index of the first modified range
                final int efirst;
                if (first >= 0) {
                    // Range that contains min exists
                    efirst = first;
                } else {
                    // Range that contains min does not exist
                    final int index = -(first + 1);
                    efirst = index;
                }

                // Index of the last modified range
                final int elast;
                if (last >= 0) {
                    elast = last;
                } else {
                    final int index = -(last + 1);
                    elast = index - 1;
                }

                if (efirst == elast) {
                    final R[] tmp = RangeUtils.remove(domain, sorted.get(efirst), range);
                    if (tmp.length == 0) {
                        sorted.remove(efirst);
                    } else if (tmp.length == 1) {
                        sorted.set(efirst, tmp[0]);
                    } else {
                        sorted.set(efirst, tmp[0]);
                        sorted.add(efirst + 1, tmp[1]);
                    }
                } else if (efirst < elast) {
                    sorted.set(efirst, RangeUtils.removeSimple(domain, sorted.get(efirst), range));
                    sorted.set(elast, RangeUtils.removeSimple(domain, sorted.get(elast), range));
                    if (elast - efirst > 1) {
                        sorted.subList(efirst + 1, elast).clear();
                    }
                    if (sorted.get(efirst + 1).isEmpty()) {
                        sorted.remove(efirst + 1);
                    }
                    if (sorted.get(efirst).isEmpty()) {
                        sorted.remove(efirst);
                    }
                }
                // check(sorted);
            }
        }
    }

    /**
     * Computes the intersection of a non empty range with a non empty sorted list of ranges
     * and adds the result to another sorted list (possibly empty) of ranges.
     *
     * @param sorted The sorted list to when intersection is added.
     * @param ranges The sorted list of ranges with which intersection is computed.
     * @param range The range with which intersection is computed.
     */
    private void intersectionInternal(List sorted,
                                      List ranges,
                                      R range) {
        // Checks.assertTrue(!ranges.isEmpty(), "ranges is empty");
        // Checks.assertTrue(!range.isEmpty(), "range is empty");

        final int size = ranges.size();
        // Index of the range that contains min
        final int first = binarySearch(ranges, range.getMin());
        if (first < 0
                && -(first + 1) == size) {
            // 1) No range contains min
            // 2) [min~min] should be created in last position
            // Conclusion: Empty intersection
        } else {
            // Index of the range that contains max
            final int last = binarySearch(ranges, range.getMax());
            if (last < 0
                    && -(last + 1) == 0) {
                // 1) No range contains max
                // 2) [max~max] should be inserted in first position
                // Conclusion: empty intersection
            } else {
                // range is overlapping / joining existing ranges

                // Index of the first intersecting range
                final int efirst;
                if (first >= 0) {
                    // Range that contains min exists
                    efirst = first;
                } else {
                    // Range that contains min does not exist
                    final int index = -(first + 1);
                    efirst = index;
                }

                // Index of the last intersecting range
                final int elast;
                if (last >= 0) {
                    elast = last;
                } else {
                    final int index = -(last + 1);
                    elast = index - 1;
                }

                if (efirst == elast) {
                    final R r = RangeUtils.intersection(domain, ranges.get(efirst), range);
                    unionInternal(sorted, r);
                } else if (efirst < elast) {
                    final R f = RangeUtils.intersection(domain, ranges.get(efirst), range);
                    unionInternal(sorted, f);
                    final R l = RangeUtils.intersection(domain, ranges.get(elast), range);
                    unionInternal(sorted, l);

                    // All ranges between efirst (exclusive) and elast (exclusive)
                    // belong to intersection.
                    for (int index = efirst + 1; index < elast; index++) {
                        unionInternal(sorted, ranges.get(index));
                    }
                }
                // check(sorted);
            }
        }
    }

    public final List getRanges() {
        return ranges;
    }

    @Override
    public final boolean isEmpty() {
        return ranges.isEmpty();
    }

    @Override
    public final boolean containsRanges() {
        for (final R range : ranges) {
            if (!range.isSingleton()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public final boolean isSingleton() {
        return ranges.size() == 1 && ranges.get(0).isSingleton();
    }

    @Override
    public V getSingletonValue() {
        checkIsSingleton();

        return ranges.get(0).getSingletonValue();
    }

    @Override
    public final boolean contains(V value) {
        return binarySearch(ranges, value) >= 0;
    }

    @Override
    public final boolean contains(S set) {
        Checks.isNotNull(set, SET);
        if (set.isEmpty()) {
            return true;
        } else {
            for (final R range : set.getRanges()) {
                if (!contains(range)) {
                    return false;
                }
            }
            return true;
        }
    }

    public final boolean contains(R range) {
        if (range.isEmpty()) {
            return true;
        } else {
            final int first = binarySearch(ranges, range.getMin());
            final int last = binarySearch(ranges, range.getMax());
            return first == last && first >= 0;
        }
    }

    @Override
    public final S union(V value) {
        if (contains(value)) {
            return self();
        } else {
            final List sorted = new ArrayList<>(this.ranges);
            unionInternal(sorted, domain.create(value));
            return create(sorted);
        }
    }

    public final S union(R range) {
        if (contains(range)) {
            return self();
        } else {
            final List sorted = new ArrayList<>(this.ranges);
            unionInternal(sorted, range);
            return create(sorted);
        }
    }

    @Override
    public final S union(S set) {
        if (set.isEmpty()) {
            return self();
        } else {
            final List sorted = new ArrayList<>(this.ranges);
            for (final R range : set.getRanges()) {
                unionInternal(sorted, range);
            }
            return create(sorted);
        }
    }

    @Override
    public final S remove(V value) {
        if (!contains(value)) {
            return self();
        } else {
            final List sorted = new ArrayList<>(this.ranges);
            removeInternal(sorted, domain.create(value));
            return create(sorted);
        }
    }

    public final S remove(R range) {
        if (isEmpty() || range.isEmpty()) {
            return self();
        } else {
            final List sorted = new ArrayList<>(this.ranges);
            removeInternal(sorted, range);
            return create(sorted);
        }
    }

    @Override
    public final S remove(S set) {
        if (isEmpty() || set.isEmpty()) {
            return self();
        } else {
            final List sorted = new ArrayList<>(this.ranges);
            for (final R range : set.getRanges()) {
                removeInternal(sorted, range);
            }
            return create(sorted);
        }
    }

    @Override
    public final S intersection(V value) {
        if (isEmpty()) {
            return self();
        } else {
            final List sorted = new ArrayList<>();
            if (contains(value)) {
                sorted.add(domain.create(value));
            }
            return create(sorted);
        }
    }

    public final S intersection(R range) {
        if (isEmpty()) {
            return self();
        } else {
            final List sorted = new ArrayList<>();
            intersectionInternal(sorted, ranges, range);
            return create(sorted);
        }
    }

    @Override
    public final S intersection(S set) {
        if (isEmpty()) {
            return self();
        } else if (set.isEmpty()) {
            return set;
        } else {
            final List sorted = new ArrayList<>();
            for (final R range : set.getRanges()) {
                intersectionInternal(sorted, ranges, range);
            }
            return create(sorted);
        }
    }

    @Override
    public String getContent() {
        final StringBuilder builder = new StringBuilder();
        boolean first = true;
        for (final R range : getRanges()) {
            if (!first) {
                builder.append(",");
            }
            builder.append(range.toString());
            first = false;
        }
        return builder.toString();
    }

    @Override
    public final boolean isChecked() {
        return true;
    }

    @Override
    public final S getChecked() {
        return self();
    }

    @Override
    public final S getBest() {
        return self();
    }

    @Override
    public final boolean isValid() {
        return true;
    }

    @Override
    public List getItems() {
        return getRanges();
    }

    @Override
    public final boolean contains(SItem item) {
        Checks.isNotNull(item, ITEM);

        if (domain.getValueClass().isInstance(item)) {
            return contains(domain.getValueClass().cast(item));
        } else if (domain.getRangeClass().isInstance(item)) {
            return contains(domain.getRangeClass().cast(item));
        } else {
            throw nonSupportedItem(item);
        }
    }

    @Override
    public final S union(SItem item) {
        Checks.isNotNull(item, ITEM);

        if (domain.getValueClass().isInstance(item)) {
            return union(domain.getValueClass().cast(item));
        } else if (domain.getRangeClass().isInstance(item)) {
            return union(domain.getRangeClass().cast(item));
        } else {
            throw nonSupportedItem(item);
        }
    }

    @Override
    public final S intersection(SItem item) {
        Checks.isNotNull(item, ITEM);

        if (domain.getValueClass().isInstance(item)) {
            return intersection(domain.getValueClass().cast(item));
        } else if (domain.getRangeClass().isInstance(item)) {
            return intersection(domain.getRangeClass().cast(item));
        } else {
            throw nonSupportedItem(item);
        }
    }

    @Override
    public final S remove(SItem item) {
        Checks.isNotNull(item, ITEM);

        if (domain.getValueClass().isInstance(item)) {
            return remove(domain.getValueClass().cast(item));
        } else if (domain.getRangeClass().isInstance(item)) {
            return remove(domain.getRangeClass().cast(item));
        } else {
            throw nonSupportedItem(item);
        }
    }

    @Override
    public final S union(SItemSet set) {
        Checks.isNotNull(set, SET);

        if (set.isCompliantWith(getClass())) {
            return union(adapt(set));
        } else {
            throw nonSupportedSet(set);
        }
    }

    @Override
    public final S intersection(SItemSet set) {
        Checks.isNotNull(set, SET);

        if (set.isCompliantWith(getClass())) {
            return intersection(adapt(set));
        } else {
            throw nonSupportedSet(set);
        }
    }

    @Override
    public final S remove(SItemSet set) {
        Checks.isNotNull(set, SET);

        if (set.isCompliantWith(getClass())) {
            return remove(adapt(set));
        } else {
            throw nonSupportedSet(set);
        }
    }

    protected static ,
                      V extends Value & Comparable,
                      R extends AbstractRange,
                      S extends AbstractRangeSet>
            S toSet(D domain,
                    S empty,
                    Function create,
                    V value,
                    PartialOrderPosition position) {
        Checks.isNotNull(value, VALUE);
        Checks.isNotNull(position, "position");

        if (position == PartialOrderPosition.EQUAL) {
            return create.apply(domain.create(value));
        } else if (position == PartialOrderPosition.UNRELATED) {
            return empty;
        } else if (position == PartialOrderPosition.LESS_THAN) {
            return create.apply(domain.create(domain.min(), domain.pred(value)));
        } else {
            return create.apply(domain.create(domain.succ(value), domain.max()));
        }
    }

    public static ,
                   R extends AbstractRange,
                   S extends AbstractRangeSet>
            Comparator lexicographicComparator() {
        return (s1,
                s2) -> {
            if (s1 == s2) {
                return 0;
            }
            if (s1 == null || s2 == null) {
                return s1 == null ? -1 : 1;
            }
            return CollectionUtils.compareLexicographic(s1.getRanges(),
                                                        s2.getRanges(),
                                                        AbstractRange.minMaxComparator());
        };
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (isEmpty() && object instanceof AbstractRangeSet) {
            final AbstractRangeSet other = (AbstractRangeSet) object;
            if (other.isEmpty()) {
                return true;
            }
        }
        if (!(object instanceof AbstractRangeSet)) {
            return false;
        }
        final AbstractRangeSet other = (AbstractRangeSet) object;
        return ranges.equals(other.ranges);
    }

    @Override
    public int hashCode() {
        return isEmpty() ? 0 : ranges.hashCode();
    }

    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append("{");
        builder.append(getContent());
        builder.append("}");
        return builder.toString();
    }

    @Override
    public int compareTo(S o) {
        if (this == o) {
            return 0;
        }
        if (o == null) {
            return 1;
        } else {
            return CollectionUtils.compareLexicographic(getItems(), o.getItems());
        }
    }
}