blob: c409796ae740209d6d998cd00c2f22dadc073520 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.sis.util;
/**
* Specifies the level of strictness when comparing two {@link LenientComparable} objects
* for equality. This enumeration allows users to specify which kind of differences can be
* tolerated between two objects: differences in implementation class, differences in
* some kinds of property, or slight difference in numerical values.
*
* <p>This enumeration is <em>ordered</em> from stricter to more lenient levels:</p>
*
* <ol>
* <li>{@link #STRICT} – All attributes of the compared objects shall be strictly equal.</li>
* <li>{@link #BY_CONTRACT} – Only the attributes published in the interface contract need to be compared.</li>
* <li>{@link #IGNORE_METADATA} – Only the attributes relevant to the object functionality are compared.</li>
* <li>{@link #APPROXIMATE} – Only the attributes relevant to the object functionality are compared,
* with some tolerance threshold on numerical values.</li>
* <li>{@link #ALLOW_VARIANT} – For objects not really equal but related (e.g. CRS using different axis order).</li>
* <li>{@link #DEBUG} – Special mode for figuring out why two objects expected to be equal are not.</li>
* </ol>
*
* If two objects are equal at some level of strictness <var>E</var>, then they should also
* be equal at all levels listed below <var>E</var> in the above list. For example if two objects
* are equal at the {@link #BY_CONTRACT} level, then they should also be equal at the
* {@link #IGNORE_METADATA} level but not necessarily at the {@link #STRICT} level.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
*
* @see LenientComparable#equals(Object, ComparisonMode)
* @see Utilities#deepEquals(Object, Object, ComparisonMode)
*
* @since 0.3
* @module
*/
public enum ComparisonMode {
/**
* All attributes of the compared objects shall be strictly equal. This comparison mode
* is equivalent to the {@link Object#equals(Object)} method, and must be compliant with
* the contract documented in that method. In particular, this comparison mode shall be
* consistent with {@link Object#hashCode()} and be symmetric ({@code A.equals(B)} implies
* {@code B.equals(A)}).
*
* <div class="section">Implementation note</div>
* In the SIS implementation, this comparison mode usually have the following
* characteristics (not always, this is only typical):
*
* <ul>
* <li>The objects being compared need to be the same implementation class.</li>
* <li>Private fields are compared directly instead than invoking public getter methods.</li>
* </ul>
*
* @see Object#equals(Object)
*/
STRICT,
/**
* Only the attributes published in some contract (typically a GeoAPI interface) need to be compared.
* The implementation classes do not need to be the same and some private attributes may be ignored.
*
* <p>Note that this comparison mode does <strong>not</strong> guaranteed {@link Object#hashCode()}
* consistency, neither comparison symmetry (i.e. {@code A.equals(B)} and {@code B.equals(A)} may
* return different results if the {@code equals} methods are implemented differently).</p>
*
* <div class="section">Implementation note</div>
* In the SIS implementation, this comparison mode usually have the following
* characteristics (not always, this is only typical):
*
* <ul>
* <li>The objects being compared need to implement the same GeoAPI interfaces.</li>
* <li>Public getter methods are used (no direct access to private fields).</li>
* </ul>
*/
BY_CONTRACT,
/**
* Only the attributes relevant to the object functionality are compared. Attributes that
* are only informative can be ignored. This comparison mode is typically less strict than
* {@link #BY_CONTRACT}.
*
* <div class="section">Application to coordinate reference systems</div>
* If the objects being compared are {@link org.opengis.referencing.crs.CoordinateReferenceSystem} instances,
* then only the properties relevant to the coordinate localization shall be compared.
* Metadata like the {@linkplain org.apache.sis.referencing.crs.AbstractCRS#getIdentifiers() identifiers}
* or the {@linkplain org.apache.sis.referencing.crs.AbstractCRS#getDomainOfValidity() domain of validity},
* which have no impact on the coordinates being calculated, shall be ignored.
*
* <div class="section">Application to coordinate operations</div>
* If the objects being compared are {@link org.opengis.referencing.operation.MathTransform} instances,
* then two transforms defined in a different way may be considered equivalent. For example it is possible
* to define a {@linkplain org.apache.sis.referencing.operation.projection.Mercator Mercator} projection in
* two different ways, as a <cite>"Mercator (1SP)"</cite> or as a <cite>"Mercator (2SP)"</cite> projection,
* each having their own set of parameters.
* The {@link #STRICT} or {@link #BY_CONTRACT} modes shall consider two projections as equal only if their
* {@linkplain org.apache.sis.referencing.operation.transform.AbstractMathTransform#getParameterValues()
* parameter values} are strictly identical, while the {@code IGNORE_METADATA} mode can consider
* those objects as equivalent despite difference in the set of parameters, as long as coordinate
* transformations still produce the same results.
*
* <div class="note"><b>Example:</b> A <cite>"Mercator (2SP)"</cite> projection with a <cite>standard parallel</cite>
* value of 60° produces the same results than a <cite>"Mercator (1SP)"</cite> projection with a <cite>scale factor</cite>
* value of 0.5.</div>
*
* @see org.apache.sis.util.Utilities#equalsIgnoreMetadata(Object, Object)
*/
IGNORE_METADATA,
/**
* Only the attributes relevant to the object functionality are compared, with some tolerance
* threshold on numerical values.
*
* <div class="section">Application to coordinate operations</div>
* If two {@link org.opengis.referencing.operation.MathTransform} objects are considered equal according this mode,
* then for any given identical source position, the two compared transforms shall compute at least approximately
* the same target position.
* A small difference is tolerated between the target coordinates calculated by the two math transforms.
* How small is “small” is implementation dependent — the threshold can not be specified in the current
* implementation, because of the non-linear nature of map projections.
*
* @since 1.0
*/
APPROXIMATE,
/**
* Most but not all attributes relevant to the object functionality are compared.
* This comparison mode is equivalent to {@link #APPROXIMATE}, except that it
* ignores some attributes that may differ between objects not equal but related.
*
* <p>The main purpose of this method is to verify if two Coordinate Reference Systems (CRS)
* are approximately equal ignoring axis order.</p>
*
* <div class="note"><b>Example:</b>
* consider two geographic coordinate reference systems with the same attributes except axis order,
* where one CRS uses (<var>latitude</var>, <var>longitude</var>) axes
* and the other CRS uses (<var>longitude</var>, <var>latitude</var>) axes.
* All comparison modes (even {@code APPROXIMATE}) will consider those two CRS as different,
* except this {@code ALLOW_VARIANT} mode which will consider one CRS to be a variant of the other.
* </div>
*
* @since 0.7
*/
ALLOW_VARIANT,
/**
* Same as {@link #APPROXIMATE}, except that an {@link AssertionError} is thrown if the two
* objects are not equal and assertions are enabled. The exception message and stack trace help
* to locate which attributes are not equal. This mode is typically used in assertions like below:
*
* {@preformat java
* assert Utilities.deepEquals(object1, object2, ComparisonMode.DEBUG);
* }
*
* Note that a comparison in {@code DEBUG} mode may still return {@code false} without
* throwing an exception, since not all corner cases are tested. The exception is only
* intended to provide more details for some common cases.
*/
@Debug
DEBUG;
/**
* Returns {@code true} if this comparison ignores metadata.
* This method currently returns {@code true} for {@code IGNORE_METADATA}, {@code APPROXIMATE}
* or {@code DEBUG} only, but this list may be extended in future SIS versions.
*
* @return whether this comparison ignore metadata.
*
* @since 0.6
*/
public boolean isIgnoringMetadata() {
return ordinal() >= IGNORE_METADATA.ordinal();
}
/**
* Returns {@code true} if this comparison uses a tolerance threshold.
* This method currently returns {@code true} for {@code APPROXIMATE} or {@code DEBUG} only,
* but this list may be extended in future SIS versions.
*
* @return whether this comparison uses a tolerance threshold.
*
* @since 1.0
*/
public boolean isApproximate() {
return ordinal() >= APPROXIMATE.ordinal();
}
/**
* If the two given objects are equal according one of the modes enumerated in this class,
* then returns that mode. Otherwise returns {@code null}.
*
* <p><b>Note:</b> this method never return the {@link #DEBUG} mode.</p>
*
* @param o1 the first object to compare, or {@code null}.
* @param o2 the second object to compare, or {@code null}.
* @return the most suitable comparison mode, or {@code null} if the two given objects
* are not equal according any mode in this enumeration.
*/
public static ComparisonMode equalityLevel(final Object o1, Object o2) {
if (o1 == o2) {
return STRICT;
}
if (o1 != null && o2 != null) {
if (o1.equals(o2)) {
return STRICT;
}
final LenientComparable cp;
if (o1 instanceof LenientComparable) {
cp = (LenientComparable) o1;
} else if (o2 instanceof LenientComparable) {
cp = (LenientComparable) o2;
o2 = o1;
} else {
return null;
}
if (cp.equals(o2, BY_CONTRACT)) return BY_CONTRACT;
if (cp.equals(o2, IGNORE_METADATA)) return IGNORE_METADATA;
if (cp.equals(o2, APPROXIMATE)) return APPROXIMATE;
if (cp.equals(o2, ALLOW_VARIANT)) return ALLOW_VARIANT;
}
return null;
}
}