/*
* Copyright 2013 Cloudera.
*
* 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.cloudera.cdk.data.spi;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import javax.annotation.concurrent.Immutable;
/**
* Represents a range of concrete partitions.
*
* Boundaries are defined by {@link Marker} objects, which contain one or more
* concrete partitions, and can be inclusive or exclusive.
*
* @since 0.9.0
*/
@Immutable
public class MarkerRange {
public static final MarkerRange UNDEFINED = new Undefined();
private final MarkerComparator comparator;
private final Boundary start;
private final Boundary end;
private MarkerRange() {
this.comparator = null;
this.start = Boundary.UNBOUNDED;
this.end = Boundary.UNBOUNDED;
}
public MarkerRange(MarkerComparator comparator) {
Preconditions.checkArgument(comparator != null,
"Comparator cannot be null.");
this.comparator = comparator;
this.start = Boundary.UNBOUNDED;
this.end = Boundary.UNBOUNDED;
}
private MarkerRange(
MarkerComparator comparator,
Boundary start, Boundary end) {
this.comparator = comparator;
this.start = start;
this.end = end;
}
public boolean contains(Marker marker) {
return (start.isLessThan(marker) && end.isGreaterThan(marker));
}
public MarkerRange from(Marker start) {
Preconditions.checkArgument(contains(start),
"Start boundary is outside of this range");
return new MarkerRange(comparator,
new Boundary(comparator, start, true), end);
}
public MarkerRange fromAfter(Marker start) {
Preconditions.checkArgument(contains(start),
"Start boundary is outside of this range");
return new MarkerRange(comparator,
new Boundary(comparator, start, false), end);
}
public MarkerRange to(Marker end) {
Preconditions.checkArgument(contains(end),
"End boundary is outside of this range");
return new MarkerRange(comparator, start,
new Boundary(comparator, end, true));
}
public MarkerRange toBefore(Marker end) {
Preconditions.checkArgument(contains(end),
"End boundary is outside of this range");
return new MarkerRange(comparator, start,
new Boundary(comparator, end, false));
}
public MarkerRange of(Marker partial) {
Preconditions.checkArgument(contains(partial),
"Marker is outside of this range");
return new MarkerRange(comparator,
new Boundary(comparator, partial, true),
new Boundary(comparator, partial, true));
}
@Override
public boolean equals(Object o) {
if (this == o) {
// this covers the UNDEFINED case
return true;
}
if (!(o instanceof MarkerRange)) {
return false;
}
MarkerRange that = (MarkerRange) o;
return (Objects.equal(this.start, that.start) &&
Objects.equal(this.end, that.end));
}
@Override
public int hashCode() {
return Objects.hashCode(start, end);
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("start", start)
.add("end", end)
.toString();
}
public Boundary getStart() {
return start;
}
public Boundary getEnd() {
return end;
}
/**
* Placeholder range that can be used when there is no PartitionStrategy.
*
* MarkerRange requires a MarkerComparator. Without a way to compare bounds,
* a Range is undefined. However it is convenient to have a place-holder that
* correctly responds to range methods for this case.
*/
private static class Undefined extends MarkerRange {
@Override
public boolean contains(Marker marker) {
return true;
}
@Override
public MarkerRange from(Marker start) {
throw new IllegalStateException("Undefined range: no PartitionStrategy");
}
@Override
public MarkerRange fromAfter(Marker start) {
throw new IllegalStateException("Undefined range: no PartitionStrategy");
}
@Override
public MarkerRange to(Marker end) {
throw new IllegalStateException("Undefined range: no PartitionStrategy");
}
@Override
public MarkerRange toBefore(Marker end) {
throw new IllegalStateException("Undefined range: no PartitionStrategy");
}
@Override
public MarkerRange of(Marker partial) {
throw new IllegalStateException("Undefined range: no PartitionStrategy");
}
@Override
public String toString() {
return Objects.toStringHelper(MarkerRange.class)
.add("range", "UNDEFINED")
.toString();
}
}
/**
* Represents the boundary of a range, either inclusive or exclusive.
*
* @since 0.9.0
*/
public static class Boundary {
public static final Boundary UNBOUNDED = new Boundary();
private final MarkerComparator comparator;
private final Marker bound;
private final boolean isInclusive;
private Boundary() {
this.comparator = null;
this.bound = null;
this.isInclusive = true;
}
public Boundary(MarkerComparator comparator, Marker bound, boolean isInclusive) {
parquet.Preconditions.checkArgument(comparator != null,
"Comparator cannot be null");
parquet.Preconditions.checkArgument(bound != null,
"Bound cannot be null");
this.comparator = comparator;
this.bound = bound;
this.isInclusive = isInclusive;
}
public boolean isLessThan(Marker other) {
if (comparator == null) {
return true;
}
if (isInclusive) {
// compare left-most side
return (comparator.leftCompare(bound, other) <= 0);
} else {
try {
return (comparator.compare(bound, other) < 0);
} catch (IllegalStateException ex) {
// one contained the other, which is not allowed for exclusive ranges
return false;
}
}
}
public boolean isGreaterThan(Marker other) {
if (comparator == null) {
return true;
}
if (isInclusive) {
// compare the right-most side
return (comparator.rightCompare(bound, other) >= 0);
} else {
try {
return (comparator.compare(bound, other) > 0);
} catch (IllegalStateException ex) {
// one contained the other, which is not allowed for exclusive ranges
return false;
}
}
}
public Marker getBound() {
return bound;
}
public boolean isInclusive() {
return isInclusive;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Boundary)) {
return false;
}
Boundary that = (Boundary) o;
return (Objects.equal(this.isInclusive, that.isInclusive) &&
Objects.equal(this.bound, that.bound) &&
Objects.equal(this.comparator, that.comparator));
}
@Override
public int hashCode() {
return Objects.hashCode(isInclusive, bound, comparator);
}
@Override
public String toString() {
if (comparator == null) {
return Objects.toStringHelper(this)
.add("bound", "UNBOUNDED")
.toString();
} else {
return Objects.toStringHelper(this)
.add("inclusive", isInclusive)
.add("bound", bound)
.toString();
}
}
}
}