/*
* Copyright (c) 2013-2017 Cinchapi Inc.
*
* Licensed 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 com.cinchapi.concourse.server.model;
import java.util.Comparator;
import java.util.List;
import com.cinchapi.concourse.server.concurrent.RangeToken;
import com.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 Jeff Nelson
*/
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 */}
}