package com.github.liblevenshtein.assertion;
import org.assertj.core.api.AbstractAssert;
import com.github.liblevenshtein.distance.IDistance;
/**
* AssertJ-style assertions for distance functions. To be considered a distance
* function, a function must satisfy the following:
* 1. Equal Self-Similarity
* 2. Minimality
* 3. Symmetry
* 4. Triangle Inequality
* @param <Type> Generic type whose elements have a distance function.
*/
public class DistanceAssertions<Type>
extends AbstractAssert<DistanceAssertions<Type>, IDistance<Type>> {
/**
* Constructs a new {@link DistanceAssertions}.
* @param actual The distance function being asserted-against.
*/
public DistanceAssertions(final IDistance<Type> actual) {
super(actual, DistanceAssertions.class);
}
/**
* Builds a new {@link DistanceAssertions}.
* @param actual The distance function being asserted-against.
* @param <Type> Generic type whose elements have a distance function.
* @return A new {@link DistanceAssertions} that asserts-against the distance
* function
*/
public static <Type> DistanceAssertions<Type> assertThat(
final IDistance<Type> actual) {
return new DistanceAssertions<>(actual);
}
/**
* Asserts that the distance function satisfies equal self-similarity.
* @param term1 First term for the comparison.
* @param term2 Second term for the comparison.
* @return This {@link DistanceAssertions} for fluency.
* @throws AssertionError When {@code d(term1, term2) != d(term2, term1)}
*/
public DistanceAssertions<Type> satisfiesEqualSelfSimilarity(
final Type term1,
final Type term2) {
isNotNull();
final int d1 = actual.between(term1, term1);
final int d2 = actual.between(term2, term2);
if (d1 != d2) {
failWithMessage(
"Expected d(%s, %s) = [%d] to be d(%s, %s) = [%d]",
term1, term2, d1, term2, term1, d2);
}
return this;
}
/**
* Asserts that the distance function satisfies minimality, such that for all
* terms {@code term1 != term2}, we have that
* {@code d(term1, term2) > d(term1, term1)} and that
* {@code d(term2, term1) > d(term1, term1)}.
* @param term1 First term for the comparison.
* @param term2 Second term for the comparison.
* @return This {@link DistanceAssertions} for fluency.
* @throws AssertionError When {@code d(term1, term2) <= d(term1, term1)}
* or when {@code d(term2, term1) <= d(term1, term1)}.
*/
public DistanceAssertions<Type> satisfiesMinimality(
final Type term1,
final Type term2) {
isNotNull();
if (null != term1 && term1.equals(term2)) {
failWithMessage("Did not expect term1 [%s] to be equal-to term2 [%s]",
term1, term2);
}
final int d11 = actual.between(term1, term1);
final int d12 = actual.between(term1, term2);
final int d21 = actual.between(term2, term1);
final int d22 = actual.between(term2, term2);
assertMinimality(term1, term2, d12, term1, d11);
assertMinimality(term2, term1, d21, term1, d11);
assertMinimality(term1, term2, d12, term2, d22);
assertMinimality(term2, term1, d21, term2, d22);
return this;
}
/**
* Asserts that the distances satisfy minimality.
* @param t1 Term to compare with {@code t2}.
* @param t2 Term to compare with {@code t1}.
* @param d12 Distance between {@code t1} and {@code t2}.
* @param t3 Term to compare with itself.
* @param d33 Distance between {@code t3} and itself.
* @throws AssertionError When minimality is not satisfied.
*/
private void assertMinimality(
final Type t1, final Type t2, final int d12,
final Type t3, final int d33) {
if (d12 <= d33) {
failWithMessage(
"Expected d(%s, %s) = [%d] > d(%s, %s) = [%d]",
t1, t2, d12, t3, t3, d33);
}
}
/**
* Asserts that the distance function satisfies symmetry, such that for all
* terms term1, term2 we have that
* {@code d(term1, term2) = d(term2, term1)}.
* @param term1 First term for the comparison.
* @param term2 Second term for the comparison.
* @return This {@link DistanceAssertions} for fluency.
* @throws AssertionError When {@code d(term1, term2) != d(term2, term1)}
*/
public DistanceAssertions<Type> satisfiesSymmetry(
final Type term1,
final Type term2) {
isNotNull();
final int d12 = actual.between(term1, term2);
final int d21 = actual.between(term2, term1);
if (d12 != d21) {
failWithMessage("Expected d(%s, %s) = [%d] to be equal-to d(%s, %s) = [%d]",
term1, term2, d12, term2, term1, d21);
}
return this;
}
/**
* Asserts that the distance function satisfies the triangle inequality, such that for all
* terms term1, term2, term3 we have that
* {@code d(term1, term2) + d(term1, term3) < d(term2, term3)}
* when terms term1, term2, term3 are not colinear, and only
* {@code d(term1, term2) + d(term1, term3) = d(term2, term3)}
* when they are colinear.
* @param term1 First term for the comparison.
* @param term2 Second term for the comparison.
* @param term3 Third term for the comparison.
* @return This {@link DistanceAssertions} for fluency.
* @throws AssertionError When any of the following hold:
* <ol>
* <li>{@code d(term1, term2) + d(term1, term3) < d(term2, term3)}</li>
* <li>{@code d(term1, term2) + d(term2, term3) < d(term1, term3)}</li>
* <li>{@code d(term1, term3) + d(term2, term3) < d(term1, term2)}</li>
* </ol>
*/
public DistanceAssertions<Type> satisfiesTriangleInequality(
final Type term1,
final Type term2,
final Type term3) {
isNotNull();
final int d12 = actual.between(term1, term2);
final int d13 = actual.between(term1, term3);
final int d23 = actual.between(term2, term3);
assertTriangleInequality(
term1, term2, d12,
term1, term3, d13,
term2, term3, d23);
assertTriangleInequality(
term1, term2, d12,
term2, term3, d23,
term1, term3, d13);
assertTriangleInequality(
term1, term3, d13,
term2, term3, d23,
term1, term2, d12);
return this;
}
/**
* Asserts that the distances satisfy the triangle inequality.
* @param t1 Term to compare with {@code t2}.
* @param t2 Term to compare with {@code t1}.
* @param d12 Distance between {@code t1} and {@code t2}
* @param t3 Term to compare with {@code t4}.
* @param t4 Term to compare with {@code t3}.
* @param d34 Distance between {@code t3} and {@code t4}
* @param t5 Term to compare with {@code t6}.
* @param t6 Term to compare with {@code t5}.
* @param d56 Distance between {@code t5} and {@code t6}
* @throws AssertionError When the triangle inequality does not hold.
*/
@SuppressWarnings("checkstyle:parameternumber")
private void assertTriangleInequality(
final Type t1, final Type t2, final int d12,
final Type t3, final Type t4, final int d34,
final Type t5, final Type t6, final int d56) {
if (d12 + d34 < d56) {
failWithMessage(
"Expected (d(%s, %s) = [%d] + d(%s, %s) = [%d]) = [%d] >= d(%s, %s) = [%d]",
t1, t2, d12, t3, t4, d34, d12 + d34, t5, t6, d56);
}
}
/**
* Asserts that the distance between terms term1, term2 is expected.
* @param expectedDistance Distance asserted-against
* @param term1 First term for the comparison.
* @param term2 Second term for the comparison.
* @return This {@link DistanceAssertions} for fluency.
* @throws AssertionError When {@code d(term1, term2) != expectedDistance}
*/
public DistanceAssertions<Type> hasDistance(
final int expectedDistance,
final Type term1,
final Type term2) {
isNotNull();
final int actualDistance = actual.between(term1, term2);
if (expectedDistance != actualDistance) {
failWithMessage("Expected d(%s, %s) = [%d], but was [%d]",
term1, term2, expectedDistance, actualDistance);
}
return this;
}
}