org.cinchapi.concourse.server.model.Ranges.java Source code

Java tutorial

Introduction

Here is the source code for org.cinchapi.concourse.server.model.Ranges.java

Source

/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2013-2015 Jeff Nelson, Cinchapi Software Collective
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.cinchapi.concourse.server.model;

import java.util.Comparator;
import java.util.List;

import org.cinchapi.concourse.server.concurrent.RangeToken;
import org.cinchapi.concourse.thrift.Operator;

import com.google.common.collect.BoundType;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;

/**
 * A collection of utilities to augment the {@link Range} class with respect to
 * coverage of {@link Value Values}.
 * 
 * @author jnelson
 */
public final class Ranges {

    /**
     * Compare {@code a} and {@code b} by their lower endpoints and, if
     * necessary, bounds.
     * 
     * @param a
     * @param b
     * @return a negative Value, zero, or a positive Value as {@code a} is
     *         less than, equal to, or greater than {@code b}.
     */
    public static int compareToLower(Range<Value> a, Range<Value> b) {
        return ComparisonChain.start().compare(getLowerEndpoint(a), getLowerEndpoint(b))
                .compare(getLowerBoundType(a), getLowerBoundType(b), LOWER_BOUND_COMPARATOR).result();
    }

    /**
     * Compare {@code a} and {@code b} by their upper endpoints and, if
     * necessary, bounds.
     * 
     * @param a
     * @param b
     * @return a negative Value, zero, or a positive Value as {@code a} is
     *         less than, equal to, or greater than {@code b}.
     */
    public static int compareToUpper(Range<Value> a, Range<Value> b) {
        return ComparisonChain.start().compare(getUpperEndpoint(a), getUpperEndpoint(b))
                .compare(getUpperBoundType(a), getUpperBoundType(b), UPPER_BOUND_COMPARATOR).result();
    }

    /**
     * Convert the given {@code range} for {@code key} to a matching
     * {@link RangeToken}.
     * 
     * @param key
     * @param range
     * @return the RangeToken
     */
    public static RangeToken convertToRangeToken(Text key, Range<Value> range) {
        Value lower = getLowerEndpoint(range);
        Value upper = getUpperEndpoint(range);
        boolean lowerClosed = getLowerBoundType(range) == BoundType.CLOSED;
        boolean upperClosed = getUpperBoundType(range) == BoundType.CLOSED;
        // We use the length of the values array in the RangeToken as a hack to
        // signal what kind of non-traditional (i.e. closedOpen) bounds to use
        // on the BETWEEN range when using the Guava data type.
        int size;
        if (!lowerClosed && !upperClosed) {
            size = 3;
        } else if (lowerClosed && upperClosed) {
            size = 4;
        } else if (!lowerClosed && upperClosed) {
            size = 5;
        } else {
            size = 2;
        }
        Value[] values = new Value[size];
        values[0] = lower;
        values[1] = upper;
        for (int i = 2; i < values.length; i++) {
            values[i] = Value.NEGATIVE_INFINITY;
        }
        return RangeToken.forReading(key, Operator.BETWEEN, values);
    }

    /**
     * Equivalent to {@link Range#lowerBoundType()} except that
     * {@link BoundType#CLOSED} is returned if the lower endpoint is equals to
     * {@link Value#NEGATIVE_INFINITY}.
     * 
     * @param range
     * @return the lower bound type
     */
    public static BoundType getLowerBoundType(Range<Value> range) {
        Value lower = getLowerEndpoint(range);
        if (lower == Value.NEGATIVE_INFINITY) {
            return BoundType.CLOSED;
        } else {
            return range.lowerBoundType();
        }
    }

    /**
     * Equivalent to {@link Range#lowerEndpoint()} except that
     * {@link Value#NEGATIVE_INFINITY} is returned if the {@code range} does not
     * have a defined lower bound.
     * 
     * @param range
     * @return the lower endpoint
     */
    public static Value getLowerEndpoint(Range<Value> range) {
        if (!range.hasLowerBound()) {
            return Value.NEGATIVE_INFINITY;
        } else {
            return range.lowerEndpoint();
        }
    }

    /**
     * Equivalent to {@link Range#upperBoundType()} except that
     * {@link BoundType#CLOSED} is returned if the upper endpoint is equals to
     * {@link Value#POSITVE_INFINITY}.
     * 
     * @param range
     * @return the upper bound type
     */
    public static BoundType getUpperBoundType(Range<Value> range) {
        Value upper = getUpperEndpoint(range);
        if (upper == Value.POSITIVE_INFINITY) {
            return BoundType.CLOSED;
        } else {
            return range.upperBoundType();
        }
    }

    /**
     * Equivalent to {@link Range#upperEndpoint()} except that
     * {@link Value#POSITVE_INFINITY} is returned if the {@code range} does not
     * have a defined upper bound.
     * 
     * @param range
     * @return the upper endpoint
     */
    public static Value getUpperEndpoint(Range<Value> range) {
        if (!range.hasUpperBound()) {
            return Value.POSITIVE_INFINITY;
        } else {
            return range.upperEndpoint();
        }
    }

    /**
     * Return a new {@link Range} that is the merger (e.g. union) of {@code a}
     * and {@code b}. The new {@link Range} maintains both the lower and higher
     * endpoint/bound between the two inputs.
     * 
     * @param a
     * @param b
     * @return the union of {@code a} and {@code b}
     */
    public static Range<Value> merge(Range<Value> a, Range<Value> b) {
        if (a.isConnected(b)) {
            boolean aStart = compareToLower(a, b) < 0;
            boolean aEnd = compareToUpper(a, b) > 0;
            boolean lower = getLowerBoundType(aStart ? a : b) == BoundType.CLOSED;
            boolean upper = getUpperBoundType(aStart ? a : b) == BoundType.CLOSED;
            if (lower && upper) {
                return Range.closed(getLowerEndpoint(aStart ? a : b), getUpperEndpoint(aEnd ? a : b));
            } else if (!lower && upper) {
                return Range.closedOpen(getLowerEndpoint(aStart ? a : b), getUpperEndpoint(aEnd ? a : b));
            } else if (lower && !upper) {
                return Range.openClosed(getLowerEndpoint(aStart ? a : b), getUpperEndpoint(aEnd ? a : b));
            } else {
                return Range.open(getLowerEndpoint(aStart ? a : b), getUpperEndpoint(aEnd ? a : b));
            }
        } else {
            return null;
        }
    }

    /**
     * Return the ranges that include the points that are in {@code a} or the
     * {@code b} one and not in their intersection. The return set will include
     * between 0 and 2 ranges that together include all the points that meet
     * this criteria.
     * <p>
     * <strong>NOTE:</strong> If the two ranges do not intersect, then a
     * collection containing both of them is returned (since they already form
     * their xor).
     * </p>
     * 
     * @param a
     * @param b
     * @return the set or ranges that make uValue the symmetric difference
     *         between this range and the {@code other} one
     */
    public static Iterable<Range<Value>> xor(Range<Value> a, Range<Value> b) {
        List<Range<Value>> ranges = Lists.newArrayList();
        try {
            Range<Value> intersection = a.intersection(b);
            boolean aStart = compareToLower(a, b) < 0;
            boolean aEnd = compareToUpper(a, b) > 0;
            boolean lower = getLowerBoundType(aStart ? a : b) == BoundType.CLOSED;
            boolean upper = getUpperBoundType(aEnd ? a : b) == BoundType.CLOSED;
            boolean interLower = getLowerBoundType(intersection) == BoundType.OPEN;
            boolean interUpper = getUpperBoundType(intersection) == BoundType.OPEN;
            Range<Value> first;
            if (lower && interLower) {
                first = Range.closed(getLowerEndpoint(aStart ? a : b), getLowerEndpoint(intersection));
            } else if (!lower && interLower) {
                first = Range.openClosed(getLowerEndpoint(aStart ? a : b), getLowerEndpoint(intersection));
            } else if (lower && !interLower) {
                first = Range.closedOpen(getLowerEndpoint(aStart ? a : b), getLowerEndpoint(intersection));
            } else {
                first = Range.open(getLowerEndpoint(aStart ? a : b), getLowerEndpoint(intersection));
            }
            Range<Value> second;
            if (interUpper && upper) {
                second = Range.closed(getUpperEndpoint(intersection), getUpperEndpoint(aEnd ? a : b));
            } else if (!interUpper && upper) {
                second = Range.openClosed(getUpperEndpoint(intersection), getUpperEndpoint(aEnd ? a : b));
            } else if (interUpper && !interUpper) {
                second = Range.closedOpen(getUpperEndpoint(intersection), getUpperEndpoint(aEnd ? a : b));
            } else {
                second = Range.open(getUpperEndpoint(intersection), getUpperEndpoint(aEnd ? a : b));
            }
            if (!first.isEmpty()) {
                ranges.add(first);
            }
            if (!second.isEmpty()) {
                ranges.add(second);
            }
        } catch (IllegalArgumentException e) { // ranges dont intersect
            ranges.add(a);
            ranges.add(b);
        }
        return ranges;
    }

    /**
     * A comparator to sort the lower bound of Ranges.
     */
    private final static Comparator<BoundType> LOWER_BOUND_COMPARATOR = new Comparator<BoundType>() {

        @Override
        public int compare(BoundType o1, BoundType o2) {
            if (o1 == o2) {
                return 0;
            } else if (o1 == BoundType.CLOSED) {
                return -1;
            } else {
                return 1;
            }
        }

    };

    /**
     * A comparator to sort the upper bound of Ranges.
     */
    private final static Comparator<BoundType> UPPER_BOUND_COMPARATOR = new Comparator<BoundType>() {

        @Override
        public int compare(BoundType o1, BoundType o2) {
            if (o1 == o2) {
                return 0;
            } else if (o1 == BoundType.CLOSED) {
                return 1;
            } else {
                return -1;
            }
        }

    };

    private Ranges() {
        /* noop */}

}