This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sis.git

commit be4deee44bac509bc7a8f21e15f4aadfde6941ed
Merge: 92cb82585e 14ecaa6557
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Sep 11 14:20:12 2025 +0200

    Merge branch 'geoapi-3.1'

 .../apache/sis/console/FormattedOutputCommand.java |   2 +-
 .../org/apache/sis/console/TransformCommand.java   |   4 +-
 .../org/apache/sis/console/CRSCommandTest.java     |  13 +
 .../sis/coverage/grid/ClippedGridCoverageTest.java |   2 +-
 .../org/apache/sis/xml/bind/lan/PT_FreeText.java   |   2 +-
 .../sis/openoffice/ReferencingFunctions.java       |  22 +-
 .../sis/openoffice/ReferencingFunctionsTest.java   |   8 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    | 171 +++++++++----
 .../referencing/datum/DefaultDatumEnsemble.java    |  15 ++
 .../referencing/factory/AuthorityFactoryProxy.java |  40 +--
 .../factory/ConcurrentAuthorityFactory.java        |   5 +-
 .../factory/MultiAuthoritiesFactory.java           |   2 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |  12 +-
 .../operation/AbstractCoordinateOperation.java     |  58 ++---
 .../operation/DefaultConcatenatedOperation.java    |  43 +++-
 .../DefaultCoordinateOperationFactory.java         |  40 ++-
 .../referencing/operation/projection/Mercator.java |  11 +-
 .../operation/provider/AbstractLambert.java        |   2 +
 .../operation/provider/AbstractMercator.java       |   2 +
 .../operation/provider/AbstractStereographic.java  |   2 +
 .../operation/provider/AxisOrderReversal.java      |   4 +-
 .../operation/provider/AxisOrderReversal3D.java    |   4 +-
 .../operation/provider/CassiniSoldner.java         |   4 +
 .../operation/provider/Equirectangular.java        |  10 +-
 .../provider/FranceGeocentricInterpolation.java    |   6 +-
 .../operation/provider/GeocentricAffine.java       |  31 ++-
 .../operation/provider/GeocentricTranslation.java  |   1 +
 .../provider/GeographicAndVerticalOffsets.java     |   5 +-
 .../operation/provider/GeographicOffsets.java      |   3 +-
 .../provider/LambertAzimuthalEqualArea.java        |   4 +
 .../operation/provider/LambertConformal1SP.java    |   3 +
 .../operation/provider/LambertConformal2SP.java    |   8 +-
 .../provider/LambertConformalMichigan.java         |   4 +-
 .../operation/provider/LambertConformalWest.java   |   1 +
 .../provider/LambertCylindricalEqualArea.java      |   4 +
 .../operation/provider/MapProjection.java          |  16 +-
 .../operation/provider/Mercator1SP.java            |   2 +
 .../provider/ModifiedAzimuthalEquidistant.java     |   4 +
 .../referencing/operation/provider/Mollweide.java  |   2 +-
 .../operation/provider/ObliqueMercator.java        |   2 +
 .../operation/provider/ObliqueStereographic.java   |   2 +
 .../operation/provider/Orthographic.java           |   4 +
 .../operation/provider/PolarStereographicA.java    |   2 +
 .../referencing/operation/provider/Polyconic.java  |   4 +
 .../operation/provider/RegionalMercator.java       |  36 ++-
 .../operation/provider/TransverseMercator.java     |   2 +
 .../provider/ZonedTransverseMercator.java          |   2 +
 .../apache/sis/referencing/privy/WKTKeywords.java  | 267 ++++++++-------------
 .../sis/io/wkt/GeodeticObjectParserTest.java       |   7 +-
 .../org/apache/sis/referencing/Assertions.java     |  67 +-----
 .../org/apache/sis/referencing/CommonCRSTest.java  |   2 +-
 .../sis/referencing/GeodeticObjectVerifier.java    |  10 +-
 .../referencing/factory/sql/EPSGFactoryTest.java   |   9 +-
 .../DefaultConcatenatedOperationTest.java          |  75 +++++-
 .../operation/DefaultTransformationTest.java       |   3 +-
 .../referencing/operation/PassThroughOperation.xml |   4 +-
 .../operation/provider/ProvidersTest.java          | 121 +++++++++-
 .../sis/test/integration/ConsistencyTest.java      |  39 +--
 .../main/org/apache/sis/storage/base/CodeType.java |  25 +-
 .../test/org/apache/sis/test/Assertions.java       |  13 +-
 .../org/apache/sis/test/ProjectDirectories.java    |  79 ++----
 61 files changed, 808 insertions(+), 539 deletions(-)

diff --cc 
endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/ReferencingFunctions.java
index 88ca0e81a1,e9a7a679a5..ad6c648d21
--- 
a/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/ReferencingFunctions.java
+++ 
b/endorsed/src/org.apache.sis.openoffice/main/org/apache/sis/openoffice/ReferencingFunctions.java
@@@ -42,10 -43,8 +43,13 @@@ import org.apache.sis.storage.DataStore
  import org.apache.sis.storage.DataStoreException;
  import org.apache.sis.storage.base.CodeType;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.ObjectDomain;
++// Specific to the main and geoapi-4.0 branches:
++import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
++
 +// Specific to the main branch:
 +import org.apache.sis.referencing.DefaultObjectDomain;
 +import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 +import org.apache.sis.referencing.internal.Legacy;
  
  
  /**
@@@ -176,7 -181,7 +185,12 @@@ public class ReferencingFunctions exten
                  return object.getName().getCode();
              }
              // In Apache SIS implementation, `getDescriptionText(…)` returns 
the identified object name.
-             name = 
CRS.getAuthorityFactory(null).getDescriptionText(codeOrPath);
 -            name = CRS.getAuthorityFactory(null).getDescriptionText(classe, 
codeOrPath).orElse(null);
++            final CRSAuthorityFactory factory = CRS.getAuthorityFactory(null);
++            if (factory instanceof GeodeticAuthorityFactory) {
++                name = ((GeodeticAuthorityFactory) 
factory).getDescriptionText(classe, codeOrPath).orElse(null);
++            } else {
++                name = factory.getDescriptionText(codeOrPath);
++            }
          } catch (Exception exception) {
              return getLocalizedMessage(exception);
          }
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index 186903334b,930c73cec9..772d4e7487
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@@ -393,6 -387,25 +402,25 @@@ class GeodeticObjectParser extends Math
          return crs;
      }
  
+     /**
+      * Parses a {@code "CoordinateMetadata"} element.
+      *
+      * @param  mode    {@link #FIRST}, {@link #OPTIONAL} or {@link 
#MANDATORY}.
+      * @param  parent  the parent element.
+      * @return the {@code "CoordinateMetadata"} element.
+      * @throws ParseException if the {@code "CoordinateMetadata"} element 
cannot be parsed.
+      */
 -    private CoordinateMetadata parseCoordinateMetadata(final int mode, final 
Element parent) throws ParseException {
++    private DefaultCoordinateMetadata parseCoordinateMetadata(final int mode, 
final Element parent) throws ParseException {
+         final Element element = parent.pullElement(mode, 
WKTKeywords.CoordinateMetadata);
+         if (element == null) {
+             return null;
+         }
+         final CoordinateReferenceSystem crs = 
parseCoordinateReferenceSystem(element, true);
+         final Temporal epoch = parseEpoch(OPTIONAL, element, 
WKTKeywords.Epoch);
+         element.close(ignoredElements);
+         return new DefaultCoordinateMetadata(crs, epoch);
+     }
+ 
      /**
       * Returns the value associated to {@link 
IdentifiedObject#IDENTIFIERS_KEY} as an {@code Identifier} object.
       * This method shall accept all value types that {@link 
#parseMetadataAndClose(Element, String, IdentifiedObject)}
@@@ -1456,20 -1466,33 +1485,33 @@@
          return epoch;
      }
  
+     /**
+      * Parses the datum {@code ANCHOR[]} element and pass the values to the 
{@link #parseMetadataAndClose(Element,
+      * String, IdentifiedObject)} method. If an anchor has been found, its 
value is stored in the returned map.
+      */
+     private Map<String,Object> parseAnchorAndClose(final Element element, 
final String name) throws ParseException {
+         String   anchor = pullElementAsString(element, WKTKeywords.Anchor);
+         Temporal epoch  = parseEpoch(OPTIONAL, element, 
WKTKeywords.AnchorEpoch);
+         final Map<String,Object> properties = parseMetadataAndClose(element, 
name, null);
 -        if (anchor != null) properties.put(Datum.ANCHOR_DEFINITION_KEY, 
anchor);
 -        if (epoch  != null) properties.put(Datum.ANCHOR_EPOCH_KEY, epoch);
++        if (anchor != null) 
properties.put(AbstractDatum.ANCHOR_DEFINITION_KEY, anchor);
++        if (epoch  != null) properties.put(AbstractDatum.ANCHOR_EPOCH_KEY, 
epoch);
+         return properties;
+     }
+ 
      /**
       * Parses an {@code "Ensemble"} (WKT 2) element.
       *
       * @param  mode       {@link #FIRST}, {@link #OPTIONAL} or {@link 
#MANDATORY}.
       * @param  parent     the parent element.
-      * @param  datumType  GeoAPI interface of the type of datum to create.
       * @param  meridian   the prime meridian, or {@code null} if the ensemble 
is not geodetic.
+      * @param  datumType  GeoAPI interface of the type of datum to create.
 -     * @return the {@code "Ensemble"} element as a {@link DatumEnsemble} 
object.
 +     * @return the {@code "Ensemble"} element as a {@code DatumEnsemble} 
object.
       * @throws ParseException if the {@code "Ensemble"} element cannot be 
parsed.
       *
       * @see 
org.apache.sis.referencing.datum.DefaultDatumEnsemble#formatTo(Formatter)
       */
 -    private <D extends Datum> DatumEnsemble<D> parseEnsemble(final int mode, 
final Element parent,
 +    private <D extends Datum> DefaultDatumEnsemble<D> parseEnsemble(final int 
mode, final Element parent,
-             final Class<D> datumType, final PrimeMeridian meridian) throws 
ParseException
+             final PrimeMeridian meridian, final Class<D> datumType) throws 
ParseException
      {
          final Element ensemble = parent.pullElement(mode, 
WKTKeywords.Ensemble);
          if (ensemble == null) {
@@@ -2017,7 -2042,7 +2059,7 @@@
                  meridian = greenwich();
              }
              final Temporal epoch = parseDynamic(element);
-             final DefaultDatumEnsemble<GeodeticDatum> ensemble = 
parseEnsemble(OPTIONAL, element, GeodeticDatum.class, meridian);
 -            final DatumEnsemble<GeodeticDatum> ensemble = 
parseEnsemble(OPTIONAL, element, meridian, GeodeticDatum.class);
++            final DefaultDatumEnsemble<GeodeticDatum> ensemble = 
parseEnsemble(OPTIONAL, element, meridian, GeodeticDatum.class);
              final GeodeticDatum datum = parseDatum(ensemble == null ? 
MANDATORY : OPTIONAL, element, meridian, epoch);
              final IdentifiedObject datumOrEnsemble = (datum != null) ? datum 
: ensemble;
              final Map<String,?> properties = parseMetadataAndClose(element, 
name, datumOrEnsemble);
@@@ -2244,7 -2270,7 +2286,7 @@@
              }
          }
          if (baseCRS == null) {      // The most usual case.
-             ensemble = parseEnsemble(OPTIONAL, element, 
DefaultParametricDatum.class, null);
 -            ensemble = parseEnsemble(OPTIONAL, element, null, 
ParametricDatum.class);
++            ensemble = parseEnsemble(OPTIONAL, element, null, 
DefaultParametricDatum.class);
              datum = parseParametricDatum(ensemble == null ? MANDATORY : 
OPTIONAL, element);
          }
          final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : 
ensemble;
diff --cc 
endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
index e53f7d944c,43b851e5bc..5889f96778
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
@@@ -175,24 -172,31 +175,34 @@@ abstract class AuthorityFactoryProxy<T
  
      /**
       * The proxy for the {@link 
GeodeticAuthorityFactory#getDescriptionText(Class, String)} method.
-      *
-      * @param  classe  the type of object for which to get a description.
       */
-     static final AuthorityFactoryProxy<InternationalString> description(final 
Class<? extends IdentifiedObject> classe) {
-         return new 
AuthorityFactoryProxy<InternationalString>(InternationalString.class, 
AuthorityFactoryIdentifier.Type.ANY) {
-             @Override InternationalString create(GeodeticAuthorityFactory 
factory, String code) throws FactoryException {
-                 return factory.getDescriptionText(classe, code).orElse(null);
-             }
-             @Override InternationalString createFromAPI(AuthorityFactory 
factory, String code) throws FactoryException {
-                 if (factory instanceof GeodeticAuthorityFactory) {
-                     return ((GeodeticAuthorityFactory) 
factory).getDescriptionText(classe, code).orElse(null);
-                 }
-                 return factory.getDescriptionText(code);
+     static final class Description extends 
AuthorityFactoryProxy<InternationalString> {
+         /** The type of object for which to get a description. */
+         private Class<? extends IdentifiedObject> typeToSearch;
+ 
+         /** Creates a new proxy for fetching an object description. */
+         Description(final Class<? extends IdentifiedObject> typeToSearch) {
+             super(InternationalString.class, 
AuthorityFactoryIdentifier.Type.ANY);
+             this.typeToSearch = typeToSearch;
+         }
+ 
+         /** Creates the object for the given code using only GeoAPI 
interfaces. */
+         @Override InternationalString createFromAPI(AuthorityFactory factory, 
String code) throws FactoryException {
 -            return factory.getDescriptionText(typeToSearch, 
code).orElse(null);
++            if (factory instanceof GeodeticAuthorityFactory) {
++                return ((GeodeticAuthorityFactory) 
factory).getDescriptionText(typeToSearch, code).orElse(null);
 +            }
-             @Override AuthorityFactoryProxy<InternationalString> 
specialize(String typeName) {
++            return factory.getDescriptionText(code);
+         }
+ 
+         /** Specialize the type of object to search. */
+         @Override AuthorityFactoryProxy<InternationalString> 
specialize(String typeName) {
+             final AuthorityFactoryProxy<?> c = 
BY_URN_TYPE.get(typeName.toLowerCase(Locale.US));
+             if (c != null && typeToSearch.isAssignableFrom(c.type)) {
+                 typeToSearch = c.type.asSubclass(IdentifiedObject.class);
                  return this;
              }
-         };
+             return null;     // The given type is illegal.
+         }
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
index 7d74482934,303e0353fd..50e42e872c
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
@@@ -515,9 -508,9 +514,9 @@@ public final class GeodeticObjectParser
       */
      @Test
      public void testGeographicCRSWithEnsemble() throws ParseException {
 -        final GeographicCRS crs = parse(GeographicCRS.class,
 +        final var crs = parse(DefaultGeographicCRS.class,
                  "GeodeticCRS[“WGS 84”,\n" +
-                 "  Ensemble[“World Geodetic System 1984”,\n" +
+                 "  Ensemble[“World Geodetic System 1984”,\n" +    // No 
"ensemble" suffix because of `verifyGeographicCRS(…)`
                  "    Member[“World Geodetic System 1984 (Transit)”],\n" +
                  "    Member[“World Geodetic System 1984 (G730)”],\n" +
                  "    Member[“World Geodetic System 1984 (G873)”],\n" +
diff --cc 
endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java
index f9c3c02003,73deaaf9fa..f99d377086
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java
@@@ -140,10 -123,10 +126,10 @@@ public final class Assertions extends S
       */
      public static void assertEpsgIdentifierEquals(final String expected, 
final Identifier actual) {
          assertNotNull(actual);
-         assertLegacyEquals(expected, actual.getCode(), "code");
+         assertEquals(expected, actual.getCode(), "code");
 -        assertEquals(Constants.EPSG,  actual.getCodeSpace(), "codeSpace");
 +        assertEquals(Constants.EPSG,  (actual instanceof ReferenceIdentifier) 
? ((ReferenceIdentifier) actual).getCodeSpace() : null, "codeSpace");
          assertEquals(Constants.EPSG,  
Citations.toCodeSpace(actual.getAuthority()), "authority");
-         assertLegacyEquals(Constants.EPSG + Constants.DEFAULT_SEPARATOR + 
expected, IdentifiedObjects.toString(actual), "identifier");
+         assertEquals(Constants.EPSG + Constants.DEFAULT_SEPARATOR + expected, 
IdentifiedObjects.toString(actual), "identifier");
      }
  
      /**
diff --cc 
endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticObjectVerifier.java
index 1e8c56bfb8,d9f5aca0fe..50e5b51fbf
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticObjectVerifier.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/GeodeticObjectVerifier.java
@@@ -39,10 -39,9 +39,11 @@@ import org.apache.sis.measure.Units
  // Test dependencies
  import static org.junit.jupiter.api.Assertions.*;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import org.opengis.referencing.ObjectDomain;
 -import org.opengis.referencing.datum.DatumEnsemble;
 +// Specific to the main branch:
 +import org.opengis.referencing.ReferenceSystem;
 +import org.opengis.referencing.datum.Datum;
++import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 +import org.opengis.referencing.operation.CoordinateOperation;
  
  
  /**
@@@ -228,7 -221,12 +229,12 @@@ public final class GeodeticObjectVerifi
       *                            {@code Extent} element for the world, or 
{@code false} if optional.
       */
      public static void assertIsWGS84(final GeodeticDatum datum, final boolean 
isExtentMandatory) {
-         Assertions.assertLegacyEquals("World Geodetic System 1984", 
datum.getName().getCode(), "name");
+         final String name = datum.getName().getCode();
+         String expected = "World Geodetic System 1984";
 -        if (datum instanceof DatumEnsemble<?>) {
++        if (datum instanceof DefaultDatumEnsemble<?>) {
+             expected += " ensemble";
+         }
+         assertEquals     (expected, name, "name");
          assertIsWorld    (datum, isExtentMandatory);
          assertIsGreenwich(datum.getPrimeMeridian());
          assertIsWGS84    (datum.getEllipsoid());
diff --cc 
endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
index 9ad9c35bd7,f1c4f1e982..3e5585ed99
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
@@@ -28,11 -39,12 +39,13 @@@ import org.apache.sis.referencing.facto
  // Test dependencies
  import org.junit.jupiter.api.Test;
  import static org.junit.jupiter.api.Assertions.*;
+ import static org.junit.jupiter.api.Assumptions.abort;
+ import static org.junit.jupiter.api.Assumptions.assumeTrue;
  import org.apache.sis.test.TestCase;
  
 -// Specific to the geoapi-3.1 and geoapi-4.0 branches:
 -import static org.opengis.test.Assertions.assertBetween;
 +// Specific to the main branch:
 +import org.apache.sis.parameter.DefaultParameterDescriptor;
 +import static org.apache.sis.test.GeoapiAssert.assertBetween;
  
  
  /**
@@@ -234,8 -246,110 +247,110 @@@ public final class ProvidersTest extend
       */
      @Test
      public void testDescription() {
 -        assertNotEquals(0, 
SatelliteTracking.SATELLITE_ORBIT_INCLINATION.getDescription().orElseThrow().length());
 -        assertNotEquals(0, SatelliteTracking.SATELLITE_ORBITAL_PERIOD   
.getDescription().orElseThrow().length());
 -        assertNotEquals(0, SatelliteTracking.ASCENDING_NODE_PERIOD      
.getDescription().orElseThrow().length());
 +        assertNotEquals(0, ((DefaultParameterDescriptor<Double>) 
SatelliteTracking.SATELLITE_ORBIT_INCLINATION).getDescription().orElseThrow().length());
 +        assertNotEquals(0, ((DefaultParameterDescriptor<Double>) 
SatelliteTracking.SATELLITE_ORBITAL_PERIOD   
).getDescription().orElseThrow().length());
 +        assertNotEquals(0, ((DefaultParameterDescriptor<Double>) 
SatelliteTracking.ASCENDING_NODE_PERIOD      
).getDescription().orElseThrow().length());
      }
+ 
+     /**
+      * Compares the method and parameter names against the declarations in 
the <abbr>EPSG</abbr> database.
+      *
+      * @throws ReflectiveOperationException if the instantiation of a service 
provider failed.
+      * @throws FactoryException if an error occurred while using the 
<abbr>EPSG</abbr> database.
+      */
+     @Test
+     public void compareWithEPSG() throws ReflectiveOperationException, 
FactoryException {
+         assumeTrue(RUN_EXTENSIVE_TESTS, "Extensive tests not enabled.");
+         final EPSGFactory factory;
+         try {
+             factory = (EPSGFactory) CRS.getAuthorityFactory(Constants.EPSG);
+         } catch (ClassCastException e) {
+             abort("This test requires the EPSG geodetic dataset.");
+             throw e;
+         }
+         final var methodAliases   = new HashMap<AbstractProvider, 
String[]>(256);
+         final var aliasUsageCount = new HashMap<String, Integer>(256);
+         for (final Class<?> c : methods()) {
+             final AbstractProvider method = instance(c);
+             final String identifier = getCodeEPSG(method);
+             if (identifier != null) {
+                 final OperationMethod authoritative = 
factory.createOperationMethod(identifier);
+                 final String[] aliases = getAliases(authoritative);
+                 for (final String alias : aliases) {
+                     aliasUsageCount.merge(alias, 1, Math::addExact);
+                 }
+                 /*
+                  * Verify that the name of the operation method is identical 
to the name used in the EPSG database.
+                  * Aliases will be checked later, after we know which aliases 
are used multiple times.
+                  */
+                 final String classe = c.getName();
+                 assertNull(methodAliases.put(method, aliases), classe);
+                 assertEquals(authoritative.getName().getCode(), 
method.getName().getCode(), classe);
+                 /*
+                  * Verify that all parameters declared in the EPSG database 
are present with an identical name.
+                  * The Apache SIS's method provider may contain additional 
parameters. They will be ignored.
+                  */
+                 int index = 0;
+                 final List<GeneralParameterDescriptor> parameters = 
method.getParameters().descriptors();
+                 for (GeneralParameterDescriptor expected : 
authoritative.getParameters().descriptors()) {
+                     final String name = expected.getName().getCode();
+                     GeneralParameterDescriptor parameter;
+                     do {
+                         if (index >= parameters.size()) {
+                             fail("Parameter \"" + name + "\" not found or not 
in expected order in class " + classe);
+                         }
+                         parameter = parameters.get(index++);
+                     } while (!name.equals(parameter.getName().getCode()));
+                     /*
+                      * Found a match. The EPSG code must be identical.
+                      * Check also the aliases, ignoring the deprecated ones.
+                      */
+                     assertEquals(getCodeEPSG(expected), 
getCodeEPSG(parameter), name);
+                     final var hardCoded = new 
HashSet<String>(Arrays.asList(getAliases(parameter)));
+                     for (final String alias : getAliases(expected)) {
+                         assertTrue(hardCoded.remove(alias),
+                                 () -> "Alias \"" + alias + "\" not found in 
parameter \"" + name + "\" of class " + classe);
+                     }
+                     assertTrue(hardCoded.isEmpty(),
+                             () -> "Unexpected alias \"" + 
hardCoded.iterator().next()
+                                     + "\" in parameter \"" + name + "\" of 
class " + classe);
+                 }
+             }
+         }
+         /*
+          * AFter we checked all operation methods, execute a second loop for 
checking method aliases.
+          * We need to ignore the aliases that are used by more than one 
method.
+          */
+         for (final Map.Entry<AbstractProvider, String[]> entry : 
methodAliases.entrySet()) {
+             final AbstractProvider method = entry.getKey();
+             final String classe = method.getClass().getName();
+             final var hardCoded = new 
HashSet<String>(Arrays.asList(getAliases(method)));
+             for (final String alias : entry.getValue()) {
+                 if (aliasUsageCount.get(alias) == 1) {
+                     assertTrue(hardCoded.remove(alias), () -> "Alias \"" + 
alias + "\" not found in class " + classe);
+                 }
+             }
+             assertTrue(hardCoded.isEmpty(),
+                     () -> "Unexpected alias \"" + hardCoded.iterator().next() 
+ "\" in " + classe);
+         }
+     }
+ 
+     /**
+      * Returns the identifier code in <abbr>EPSG</abbr> namespace for the 
given object, or {@code null} if none.
+      */
+     private static String getCodeEPSG(final IdentifiedObject object) {
+         Identifier identifier = IdentifiedObjects.getIdentifier(object, 
Citations.EPSG);
+         return (identifier != null) ? identifier.getCode() : null;
+     }
+ 
+     /**
+      * Returns the collection of <abbr>EPSG</abbr> aliases or abbreviations 
for the given object.
+      */
+     private static String[] getAliases(final IdentifiedObject object) {
+         return object.getAlias()
+                 .stream()
+                 .filter((alias) -> 
alias.scope().name().toString().startsWith(Constants.EPSG))
+                 .map(GenericName::toString)
+                 .toArray(String[]::new);
+     }
  }

Reply via email to