This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new b9ba379a4f Clarify which exceptions are thrown when using reflection
on metadata object. Make `Merger` and `equals(…)` more robust against
`ClassCastException`.
b9ba379a4f is described below
commit b9ba379a4f871d90219a0bf0a7ccee91f7438555
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Jul 23 19:57:59 2026 +0200
Clarify which exceptions are thrown when using reflection on metadata
object.
Make `Merger` and `equals(…)` more robust against `ClassCastException`.
---
.../org/apache/sis/feature/AbstractFeature.java | 5 -
.../org/apache/sis/metadata/AbstractMetadata.java | 3 +-
.../main/org/apache/sis/metadata/CacheKey.java | 7 +
.../org/apache/sis/metadata/MetadataCopier.java | 2 +-
.../org/apache/sis/metadata/MetadataStandard.java | 369 +++++++++++++--------
.../org/apache/sis/metadata/MetadataVisitor.java | 15 +-
.../main/org/apache/sis/metadata/Pruner.java | 4 +-
.../main/org/apache/sis/metadata/StateChanger.java | 2 +-
.../main/org/apache/sis/metadata/TreeNode.java | 10 +-
.../apache/sis/metadata/UnresolvedTypePolicy.java | 49 +++
.../apache/sis/metadata/internal/Resources.java | 5 +
.../sis/metadata/internal/Resources.properties | 1 +
.../sis/metadata/internal/Resources_fr.properties | 1 +
.../sis/metadata/internal/shared/Merger.java | 14 +-
.../iso/extent/DefaultGeographicBoundingBox.java | 2 +-
.../main/org/apache/sis/metadata/package-info.java | 2 +-
.../org/apache/sis/util/iso/DefaultRecordType.java | 2 +-
.../test/org/apache/sis/metadata/HashCodeTest.java | 3 +-
.../apache/sis/metadata/InformationMapTest.java | 1 +
.../apache/sis/metadata/MetadataStandardTest.java | 26 +-
.../test/org/apache/sis/metadata/NameMapTest.java | 1 +
.../test/org/apache/sis/metadata/PrunerTest.java | 1 +
.../org/apache/sis/metadata/SpecialCasesTest.java | 1 +
.../apache/sis/metadata/TreeNodeChildrenTest.java | 2 +-
.../test/org/apache/sis/metadata/TypeMapTest.java | 1 +
.../org/apache/sis/parameter/MatrixParameters.java | 2 +-
.../transform/AbstractLinearTransform.java | 7 +-
.../apache/sis/util/internal/shared/Numerics.java | 2 +-
.../main/org/apache/sis/util/resources/Errors.java | 5 +
.../apache/sis/util/resources/Errors.properties | 1 +
.../apache/sis/util/resources/Errors_fr.properties | 1 +
.../apache/sis/util/collection/RangeSetTest.java | 4 +-
32 files changed, 362 insertions(+), 189 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
index ce4087ebc5..17408ae4a8 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
@@ -99,11 +99,6 @@ public abstract class AbstractFeature implements Feature,
Serializable {
*/
private static final long serialVersionUID = -5637918246427380190L;
- /**
- * Sentinel value for missing property.
- */
- private static final Object MISSING = new Object();
-
/**
* Information about the feature (name, characteristics, <i>etc.</i>).
*/
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java
index 076d3b13f1..3537fa3e32 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java
@@ -362,7 +362,8 @@ public abstract class AbstractMetadata implements
LenientComparable, Emptiable {
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
final MetadataStandard standard = getStandard();
- return standard.equals(this, object,
standard.getInterface(getClass()), mode);
+ final Boolean equals = standard.equals(this, object,
standard.getInterface(getClass()), mode);
+ return (equals != null) && equals;
}
/**
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/CacheKey.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/CacheKey.java
index 9137c767aa..ed34aeddd7 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/CacheKey.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/CacheKey.java
@@ -112,4 +112,11 @@ final class CacheKey {
final String invalid() {
return Errors.format(Errors.Keys.IllegalArgumentClass_3, "type",
propertyType, type);
}
+
+ /**
+ * Creates an error message for an ambiguous property type.
+ */
+ final String ambiguous() {
+ return Errors.format(Errors.Keys.AmbiguousType_2, propertyType, type);
+ }
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataCopier.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataCopier.java
index f0740c35c0..af57f1edbd 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataCopier.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataCopier.java
@@ -187,7 +187,7 @@ public class MetadataCopier extends MetadataVisitor<Object>
{
if (metadata != null) {
final MetadataStandard std = getStandard(metadata);
if (std != null) {
- final Object result = walk(std, type, metadata, false);
+ final Object result = walk(std, type, metadata,
UnresolvedTypePolicy.NULL);
if (result != null) {
return result;
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java
index da2e62ba32..26091cfac2 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java
@@ -22,8 +22,8 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
-import java.util.Objects;
import java.util.Optional;
+import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import java.io.IOException;
@@ -44,6 +44,7 @@ import org.apache.sis.system.Configuration;
import org.apache.sis.system.Modules;
import org.apache.sis.system.Semaphores;
import org.apache.sis.system.SystemListener;
+import org.apache.sis.metadata.internal.Resources;
import org.apache.sis.metadata.simple.SimpleCitation;
import org.apache.sis.xml.NilReason;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
@@ -296,8 +297,8 @@ public class MetadataStandard implements Serializable {
return candidate;
}
}
- for (final Class<?> interf : Classes.getAllInterfaces(type)) {
- classname = interf.getName();
+ for (final Class<?> parent : Classes.getAllInterfaces(type)) {
+ classname = parent.getName();
for (final MetadataStandard candidate : INSTANCES) {
if (candidate.isSupported(classname)) {
return candidate;
@@ -345,18 +346,21 @@ public class MetadataStandard implements Serializable {
* Returns the accessor for the specified interface or implementation
class.
* The type may be an interface (typically a GeoAPI interface) or an
implementation class.
*
- * @param key the interface or implementation class.
- * @param mandatory whether this method shall throw an exception if no
accessor is found.
- * @return the accessor for the given type, or {@code null} if none and
{@code mandatory} is {@code false}.
+ * @param type the interface or implementation class. Caller shall
verify that it is non-null.
+ * @param policy whether this method shall throw an exception if no
non-ambiguous accessor is found.
+ * @return the accessor for the given type, or {@code null} if none or
ambiguous and {@code policy} is {@code NULL}.
+ * @throws ClassCastException if {@code getImplementation(type)} is not
assignable to {@code type}.
+ * @throws NoSuchElementException if the given type not implement a
metadata interface and {@code policy} is {@code THROW}.
+ * @throws IllegalArgumentException if the type implements more than one
metadata interface and {@code policy} is {@code THROW}.
*/
- final PropertyAccessor getTypeAccessor(Class<?> type, final boolean
mandatory) {
+ final PropertyAccessor getTypeAccessor(Class<?> type, final
UnresolvedTypePolicy policy) {
Class<?> propertyType = Object.class;
final Class<?> implementation = getImplementation(type);
if (implementation != null) {
propertyType = type;
type = implementation;
}
- return getAccessor(new CacheKey(type, propertyType), null, mandatory);
+ return getAccessor(new CacheKey(type, propertyType), policy, null);
}
/**
@@ -372,12 +376,13 @@ public class MetadataStandard implements Serializable {
*
* @param metadata the metadata object.
* @param propertyType base class of the metadata object, or {@code
null} if unknown.
- * @param mandatory whether this method shall throw an exception if
no accessor is found.
- * @return the accessor for the given object, or {@code null} if none and
{@code mandatory} is {@code false}.
- * @throws ClassCastException if the metadata object does not implement an
expected metadata interface
- * and {@code mandatory} is {@code true}.
+ * @param policy whether this method shall throw an exception if
no non-ambiguous accessor is found.
+ * @return the accessor for the given object, or {@code null} if none or
ambiguous and {@code policy} is {@code NULL}.
+ * @throws ClassCastException if {@code metadata} is not an instance of
{@code propertyType}.
+ * @throws NoSuchElementException if the object does not implement a
metadata interface and {@code policy} is {@code THROW}.
+ * @throws IllegalArgumentException if the object implements more than one
metadata interface and {@code policy} is {@code THROW}.
*/
- final PropertyAccessor getInstanceAccessor(final Object metadata, Class<?>
propertyType, final boolean mandatory) {
+ final PropertyAccessor getInstanceAccessor(final Object metadata, Class<?>
propertyType, final UnresolvedTypePolicy policy) {
if (propertyType == null) {
if (metadata instanceof LenientComparable) {
propertyType = Classes.getRawClass(((LenientComparable)
metadata).getStandardType());
@@ -386,45 +391,44 @@ public class MetadataStandard implements Serializable {
propertyType = Object.class;
}
}
- return getAccessor(new CacheKey(metadata.getClass(), propertyType),
null, mandatory);
+ return getAccessor(new CacheKey(metadata.getClass(), propertyType),
policy, null);
}
/**
* Returns the accessor for the specified implementation class, or {@code
null} if none.
- * The given class shall not be the standard interface, unless the
metadata is read-only.
- * More specifically, the given {@code type} shall be one of the following:
+ * {@link CacheKey#type} shall not be the standard interface, unless the
metadata is read-only.
+ * More specifically, {@code CacheKey.type} shall be one of the followings:
*
* <ul>
- * <li>The value of {@code metadata.getClass()};</li>
- * <li>The value of {@link #getImplementation(Class)} after check for
non-null value.</li>
+ * <li>the value of {@code metadata.getClass()}, or</li>
+ * <li>the value of {@link #getImplementation(Class)} after check for
non-null value.</li>
* </ul>
*
- * @param key the implementation class together with the type
declared by the property.
- * @param visited metadata standards already visited, or {@code null}
if none.
- * @param mandatory whether this method shall throw an exception or
return {@code null}
- * if no accessor is found for the given implementation
class.
- * @return the accessor for the given implementation, or {@code null} if
the given class does not
- * implement a metadata interface of the expected package and
{@code mandatory} is {@code false}.
- * @throws ClassCastException if the specified class does not implement an
expected metadata interface
- * and {@code mandatory} is {@code true}.
+ * @param key the implementation class together with the type
declared by the property.
+ * @param policy whether this method shall throw an exception if no
non-ambiguous accessor is found.
+ * @param visited metadata standards already visited, or {@code null} if
not yet created.
+ * @return the accessor for the given key, or {@code null} if none or
ambiguous and {@code policy} is {@code NULL}.
+ * @throws ClassCastException if {@code key.type} is not assignable to
{@code key.propertyType}.
+ * @throws NoSuchElementException if the object does not implement a
metadata interface and {@code policy} is {@code THROW}.
+ * @throws IllegalArgumentException if the object implements more than one
metadata interface and {@code policy} is {@code THROW}.
*/
- private PropertyAccessor getAccessor(final CacheKey key,
Set<MetadataStandard> visited, final boolean mandatory) {
+ private PropertyAccessor getAccessor(final CacheKey key, final
UnresolvedTypePolicy policy, Set<MetadataStandard> visited) {
/*
* Check for accessors created by previous calls to this method.
* Values are added to this cache but never cleared.
*/
- final Object value = accessors.get(key);
- if (value instanceof PropertyAccessor) {
- return (PropertyAccessor) value;
+ final Object cache = accessors.get(key);
+ if (cache instanceof PropertyAccessor) {
+ return (PropertyAccessor) cache;
}
/*
* Check if we started some computation that we can finish. A partial
computation exists
* when we already found the Class<?> for the interface, but didn't
created the accessor.
*/
final Class<?> type;
- if (value instanceof Class<?>) {
- type = (Class<?>) value; // Stored result
of previous call to findInterface(…).
- assert type == findInterface(key) : key;
+ if (cache instanceof Class<?>) {
+ type = (Class<?>) cache; // Stored result of previous call to
findInterface(…).
+ assert type == findInterface(key, UnresolvedTypePolicy.NULL) : key;
} else if (key.isValid()) {
/*
* Nothing was computed, we need to start from scratch. The first
step is to find
@@ -432,22 +436,22 @@ public class MetadataStandard implements Serializable {
* we will delegate to the dependencies and store the result for
avoiding redoing
* this search next time.
*/
- type = findInterface(key);
+ type = findInterface(key, policy);
if (type == null) {
visited = createIfNull(visited);
for (final MetadataStandard dependency : dependencies) {
if (visited.add(dependency)) {
- final PropertyAccessor accessor =
dependency.getAccessor(key, visited, false);
+ final PropertyAccessor accessor =
dependency.getAccessor(key, policy, visited);
if (accessor != null) {
- accessors.put(key, accessor); // Ok
to overwrite existing instance here.
+ accessors.put(key, accessor); // Ok to overwrite
existing instance here.
return accessor;
}
}
}
- if (mandatory) {
- throw new ClassCastException(key.unrecognized());
+ if (policy != UnresolvedTypePolicy.THROW) {
+ return null;
}
- return null;
+ throw new NoSuchElementException(key.unrecognized());
}
} else {
throw new ClassCastException(key.invalid());
@@ -473,12 +477,13 @@ public class MetadataStandard implements Serializable {
/**
* Returns {@code true} if the given type is assignable to a type from
this standard or one of its dependencies.
- * If this method returns {@code true}, then invoking {@link
#getInterface(Class)} is guaranteed to succeed
- * without throwing an exception.
+ * If this method returns {@code true}, then a call to {@link
#getInterface(Class)} with the same argument will
+ * not throw {@link ClassCastException}. Note however that {@code
getInterface(type)} may throw
+ * {@link IllegalArgumentException} if the given type is ambiguous.
*
* @param type the implementation class (can be {@code null}).
* @return {@code true} if the given class is an interface of this
standard,
- * or implements an interface of this standard.
+ * or is a subtype of at least one interface of this standard.
*/
public boolean isMetadata(final Class<?> type) {
return (type != null) && !type.isPrimitive() && isMetadata(new
CacheKey(type, Object.class));
@@ -487,10 +492,13 @@ public class MetadataStandard implements Serializable {
/**
* Implementation of {@link #isMetadata(Class)} with the possibility to
specify the property type.
* We do not provide the additional functionality of this method in public
<abbr>API</abbr> on the
- * assumption that if the user know the base metadata type implemented by
the value,
- * then (s)he already know that the value is a metadata instance.
+ * assumption that if the user knows the base metadata type implemented by
the value,
+ * then (s)he already knows that the value is a metadata instance.
+ *
+ * @param key the implementation class together with the type declared
by the property.
+ * @throws ClassCastException if {@code key.type} is not assignable to
{@code key.propertyType}.
*
- * @see #getInterface(CacheKey, Set)
+ * @see #getInterface(CacheKey, UnresolvedTypePolicy, Set)
*/
private boolean isMetadata(final CacheKey key) {
assert key.isValid() : key;
@@ -508,18 +516,14 @@ public class MetadataStandard implements Serializable {
* Performs the `findInterface(…)` computation only in last resort.
Current implementation
* does not store negative results in order to avoid filling the cache
with unrelated classes.
*/
- final Class<?> standardType = findInterface(key);
- if (standardType != null) {
- accessors.putIfAbsent(key, standardType);
- return true;
- }
- return false;
+ return findInterface(key, UnresolvedTypePolicy.ANY) != null;
}
/**
- * Returns {@code true} if the given implementation class, normally
rejected by {@link #findInterface(CacheKey)},
- * should be accepted as a pseudo-interface. We use this undocumented
feature when Apache SIS experiments a new
- * API which is not yet published in GeoAPI. This happen for example when
upgrading Apache SIS public API from
+ * Returns {@code true} if the given implementation class, normally
rejected by
+ * {@link #findInterface(CacheKey, UnresolvedTypePolicy)}, should be
accepted as a pseudo-interface.
+ * We use this undocumented feature when Apache <abbr>SIS</abbr>
experiments a new <abbr>API</abbr>
+ * which is not yet published in GeoAPI. This happen for example when
upgrading Apache SIS public API from
* the ISO 19115:2003 standard to the ISO 19115:2014 version, but GeoAPI
interfaces are still the old version.
* In such case, API that would normally be present in GeoAPI interfaces
are temporarily available only in
* Apache SIS implementation classes.
@@ -530,8 +534,8 @@ public class MetadataStandard implements Serializable {
/**
* Returns the metadata interface implemented by the specified
implementation.
- * Only one metadata interface can be implemented. If the given type is
already
- * an interface from the standard, then it is returned directly.
+ * Only one metadata interface can be implemented, except with {@link
UnresolvedTypePolicy#ANY}.
+ * If the given type is already an interface from the standard, then it is
returned directly.
*
* <p>If the given class is the return value of a property, then the type
of that property should be specified
* in the {@code key.propertyType} argument. This information allows this
method to take in account only types
@@ -539,16 +543,22 @@ public class MetadataStandard implements Serializable {
* For example, the {@link org.apache.sis.metadata.simple} package have
various examples of implementing more than
* one interface for convenience.</p>
*
+ * <p>This method does not check whether the interface is cached. However,
if this method finds an interface,
+ * then it caches the result in {@link #accessors} if there is no value
already associated to the given key.</p>
+ *
* <p>This method ignores dependencies. Fallback on metadata standard
dependencies shall be done by the caller.</p>
*
- * @param key the standard interface or the implementation class.
- * @return the single interface, or {@code null} if none was found.
+ * @param key the standard interface or the implementation class.
+ * @param policy whether to throw an exception in case of ambiguity.
+ * @return the single interface, or {@code null} if none or ambiguous.
+ * @throws IllegalArgumentException if the specified type is ambiguous and
{@code policy} if {@code THROW}.
*/
- private Class<?> findInterface(final CacheKey key) {
+ private Class<?> findInterface(final CacheKey key, final
UnresolvedTypePolicy policy) {
assert key.isValid() : key;
final Class<?> type = key.type;
if (type.isInterface()) {
if (isSupported(type.getName())) {
+ accessors.putIfAbsent(key, type);
return type;
}
} else {
@@ -582,7 +592,13 @@ public class MetadataStandard implements Serializable {
if (it.hasNext()) {
final Class<?> candidate = it.next();
if (!it.hasNext()) {
+ accessors.putIfAbsent(key, candidate);
return candidate;
+ } else if (policy == UnresolvedTypePolicy.ANY) {
+ // Do not cache this result since it is ambiguous.
+ return candidate;
+ } else if (policy == UnresolvedTypePolicy.THROW) {
+ throw new IllegalArgumentException(key.ambiguous());
}
/*
* Found more than one interface and we don't know which one
to pick.
@@ -599,6 +615,7 @@ public class MetadataStandard implements Serializable {
* So we use those implementation classes as a temporary
substitute for the interfaces.
*/
if (isPendingAPI(type)) {
+ accessors.putIfAbsent(key, type);
return type;
}
}
@@ -641,8 +658,8 @@ public class MetadataStandard implements Serializable {
* Returns the metadata interface implemented by the specified
implementation class.
* If the given type is already an interface from this standard, then that
type is returned directly.
* If the given type does not implement an interface from this standard,
then a {@link ClassCastException} is thrown.
- * If the given type implements more than one interface from this
standard, then a {@link ClassCastException} is also
- * thrown because of the ambiguity.
+ * If the given type implements more than one interface from this
standard, then an {@link IllegalArgumentException}
+ * is thrown because of the ambiguity.
*
* <div class="note"><b>Note:</b>
* in this context, "interface" should be understood as
@@ -652,13 +669,17 @@ public class MetadataStandard implements Serializable {
* @param <T> the compile-time {@code type}.
* @param type the implementation class.
* @return the interface implemented by the given implementation class.
- * @throws ClassCastException if the specified implementation class does
not implement exactly one interface
- * of this standard,
+ * @throws ClassCastException if the given type does not implement an
interface of this standard.
+ * @throws IllegalArgumentException if the given type implements more than
one interface of this standard.
*
* @see AbstractMetadata#getStandardType()
*/
public <T> Class<? super T> getInterface(final Class<T> type) {
- return getInterface(new CacheKey(Objects.requireNonNull(type),
Object.class), null);
+ try {
+ return getInterface(type, null);
+ } catch (NoSuchElementException e) {
+ throw unrecognized(e, type);
+ }
}
/**
@@ -671,64 +692,67 @@ public class MetadataStandard implements Serializable {
* @param type the implementation class.
* @param baseType base type of the metadata of interest, or {@code
null} if unspecified.
* @return the interface implemented by the given implementation class.
- * @throws ClassCastException if the specified implementation class is not
assignable to {@code baseType},
- * or if the class does not implement exactly one interface which
is a subtype of {@code baseType}.
+ * @throws ClassCastException if {@code type} is not assignable to {@code
baseType}.
+ * @throws NoSuchElementException if the given type does not implement a
standard subtype of {@code baseType}.
+ * @throws IllegalArgumentException if the given type implements more than
one standard subtype of {@code baseType}.
*
* @since 1.7
*/
+ @SuppressWarnings("unchecked")
public <T> Class<? super T> getInterface(final Class<T> type, Class<?>
baseType) {
+ ensureNonNull("type", type);
if (baseType == null) {
baseType = Object.class;
}
- final var key = new CacheKey(Objects.requireNonNull(type), baseType);
- if (key.isValid()) {
- return getInterface(key, null);
- }
- throw new ClassCastException(key.invalid());
+ final var key = new CacheKey(type, baseType);
+ return (Class) getInterface(key, UnresolvedTypePolicy.THROW, null);
}
/**
- * Implementation of {@link #getInterface(Class)} with the possibility to
specify the property type.
- * We do not provide the additional functionality of this method in public
API on the assumption that
- * users who want to invoke a {@code getInterface(…)} method does not know
what that interface is.
- * In Apache SIS case, we invoke this method when we almost know what the
interface is but want to
- * check if the actual value is a subtype.
+ * Implementation of {@link #getInterface(Class)} and {@link
#getInterface(Class, Class)}.
+ * This method may invoke itself recursively, and therefore needs a safety
against infinite
+ * recursive calls.
*
* @param key the implementation class together with the type
declared by the property.
- * @param visited metadata standards already visited, or {@code null} if
none.
- * @throws ClassCastException if the implementation class does not
implement an interface of this standard.
+ * @param policy whether to throw an exception in case of ambiguity.
+ * @param visited metadata standards already visited, or {@code null} if
not yet created.
+ * @return the interface implemented by the given class, or {@code null}
if unknown and the policy is {@code NULL}.
+ * @throws ClassCastException if {@code key.type} is not assignable to
{@code key.propertyType}.
+ * @throws NoSuchElementException if the metadata class does not implement
a metadata interface of this standard.
+ * @throws IllegalArgumentException if the metadata class implements more
than one metadata interface of this standard.
*
* @see #isMetadata(CacheKey)
*/
- @SuppressWarnings("unchecked")
- final <T> Class<? super T> getInterface(final CacheKey key,
Set<MetadataStandard> visited) {
- final Class<?> interf;
+ final Class<?> getInterface(final CacheKey key, final UnresolvedTypePolicy
policy, Set<MetadataStandard> visited) {
+ final Class<?> type;
final Object value = accessors.get(key);
if (value instanceof PropertyAccessor) {
- interf = ((PropertyAccessor) value).type;
+ type = ((PropertyAccessor) value).type;
} else if (value instanceof Class<?>) {
- interf = (Class<?>) value;
+ type = (Class<?>) value;
} else if (value instanceof MetadataStandard) {
- interf = ((MetadataStandard) value).getInterface(key,
createIfNull(visited));
+ type = ((MetadataStandard) value).getInterface(key, policy,
createIfNull(visited));
} else if (key.isValid()) {
- interf = findInterface(key);
- if (interf != null) {
- accessors.putIfAbsent(key, interf);
- } else {
+ type = findInterface(key, policy);
+ if (type == null) {
visited = createIfNull(visited);
for (final MetadataStandard dependency : dependencies) {
- if (dependency.isMetadata(key)) {
- accessors.putIfAbsent(key, dependency);
- return dependency.getInterface(key, visited);
+ final Class<?> candidate = dependency.getInterface(key,
policy.lenient(), visited);
+ if (candidate != null) {
+ accessors.putIfAbsent(key, dependency); // Not the
same cache as `dependency`.
+ return candidate;
}
}
- throw new ClassCastException(key.unrecognized());
+ if (policy != UnresolvedTypePolicy.THROW) {
+ return null;
+ }
+ throw new NoSuchElementException(key.unrecognized());
}
} else {
throw new ClassCastException(key.invalid());
}
- assert interf.isAssignableFrom(key.type) : key;
- return (Class<? super T>) interf;
+ assert type.isAssignableFrom(key.type) : key;
+ return type;
}
/**
@@ -781,7 +805,7 @@ public class MetadataStandard implements Serializable {
* The {@code isStarted} parameter is required for callers that need to
distinguish these two cases.</p>
*
* @param metadata the metadata for which to get the title property,
or {@code null}.
- * @param propertyType the interface which should be implemented to the
metadata.
+ * @param propertyType the interface which should be implemented to the
metadata, or {@code null} if unknown.
* @param isTypeOnly {@code false} if {@code metadata} is an instance,
or {@code true} if a {@code Class}.
* @param wantTypeOnly whether to return the property type (as a {@link
Class}) rather than the value.
* @param isStarted if this method is invoked for a search that
already started on the first annotated property.
@@ -799,10 +823,10 @@ public class MetadataStandard implements Serializable {
final PropertyAccessor accessor;
if (isTypeOnly) {
type = (Class<?>) metadata;
- accessor = getTypeAccessor(type, false);
+ accessor = getTypeAccessor(type, UnresolvedTypePolicy.NULL);
} else {
type = metadata.getClass();
- accessor = getInstanceAccessor(metadata, propertyType, false);
+ accessor = getInstanceAccessor(metadata, propertyType,
UnresolvedTypePolicy.NULL);
}
if (accessor == null) break; // Not a metadata.
TitleProperty a = type.getAnnotation(TitleProperty.class);
@@ -855,14 +879,18 @@ public class MetadataStandard implements Serializable {
* @param keyPolicy determines the string representation of map keys.
* @param valuePolicy determines the string representation of map values.
* @return the names of all properties defined by the given metadata type.
- * @throws ClassCastException if the specified interface or implementation
class does
- * not extend or implement a metadata interface of the expected
package.
+ * @throws ClassCastException if the metadata class does not implement a
metadata interface of this standard.
+ * @throws IllegalArgumentException if the metadata class implements more
than one metadata interface of this standard.
*/
public Map<String, String> asNameMap(Class<?> type, KeyNamePolicy
keyPolicy, KeyNamePolicy valuePolicy) {
ensureNonNull("type", type);
ensureNonNull("keyPolicy", keyPolicy);
ensureNonNull("valuePolicy", valuePolicy);
- return new NameMap(getTypeAccessor(type, true), keyPolicy,
valuePolicy);
+ try {
+ return new NameMap(getTypeAccessor(type,
UnresolvedTypePolicy.THROW), keyPolicy, valuePolicy);
+ } catch (NoSuchElementException e) {
+ throw unrecognized(e, type);
+ }
}
/**
@@ -887,14 +915,18 @@ public class MetadataStandard implements Serializable {
* @param valuePolicy whether the values shall be property types, the
element types
* (same as property types except for collections) or the
declaring interface or class.
* @return the types or declaring type of all properties defined in the
given metadata type.
- * @throws ClassCastException if the specified interface or implementation
class does
- * not extend or implement a metadata interface of the expected
package.
+ * @throws ClassCastException if the metadata class does not implement a
metadata interface of this standard.
+ * @throws IllegalArgumentException if the metadata class implements more
than one metadata interface of this standard.
*/
public Map<String, Class<?>> asTypeMap(Class<?> type, KeyNamePolicy
keyPolicy, TypeValuePolicy valuePolicy) {
ensureNonNull("type", type);
ensureNonNull("keyPolicy", keyPolicy);
ensureNonNull("valuePolicy", valuePolicy);
- return new TypeMap(getTypeAccessor(type, true), keyPolicy,
valuePolicy);
+ try {
+ return new TypeMap(getTypeAccessor(type,
UnresolvedTypePolicy.THROW), keyPolicy, valuePolicy);
+ } catch (NoSuchElementException e) {
+ throw unrecognized(e, type);
+ }
}
/**
@@ -937,14 +969,19 @@ public class MetadataStandard implements Serializable {
* @param type the metadata interface or implementation class.
* @param keyPolicy determines the string representation of map keys.
* @return information about all properties defined in the given metadata
type.
- * @throws ClassCastException if the given type does not implement a
metadata interface of the expected package.
+ * @throws ClassCastException if the metadata class does not implement a
metadata interface of this standard.
+ * @throws IllegalArgumentException if the metadata class implements more
than one metadata interface of this standard.
*
* @see org.apache.sis.metadata.iso.DefaultExtendedElementInformation
*/
public Map<String, ExtendedElementInformation> asInformationMap(Class<?>
type, KeyNamePolicy keyPolicy) {
ensureNonNull("type", type);
ensureNonNull("keyNames", keyPolicy);
- return new InformationMap(citation, getTypeAccessor(type, true),
keyPolicy);
+ try {
+ return new InformationMap(citation, getTypeAccessor(type,
UnresolvedTypePolicy.THROW), keyPolicy);
+ } catch (NoSuchElementException e) {
+ throw unrecognized(e, type);
+ }
}
/**
@@ -961,13 +998,17 @@ public class MetadataStandard implements Serializable {
* @param type the interface or implementation class of a metadata.
* @param keyPolicy determines the string representation of map keys.
* @return indices of all properties defined by the given metadata type.
- * @throws ClassCastException if the specified interface or implementation
class does
- * not extend or implement a metadata interface of the expected
package.
+ * @throws ClassCastException if the metadata class does not implement a
metadata interface of this standard.
+ * @throws IllegalArgumentException if the metadata class implements more
than one metadata interface of this standard.
*/
public Map<String, Integer> asIndexMap(Class<?> type, KeyNamePolicy
keyPolicy) {
ensureNonNull("type", type);
ensureNonNull("keyPolicy", keyPolicy);
- return new IndexMap(getTypeAccessor(type, true), keyPolicy);
+ try {
+ return new IndexMap(getTypeAccessor(type,
UnresolvedTypePolicy.THROW), keyPolicy);
+ } catch (NoSuchElementException e) {
+ throw unrecognized(e, type);
+ }
}
/**
@@ -1017,7 +1058,8 @@ public class MetadataStandard implements Serializable {
* @param keyPolicy determines the string representation of map keys.
* @param valuePolicy whether the entries having null value or empty
collection shall be included in the map.
* @return a map view over the metadata object.
- * @throws ClassCastException if the metadata object does not implement a
metadata interface of the expected package.
+ * @throws ClassCastException if the metadata object does not implement a
metadata interface of this standard.
+ * @throws IllegalArgumentException if the metadata object implements more
than one metadata interface of this standard.
*
* @see AbstractMetadata#asMap()
*
@@ -1031,7 +1073,11 @@ public class MetadataStandard implements Serializable {
ensureNonNull("metadata", metadata);
ensureNonNull("keyPolicy", keyPolicy);
ensureNonNull("valuePolicy", valuePolicy);
- return new ValueMap(metadata, getInstanceAccessor(metadata, baseType,
true), keyPolicy, valuePolicy);
+ try {
+ return new ValueMap(metadata, getInstanceAccessor(metadata,
baseType, UnresolvedTypePolicy.THROW), keyPolicy, valuePolicy);
+ } catch (NoSuchElementException e) {
+ throw unrecognized(e, metadata.getClass());
+ }
}
/**
@@ -1057,7 +1103,8 @@ public class MetadataStandard implements Serializable {
* @param baseType base type of the metadata of interest, or {@code
null} if unspecified.
* @param keyPolicy determines the string representation of map keys.
* @return a map view over the metadata object.
- * @throws ClassCastException if the metadata object does not implement a
metadata interface of the expected package.
+ * @throws ClassCastException if the metadata object does not implement a
metadata interface of this standard.
+ * @throws IllegalArgumentException if the metadata object implements more
than one metadata interface of this standard.
*
* @see AbstractMetadata#nilReasons()
*
@@ -1066,7 +1113,11 @@ public class MetadataStandard implements Serializable {
public Map<String, NilReason> asNilReasonMap(Object metadata, Class<?>
baseType, KeyNamePolicy keyPolicy) {
ensureNonNull("metadata", metadata);
ensureNonNull("keyPolicy", keyPolicy);
- return new NilReasonMap(metadata, getInstanceAccessor(metadata,
baseType, true), keyPolicy);
+ try {
+ return new NilReasonMap(metadata, getInstanceAccessor(metadata,
baseType, UnresolvedTypePolicy.THROW), keyPolicy);
+ } catch (NoSuchElementException e) {
+ throw unrecognized(e, metadata.getClass());
+ }
}
/**
@@ -1145,7 +1196,8 @@ public class MetadataStandard implements Serializable {
* @param baseType base type of the metadata of interest, or {@code
null} if unspecified.
* @param valuePolicy whether the property having null value or empty
collection shall be included in the tree.
* @return a tree table representation of the specified metadata.
- * @throws ClassCastException if the metadata object does not implement a
metadata interface of the expected package.
+ * @throws ClassCastException if the metadata object does not implement a
metadata interface of this standard.
+ * @throws IllegalArgumentException if the metadata object implements more
than one metadata interface of this standard.
*
* @see AbstractMetadata#asTreeTable()
*
@@ -1154,8 +1206,10 @@ public class MetadataStandard implements Serializable {
public TreeTable asTreeTable(final Object metadata, Class<?> baseType,
final ValueExistencePolicy valuePolicy) {
ensureNonNull("metadata", metadata);
ensureNonNull("valuePolicy", valuePolicy);
- if (baseType == null) {
+ if (baseType == null) try {
baseType = getInterface(metadata.getClass());
+ } catch (NoSuchElementException e) {
+ throw unrecognized(e, metadata.getClass());
}
return new TreeTableView(this, metadata, baseType, valuePolicy);
}
@@ -1170,59 +1224,77 @@ public class MetadataStandard implements Serializable {
* @param metadata1 the first metadata object to compare, or {@code
null}.
* @param metadata2 the second metadata object to compare, or {@code
null}.
* @param mode the strictness level of the comparison.
- * @return {@code true} if the given metadata objects are equals or if the
two arguments are {@code null}.
- * @throws ClassCastException if {@code metadata1} does not implement an
expected metadata interface.
+ * @return {@code true} if the given metadata objects are equal or if the
two arguments are {@code null}.
*
* @deprecated Replaced by {@link #equals(Object, Object, Class,
ComparisonMode)} because this method
* is ambiguous when one of the given metadata instances implements more
than one metadata interface.
*/
@Deprecated(since="1.7", forRemoval=true)
public boolean equals(final Object metadata1, final Object metadata2,
final ComparisonMode mode) {
- return equals(metadata1, metadata2, null, mode);
+ final Boolean equals = equals(metadata1, metadata2, null, mode);
+ if (equals == null) throw new ClassCastException();
+ return equals;
}
/**
* Compares the two specified metadata instances for equality.
- * Unless the {@code mode} argument is {@link ComparisonMode#STRICT},
- * the two metadata objects do not need to be instances of the same class.
- * However, {@code metadata1} shall implement an interface defined by this
{@code MetadataStandard},
- * otherwise a {@link ClassCastException} may be thrown. If {@code
metadata1} implements more than
- * one interface managed by this {@code MetadataStandard}, the ambiguity
can be resolved by specifying
- * the interface of interest (or a non-ambiguous super-type of it) in the
{@code baseType} argument.
+ * The two metadata arguments should be implementations of a
+ * {@linkplain #getInterface(Class, Class) metadata interface} defined by
this {@code MetadataStandard}.
+ * However, they do not need to be of the exact same class unless {@code
criteria} is {@link ComparisonMode#STRICT}.
+ * If the equality cannot be determined in a non-ambiguous way, for
example because the given metadata instances
+ * do not implement exactly one interface assignable to {@code baseType},
then this method returns {@code null}.
+ *
+ * <h4>Resolution of type ambiguity</h4>
+ * Because some metadata standards define hundreds of interfaces,
+ * some metadata classes may implement many interfaces at once for
convenience.
+ * For avoiding ambiguity, the interface of interest, or a non-ambiguous
super-type of the interface of interest,
+ * should be specified in the {@code baseType} argument. This information
can be obtained, for example, from the
+ * {@linkplain java.lang.reflect.Method#getReturnType() return type} of
the method that provided the metadata.
+ * Only properties inherited from interfaces that are assignable to {@code
baseType} will be compared.
+ * If {@code baseType} is {@code null}, then this method tries to find the
base type automatically.
+ * The latter is not always equivalent to specifying {@code Object.class}
as the base type.
*
* @param metadata1 the first metadata object to compare, or {@code
null}.
* @param metadata2 the second metadata object to compare, or {@code
null}.
* @param baseType base type of the metadata of interest, or {@code
null} if unspecified.
- * @param mode the strictness level of the comparison.
- * @return {@code true} if the given metadata objects are equals or if the
two arguments are {@code null}.
- * @throws ClassCastException if {@code metadata1} does not implement an
expected metadata interface.
+ * @param criteria the strictness level of the comparison.
+ * @return whether the given metadata objects are equal, or {@code null}
if undetermined.
+ * @throws ClassCastException if {@code metadata1} is non-null and not an
instance of {@code baseType}.
*
* @see AbstractMetadata#equals(Object, ComparisonMode)
*
* @since 1.7
*/
- public boolean equals(final Object metadata1, final Object metadata2,
final Class<?> baseType, final ComparisonMode mode) {
+ public Boolean equals(final Object metadata1, final Object metadata2,
final Class<?> baseType, final ComparisonMode criteria) {
+ ensureNonNull("criteria", criteria);
if (metadata1 == metadata2) {
- return true;
+ return Boolean.TRUE;
}
if (metadata1 == null || metadata2 == null) {
- return false;
+ return Boolean.FALSE;
}
final Class<?> type1 = metadata1.getClass();
final Class<?> type2 = metadata2.getClass();
- if (type1 != type2 && mode == ComparisonMode.STRICT) {
- return false;
+ if (type1 != type2 && criteria == ComparisonMode.STRICT) {
+ return Boolean.FALSE;
+ }
+ final PropertyAccessor accessor = getInstanceAccessor(metadata1,
baseType, UnresolvedTypePolicy.NULL);
+ if (accessor == null) {
+ // Do not invoke `Object.equals(Object)` because it may cause an
infinite loop.
+ return null;
}
- final PropertyAccessor accessor = getInstanceAccessor(metadata1,
baseType, true);
if (type1 != type2) {
final var key = new CacheKey(type2, accessor.type);
// Not strictly necessary, but can avoid the relatively costly
creation of new `PropertyAccessor`.
- if (!accessor.type.isAssignableFrom(type2)) {
- return false;
+ if (!key.isValid()) {
+ return Boolean.FALSE;
}
- final PropertyAccessor other = getAccessor(key, null, false);
- if (other == null || accessor.type != other.type) {
- return false;
+ final PropertyAccessor other = getAccessor(key,
UnresolvedTypePolicy.NULL, null);
+ if (other == null) {
+ return null;
+ }
+ if (accessor.type != other.type) {
+ return Boolean.FALSE;
}
}
/*
@@ -1240,7 +1312,7 @@ public class MetadataStandard implements Serializable {
* some getters with an implementation invoking other getters.
*/
try {
- return Semaphores.NULL_FOR_EMPTY_COLLECTION.execute(() ->
accessor.equals(metadata1, metadata2, mode));
+ return Semaphores.NULL_FOR_EMPTY_COLLECTION.execute(() ->
accessor.equals(metadata1, metadata2, criteria));
} finally {
inProgress.remove(pair);
}
@@ -1250,7 +1322,7 @@ public class MetadataStandard implements Serializable {
* comparing other properties. It is okay because someone else is
comparing those two same objects,
* and that later comparison will do the actual check for property
values.
*/
- return true;
+ return Boolean.TRUE;
}
}
@@ -1262,13 +1334,14 @@ public class MetadataStandard implements Serializable {
*
* @param metadata the metadata object to compute hash code.
* @return a hash code value for the specified metadata, or 0 if the given
metadata is null.
- * @throws ClassCastException if the metadata object does not implement a
metadata interface of the expected package.
+ * @throws ClassCastException if the metadata object does not implement a
metadata interface of this standard.
+ * @throws IllegalArgumentException if the metadata object implements more
than one metadata interface of this standard.
*
* @see AbstractMetadata#hashCode()
*/
public int hashCode(final Object metadata) {
- if (metadata != null) {
- final Integer hash = HashCode.getOrCreate().walk(this,
Object.class, metadata, true);
+ if (metadata != null) try {
+ final Integer hash = HashCode.getOrCreate().walk(this,
Object.class, metadata, UnresolvedTypePolicy.NULL);
if (hash != null) return hash;
/*
* `hash` may be null if a cycle has been found. Example: A
depends on B which depends on A,
@@ -1277,10 +1350,22 @@ public class MetadataStandard implements Serializable {
* of a bigger metadata object, and that enclosing object should
have other properties for computing
* its hash code.
*/
+ } catch (NoSuchElementException e) {
+ throw unrecognized(e, metadata.getClass());
}
return 0;
}
+ /**
+ * Creates an exception for an object which is not an instance of this
standard.
+ */
+ private ClassCastException unrecognized(final NoSuchElementException
cause, final Class<?> type) {
+ final var e = new ClassCastException(Resources.format(
+ Resources.Keys.CannotHandleAsStandardType_2,
citation.getTitle(), Classes.getShortName(type)));
+ e.initCause(cause);
+ return e;
+ }
+
/**
* Returns a string representation of this metadata standard.
* This is for debugging purpose only and may change in any future version.
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataVisitor.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataVisitor.java
index 3b2b53fd44..0682ccfec5 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataVisitor.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataVisitor.java
@@ -143,17 +143,20 @@ abstract class MetadataVisitor<R> {
* {@link AbstractMetadata#getStandardType()} to be invoked, then it is
preferable
* to specify {@code Object.class} for avoiding to do reflection twice.
*
- * @param standard the metadata standard implemented by the object to
visit.
- * @param baseType the standard interface implemented by the metadata
object, or {@code null} if unknown.
- * @param metadata the metadata to visit.
- * @param mandatory {@code true} for throwing an exception for
unsupported metadata type, or {@code false} for ignoring.
+ * @param standard the metadata standard implemented by the object to
visit.
+ * @param baseType the standard interface implemented by the metadata
object, or {@code null} if unknown.
+ * @param metadata the metadata to visit.
+ * @param policy {@code true} for throwing an exception for
unsupported metadata type, or {@code false} for ignoring.
* @return the value of {@link #result()} after all elements of the given
metadata have been visited.
* If the given metadata instance has already been visited, then
this is the previously computed value.
* If the computation is in progress (e.g. a cyclic graph), then
this method returns {@code null}.
+ * @throws ClassCastException if {@code metadata} is not an instance of
{@code baseType}.
+ * @throws NoSuchElementException if the object does not implement a
metadata interface and {@code policy} is {@code THROW}.
+ * @throws IllegalArgumentException if the object implements more than one
metadata interface and {@code policy} is {@code THROW}.
*/
- final R walk(final MetadataStandard standard, final Class<?> baseType,
final Object metadata, final boolean mandatory) {
+ final R walk(final MetadataStandard standard, final Class<?> baseType,
final Object metadata, final UnresolvedTypePolicy policy) {
if (!visited.containsKey(metadata)) { // Reminder: the
associated value may be null.
- final PropertyAccessor accessor =
standard.getInstanceAccessor(metadata, baseType, mandatory);
+ final PropertyAccessor accessor =
standard.getInstanceAccessor(metadata, baseType, policy);
if (accessor != null) {
final Filter filter = preVisit(accessor); // NONE,
NON_EMPTY, WRITABLE or WRITABLE_RESULT.
final boolean preconstructed; // Whether to
write in instance provided by `result()`.
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java
index 91029169c3..10cfcc2538 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java
@@ -81,7 +81,7 @@ final class Pruner extends MetadataVisitor<Boolean> {
final Pruner visitor = VISITORS.get();
final boolean p = visitor.prune;
visitor.prune = prune;
- final Boolean r = visitor.walk(metadata.getStandard(), Object.class,
metadata, false);
+ final Boolean r = visitor.walk(metadata.getStandard(), Object.class,
metadata, UnresolvedTypePolicy.NULL);
visitor.prune = p;
return (r != null) && r; // If there is a cycle (r == null),
then the metadata is non-empty.
}
@@ -140,7 +140,7 @@ final class Pruner extends MetadataVisitor<Boolean> {
* For implementation that are not subtype of
AbstractMetadata but nevertheless
* implement some metadata interfaces, we will invoke
recursively this method.
*/
- final Boolean r = walk(standard, type, element, false);
+ final Boolean r = walk(standard, type, element,
UnresolvedTypePolicy.NULL);
if (r != null) {
isEmptyElement = r;
if (!isEmptyElement && element instanceof
Emptiable) {
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StateChanger.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StateChanger.java
index 5a0a8efc38..e0490b6324 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StateChanger.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StateChanger.java
@@ -71,7 +71,7 @@ final class StateChanger extends MetadataVisitor<Boolean> {
final StateChanger changer = VISITORS.get();
final ModifiableMetadata.State previous = changer.target;
changer.target = target;
- changer.walk(metadata.getStandard(), Object.class, metadata, true);
+ changer.walk(metadata.getStandard(), Object.class, metadata,
UnresolvedTypePolicy.THROW);
changer.target = previous;
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java
index ef951e6654..48c2755539 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java
@@ -209,9 +209,11 @@ class TreeNode implements Node {
/**
* Returns the metadata interface for the object represented by this node.
+ *
+ * @return the interface implemented by the value, or {@code null} if
unknown.
*/
private Class<?> getInterface() {
- return table.standard.getInterface(key(), null);
+ return table.standard.getInterface(key(), UnresolvedTypePolicy.NULL,
null);
}
/**
@@ -898,9 +900,11 @@ class TreeNode implements Node {
* If we need to create a new collection, we know that the
property accessor
* exists otherwise the call to `isLeaf()` above would have
returned `true`.
*/
- if (children == null || ((TreeNodeChildren) children).metadata !=
value) {
- PropertyAccessor accessor =
table.standard.getInstanceAccessor(value, baseType, true);
+ if (children == null || ((TreeNodeChildren) children).metadata !=
value) try {
+ PropertyAccessor accessor =
table.standard.getInstanceAccessor(value, baseType, UnresolvedTypePolicy.THROW);
children = new TreeNodeChildren(this, value, accessor);
+ } catch (ClassCastException | NoSuchElementException |
IllegalArgumentException e) {
+ throw new IllegalStateException(e.getMessage(), e);
}
}
return children;
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/UnresolvedTypePolicy.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/UnresolvedTypePolicy.java
new file mode 100644
index 0000000000..a34499afb3
--- /dev/null
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/UnresolvedTypePolicy.java
@@ -0,0 +1,49 @@
+/*
+ * 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.metadata;
+
+
+/**
+ * Controls the behavior when a metadata type cannot be resolved.
+ * {@link MetadataStandard} may throw an exception, return {@code null}
+ * or return an arbitrary instance.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+enum UnresolvedTypePolicy {
+ /**
+ * Returns {@code null} if the metadata type cannot be identified or is
ambiguous.
+ */
+ NULL,
+
+ /**
+ * Returns an arbitrary value if the metadata type is ambiguous.
+ */
+ ANY,
+
+ /**
+ * Throw an exception if the metadata type cannot be identified or is
ambiguous.
+ */
+ THROW;
+
+ /**
+ * Returns a variant of this policy that does not throw exception.
+ */
+ UnresolvedTypePolicy lenient() {
+ return (this != THROW) ? this : NULL;
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.java
index d2322ec966..b9cd253db8 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.java
@@ -67,6 +67,11 @@ public class Resources extends IndexedResourceBundle {
throw new IllegalAccessException();
}
+ /**
+ * Cannot handle `{1}` as a type derived from the {0} standard.
+ */
+ public static final short CannotHandleAsStandardType_2 = 9;
+
/**
* Connection to “{0}” database is already initialized.
*/
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.properties
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.properties
index 203146e98e..b20abf46fc 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.properties
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources.properties
@@ -19,6 +19,7 @@
# Resources in this file are for `org.apache.sis.metadata` usage only and
should not be used by any other module.
# For resources shared by all modules in the Apache SIS project, see
"org.apache.sis.util.resources" package.
#
+CannotHandleAsStandardType_2 = Cannot handle `{1}` as a type derived from
the {0} standard.
ConnectionAlreadyInitialized_1 = Connection to \u201c{0}\u201d database is
already initialized.
ElementAlreadyInitialized_1 = This metadata element is already
initialized with value \u201c{0}\u201d.
ExpectedInterface_2 = Illegal class `{1}`. Specify the `{0}`
interface instead.
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources_fr.properties
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources_fr.properties
index aa6fc11df7..12e98cbee0 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources_fr.properties
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/Resources_fr.properties
@@ -24,6 +24,7 @@
# U+202F NARROW NO-BREAK SPACE before ; ! and ?
# U+00A0 NO-BREAK SPACE before :
#
+CannotHandleAsStandardType_2 = Ne peut pas g\u00e9rer `{1}` comme un type
d\u00e9riv\u00e9 du standard {0}.
ConnectionAlreadyInitialized_1 = La connexion \u00e0 la base de
donn\u00e9es \u00ab\u202f{0}\u202f\u00bb est d\u00e9j\u00e0 initialis\u00e9e.
ElementAlreadyInitialized_1 = Cet \u00e9l\u00e9ment de
m\u00e9ta-donn\u00e9e est d\u00e9j\u00e0 initialis\u00e9 avec la valeur
\u00ab\u202f{0}\u202f\u00bb.
ExpectedInterface_2 = La classe `{1}` est ill\u00e9gale.
Sp\u00e9cifiez l\u2019interface `{0}` \u00e0 la place.
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/Merger.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/Merger.java
index 7cf88b7193..4d4120d541 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/Merger.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/Merger.java
@@ -34,6 +34,7 @@ import org.apache.sis.util.CorruptedObjectException;
import org.apache.sis.util.Classes;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.LenientComparable;
import org.apache.sis.util.collection.CheckedContainer;
import org.apache.sis.util.internal.shared.Unsafe;
import org.apache.sis.util.resources.Errors;
@@ -212,8 +213,8 @@ deeper: if (!baseType.isInstance(source)) {
if (!success) {
/*
* This exception may happen if the source is a
subclass of the target. This is the converse
- * of what we usually have in Java (we can assign a
sub-type to a more generic Java variable)
- * but happen here because if the source is a
sub-type, we may not be able to copy all values
+ * of what we usually have in Java (we can assign a
subtype to a more generic Java variable)
+ * but happen here because if the source is a subtype,
we may not be able to copy all values
* from the source to the target. We do not use
ClassCastException type in the hope to reduce
* confusion.
*/
@@ -245,17 +246,18 @@ deeper: if (!baseType.isInstance(source)) {
* may implement many interfaces, while we are
interrested only in the interface
* implemented by the target `element`.
*/
- final boolean equals;
+ final Boolean equals;
final var criteria =
ComparisonMode.BY_CONTRACT;
- if (element instanceof AbstractMetadata) {
- equals = ((AbstractMetadata)
element).equals(value, criteria);
+ if (element instanceof LenientComparable) {
+ equals = ((LenientComparable)
element).equals(value, criteria);
} else if (targetList instanceof
CheckedContainer<?>) {
Class<?> propertyType =
((CheckedContainer<?>) targetList).getElementType();
equals = standard.equals(element, value,
propertyType, criteria);
} else {
equals = Utilities.deepEquals(element,
value, criteria);
}
- if (!equals) continue;
+ // In case of doubt (null), do not remove.
+ if (equals == null || !equals) continue;
}
it.remove();
break;
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
index ca60a06bf5..595195993f 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
@@ -776,7 +776,7 @@ public class DefaultGeographicBoundingBox extends
AbstractGeographicExtent imple
* is generic enough for all other cases.
*/
if (object != null && object.getClass() ==
DefaultGeographicBoundingBox.class) {
- final DefaultGeographicBoundingBox that =
(DefaultGeographicBoundingBox) object;
+ final var that = (DefaultGeographicBoundingBox) object;
return Objects.equals(getInclusion(), that.getInclusion()) &&
doubleToLongBits(southBoundLatitude) ==
doubleToLongBits(that.southBoundLatitude) &&
doubleToLongBits(northBoundLatitude) ==
doubleToLongBits(that.northBoundLatitude) &&
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
index 28ea1288ae..1d7cc263aa 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
@@ -49,7 +49,7 @@
* <ul class="verbose">
* <li>The {@code Abstract} prefix means that the class is abstract in the
sense of the implemented standard.
* It it not necessarily abstract in the sense of Java. Because
incomplete metadata are common in practice,
- * sometimes we wish to instantiate an "abstract" class because of the
lack of knowledge about the exact sub-type.</li>
+ * sometimes we wish to instantiate an "abstract" class because of the
lack of knowledge about the exact subtype.</li>
* <li>The properties are determined by the getter methods declared in the
interfaces.
* Getter methods declared in the implementation classes are
ignored.</li>
* <li>Setter methods, if any, can be declared in the implementation classes
without the need for declarations
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
index 0c94341564..f83a933802 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/DefaultRecordType.java
@@ -431,7 +431,7 @@ public class DefaultRecordType extends RecordDefinition
implements RecordType, S
*
* <h4>Implementation note</h4>
* We do not require that {@code record.getRecordType() == this} in order
to allow record
- * "sub-types" to define additional fields, in a way similar to Java
sub-classing.
+ * subtypes to define additional fields, in a way similar to Java
sub-classing.
*
* @param record the record to test for compatibility with this type, or
{@code null}.
* @return {@code true} if the given record is non-null and compatible
with this record type.
diff --git
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/HashCodeTest.java
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/HashCodeTest.java
index 08732ebaa8..44ceb944e0 100644
---
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/HashCodeTest.java
+++
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/HashCodeTest.java
@@ -50,6 +50,7 @@ import
org.apache.sis.metadata.iso.citation.DefaultResponsibility;
*
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class HashCodeTest extends TestCase {
/**
* Creates a new test case.
@@ -61,7 +62,7 @@ public final class HashCodeTest extends TestCase {
* Computes the hash code value of the given object.
*/
private static Integer hash(final Object metadata) {
- return HashCode.getOrCreate().walk(MetadataStandard.ISO_19115, null,
metadata, true);
+ return HashCode.getOrCreate().walk(MetadataStandard.ISO_19115, null,
metadata, UnresolvedTypePolicy.THROW);
}
/**
diff --git
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/InformationMapTest.java
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/InformationMapTest.java
index 189a1a8d38..1e58f7e5e5 100644
---
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/InformationMapTest.java
+++
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/InformationMapTest.java
@@ -38,6 +38,7 @@ import org.apache.sis.test.TestCase;
*
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class InformationMapTest extends TestCase {
/**
* Creates a new test case.
diff --git
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java
index 94d2da09df..a9159376c5 100644
---
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java
+++
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java
@@ -19,6 +19,7 @@ package org.apache.sis.metadata;
import java.util.Set;
import java.util.Map;
import java.util.HashSet;
+import java.util.NoSuchElementException;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.quality.Completeness;
import org.opengis.metadata.extent.GeographicExtent;
@@ -151,7 +152,8 @@ public final class MetadataStandardTest extends TestCase {
* Returns the interface type declared by the accessor for the given class.
*/
private Class<?> getAccessor(final Class<?> type, final boolean mandatory)
{
- final PropertyAccessor accessor = standard.getTypeAccessor(type,
mandatory);
+ final PropertyAccessor accessor = standard.getTypeAccessor(type,
+ mandatory ? UnresolvedTypePolicy.THROW :
UnresolvedTypePolicy.NULL);
return (accessor != null) ? accessor.type : null;
}
@@ -180,9 +182,11 @@ public final class MetadataStandardTest extends TestCase {
*/
@Test
public void testGetWrongInterface() {
- standard = new MetadataStandard("SIS", "org.apache.sis.dummy.");
+ standard = new MetadataStandard("Pseudo Standard",
"org.apache.sis.dummy.");
var e = assertThrows(ClassCastException.class, () ->
getInterface(DefaultCitation.class));
- assertMessageContains(e, "DefaultCitation");
+ assertMessageContains(e, "DefaultCitation", "Pseudo Standard");
+ var cause = assertInstanceOf(NoSuchElementException.class,
e.getCause());
+ assertMessageContains(cause, "DefaultCitation");
}
/**
@@ -192,19 +196,19 @@ public final class MetadataStandardTest extends TestCase {
public void testEquals() {
standard = MetadataStandard.ISO_19115;
- // Self equality test
+ // Self equality test.
DefaultCitation instance = HardCodedCitations.EPSG;
- assertFalse(standard.equals(instance, HardCodedCitations.SIS, null,
ComparisonMode.STRICT));
- assertTrue (standard.equals(instance, HardCodedCitations.EPSG, null,
ComparisonMode.STRICT));
+ assertFalse(standard.equals(instance, HardCodedCitations.SIS,
Object.class, ComparisonMode.STRICT));
+ assertTrue (standard.equals(instance, HardCodedCitations.EPSG,
Object.class, ComparisonMode.STRICT));
- // Test comparison with a copy
+ // Test comparison with a copy.
instance = new DefaultCitation(HardCodedCitations.EPSG);
- assertFalse(standard.equals(instance, HardCodedCitations.SIS, null,
ComparisonMode.STRICT));
- assertTrue (standard.equals(instance, HardCodedCitations.EPSG, null,
ComparisonMode.STRICT));
+ assertFalse(standard.equals(instance, HardCodedCitations.SIS,
Object.class, ComparisonMode.STRICT));
+ assertTrue (standard.equals(instance, HardCodedCitations.EPSG,
Object.class, ComparisonMode.STRICT));
- // test comparison with a modified copy
+ // Test comparison with a modified copy.
instance.setTitle(new SimpleInternationalString("A dummy title"));
- assertFalse(standard.equals(instance, HardCodedCitations.EPSG, null,
ComparisonMode.STRICT));
+ assertFalse(standard.equals(instance, HardCodedCitations.EPSG,
Object.class, ComparisonMode.STRICT));
}
/**
diff --git
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/NameMapTest.java
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/NameMapTest.java
index 46ab70956d..69c7981e69 100644
---
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/NameMapTest.java
+++
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/NameMapTest.java
@@ -36,6 +36,7 @@ import org.apache.sis.test.TestCase;
*
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class NameMapTest extends TestCase {
/**
* Creates a new test case.
diff --git
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PrunerTest.java
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PrunerTest.java
index 3d68f5b171..f0de5727bf 100644
---
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PrunerTest.java
+++
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PrunerTest.java
@@ -38,6 +38,7 @@ import org.apache.sis.test.TestCase;
*
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class PrunerTest extends TestCase {
/**
* The root metadata object being tested.
diff --git
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/SpecialCasesTest.java
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/SpecialCasesTest.java
index ed09fdd515..827fdcf02b 100644
---
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/SpecialCasesTest.java
+++
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/SpecialCasesTest.java
@@ -35,6 +35,7 @@ import
org.apache.sis.metadata.iso.citation.HardCodedCitations;
*
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class SpecialCasesTest extends TestCase {
/**
* The accessor instance to be tested.
diff --git
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
index 13784c2647..90678615fc 100644
---
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
+++
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
@@ -151,7 +151,7 @@ public final class TreeNodeChildrenTest extends TestCase {
final MetadataStandard standard = MetadataStandard.ISO_19115;
final TreeTableView table = new TreeTableView(standard,
citation, Citation.class, valuePolicy);
final TreeNode node = (TreeNode) table.getRoot();
- final PropertyAccessor accessor =
standard.getTypeAccessor(citation.getClass(), true);
+ final PropertyAccessor accessor =
standard.getTypeAccessor(citation.getClass(), UnresolvedTypePolicy.THROW);
return new TreeNodeChildren(node, citation, accessor);
}
diff --git
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TypeMapTest.java
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TypeMapTest.java
index c2caea2066..7645bccb21 100644
---
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TypeMapTest.java
+++
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TypeMapTest.java
@@ -50,6 +50,7 @@ import org.opengis.metadata.citation.Responsibility;
*
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class TypeMapTest extends TestCase {
/**
* Creates a new test case.
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/MatrixParameters.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/MatrixParameters.java
index f8d7dace0e..fb5a53815a 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/MatrixParameters.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/MatrixParameters.java
@@ -700,7 +700,7 @@ public class MatrixParameters<E> implements
CheckedContainer<E>, Serializable {
/**
* Returns the default value for the parameter descriptor at the given
indices.
- * The default implementation returns 1 if all indices are equals, or 0
otherwise.
+ * The default implementation returns 1 if all indices are equal, or 0
otherwise.
*
* @param indices the indices of the matrix element for which to get the
default value,
* in (<var>row</var>, <var>column</var>, …) order.
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java
index 6921b185e6..bca744f72e 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java
@@ -267,9 +267,10 @@ abstract class AbstractLinearTransform extends
AbstractMathTransform implements
* Compares the specified object with this linear transform for equality.
* This implementation returns {@code true} if the following conditions
are met:
* <ul>
- * <li>In {@code STRICT} mode, the objects are of the same class and
{@link #equalsSameClass(Object)}
- * returns {@code true}.</li>
- * <li>In other modes, the matrix are equals or approximately equals
(depending on the mode).</li>
+ * <li>In {@code STRICT} mode, the objects are of the same class
+ * and {@link #equalsSameClass(Object)} returns {@code true}.</li>
+ * <li>In other modes, the matrices are equal or approximately equal
+ * (depending on the comparison criteria).</li>
* </ul>
*
* @param object the object to compare with this transform.
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
index b015ade95b..b1b53c0cdc 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
@@ -343,7 +343,7 @@ public final class Numerics {
}
/**
- * Returns {@code true} if the given floats are equals. Positive and
negative zero are
+ * Returns {@code true} if the given floats are equal. Positive and
negative zero are
* considered different, while a NaN value is considered equal to all
other NaN values.
*
* @param v1 the first value to compare.
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
index ae65d1bfd1..ca443d01ff 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
@@ -81,6 +81,11 @@ public class Errors extends IndexedResourceBundle {
*/
public static final short AmbiguousName_3 = 2;
+ /**
+ * Type ‘{1}’ is ambiguous because it implements more than one subtype
of ‘{0}’.
+ */
+ public static final short AmbiguousType_2 = 207;
+
/**
* Computation in background failed.
*/
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
index f4e2bbf66d..14a647c6b6 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
@@ -26,6 +26,7 @@
#
AlreadyInitialized_1 = \u2018{0}\u2019 is already initialized.
AmbiguousName_3 = Name \u201c{2}\u201d is ambiguous because
it can be understood as either \u201c{0}\u201d or \u201c{1}\u201d.
+AmbiguousType_2 = Type \u2018{1}\u2019 is ambiguous because
it implements more than one subtype of \u2018{0}\u2019.
BackgroundComputationFailed = Computation in background failed.
CanIterateOnlyOnce = This object can iterate only once.
CanNotAddToExclusiveSet_2 = No element can be added to this set
because properties \u2018{0}\u2019 and \u2018{1}\u2019 are mutually exclusive.
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
index 5e4ec30103..e0a2b070fc 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
@@ -23,6 +23,7 @@
#
AlreadyInitialized_1 = \u2018{0}\u2019 est d\u00e9j\u00e0
initialis\u00e9.
AmbiguousName_3 = Le nom \u00ab\u202f{2}\u202f\u00bb est
ambigu\u00eb car il peut \u00eatre interpr\u00e9t\u00e9 comme
\u00ab\u202f{0}\u202f\u00bb ou \u00ab\u202f{1}\u202f\u00bb.
+AmbiguousType_2 = Le type \u2018{1}\u2019 est ambigu\u00eb
car il impl\u00e9mente plus qu'un sous-type de \u2018{0}\u2019.
BackgroundComputationFailed = Le calcul en arri\u00e8re-plan a
\u00e9chou\u00e9.
CanIterateOnlyOnce = Cet objet ne peut it\u00e9rer qu\u2019une
seule fois.
CanNotAddToExclusiveSet_2 = Aucun \u00e9l\u00e9ment ne peut \u00eatre
ajout\u00e9 \u00e0 cet ensemble car les propri\u00e9t\u00e9s \u2018{0}\u2019 et
\u2018{1}\u2019 sont mutuellement exclusives.
diff --git
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/RangeSetTest.java
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/RangeSetTest.java
index d9a34539a5..3dec096daa 100644
---
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/RangeSetTest.java
+++
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/RangeSetTest.java
@@ -43,6 +43,7 @@ import static
org.apache.sis.test.Assertions.assertSerializedEquals;
* @author Martin Desruisseaux (Geomatys)
* @author Rémi Maréchal (Geomatys)
*/
+@SuppressWarnings("exports")
public final class RangeSetTest extends TestCase {
/**
* Tolerance factor for comparison of floating point numbers.
@@ -58,7 +59,7 @@ public final class RangeSetTest extends TestCase {
}
/**
- * Asserts that the two given values are equals to the expected one.
+ * Asserts that the two given values are equal to the expected range.
* This method is used for testing {@link RangeSet#first()} and {@link
RangeSet#last()}
* at the same time as the values from the iterator.
*/
@@ -573,6 +574,7 @@ public final class RangeSetTest extends TestCase {
* @throws InterruptedException if the test has been interrupted.
*/
@Benchmark
+ @SuppressWarnings("SleepWhileInLoop")
public void stress() throws InterruptedException {
final Random r = TestUtilities.createRandomNumberGenerator();
for (int p=0; p<10; p++) {