/*
* 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.cloudera.cdk.data.FieldPartitioner;
import com.cloudera.cdk.data.PartitionStrategy;
import java.util.Comparator;
import javax.annotation.concurrent.Immutable;
/**
* Comparison methods for {@link Marker} objects with respect to a
* {@link PartitionStrategy}.
*
* @since 0.9.0
*/
@Immutable
@edu.umd.cs.findbugs.annotations.SuppressWarnings(
value="SE_COMPARATOR_SHOULD_BE_SERIALIZABLE",
justification="Implement if we intend to use in Serializable objects "
+ " (e.g., TreeMaps) and use java serialization.")
public class MarkerComparator implements Comparator<Marker> {
private final PartitionStrategy strategy;
public MarkerComparator(PartitionStrategy strategy) {
this.strategy = strategy;
}
/**
* Returns true if {@code container} contains {@code test}, false otherwise.
*
* Fields for which {@code set} has no value
* ({@link Marker#valueFor(FieldPartitioner)} returns
* null) are treated as wildcards and always match.
*
* All fields in the {@link PartitionStrategy} are compared.
*
* @param container a {@code Marker} that defines a set of partitions
* @param test a {@code Marker} that may be a subset of {@code container}
* @return
* {@code true} if the partitions in {@code test} are a subset of
* {@code container}, {@code false} otherwise
*/
@SuppressWarnings("unchecked")
public boolean contains(Marker container, Marker test) {
for (FieldPartitioner field : strategy.getFieldPartitioners()) {
Object containerValue = container.valueFor(field);
if (containerValue != null) {
Object testValue = test.valueFor(field);
if (field.compare(containerValue, testValue) != 0) {
return false;
}
}
/*
* Rather than returning true if containerValue is null, this treats
* null as a wildcard. Everything matches null, so all non-null fields
* will be checked.
*/
}
return true;
}
/**
* Compare two {@link Marker} objects under the {@link PartitionStrategy}.
*
* All comparisons are with respect to the partition ordering defined by
* this comparator's {@code PartitionStrategy}. Under a
* {@code PartitionStrategy}, a {@code Marker} contains a set of one or
* more partitions. A {@code Marker} is strictly less than another if all of
* the partitions it contains are less than the partitions of the other.
* Similarly, if all partitions are greater than the partitions of the other,
* then the {@code Marker} is greater. Two {@code Markers} are equal if they
* contain the same set of partitions.
*
* This method implements strictly exclusive comparison: if either
* {@code Marker} contains the other, then this throws
* {@code IllegalStateException}. This is because there is at least one
* partition in the containing {@code Marker} that is less than or equal to
* all partitions in the contained {@code Marker} and at least one partition
* that is greater than or equal to all partitions in the contained
* {@code Marker}.
*
* Alternatively, the comparison methods {@link #leftCompare(Marker, Marker)}
* and {@link #rightCompare(Marker, Marker)} consider contained {@code Marker}
* objects to be greater-than and less-than respectively.
*
* Note: Because {@code Marker} objects are hierarchical, they are either
* completely disjoint or one marker contains the other. If one contains the
* other and the two are not equal, this method throws
* {@code IllegalStateException}.
*
* TODO: catch wildcard to concrete comparisons and throw an Exception
*
* @param m1 a {@code Marker}
* @param m2 a {@code Marker}
* @return
* -1 If all partitions in m1 are less than the partitions in m2
* 0 If m1 and m2 contain the same set of partitions
* 1 If all partitions of m1 are greater than the partitions in m2
* @throws IllegalStateException
* If either {@code Marker} is a proper subset of the other
*
* @see MarkerComparator#leftCompare(Marker, Marker)
* @see MarkerComparator#rightCompare(Marker, Marker)
*
* @since 0.9.0
*/
@Override
@SuppressWarnings("unchecked")
public int compare(Marker m1, Marker m2) {
for (FieldPartitioner field : strategy.getFieldPartitioners()) {
Object m1Value = m1.valueFor(field);
Object m2Value = m2.valueFor(field);
// if either is null, but not both, then they are Incomparable
if (m1Value == null) {
if (m2Value != null) {
// m1 contains m2
throw new IllegalStateException("Incomparable");
}
} else if (m2Value == null) {
// m2 contains m1
throw new IllegalStateException("Incomparable");
} else {
int cmp = field.compare(m1Value, m2Value);
if (cmp != 0) {
return cmp;
}
}
}
return 0;
}
/**
* Compare two {@link Marker} objects under the {@link PartitionStrategy}.
*
* All comparisons are with respect to the partition ordering defined by
* this comparator's {@code PartitionStrategy}. Under a
* {@code PartitionStrategy}, a {@code Marker} contains a set of one or
* more partitions. A {@code Marker} is strictly less than another if all of
* the partitions it contains are less than the partitions of the other.
* Similarly, if all partitions are greater than the partitions of the other,
* then the {@code Marker} is greater. Two {@code Markers} are equal if they
* contain the same set of partitions.
*
* This method implements right-inclusive comparison: if either {@code Marker}
* contains the other, then it is considered greater. This means that there
* is at least one partition in the containing {@code Marker} that is greater
* than all of the partitions in the contained {@code Marker}. This behavior
* is for checking an inclusive upper bound for a range.
*
* m1 = [ 2013, Oct, * ]
* m2 = [ 2013, Oct, 12 ]
* rightCompare(m1, m2) returns 1
* rightCompare(m2, m1) returns -1
*
* The comparison method {@link #leftCompare(Marker, Marker)} implements
* left-inclusive comparison.
*
* Note: Because {@code Marker} objects are hierarchical, they are either
* completely disjoint or one marker contains the other. If one contains the
* other and the two are not equal, this method considers it to be greater
* than the other.
*
* TODO: catch wildcard to concrete comparisons and throw an Exception
*
* @param m1 a {@code Marker}
* @param m2 a {@code Marker}
* @return
* -1 If all partitions in m1 are less than the partitions in m2
* 0 If m1 and m2 contain the same set of partitions
* 1 If all partitions of m1 are greater than the partitions in m2
*
* @see MarkerComparator#compare(Marker, Marker)
* @see MarkerComparator#leftCompare(Marker, Marker)
*
* @since 0.9.0
*/
@SuppressWarnings("unchecked")
public int rightCompare(Marker m1, Marker m2) {
for (FieldPartitioner field : strategy.getFieldPartitioners()) {
Object m1Value = m1.valueFor(field);
Object m2Value = m2.valueFor(field);
if (m1Value == null) {
if (m2Value != null) {
// m1 contains m2
return 1;
}
} else if (m2Value == null) {
// m2 contains m1
return -1;
} else {
int cmp = field.compare(m1Value, m2Value);
if (cmp != 0) {
return cmp;
}
}
}
return 0;
}
/**
* Compare two {@link Marker} objects under the {@link PartitionStrategy}.
*
* All comparisons are with respect to the partition ordering defined by
* this comparator's {@code PartitionStrategy}. Under a
* {@code PartitionStrategy}, a {@code Marker} contains a set of one or
* more partitions. A {@code Marker} is strictly less than another if all of
* the partitions it contains are less than the partitions of the other.
* Similarly, if all partitions are greater than the partitions of the other,
* then the {@code Marker} is greater. Two {@code Markers} are equal if they
* contain the same set of partitions.
*
* This method implements left-inclusive comparison: if either {@code Marker}
* contains the other, then it is considered lesser. This means that there
* is at least one partition in the containing {@code Marker} that is less
* than all of the partitions in the contained {@code Marker}. This behavior
* is for checking an inclusive lower bound for a range.
*
* m1 = [ 2013, Oct, * ]
* m2 = [ 2013, Oct, 12 ]
* leftCompare(m1, m2) returns 1
* leftCompare(m2, m1) returns -1
*
* The comparison method {@link #rightCompare(Marker, Marker)} implements
* right-inclusive comparison.
*
* Note: Because {@code Marker} objects are hierarchical, they are either
* completely disjoint or one marker contains the other. If one contains the
* other and the two are not equal, this method considers it to be less than
* than the other.
*
* TODO: catch wildcard to concrete comparisons and throw an Exception
*
* @param m1 a {@code Marker}
* @param m2 a {@code Marker}
* @return
* -1 If all partitions in m1 are less than the partitions in m2
* 0 If m1 and m2 contain the same set of partitions
* 1 If all partitions of m1 are greater than the partitions in m2
*
* @see MarkerComparator#compare(Marker, Marker)
* @see MarkerComparator#rightCompare(Marker, Marker)
*
* @since 0.9.0
*/
@SuppressWarnings("unchecked")
public int leftCompare(Marker m1, Marker m2) {
for (FieldPartitioner field : strategy.getFieldPartitioners()) {
Object m1Value = m1.valueFor(field);
Object m2Value = m2.valueFor(field);
if (m1Value == null) {
if (m2Value != null) {
// m1 contains m2
return -1;
}
} else if (m2Value == null) {
// m2 contains m1
return 1;
} else {
int cmp = field.compare(m1Value, m2Value);
if (cmp != 0) {
return cmp;
}
}
}
return 0;
}
}