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

commit b5bad671d8a45577655c1b4378f1096f503119e2
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri May 22 16:28:55 2026 +0200

    Remove the `SecondaryTrait` internal annotation and use the information 
provided by the `getStandardType()` method instead.
---
 .../org/apache/sis/metadata/AbstractMetadata.java  |  17 +-
 .../main/org/apache/sis/metadata/CacheKey.java     |  29 ++-
 .../org/apache/sis/metadata/MetadataStandard.java  | 279 ++++++++++++---------
 .../org/apache/sis/metadata/MetadataVisitor.java   |  14 +-
 .../apache/sis/metadata/ModifiableMetadata.java    |   3 +-
 .../main/org/apache/sis/metadata/Pruner.java       |   2 +-
 .../sis/metadata/StandardImplementation.java       |   6 +-
 .../main/org/apache/sis/metadata/StateChanger.java |   2 +-
 .../main/org/apache/sis/metadata/TreeNode.java     |  10 +-
 .../org/apache/sis/metadata/TreeNodeChildren.java  |   4 +-
 .../sis/metadata/internal/shared/Merger.java       |  10 +-
 .../metadata/internal/shared/SecondaryTrait.java   |  45 ----
 .../apache/sis/metadata/simple/SimpleMetadata.java |  31 ++-
 .../apache/sis/metadata/MetadataStandardTest.java  |   8 +-
 .../apache/sis/metadata/TreeNodeChildrenTest.java  |   2 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |  24 +-
 .../main/org/apache/sis/referencing/Builder.java   |   6 +-
 .../referencing/datum/DefaultDatumEnsemble.java    |   2 -
 .../datum/DefaultDatumEnsembleTest.java            |  47 +++-
 .../main/org/apache/sis/storage/gpx/Metadata.java  |   6 +-
 .../main/org/apache/sis/storage/gpx/Store.java     |   2 +-
 .../main/org/apache/sis/util/Classes.java          |  44 ++--
 .../org/apache/sis/util/LenientComparable.java     |   3 +-
 23 files changed, 332 insertions(+), 264 deletions(-)

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 83a8444a18..44b6af2dce 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
@@ -160,14 +160,9 @@ public abstract class AbstractMetadata implements 
LenientComparable, Emptiable {
      * @return the standard interface implemented by this class.
      * @deprecated Renamed {@link #getStandardType()} for integration with 
{@link LenientComparable}.
      */
-    // TODO: make package private with modification: return null instead of 
ClassCastException.
     @Deprecated(since="1.7", forRemoval=true)
     public final Class<?> getInterface() {
-        Type type = getStandardType();
-        if (type instanceof ParameterizedType) {
-            type = ((ParameterizedType) type).getRawType();
-        }
-        return (Class<?>) type;
+        return org.apache.sis.util.Classes.getRawClass(getStandardType());
     }
 
     /**
@@ -353,14 +348,16 @@ public abstract class AbstractMetadata implements 
LenientComparable, Emptiable {
     }
 
     /**
-     * Compares this metadata with the specified object for equality. The 
default
-     * implementation uses Java reflection. Subclasses may override this method
-     * for better performances, or for comparing "hidden" properties not 
specified
-     * by the GeoAPI (or other standard) interface.
+     * Compares this metadata with the specified object for equality.
+     * The default implementation uses Java reflection using {@link 
MetadataStandard} methods.
+     * Subclasses may override this method for comparing properties that are 
not specified by
+     * the GeoAPI (or other standard) interface.
      *
      * @param  object  the object to compare with this metadata.
      * @param  mode    the strictness level of the comparison.
      * @return {@code true} if the given object is equal to this metadata.
+     *
+     * @see MetadataStandard#equals(Object, Object, ComparisonMode)
      */
     @Override
     public boolean equals(final Object object, final ComparisonMode mode) {
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 9dc8c4ac42..87b3c705e7 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
@@ -33,34 +33,33 @@ final class CacheKey {
     final Class<?> type;
 
     /**
-     * If the {@link #type} is an implementation class of a property, then the 
type declared in the signature for
-     * that property. This information allows to handle classes that implement 
more than one metadata interfaces
-     * for their convenience. Some examples are found in the {@link 
org.apache.sis.metadata.simple} package.
+     * If the {@link #type} is the type of the value of a property, the type 
declared in the property signature.
+     * This information allows to resolve ambiguities when a class implements 
more than one metadata interface.
+     * Some examples are found in the {@link org.apache.sis.metadata.simple} 
package.
+     *
+     * <p><b>Invariant:</b> {@code propertyType.isAssignableFrom(type)}</p>
      *
      * <p>This field shall never be null. If there is no property type 
information,
      * then this field shall be set to {@code Object.class}.</p>
      */
     final Class<?> propertyType;
 
-    /**
-     * Creates a new key without information on the property type.
-     */
-    CacheKey(final Class<?> type) {
-        this.type = type;
-        propertyType = Object.class;
-    }
-
     /**
      * Creates a new key to use in the cache.
+     * The {@code propertyType.isInstance(metadata)} condition should always 
be {@code true},
+     * but this is not verified by this constructor. Instead, the validity can 
be verified
+     * after constructor with {@link #isValid()}.
+     *
+     * @param  type          the metadata class, or {@code null}.
+     * @param  propertyType  base class of the metadata object.
      */
     CacheKey(final Class<?> type, final Class<?> propertyType) {
         this.type = type;
-        this.propertyType = (propertyType != null) ? propertyType : 
Object.class;
+        this.propertyType = propertyType;
     }
 
     /**
-     * Returns {@code true} if the {@link #type} can possibly be a value of a 
property of type
-     * {@link #propertyType}.
+     * Returns whether instances of {@link #type} can can be values of a 
property of type {@link #propertyType}.
      */
     final boolean isValid() {
         return (type != null) && propertyType.isAssignableFrom(type);
@@ -82,7 +81,7 @@ final class CacheKey {
     @Override
     public boolean equals(final Object obj) {
         if (obj instanceof CacheKey) {
-            final CacheKey other = (CacheKey) obj;
+            final var other = (CacheKey) obj;
             return (type == other.type) && (propertyType == 
other.propertyType);
         }
         return false;
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 cbda84ff8d..f485016d8f 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
@@ -18,6 +18,7 @@ package org.apache.sis.metadata;
 
 import java.util.Set;
 import java.util.Map;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Iterator;
 import java.util.Objects;
@@ -29,9 +30,10 @@ import java.io.ObjectInputStream;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.ExtendedElementInformation;
 import org.opengis.metadata.citation.Citation;
-import org.apache.sis.metadata.internal.shared.SecondaryTrait;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.LenientComparable;
+import org.apache.sis.util.OptionalCandidate;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.CheckedContainer;
@@ -91,7 +93,7 @@ import static 
org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
  * by a large number of {@link ModifiableMetadata} instances.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.7
  *
  * @see AbstractMetadata
  *
@@ -101,7 +103,12 @@ public class MetadataStandard implements Serializable {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 7549790450195184843L;
+    private static final long serialVersionUID = -162351202766308250L;
+
+    /**
+     * Constant array for a standard with no dependency.
+     */
+    private static final MetadataStandard[] NO_DEPENDENCY = {};
 
     /**
      * {@code true} if implementations can alter the API defined in the 
interfaces by
@@ -154,7 +161,7 @@ public class MetadataStandard implements Serializable {
     static {
         final String[] acronyms = {"CoordinateSystem", "CS", 
"CoordinateReferenceSystem", "CRS"};
 
-        ISO_19115 = new StandardImplementation("ISO 19115", 
"org.opengis.metadata.", "org.apache.sis.metadata.iso.", null, 
(MetadataStandard[]) null);
+        ISO_19115 = new StandardImplementation("ISO 19115", 
"org.opengis.metadata.", "org.apache.sis.metadata.iso.", null, NO_DEPENDENCY);
         ISO_19157 = new StandardImplementation("ISO 19157", 
"org.opengis.metadata.quality.", "org.apache.sis.metadata.iso.quality.", null, 
ISO_19115);
         ISO_19111 = new StandardImplementation("ISO 19111", 
"org.opengis.referencing.", "org.apache.sis.referencing.", acronyms, ISO_19157, 
ISO_19115);
         ISO_19123 = new MetadataStandard      ("ISO 19123", 
"org.opengis.coverage.", ISO_19111);
@@ -185,12 +192,10 @@ public class MetadataStandard implements Serializable {
     final String interfacePackage;
 
     /**
-     * The dependencies, or {@code null} if none.
-     * If non-null, dependencies will be tested in the order they appear in 
this array.
+     * The dependencies, or an empty array if none.
+     * Dependencies will be tested in the order they appear in this array.
      * Consequently, if {@link #isMetadata(Class)} may return {@code true} for 
two or more
      * dependencies, then the dependency which should have precedence should 
be declared first.
-     *
-     * <p>Note: the {@code null} value is for serialization compatibility.</p>
      */
     private final MetadataStandard[] dependencies;
 
@@ -207,7 +212,7 @@ public class MetadataStandard implements Serializable {
      * Consider this field as final.
      * It is not final only for {@link #readObject(ObjectInputStream)} purpose.
      */
-    private transient ConcurrentMap<CacheKey,Object> accessors;
+    private transient ConcurrentMap<CacheKey, Object> accessors;
 
     /**
      * Creates a new instance working on implementation of interfaces defined 
in the specified package.
@@ -233,13 +238,14 @@ public class MetadataStandard implements Serializable {
         this.interfacePackage = interfacePackage.getName() + '.';
         this.accessors        = new ConcurrentHashMap<>();                     
     // Also defined in readObject(…)
         if (dependencies.length == 0) {
-            this.dependencies = null;
+            dependencies = NO_DEPENDENCY;
         } else {
-            this.dependencies = dependencies = dependencies.clone();
+            dependencies = dependencies.clone();
             for (int i=0; i<dependencies.length; i++) {
                 ensureNonNullElement("dependencies", i, dependencies[i]);
             }
         }
+        this.dependencies = dependencies;
     }
 
     /**
@@ -248,7 +254,7 @@ public class MetadataStandard implements Serializable {
      *
      * @param  citation          bibliographical reference to the 
international standard.
      * @param  interfacePackage  the root package for metadata interfaces.
-     * @param  dependencies      the dependencies to other metadata standards, 
or {@code null} if none.
+     * @param  dependencies      the dependencies to other metadata standards.
      */
     MetadataStandard(final String citation, final String interfacePackage, 
final MetadataStandard... dependencies) {
         this.citation         = new SimpleCitation(citation);
@@ -320,15 +326,65 @@ public class MetadataStandard implements Serializable {
     }
 
     /**
-     * Returns a key for use in {@link #getAccessor(CacheKey, boolean)} for 
the given type.
+     * Creates a new set initialized with {@code this} if the given set is 
null.
+     *
+     * @param  visited  metadata standards already visited, or {@code null} if 
none.
+     * @return non-null set where to record that a metadata standard have been 
visited.
+     */
+    private Set<MetadataStandard> createIfNull(Set<MetadataStandard> visited) {
+        if (visited == null) {
+            visited = new HashSet<>();
+            visited.add(this);
+        }
+        return visited;
+    }
+
+    /**
+     * 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}.
      */
-    private CacheKey createCacheKey(Class<?> type) {
+    final PropertyAccessor getTypeAccessor(Class<?> type, final boolean 
mandatory) {
+        Class<?> propertyType = Object.class;
         final Class<?> implementation = getImplementation(type);
         if (implementation != null) {
+            propertyType = type;
             type = implementation;
         }
-        return new CacheKey(type);
+        return getAccessor(new CacheKey(type, propertyType), null, mandatory);
+    }
+
+    /**
+     * Returns the accessor for the specified metadata instance.
+     * The {@code propertyType.isInstance(metadata)} condition should always 
be {@code true},
+     * but this is not verified by this constructor. Instead, the validity can 
be verified
+     * after constructor with {@link CacheKey#isValid()}.
+     *
+     * <p>A null value for {@code propertyType} is not equivalent to {@code 
Object.class}.
+     * If the value is null, this constructor tries to detect the interface 
automatically.
+     * If the value is {@code Object.class}, no detection is attempted, which 
is faster.
+     * In particular, if a {@code null} value would cause the default 
implementation of
+     * {@link AbstractMetadata#getStandardType()} to be invoked, then it is 
preferable
+     * to specify {@code Object.class} for avoiding to do reflection twice.</p>
+     *
+     * @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}.
+     */
+    final PropertyAccessor getInstanceAccessor(final Object metadata, Class<?> 
propertyType, final boolean mandatory) {
+        if (propertyType == null) {
+            if (metadata instanceof LenientComparable) {
+                propertyType = Classes.getRawClass(((LenientComparable) 
metadata).getStandardType());
+            }
+            if (propertyType == null) {
+                propertyType = Object.class;
+            }
+        }
+        return getAccessor(new CacheKey(metadata.getClass(), propertyType), 
null, mandatory);
     }
 
     /**
@@ -342,14 +398,15 @@ public class MetadataStandard implements Serializable {
      * </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.
+     *                    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 a 
metadata interface
-     *         of the expected package and {@code mandatory} is {@code true}.
+     * @throws ClassCastException if the specified class does not implement an 
expected metadata interface
+     *         and {@code mandatory} is {@code true}.
      */
-    final PropertyAccessor getAccessor(final CacheKey key, final boolean 
mandatory) {
+    private PropertyAccessor getAccessor(final CacheKey key, 
Set<MetadataStandard> visited, final boolean mandatory) {
         /*
          * Check for accessors created by previous calls to this method.
          * Values are added to this cache but never cleared.
@@ -375,9 +432,10 @@ public class MetadataStandard implements Serializable {
              */
             type = findInterface(key);
             if (type == null) {
-                if (dependencies != null) {
-                    for (final MetadataStandard dependency : dependencies) {
-                        final PropertyAccessor accessor = 
dependency.getAccessor(key, false);
+                visited = createIfNull(visited);
+                for (final MetadataStandard dependency : dependencies) {
+                    if (visited.add(dependency)) {
+                        final PropertyAccessor accessor = 
dependency.getAccessor(key, visited, false);
                         if (accessor != null) {
                             accessors.put(key, accessor);               // Ok 
to overwrite existing instance here.
                             return accessor;
@@ -421,28 +479,26 @@ public class MetadataStandard implements Serializable {
      *         or implements an interface of this standard.
      */
     public boolean isMetadata(final Class<?> type) {
-        return (type != null) && !type.isPrimitive() && isMetadata(new 
CacheKey(type));
+        return (type != null) && !type.isPrimitive() && isMetadata(new 
CacheKey(type, Object.class));
     }
 
     /**
      * 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 
API 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.
+     * 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.
      *
-     * @see #getInterface(CacheKey)
+     * @see #getInterface(CacheKey, Set)
      */
     private boolean isMetadata(final CacheKey key) {
         assert key.isValid() : key;
         if (accessors.containsKey(key)) {
             return true;
         }
-        if (dependencies != null) {
-            for (final MetadataStandard dependency : dependencies) {
-                if (dependency.isMetadata(key)) {
-                    accessors.putIfAbsent(key, dependency);
-                    return true;
-                }
+        for (final MetadataStandard dependency : dependencies) {
+            if (dependency.isMetadata(key)) {
+                accessors.putIfAbsent(key, dependency);
+                return true;
             }
         }
         /*
@@ -488,9 +544,10 @@ public class MetadataStandard implements Serializable {
      */
     private Class<?> findInterface(final CacheKey key) {
         assert key.isValid() : key;
-        if (key.type.isInterface()) {
-            if (isSupported(key.type.getName())) {
-                return key.type;
+        final Class<?> type = key.type;
+        if (type.isInterface()) {
+            if (isSupported(type.getName())) {
+                return type;
             }
         } else {
             /*
@@ -499,38 +556,35 @@ public class MetadataStandard implements Serializable {
              * tells whether the type is supported. Types associated to `FALSE`
              * shall be ignored.
              */
-            final var validities = new LinkedHashMap<Class<?>, Boolean>();
-            final SecondaryTrait ignore = 
key.type.getAnnotation(SecondaryTrait.class);
-            if (ignore != null) {
-                validities.put(ignore.value(), Boolean.FALSE);
-            }
-            for (Class<?> t=key.type; t!=null; t=t.getSuperclass()) {
-                getInterfaces(t, key.propertyType, validities);
+            final var interfaces = new LinkedHashMap<Class<?>, Boolean>();
+            for (Class<?> t = type; t != null; t = t.getSuperclass()) {
+                getInterfaces(t, key.propertyType, interfaces);
             }
             /*
              * Remove all unsupported types. Then, if we found more than one 
supported
              * interface, remove the ones that are sub-interfaces of the other.
              */
-            validities.values().removeIf((isSupported) -> !isSupported);
-            final Set<Class<?>> interfaces = validities.keySet();
-            for (final Iterator<Class<?>> it=interfaces.iterator(); 
it.hasNext();) {
+            interfaces.values().removeIf((isSupported) -> !isSupported);
+            final Set<Class<?>> validInterfaces = interfaces.keySet();
+            Iterator<Class<?>> it = validInterfaces.iterator();
+            while (it.hasNext()) {
                 final Class<?> candidate = it.next();
-                for (final Class<?> other : interfaces) {
+                for (final Class<?> other : validInterfaces) {
                     if (candidate != other && 
candidate.isAssignableFrom(other)) {
                         it.remove();
                         break;
                     }
                 }
             }
-            final Iterator<Class<?>> it = interfaces.iterator();
+            it = validInterfaces.iterator();
             if (it.hasNext()) {
                 final Class<?> candidate = it.next();
                 if (!it.hasNext()) {
                     return candidate;
                 }
                 /*
-                 * Found more than one interface; we don't know which one to 
pick.
-                 * Returns `null` for now; the caller will throw an exception.
+                 * Found more than one interface and we don't know which one 
to pick.
+                 * Returns `null` for now, the caller will throw an exception.
                  */
             } else if (IMPLEMENTATION_CAN_ALTER_API) {
                 /*
@@ -542,8 +596,8 @@ public class MetadataStandard implements Serializable {
                  * have to go through a voting process inside the Open 
Geospatial Consortium (OGC).
                  * So we use those implementation classes as a temporary 
substitute for the interfaces.
                  */
-                if (isPendingAPI(key.type)) {
-                    return key.type;
+                if (isPendingAPI(type)) {
+                    return type;
                 }
             }
         }
@@ -553,7 +607,7 @@ public class MetadataStandard implements Serializable {
     /**
      * Puts every interfaces for the given type in the specified map.
      * This method invokes itself recursively for scanning parent interfaces.
-     * The keys tell whether the interface is supported. validities
+     * The keys tell whether the interface is supported.
      *
      * <p>If the given class is the return value of a property, then the type 
of that property should be specified
      * in the {@code propertyType} argument. This information allows this 
method to take in account only the types
@@ -563,7 +617,7 @@ public class MetadataStandard implements Serializable {
      *
      * @see Classes#getAllInterfaces(Class)
      */
-    private void getInterfaces(final Class<?> type, final Class<?> 
propertyType, final Map<Class<?>, Boolean> validities) {
+    private void getInterfaces(final Class<?> type, final Class<?> 
propertyType, final Map<Class<?>, Boolean> addTo) {
         for (final Class<?> candidate : type.getInterfaces()) {
             final boolean recursive = propertyType.isAssignableFrom(candidate);
             if (recursive || (IMPLEMENTATION_CAN_ALTER_API && 
isPendingAPI(propertyType))) {
@@ -574,8 +628,8 @@ public class MetadataStandard implements Serializable {
                  * we skip the `isAssignableFrom` check, but without recursive 
addition of parent interfaces since we
                  * would not know when to stop.
                  */
-                if (validities.putIfAbsent(candidate, 
isSupported(candidate.getName())) == null && recursive) {
-                    getInterfaces(candidate, propertyType, validities);
+                if (addTo.putIfAbsent(candidate, 
isSupported(candidate.getName())) == null && recursive) {
+                    getInterfaces(candidate, propertyType, addTo);
                 }
             }
         }
@@ -599,8 +653,8 @@ public class MetadataStandard implements Serializable {
      *
      * @see AbstractMetadata#getStandardType()
      */
-    public <T> Class<? super T> getInterface(final Class<T> type) throws 
ClassCastException {
-        return getInterface(new CacheKey(Objects.requireNonNull(type)));
+    public <T> Class<? super T> getInterface(final Class<T> type) {
+        return getInterface(new CacheKey(Objects.requireNonNull(type), 
Object.class), null);
     }
 
     /**
@@ -610,10 +664,14 @@ public class MetadataStandard implements Serializable {
      * 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.
      *
+     * @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.
+     *
      * @see #isMetadata(CacheKey)
      */
     @SuppressWarnings("unchecked")
-    final <T> Class<? super T> getInterface(final CacheKey key) throws 
ClassCastException {
+    final <T> Class<? super T> getInterface(final CacheKey key, 
Set<MetadataStandard> visited) {
         final Class<?> interf;
         final Object value = accessors.get(key);
         if (value instanceof PropertyAccessor) {
@@ -621,18 +679,17 @@ public class MetadataStandard implements Serializable {
         } else if (value instanceof Class<?>) {
             interf = (Class<?>) value;
         } else if (value instanceof MetadataStandard) {
-            interf = ((MetadataStandard) value).getInterface(key);
+            interf = ((MetadataStandard) value).getInterface(key, 
createIfNull(visited));
         } else if (key.isValid()) {
             interf = findInterface(key);
             if (interf != null) {
                 accessors.putIfAbsent(key, interf);
             } else {
-                if (dependencies != null) {
-                    for (final MetadataStandard dependency : dependencies) {
-                        if (dependency.isMetadata(key)) {
-                            accessors.putIfAbsent(key, dependency);
-                            return dependency.getInterface(key);
-                        }
+                visited = createIfNull(visited);
+                for (final MetadataStandard dependency : dependencies) {
+                    if (dependency.isMetadata(key)) {
+                        accessors.putIfAbsent(key, dependency);
+                        return dependency.getInterface(key, visited);
                     }
                 }
                 throw new ClassCastException(key.unrecognized());
@@ -657,6 +714,7 @@ public class MetadataStandard implements Serializable {
      * @param  type  the interface, typically from the {@code 
org.opengis.metadata} package.
      * @return the implementation class, or {@code null} if none.
      */
+    @OptionalCandidate
     public <T> Class<? extends T> getImplementation(final Class<T> type) {
         return null;
     }
@@ -674,7 +732,7 @@ public class MetadataStandard implements Serializable {
     final Object getTitle(final Object metadata) {
         if (metadata != null) {
             final Class<?> type = metadata.getClass();
-            final PropertyAccessor accessor = 
getAccessor(createCacheKey(type), false);
+            final PropertyAccessor accessor = getTypeAccessor(type, false);
             if (accessor != null) {
                 TitleProperty an = type.getAnnotation(TitleProperty.class);
                 if (an != null || (an = 
accessor.implementation.getAnnotation(TitleProperty.class)) != null) {
@@ -714,13 +772,11 @@ public class MetadataStandard implements Serializable {
      * @throws ClassCastException if the specified interface or implementation 
class does
      *         not extend or implement a metadata interface of the expected 
package.
      */
-    public Map<String,String> asNameMap(final Class<?> type, final 
KeyNamePolicy keyPolicy,
-            final KeyNamePolicy valuePolicy) throws ClassCastException
-    {
+    public Map<String, String> asNameMap(Class<?> type, KeyNamePolicy 
keyPolicy, KeyNamePolicy valuePolicy) {
         ensureNonNull("type",        type);
         ensureNonNull("keyPolicy",   keyPolicy);
         ensureNonNull("valuePolicy", valuePolicy);
-        return new NameMap(getAccessor(createCacheKey(type), true), keyPolicy, 
valuePolicy);
+        return new NameMap(getTypeAccessor(type, true), keyPolicy, 
valuePolicy);
     }
 
     /**
@@ -735,7 +791,7 @@ public class MetadataStandard implements Serializable {
      *
      * {@snippet lang="java" :
      *     MetadataStandard  standard = MetadataStandard.ISO_19115;
-     *     Map<String,Class<?>> types = standard.asTypeMap(Citation.class, 
UML_IDENTIFIER, ELEMENT_TYPE);
+     *     Map<String, Class<?>> types = standard.asTypeMap(Citation.class, 
UML_IDENTIFIER, ELEMENT_TYPE);
      *     Class<?> value = types.get("alternateTitle");
      *     System.out.println(value);                       // class 
org.opengis.util.InternationalString
      *     }
@@ -748,13 +804,11 @@ public class MetadataStandard implements Serializable {
      * @throws ClassCastException if the specified interface or implementation 
class does
      *         not extend or implement a metadata interface of the expected 
package.
      */
-    public Map<String,Class<?>> asTypeMap(final Class<?> type, final 
KeyNamePolicy keyPolicy,
-            final TypeValuePolicy valuePolicy) throws ClassCastException
-    {
+    public Map<String, Class<?>> asTypeMap(Class<?> type, KeyNamePolicy 
keyPolicy, TypeValuePolicy valuePolicy) {
         ensureNonNull("type",        type);
         ensureNonNull("keyPolicy",   keyPolicy);
         ensureNonNull("valuePolicy", valuePolicy);
-        return new TypeMap(getAccessor(createCacheKey(type), true), keyPolicy, 
valuePolicy);
+        return new TypeMap(getTypeAccessor(type, true), keyPolicy, 
valuePolicy);
     }
 
     /**
@@ -801,12 +855,10 @@ public class MetadataStandard implements Serializable {
      *
      * @see org.apache.sis.metadata.iso.DefaultExtendedElementInformation
      */
-    public Map<String,ExtendedElementInformation> asInformationMap(final 
Class<?> type, final KeyNamePolicy keyPolicy)
-            throws ClassCastException
-    {
+    public Map<String, ExtendedElementInformation> asInformationMap(Class<?> 
type, KeyNamePolicy keyPolicy) {
         ensureNonNull("type",     type);
         ensureNonNull("keyNames", keyPolicy);
-        return new InformationMap(citation, getAccessor(createCacheKey(type), 
true), keyPolicy);
+        return new InformationMap(citation, getTypeAccessor(type, true), 
keyPolicy);
     }
 
     /**
@@ -826,12 +878,10 @@ public class MetadataStandard implements Serializable {
      * @throws ClassCastException if the specified interface or implementation 
class does
      *         not extend or implement a metadata interface of the expected 
package.
      */
-    public Map<String,Integer> asIndexMap(final Class<?> type, final 
KeyNamePolicy keyPolicy)
-            throws ClassCastException
-    {
+    public Map<String, Integer> asIndexMap(Class<?> type, KeyNamePolicy 
keyPolicy) {
         ensureNonNull("type",      type);
         ensureNonNull("keyPolicy", keyPolicy);
-        return new IndexMap(getAccessor(createCacheKey(type), true), 
keyPolicy);
+        return new IndexMap(getTypeAccessor(type, true), keyPolicy);
     }
 
     /**
@@ -887,13 +937,15 @@ public class MetadataStandard implements Serializable {
      *
      * @since 0.8
      */
-    public Map<String,Object> asValueMap(final Object metadata, final Class<?> 
baseType,
-            final KeyNamePolicy keyPolicy, final ValueExistencePolicy 
valuePolicy) throws ClassCastException
+    public Map<String, Object> asValueMap(final Object metadata,
+                                          final Class<?> baseType,
+                                          final KeyNamePolicy keyPolicy,
+                                          final ValueExistencePolicy 
valuePolicy)
     {
         ensureNonNull("metadata",    metadata);
         ensureNonNull("keyPolicy",   keyPolicy);
         ensureNonNull("valuePolicy", valuePolicy);
-        return new ValueMap(metadata, getAccessor(new 
CacheKey(metadata.getClass(), baseType), true), keyPolicy, valuePolicy);
+        return new ValueMap(metadata, getInstanceAccessor(metadata, baseType, 
true), keyPolicy, valuePolicy);
     }
 
     /**
@@ -925,12 +977,10 @@ public class MetadataStandard implements Serializable {
      *
      * @since 1.5
      */
-    public Map<String,NilReason> asNilReasonMap(final Object metadata, final 
Class<?> baseType,
-            final KeyNamePolicy keyPolicy) throws ClassCastException
-    {
+    public Map<String, NilReason> asNilReasonMap(Object metadata, Class<?> 
baseType, KeyNamePolicy keyPolicy) {
         ensureNonNull("metadata",  metadata);
         ensureNonNull("keyPolicy", keyPolicy);
-        return new NilReasonMap(metadata, getAccessor(new 
CacheKey(metadata.getClass(), baseType), true), keyPolicy);
+        return new NilReasonMap(metadata, getInstanceAccessor(metadata, 
baseType, true), keyPolicy);
     }
 
     /**
@@ -1015,9 +1065,7 @@ public class MetadataStandard implements Serializable {
      *
      * @since 0.8
      */
-    public TreeTable asTreeTable(final Object metadata, Class<?> baseType, 
final ValueExistencePolicy valuePolicy)
-            throws ClassCastException
-    {
+    public TreeTable asTreeTable(final Object metadata, Class<?> baseType, 
final ValueExistencePolicy valuePolicy) {
         ensureNonNull("metadata",    metadata);
         ensureNonNull("valuePolicy", valuePolicy);
         if (baseType == null) {
@@ -1027,31 +1075,21 @@ public class MetadataStandard implements Serializable {
     }
 
     /**
-     * Compares the two specified metadata objects.
+     * Compares the two specified metadata objects for equality.
      * The two metadata arguments shall be implementations of a metadata 
interface defined by
      * this {@code MetadataStandard}, otherwise an exception will be thrown. 
However, the two
-     * arguments do not need to be the same implementation class.
-     *
-     * <h4>Shallow or deep comparisons</h4>
-     * This method implements a <em>shallow</em> comparison in that properties 
are compared by
-     * invoking their {@code properties.equals(…)} method without 
<em>explicit</em> recursive call
-     * to this {@code standard.equals(…)} method for children metadata. 
However, the comparison will
-     * do <em>implicit</em> recursive calls if the {@code 
properties.equals(…)} implementations
-     * delegate their work to this {@code standard.equals(…)} method, as 
{@link AbstractMetadata} does.
-     * In the latter case, the final result is a deep comparison.
-     *
-     * @param  metadata1  the first metadata object to compare.
-     * @param  metadata2  the second metadata object to compare.
+     * arguments do not need to be instances of the same implementation class
+     * except for {@link ComparisonMode#STRICT}.
+     *
+     * @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.
-     * @throws ClassCastException if at least one metadata object does not
-     *         implement a metadata interface of the expected package.
+     * @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.
      *
      * @see AbstractMetadata#equals(Object, ComparisonMode)
      */
-    public boolean equals(final Object metadata1, final Object metadata2,
-            final ComparisonMode mode) throws ClassCastException
-    {
+    public boolean equals(final Object metadata1, final Object metadata2, 
final ComparisonMode mode) {
         if (metadata1 == metadata2) {
             return true;
         }
@@ -1063,14 +1101,15 @@ public class MetadataStandard implements Serializable {
         if (type1 != type2 && mode == ComparisonMode.STRICT) {
             return false;
         }
-        final PropertyAccessor accessor = getAccessor(new CacheKey(type1), 
true);
+        final PropertyAccessor accessor = getInstanceAccessor(metadata1, null, 
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;
             }
-            // The real condition.
-            if (accessor.type != getAccessor(new CacheKey(type2), false).type) 
{
+            final PropertyAccessor other = getAccessor(key, null, false);
+            if (other == null || accessor.type != other.type) {
                 return false;
             }
         }
@@ -1079,7 +1118,7 @@ public class MetadataStandard implements Serializable {
          * Cycle may exist in metadata tree, so we have to keep trace of pair 
in process
          * of being compared for avoiding infinite recursion.
          */
-        final ObjectPair pair = new ObjectPair(metadata1, metadata2);
+        final var pair = new ObjectPair(metadata1, metadata2);
         final Set<ObjectPair> inProgress = ObjectPair.CURRENT.get();
         if (inProgress.add(pair)) {
             /*
@@ -1115,9 +1154,9 @@ public class MetadataStandard implements Serializable {
      *
      * @see AbstractMetadata#hashCode()
      */
-    public int hashCode(final Object metadata) throws ClassCastException {
+    public int hashCode(final Object metadata) {
         if (metadata != null) {
-            final Integer hash = HashCode.getOrCreate().walk(this, null, 
metadata, true);
+            final Integer hash = HashCode.getOrCreate().walk(this, 
Object.class, metadata, true);
             if (hash != null) return hash;
             /*
              * `hash` may be null if a cycle has been found. Example: A 
depends on B which depends on A,
@@ -1133,6 +1172,8 @@ public class MetadataStandard implements Serializable {
     /**
      * Returns a string representation of this metadata standard.
      * This is for debugging purpose only and may change in any future version.
+     *
+     * @return a string representation for debugging purposes.
      */
     @Override
     public String toString() {
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 30349c3056..3b2b53fd44 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
@@ -135,17 +135,25 @@ abstract class MetadataVisitor<R> {
      * <p>This method is typically invoked recursively while we iterate down 
the metadata tree.
      * It maintains a map of visited nodes for preventing the same node to be 
visited twice.</p>
      *
+     * <h4>Note about {@code baseType} argument when the type is unknown</h4>
+     * A null value for {@code baseType} is not equivalent to {@code 
Object.class}.
+     * If the value is null, this method tries to detect the interface 
automatically.
+     * If the value is {@code Object.class}, no detection is attempted, which 
is faster.
+     * In particular, if a {@code null} value would cause the default 
implementation of
+     * {@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  type       the standard interface implemented by the metadata 
object, or {@code null} if unknown.
+     * @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.
      * @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}.
      */
-    final R walk(final MetadataStandard standard, final Class<?> type, final 
Object metadata, final boolean mandatory) {
+    final R walk(final MetadataStandard standard, final Class<?> baseType, 
final Object metadata, final boolean mandatory) {
         if (!visited.containsKey(metadata)) {               // Reminder: the 
associated value may be null.
-            final PropertyAccessor accessor = standard.getAccessor(new 
CacheKey(metadata.getClass(), type), mandatory);
+            final PropertyAccessor accessor = 
standard.getInstanceAccessor(metadata, baseType, mandatory);
             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/ModifiableMetadata.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ModifiableMetadata.java
index f57c6fafb3..b21e83387b 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ModifiableMetadata.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ModifiableMetadata.java
@@ -29,6 +29,7 @@ import java.nio.charset.Charset;
 import jakarta.xml.bind.annotation.XmlTransient;
 import org.opengis.util.CodeList;
 import org.opengis.metadata.Metadata;               // For javadoc
+import org.apache.sis.util.Classes;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.metadata.internal.Resources;
 import org.apache.sis.system.Semaphores;
@@ -351,7 +352,7 @@ public abstract class ModifiableMetadata extends 
AbstractMetadata {
         } else {
             copier = new MetadataCopier(getStandard());
         }
-        final ModifiableMetadata md = (ModifiableMetadata) 
copier.copyRecursively(getInterface(), this);
+        final var md = (ModifiableMetadata) 
copier.copyRecursively(Classes.getRawClass(getStandardType()), this);
         if (target.code > EDITABLE) {
             md.transitionTo(target);
         }
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 8682869870..91029169c3 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(), 
metadata.getInterface(), metadata, false);
+        final Boolean r = visitor.walk(metadata.getStandard(), Object.class, 
metadata, false);
         visitor.prune = p;
         return (r != null) && r;        // If there is a cycle (r == null), 
then the metadata is non-empty.
     }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java
index 68acb877f5..54bcd4aff2 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java
@@ -72,7 +72,7 @@ final class StandardImplementation extends MetadataStandard {
      * But maybe the most interesting property is that it allocates less 
objects since {@code IdentityHashMap}
      * implementation doesn't need the chain of objects created by {@code 
HashMap}.
      */
-    private transient Map<Class<?>,Class<?>> implementations;
+    private transient Map<Class<?>, Class<?>> implementations;
 
     /**
      * Creates a new instance working on implementation of interfaces defined 
in the specified package.
@@ -82,7 +82,7 @@ final class StandardImplementation extends MetadataStandard {
      * @param interfacePackage       the root package for metadata interfaces, 
with a trailing {@code '.'}.
      * @param implementationPackage  the root package for metadata 
implementations. with a trailing {@code '.'}.
      * @param acronyms               an array of (full text, acronyms) pairs. 
This array is not cloned.
-     * @param dependencies           the dependencies to other metadata 
standards, or {@code null} if none.
+     * @param dependencies           the dependencies to other metadata 
standards.
      */
     StandardImplementation(final String citation, final String 
interfacePackage, final String implementationPackage,
             final String[] acronyms, final MetadataStandard... dependencies)
@@ -141,7 +141,7 @@ final class StandardImplementation extends MetadataStandard 
{
                      * package has been replaced by the implementation 
package, and some text
                      * have been replaced by their acronym (if any).
                      */
-                    final StringBuilder buffer = new 
StringBuilder(implementationPackage)
+                    final var buffer = new StringBuilder(implementationPackage)
                             .append(classname, interfacePackage.length(), 
classname.length());
                     if (acronyms != null) {
                         for (int i=0; i<acronyms.length; i+=2) {
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 69c2c50d35..5a0a8efc38 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(), null, metadata, true);
+        changer.walk(metadata.getStandard(), Object.class, metadata, true);
         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 2261a1b70d..c92448097d 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,8 +209,8 @@ class TreeNode implements Node {
 
     /**
      * Returns the key to use for calls to {@link MetadataStandard} methods.
-     * This key is used only for some default method implementations in the 
root node;
-     * children will use the class of their node value instead.
+     * This key is used only for some default method implementations in the 
root node.
+     * Children will use the class of their node value instead.
      */
     private CacheKey key() {
         return new CacheKey(metadata.getClass(), baseType);
@@ -236,7 +236,7 @@ class TreeNode implements Node {
      * order to return the property identifier instead.
      */
     String getIdentifier() {
-        final Class<?> type = table.standard.getInterface(key());
+        final Class<?> type = table.standard.getInterface(key(), null);
         final String id = Types.getStandardName(type);
         return (id != null) ? id : Classes.getShortName(type);
     }
@@ -259,7 +259,7 @@ class TreeNode implements Node {
      */
     CharSequence getName() {
         return CharSequences.camelCaseToSentence(Classes.getShortName(
-                table.standard.getInterface(key()))).toString();
+                table.standard.getInterface(key(), null))).toString();
     }
 
     /**
@@ -890,7 +890,7 @@ class TreeNode implements Node {
              * exists otherwise the call to `isLeaf()` above would have 
returned `true`.
              */
             if (children == null || ((TreeNodeChildren) children).metadata != 
value) {
-                PropertyAccessor accessor = table.standard.getAccessor(new 
CacheKey(value.getClass(), baseType), true);
+                PropertyAccessor accessor = 
table.standard.getInstanceAccessor(value, baseType, true);
                 children = new TreeNodeChildren(this, value, accessor);
             }
         }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java
index 92dca2a1f9..04538753ac 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java
@@ -77,10 +77,10 @@ final class TreeNodeChildren extends 
AbstractCollection<TreeTable.Node> {
 
     /**
      * The accessor to use for accessing the property names, types and values 
of the {@link #metadata} object.
-     * This is given at construction time and shall be the same as the 
following code:
+     * This is given at construction time and shall be the equivalent to the 
following code:
      *
      * {@snippet lang="java" :
-     *     accessor = parent.table.standard.getAccessor(metadata.getClass(), 
true);
+     *     accessor = 
parent.table.standard.getTypeAccessor(metadata.getClass());
      *     }
      */
     final PropertyAccessor accessor;
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 77a04ef7a7..f2dac5a0e2 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
@@ -166,8 +166,8 @@ public class Merger {
          * Get views of metadata as maps. Those maps are live: write operations
          * on those maps will be reflected on the metadata objects and 
conversely.
          */
-        final Map<String,Object> targetMap = target.asMap();
-        final Map<String,Object> sourceMap;
+        final Map<String, Object> targetMap = target.asMap();
+        final Map<String, Object> sourceMap;
         if (source instanceof AbstractMetadata) {
             sourceMap = ((AbstractMetadata) source).asMap();          // Gives 
to subclasses a chance to override.
         } else {
@@ -178,7 +178,7 @@ public class Merger {
          * If the value does not exist in the target map, then it can be 
copied directly.
          */
         boolean success = true;
-        for (final Map.Entry<String,Object> entry : sourceMap.entrySet()) {
+        for (final Map.Entry<String, Object> entry : sourceMap.entrySet()) {
             final String propertyName = entry.getKey();
             final Object sourceValue  = entry.getValue();
             final Object targetValue  = dryRun ? targetMap.get(propertyName)
@@ -212,8 +212,8 @@ public class Merger {
                      * a ClassCastException is conform to this method 
contract). The loop tries to merge the
                      * source elements to target elements that are specialized 
enough.
                      */
-                    final Collection<?> targetList = (Collection<?>) 
targetValue;
-                    final Collection<?> sourceList = new 
LinkedList<>((Collection<?>) sourceValue);
+                    final var targetList = (Collection<?>) targetValue;
+                    final var sourceList = new LinkedList<>((Collection<?>) 
sourceValue);
                     for (final Object element : targetList) {
                         if (element instanceof ModifiableMetadata) {
                             final Iterator<?> it = sourceList.iterator();
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/SecondaryTrait.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/SecondaryTrait.java
deleted file mode 100644
index 0e5679625b..0000000000
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/SecondaryTrait.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.internal.shared;
-
-import java.lang.annotation.Target;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-
-/**
- * Indicates that an interface implemented by a class is considered less 
significant compared to the primary interface.
- * This annotation is used when a class implements two or more interfaces, but 
some of those interfaces can be ignored.
- * This information is needed for identifying the main interface in metadata.
- *
- * <h2>Design note</h2>
- * {@link ElementType#TYPE_USE} would be more appropriate. However, in the way 
that the metadata module currently uses
- * this interface, an annotation on the class is more convenient (more 
straightforward code).
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface SecondaryTrait {
-    /**
-     * The interface which is less significant compared to the primary 
interface.
-     *
-     * @return the less significant interface.
-     */
-    Class<?> value();
-}
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java
index d33e5e49e5..3357d29223 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java
@@ -77,9 +77,11 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
     /**
      * Language(s) used for documenting metadata.
      * Also the language(s) used within the data.
+     *
+     * @return empty map.
      */
     @Override
-    public Map<Locale,Charset> getLocalesAndCharsets() {
+    public Map<Locale, Charset> getLocalesAndCharsets() {
         return Collections.emptyMap();
     }
 
@@ -87,6 +89,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
      * The scope or type of resource for which metadata is provided.
      * This method returns {@code this} for allowing call to {@link 
#getResourceScope()}.
      *
+     * @return empty collection.
+     *
      * @see #getResourceScope()
      * @see #getName()
      */
@@ -99,6 +103,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
      * Code for the metadata scope, fixed to {@link ScopeCode#DATASET} by 
default. This is part of the information
      * provided by {@link #getMetadataScopes()}. The {@code DATASET} default 
value is consistent with the fact that
      * {@code SimpleMetadata} implements {@link DataIdentification}.
+     *
+     * @return {@code DATASET}.
      */
     @Override
     public ScopeCode getResourceScope() {
@@ -107,6 +113,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
 
     /**
      * Parties responsible for the metadata information.
+     *
+     * @return empty collection.
      */
     @Override
     public Collection<Responsibility> getContacts() {
@@ -115,6 +123,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
 
     /**
      * Date(s) associated with the metadata.
+     *
+     * @return empty collection.
      */
     @Override
     public Collection<CitationDate> getDateInfo() {
@@ -123,8 +133,9 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
 
     /**
      * Basic information about the resource(s) to which the metadata applies.
-     * This method returns {@code this} for allowing call to {@link 
#getCitation()}.
-     * and other methods.
+     * This method returns {@code this} for allowing call to {@link 
#getCitation()} and other methods.
+     *
+     * @return empty collection.
      *
      * @see #getCitation()
      * @see #getAbstract()
@@ -151,6 +162,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
      * Citation for the resource.
      * This is part of the information returned by {@link 
#getIdentificationInfo()}.
      * This method returns {@code this} for allowing call to {@link 
#getTitle()} and other methods.
+     *
+     * @return citation for the resource.
      */
     @Override
     public Citation getCitation() {
@@ -160,6 +173,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
     /**
      * Brief narrative summary of the resource.
      * This is part of the information returned by {@link 
#getIdentificationInfo()}.
+     *
+     * @return null.
      */
     @Override
     public InternationalString getAbstract() {
@@ -171,6 +186,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
      * This is part of the information returned by {@link 
#getIdentificationInfo()}.
      * Default implementation returns {@link SpatialRepresentationType#VECTOR}.
      * Subclasses should override this method if they represent gridded data 
instead of vector data.
+     *
+     * @return {@code VECTOR}.
      */
     @Override
     public Collection<SpatialRepresentationType> 
getSpatialRepresentationTypes() {
@@ -182,6 +199,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
      * This is part of the information returned by {@link 
#getIdentificationInfo()}.
      * Default implementation returns {@link TopicCategory#LOCATION}.
      * Subclasses should override this method if they represent other kind of 
data.
+     *
+     * @return {@code LOCATION}.
      */
     @Override
     public Collection<TopicCategory> getTopicCategories() {
@@ -191,6 +210,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
     /**
      * Spatial and temporal extent of the resource.
      * This is part of the information returned by {@link 
#getIdentificationInfo()}.
+     *
+     * @return empty collection.
      */
     @Override
     public Collection<Extent> getExtents() {
@@ -205,6 +226,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
     /**
      * Name by which the cited resource is known.
      * This is part of the information returned by {@link #getCitation()}.
+     *
+     * @return null.
      */
     @Override
     public InternationalString getTitle() {
@@ -216,6 +239,8 @@ public class SimpleMetadata implements Metadata, 
MetadataScope, DataIdentificati
      * This is part of the information returned by {@link #getCitation()}.
      * Default implementation returns {@link PresentationForm#TABLE_DIGITAL}.
      * Subclasses should override this method if they represent other kind of 
data.
+     *
+     * @return empty collection.
      */
     @Override
     public Collection<PresentationForm> getPresentationForms() {
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 7a9448e055..b1df00a083 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
@@ -151,15 +151,15 @@ 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.getAccessor(new 
CacheKey(type), mandatory);
+        final PropertyAccessor accessor = standard.getTypeAccessor(type, 
mandatory);
         return (accessor != null) ? accessor.type : null;
     }
 
     /**
-     * Tests {@link MetadataStandard#getAccessor(CacheKey, boolean)}.
+     * Tests {@link MetadataStandard#getTypeAccessor(Class, boolean)}.
      */
     @Test
-    public void testGetAccessor() {
+    public void testGetTypeAccessor() {
         standard = MetadataStandard.ISO_19115;
         assertEquals(Citation.class,         
getAccessor(DefaultCitation.class, true));
         assertEquals(Completeness.class,     
getAccessor(AbstractCompleteness.class, true));
@@ -180,7 +180,7 @@ public final class MetadataStandardTest extends TestCase {
      */
     @Test
     public void testGetWrongInterface() {
-        standard = new MetadataStandard("SIS", "org.apache.sis.dummy.", 
(MetadataStandard[]) null);
+        standard = new MetadataStandard("SIS", "org.apache.sis.dummy.");
         var e = assertThrows(ClassCastException.class, () -> 
getInterface(DefaultCitation.class));
         assertMessageContains(e, "DefaultCitation");
     }
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 b38e966ce3..13784c2647 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.getAccessor(new 
CacheKey(citation.getClass()), true);
+        final PropertyAccessor accessor = 
standard.getTypeAccessor(citation.getClass(), true);
         return new TreeNodeChildren(node, citation, accessor);
     }
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
index 9898b90714..43076c6565 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
@@ -540,11 +540,7 @@ public class AbstractIdentifiedObject extends 
FormattableObject implements Ident
     @Deprecated(since="1.7", forRemoval=true)
     // TODO: make package private.
     public final Class<? extends IdentifiedObject> getInterface() {
-        Type type = getStandardType();
-        if (type instanceof ParameterizedType) {
-            type = ((ParameterizedType) type).getRawType();
-        }
-        return (type instanceof Class<?>) ? (Class<? extends 
IdentifiedObject>) type : IdentifiedObject.class;
+        return 
Classes.getRawClass(getStandardType()).asSubclass(IdentifiedObject.class);
     }
 
     /**
@@ -828,16 +824,11 @@ public class AbstractIdentifiedObject extends 
FormattableObject implements Ident
         /*
          * Fallback for non-SIS implementations.
          */
-        if (type instanceof ParameterizedType) {
-            type = ((ParameterizedType) type).getRawType();
-        }
-        if (type instanceof Class<?>) {
-            final Class<?> c = (Class<?>) type;
-            if (c.isInstance(object)) {
-                final Class<?>[] t = 
Classes.getLeafInterfaces(object.getClass(), c);
-                if (t.length == 1 && t[0] == type) {
-                    return true;
-                }
+        final Class<?> c = Classes.getRawClass(type);
+        if (c != null && c.isInstance(object)) {
+            final Class<?>[] t = Classes.getLeafInterfaces(object.getClass(), 
c);
+            if (t.length == 1 && t[0] == type) {
+                return true;
             }
         }
         return false;
@@ -1090,7 +1081,8 @@ public class AbstractIdentifiedObject extends 
FormattableObject implements Ident
             final Identifier id = identifier.getIdentifier();
             if (id != null) {
                 identifiers = Collections.singleton(id);
-                ScopedIdentifier<IdentifiedObject> key = new 
ScopedIdentifier<>(getInterface(), identifier.toString());
+                Class<? extends IdentifiedObject> type = 
Classes.getStandardClass(this, IdentifiedObject.class);
+                ScopedIdentifier<IdentifiedObject> key = new 
ScopedIdentifier<>(type, identifier.toString());
                 key.store(IdentifiedObject.class, this, 
AbstractIdentifiedObject.class, "setIdentifier");
                 if (key != (key = key.rename(identifier.code))) {
                     key.store(IdentifiedObject.class, this, null, null);       
 // Shorter form without codespace.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
index a66a30b765..efb262a864 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
@@ -236,7 +236,7 @@ public abstract class Builder<B extends Builder<B>> {
         for (Class<?> c = expected; c != null; c = c.getSuperclass()) {
             Type type = c.getGenericSuperclass();
             if (type instanceof ParameterizedType) {
-                final ParameterizedType p = (ParameterizedType) type;
+                final var p = (ParameterizedType) type;
                 if (p.getRawType() == Builder.class) {
                     type = p.getActualTypeArguments()[0];
                     if (type == expected) return true;
@@ -273,8 +273,8 @@ public abstract class Builder<B extends Builder<B>> {
         this();
         if (object != null) {
             properties.putAll(IdentifiedObjects.getProperties(object));
-            final GenericName[] valueAlias = (GenericName[]) 
properties.remove(IdentifiedObject.ALIAS_KEY);
-            final Identifier[]  valueIds   = (Identifier[])  
properties.remove(IdentifiedObject.IDENTIFIERS_KEY);
+            final var valueAlias = (GenericName[]) 
properties.remove(IdentifiedObject.ALIAS_KEY);
+            final var  valueIds  = (Identifier[])  
properties.remove(IdentifiedObject.IDENTIFIERS_KEY);
             if (valueAlias != null) Collections.addAll(aliases, valueAlias);
             if (valueIds   != null) Collections.addAll(identifiers, valueIds);
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
index ebfda81ba2..4558c0a7c4 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
@@ -48,7 +48,6 @@ import 
org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.referencing.internal.shared.WKTKeywords;
 import org.apache.sis.referencing.internal.shared.WKTUtilities;
 import org.apache.sis.metadata.internal.shared.NameToIdentifier;
-import org.apache.sis.metadata.internal.shared.SecondaryTrait;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ComparisonMode;
@@ -90,7 +89,6 @@ import org.opengis.referencing.datum.RealizationMethod;
  *
  * @since 1.5
  */
-@SecondaryTrait(Datum.class)
 public class DefaultDatumEnsemble<D extends Datum> extends 
AbstractIdentifiedObject implements DatumEnsemble<D>, Datum {
     /**
      * Serial number for inter-operability with different versions.
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java
index 75a28b7bd8..e909944bec 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java
@@ -18,9 +18,13 @@ package org.apache.sis.referencing.datum;
 
 import java.util.Map;
 import java.util.List;
+import org.opengis.metadata.Identifier;
 import org.opengis.referencing.datum.GeodeticDatum;
-import 
org.apache.sis.metadata.iso.quality.DefaultAbsoluteExternalPositionalAccuracy;
 import org.apache.sis.referencing.GeodeticException;
+import org.apache.sis.metadata.KeyNamePolicy;
+import org.apache.sis.metadata.MetadataStandard;
+import org.apache.sis.metadata.ValueExistencePolicy;
+import 
org.apache.sis.metadata.iso.quality.DefaultAbsoluteExternalPositionalAccuracy;
 
 // Test dependencies
 import org.junit.jupiter.api.Test;
@@ -28,6 +32,9 @@ import static org.junit.jupiter.api.Assertions.*;
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import org.apache.sis.test.TestCase;
 
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.referencing.datum.DatumEnsemble;
+
 
 /**
  * Tests the {@link DefaultDatumEnsemble} class.
@@ -43,16 +50,23 @@ public class DefaultDatumEnsembleTest extends TestCase {
     }
 
     /**
-     * Tests the creation of a datum ensemble with some arbitrary geodetic 
datum.
+     * Creates a dummy ensemble with <abbr>WGS</abbr> datum.
      */
-    @Test
-    public void testGeodetic() {
-        final DefaultDatumEnsemble<GeodeticDatum> ensemble = 
DefaultDatumEnsemble.create(
-                Map.of(DefaultDatumEnsemble.NAME_KEY, "Any WGS"),
+    private static DefaultDatumEnsemble<GeodeticDatum> WGS() {
+        return DefaultDatumEnsemble.create(
+                Map.of(DefaultDatumEnsemble.NAME_KEY, "Various WGS"),
                 GeodeticDatum.class,
                 List.of(HardCodedDatum.WGS84, HardCodedDatum.WGS72),
                 new DefaultAbsoluteExternalPositionalAccuracy());
+    }
 
+    /**
+     * Tests the creation of a datum ensemble with some arbitrary geodetic 
datum.
+     */
+    @Test
+    public void testGeodetic() {
+        final DefaultDatumEnsemble<GeodeticDatum> ensemble = WGS();
+        assertEquals("Various WGS", ensemble.getName().getCode());
         assertTrue(ensemble.getMembers().contains(HardCodedDatum.WGS84));
         assertTrue(ensemble.getMembers().contains(HardCodedDatum.WGS72));
         final GeodeticDatum geodetic = assertInstanceOf(GeodeticDatum.class, 
ensemble);
@@ -61,4 +75,25 @@ public class DefaultDatumEnsembleTest extends TestCase {
         GeodeticException e = assertThrows(GeodeticException.class, () -> 
geodetic.getEllipsoid());
         assertMessageContains(e, "WGS");
     }
+
+    /**
+     * Tests the view as a map. This test depends on {@link 
DefaultDatumEnsemble#getStandardType()},
+     * which is invoked by the metadata module for determining which interface 
is the main one.
+     */
+    @Test
+    public void testValueMap() {
+        final Map<String, Object> values = 
MetadataStandard.ISO_19111.asValueMap(
+                WGS(), null, KeyNamePolicy.UML_IDENTIFIER, 
ValueExistencePolicy.ALL);
+        assertEquals("Various WGS", assertInstanceOf(Identifier.class, 
values.get("name")).getCode());
+    }
+
+    /**
+     * Tests another view as a map where the object is specified as a class 
instead of as an instance.
+     */
+    @Test
+    public void testNameMap() {
+        final Map<String, String> names = MetadataStandard.ISO_19111.asNameMap(
+                DatumEnsemble.class, KeyNamePolicy.UML_IDENTIFIER, 
KeyNamePolicy.SENTENCE);
+        assertEquals("Name", names.get("name"));
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
 
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
index 309f516221..6e7c4d4681 100644
--- 
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
+++ 
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
@@ -41,9 +41,9 @@ import org.opengis.metadata.distribution.Format;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.util.InternationalString;
 import org.apache.sis.io.TableAppender;
-import org.apache.sis.metadata.simple.SimpleMetadata;
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.util.iso.Types;
+import org.apache.sis.metadata.simple.SimpleMetadata;
 import org.apache.sis.metadata.iso.citation.DefaultCitationDate;
 import org.apache.sis.metadata.iso.identification.DefaultKeywords;
 import org.apache.sis.metadata.iso.extent.Extents;
@@ -56,8 +56,8 @@ import org.apache.sis.metadata.iso.citation.Citations;
 
 
 /**
- * Information about the GPX file, author, and copyright restrictions.
- * This is the root of the {@code <metadata>} element in a GPX file.
+ * Information about the <abbr>GPX</abbr> file, author, and copyright 
restrictions.
+ * This is the root of the {@code <metadata>} element in a <abbr>GPX</abbr> 
file.
  * At most one such element may appear in the document.
  * The XML content is like below:
  *
diff --git 
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java
 
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java
index 180cc537e9..3203e7f6d2 100644
--- 
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java
+++ 
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java
@@ -45,7 +45,7 @@ import org.opengis.feature.FeatureType;
 
 
 /**
- * A data store backed by GPX files.
+ * A data store backed by <abbr>GPX</abbr> files.
  * This store does not cache the feature instances.
  * Any new {@linkplain #features(boolean) request for features} will re-read 
from the file.
  *
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
index 90f12301a7..13c3175627 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
@@ -293,11 +293,9 @@ public final class Classes {
                  * Then replace (for example) `ParameterDescriptor<?>` by 
`ParameterDescriptor`
                  * in order to get an instance of `Class` instead of other 
kind of `Type`.
                  */
-                if (type instanceof ParameterizedType) {
-                    type = ((ParameterizedType) type).getRawType();
-                }
-                if (type instanceof Class<?>) {
-                    return changeArrayDimension((Class<?>) type, dimension);
+                final Class<?> element = getRawClass(type);
+                if (element != null) {
+                    return changeArrayDimension(element, dimension);
                 }
             }
         }
@@ -322,6 +320,27 @@ public final class Classes {
         }
     }
 
+    /**
+     * Returns the raw class for the given generic type, or {@code null} if 
the type is not recognized.
+     * The current implementation recognizes instance of {@link Class} and 
{@link ParameterizedType}.
+     *
+     * <p><b>Purpose:</b> this method can be used for fetching the {@link 
Class} part
+     * from the value returned by {@link 
LenientComparable#getStandardType()}.</p>
+     *
+     * @param  type  type for which to get the raw type, or {@code null}.
+     * @return the raw class of the given type, or {@code null} if unknown.
+     *
+     * @see ParameterizedType#getRawType()
+     * @see LenientComparable#getStandardType()
+     * @since 1.7
+     */
+    public static Class<?> getRawClass(Type type) {
+        if (type instanceof ParameterizedType) {
+            type = ((ParameterizedType) type).getRawType();
+        }
+        return (type instanceof Class<?>) ? (Class<?>) type : null;
+    }
+
     /**
      * Returns the class of the specified object, or {@code null} if {@code 
object} is null.
      * This method is also useful for fetching the class of an object known 
only by its bound
@@ -372,12 +391,9 @@ public final class Classes {
      */
     public static <T> Class<? extends T> getStandardClass(final T object, 
final Class<T> baseType) {
         if (object instanceof LenientComparable) {
-            Type type = ((LenientComparable) object).getStandardType();
-            if (type instanceof ParameterizedType) {
-                type = ((ParameterizedType) type).getRawType();
-            }
-            if (type instanceof Class<?>) {
-                return ((Class<?>) type).asSubclass(baseType);
+            final Class<?> type = getRawClass(((LenientComparable) 
object).getStandardType());
+            if (type != null) {
+                return type.asSubclass(baseType);
             }
         }
         Class<?> type = getClass(object);
@@ -599,12 +615,12 @@ next:       for (final Class<?> candidate : candidates) {
 
     /**
      * Returns the interfaces which are implemented by the two given classes. 
The returned set
-     * does not include the parent interfaces. For example if the two given 
objects implement the
+     * does not include the parent interfaces. For example, if the two given 
types extend the
      * {@link Collection} interface, then the returned set will contain the 
{@code Collection}
      * type but not the {@link Iterable} type, since it is implied by the 
collection type.
      *
-     * @param  c1  the first class.
-     * @param  c2  the second class.
+     * @param  c1  the first class, or {@code null}.
+     * @param  c2  the second class, or {@code null}.
      * @return the interfaces common to both classes, or an empty set if none.
      *         Callers can freely modify the returned set.
      */
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/LenientComparable.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/LenientComparable.java
index 2f196c4039..dfd585384a 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/LenientComparable.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/LenientComparable.java
@@ -124,7 +124,8 @@ public interface LenientComparable {
      * @return the type that defines the public properties of this instance.
      *
      * @see ComparisonMode#BY_CONTRACT
-     * @see Classes#getStandardClass(Object)
+     * @see Classes#getRawClass(Type)
+     * @see Classes#getStandardClass(Object, Class)
      *
      * @since 1.7
      */

Reply via email to