Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import java.io.Serializable;
import static java.lang.Double.doubleToRawLongBits;
import javax.measure.Unit;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.LenientComparable;
import org.apache.sis.util.Utilities;
import org.opengis.util.InternationalString;
import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.operation.TransformException;
Expand Down Expand Up @@ -71,7 +74,7 @@
* @version 1.1
* @since 1.0
*/
public class Category implements Serializable {
public class Category implements LenientComparable, Serializable {
/**
* Serial number for inter-operability with different versions.
*/
Expand Down Expand Up @@ -507,29 +510,41 @@ public int hashCode() {
* @param object the object to compare with.
* @return {@code true} if the given object is equal to this category.
*/
public final boolean equals(final Object object) {
return equals(object, ComparisonMode.STRICT);
}

/**
* Compares this category with the given object for equality at the given level of strictness.
*
* <ul>
* <li>{@link ComparisonMode#STRICT}: same implementation class, same name, exact range and transfer function.</li>
* <li>{@link ComparisonMode#BY_CONTRACT}: same name, exact range and transfer function; class may differ.</li>
* <li>{@link ComparisonMode#IGNORE_METADATA} and more lenient: range and transfer function only (name is ignored).</li>
* <li>{@link ComparisonMode#APPROXIMATE}: range and transfer function approximately equal.</li>
* </ul>
*
* @param object the object to compare with.
* @param mode the comparison strictness level.
* @return {@code true} if both objects are equal according the given comparison mode.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
// Slight optimization
return true;
}
if (object != null && getClass().equals(object.getClass())) {
final Category that = (Category) object;
if (name.equals(that.name)) {
final NumberRange<?> other = that.range;
/*
* The NumberRange.equals(Object) comparison is not sufficient because it considers all NaN values as equal.
* For the purpose of Category, we need to distinguish the different NaN values.
*/
if (range == other || (range.equals(other)
&& doubleToRawLongBits(range.getMinDouble()) == doubleToRawLongBits(other.getMinDouble())
&& doubleToRawLongBits(range.getMaxDouble()) == doubleToRawLongBits(other.getMaxDouble())))
{
return toConverse.equals(that.toConverse);
}
public boolean equals(final Object object, final ComparisonMode mode) {
if (object == this) return true;
if (!(object instanceof Category)) return false;
final Category that = (Category) object;

switch (mode) {
case STRICT: {
if (!getClass().equals(that.getClass())) return false;
}
case BY_CONTRACT: {
if (!name.equals(that.name)) return false;
}
default:
return Utilities.deepEquals(range, that.range, mode)
&& Utilities.deepEquals(toConverse, that.toConverse, mode);
}
return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import org.apache.sis.feature.internal.Resources;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.LenientComparable;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.math.MathFunctions;

Expand Down Expand Up @@ -69,7 +71,7 @@
*
* @author Martin Desruisseaux (IRD, Geomatys)
*/
final class CategoryList extends AbstractList<Category> implements MathTransform1D, Serializable {
final class CategoryList extends AbstractList<Category> implements MathTransform1D, LenientComparable, Serializable {
/**
* Serial number for inter-operability with different versions.
*/
Expand Down Expand Up @@ -825,16 +827,34 @@ public final Category get(final int i) {
* Compares the specified object with this category list for equality.
*/
@Override
public boolean equals(final Object object) {
public final boolean equals(final Object object) {
if (object instanceof CategoryList) {
final CategoryList that = (CategoryList) object;
if (Arrays.equals(categories, that.categories)) {
assert Arrays.equals(minimums, that.minimums);
} else {
return equals(object, ComparisonMode.STRICT);
}
return super.equals(object);
}

/**
* Compares this category list with the given object for equality at the given level of strictness.
* The comparison is performed element-wise using {@link Category#equals(Object, ComparisonMode)}.
*
* @param object the object to compare with.
* @param mode the comparison strictness level.
* @return {@code true} if both lists are equal according the given comparison mode.
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
if (object == this) return true;
if (!(object instanceof CategoryList)) return false;
final CategoryList that = (CategoryList) object;
final int count = categories.length;
if (that.categories.length != count) return false;
for (int i = 0; i < count; i++) {
if (!categories[i].equals(that.categories[i], mode)) {
return false;
}
}
return super.equals(object);
return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
import java.util.Optional;
import java.io.Serializable;
import javax.measure.Unit;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.LenientComparable;
import org.apache.sis.util.Utilities;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
import org.opengis.referencing.operation.MathTransform1D;
Expand Down Expand Up @@ -79,13 +82,13 @@
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Alexis Manin (Geomatys)
* @version 1.5
* @version 1.7
*
* @see org.opengis.metadata.content.SampleDimension
*
* @since 1.0
*/
public class SampleDimension implements IdentifiedType, Serializable {
public class SampleDimension implements IdentifiedType, LenientComparable, Serializable {
/**
* Serial number for inter-operability with different versions.
*/
Expand Down Expand Up @@ -487,15 +490,29 @@ public int hashCode() {
* @return {@code true} if the given object is equal to this sample dimension.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
if (object instanceof SampleDimension) {
final SampleDimension that = (SampleDimension) object;
return name.equals(that.name) && Objects.equals(background, that.background) && categories.equals(that.categories);
public final boolean equals(final Object object) {
return equals(object, ComparisonMode.STRICT);
}

@Override
public boolean equals(Object other, ComparisonMode mode) {
if (other == this) return true;
if (!(other instanceof SampleDimension)) return false;

switch (mode) {
case STRICT: {
if (other.getClass() == getClass()) {
final SampleDimension that = (SampleDimension) other;
return name.equals(that.name) && Objects.equals(background, that.background) && categories.equals(that.categories);
}
}
default: {
final var otherDim = (SampleDimension) other;
return Utilities.deepEquals(this.transferFunction, otherDim.transferFunction, mode)
&& Utilities.deepEquals(this.categories, otherDim.categories, mode)
&& Utilities.deepEquals(this.background, otherDim.background, mode);
}
}
return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.util.ComparisonMode;

// Test dependencies
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -414,4 +415,116 @@ public void testFromConvertedCategories() {
assertTrue(category.toConverse.isIdentity());
}
}

/**
* Tests {@link CategoryList#equals(Object, ComparisonMode)} for all comparison modes.
* Covers null/incompatible-type rejection, empty list, element-wise comparison,
* size mismatch, NaN-ordinal differences, name-only differences, and approximate
* transfer-function tolerance.
*/
@Test
public void testLenientEquality() {
final CategoryList list1 = CategoryList.create(categories(), null);

// ------------------------------------------------------------------
// Null and incompatible type.
// ------------------------------------------------------------------
assertFalse(list1.equals(null, ComparisonMode.STRICT), "null/STRICT");
assertFalse(list1.equals("not a list", ComparisonMode.APPROXIMATE), "String/APPROXIMATE");

// ------------------------------------------------------------------
// Empty list: same reference → true.
// ------------------------------------------------------------------
assertTrue(CategoryList.EMPTY.equals(CategoryList.EMPTY, ComparisonMode.STRICT), "empty/STRICT");

// ------------------------------------------------------------------
// Two lists built from independent category arrays with the same
// configuration. All modes must return true.
// ------------------------------------------------------------------
final CategoryList list2 = CategoryList.create(categories(), null);
assertTrue(list1.equals(list2, ComparisonMode.STRICT), "same-cfg/STRICT");
assertTrue(list1.equals(list2, ComparisonMode.BY_CONTRACT), "same-cfg/BY_CONTRACT");
assertTrue(list1.equals(list2, ComparisonMode.IGNORE_METADATA), "same-cfg/IGNORE_METADATA");
assertTrue(list1.equals(list2, ComparisonMode.APPROXIMATE), "same-cfg/APPROXIMATE");

// ------------------------------------------------------------------
// Different size: list of 5 categories vs 3 categories.
// ------------------------------------------------------------------
final ToNaN toNaN = new ToNaN();
final Category[] fewer = {
new Category("No data", NumberRange.create( 0, true, 0, true), null, null, toNaN),
new Category("Land", NumberRange.create( 7, true, 7, true), null, null, toNaN),
new Category("Temperature", NumberRange.create( 10, true, 100, false),
(MathTransform1D) MathTransforms.linear(0.1, 5), null, toNaN)
};
final CategoryList shortList = CategoryList.create(fewer, null);
assertFalse(list1.equals(shortList, ComparisonMode.STRICT), "diff-size/STRICT");
assertFalse(list1.equals(shortList, ComparisonMode.APPROXIMATE), "diff-size/APPROXIMATE");

// ------------------------------------------------------------------
// Different NaN ordinal in a qualitative category.
// Replace sample value 0 with a category that forces ordinal 99.
// ------------------------------------------------------------------
final Category[] diffNaN = {
new Category("No data", NumberRange.create( 0, true, 0, true), null, null, (v) -> 99),
new Category("Land", NumberRange.create( 7, true, 7, true), null, null, toNaN),
new Category("Clouds", NumberRange.create( 3, true, 3, true), null, null, toNaN),
new Category("Temperature", NumberRange.create( 10, true, 100, false),
(MathTransform1D) MathTransforms.linear(0.1, 5), null, toNaN),
new Category("Foo", NumberRange.create(100, true, 120, false),
(MathTransform1D) MathTransforms.linear(-1, 3), null, toNaN)
};
final CategoryList listDiffNaN = CategoryList.create(diffNaN, null);
assertFalse(list1.equals(listDiffNaN, ComparisonMode.STRICT), "diff-NaN/STRICT");
assertTrue(list1.equals(listDiffNaN, ComparisonMode.APPROXIMATE), "diff-NaN/APPROXIMATE");

// ------------------------------------------------------------------
// Different name only in the qualitative "No data" category.
// ------------------------------------------------------------------
final Category[] diffName = {
new Category("Renamed", NumberRange.create( 0, true, 0, true), null, null, toNaN),
new Category("Land", NumberRange.create( 7, true, 7, true), null, null, toNaN),
new Category("Clouds", NumberRange.create( 3, true, 3, true), null, null, toNaN),
new Category("Temperature", NumberRange.create( 10, true, 100, false),
(MathTransform1D) MathTransforms.linear(0.1, 5), null, toNaN),
new Category("Foo", NumberRange.create(100, true, 120, false),
(MathTransform1D) MathTransforms.linear(-1, 3), null, toNaN)
};
final CategoryList listDiffName = CategoryList.create(diffName, null);
assertFalse(list1.equals(listDiffName, ComparisonMode.STRICT), "diff-name/STRICT");
assertTrue (list1.equals(listDiffName, ComparisonMode.IGNORE_METADATA), "diff-name/IGNORE_METADATA");

// ------------------------------------------------------------------
// Significantly different transfer function (scale 0.1 → 0.2).
// ------------------------------------------------------------------
final Category[] bigDiff = {
new Category("No data", NumberRange.create( 0, true, 0, true), null, null, toNaN),
new Category("Land", NumberRange.create( 7, true, 7, true), null, null, toNaN),
new Category("Clouds", NumberRange.create( 3, true, 3, true), null, null, toNaN),
new Category("Temperature", NumberRange.create( 10, true, 100, false),
(MathTransform1D) MathTransforms.linear(0.2, 5), null, toNaN),
new Category("Foo", NumberRange.create(100, true, 120, false),
(MathTransform1D) MathTransforms.linear(-1, 3), null, toNaN)
};
final CategoryList listBigDiff = CategoryList.create(bigDiff, null);
assertFalse(list1.equals(listBigDiff, ComparisonMode.STRICT), "big-diff/STRICT");
assertFalse(list1.equals(listBigDiff, ComparisonMode.APPROXIMATE), "big-diff/APPROXIMATE");

// ------------------------------------------------------------------
// Tiny transfer function difference: offset 5.0 vs 5.0 - 1e-13.
// Relative threshold for offset ≈ 5 is 5E-13 > 1E-13, so within tolerance.
// ------------------------------------------------------------------
final Category[] tinyDiff = {
new Category("No data", NumberRange.create( 0, true, 0, true), null, null, toNaN),
new Category("Land", NumberRange.create( 7, true, 7, true), null, null, toNaN),
new Category("Clouds", NumberRange.create( 3, true, 3, true), null, null, toNaN),
new Category("Temperature", NumberRange.create( 10, true, 100, false),
(MathTransform1D) MathTransforms.linear(0.1, 5.0 - 1e-13), null, toNaN),
new Category("Foo", NumberRange.create(100, true, 120, false),
(MathTransform1D) MathTransforms.linear(-1, 3), null, toNaN)
};
final CategoryList listTinyDiff = CategoryList.create(tinyDiff, null);
assertFalse(list1.equals(listTinyDiff, ComparisonMode.STRICT), "tiny-diff/STRICT");
assertTrue (list1.equals(listTinyDiff, ComparisonMode.APPROXIMATE), "tiny-diff/APPROXIMATE");
}
}
Loading