You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2025/11/28 18:08:44 UTC

(sis) branch geoapi-4.0 updated (c0da944894 -> d650ed458d)

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

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


    from c0da944894 When a CRS is specified in a netCDF file both as attributes and by WKT, and when the attributes do not specify object names, get the names from the WKT.
     new 8df5c5e20c Replace `Semaphores` bitmask by an `EnumSet` for easier debugging and as anticipation for `ScopedValue` in Java 25. Add a new enumeration value for temporarily disable warnings during the use of EPSG codes that are known to be deprecated.
     new d650ed458d Cleanup with one new public method but no other significant code change.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../feature/internal/shared/FeatureProjection.java |   2 +-
 .../apache/sis/filter/DefaultFilterFactory.java    |   4 +-
 .../org/apache/sis/filter/FunctionRegister.java    |  10 +-
 .../geometry/wrapper/SpatialOperationContext.java  |   2 +-
 .../apache/sis/geometry/wrapper/jts/Wrapper.java   |   4 +-
 .../org/apache/sis/image/BandAggregateLayout.java  |   2 +-
 .../org/apache/sis/metadata/MetadataStandard.java  |   9 +-
 .../org/apache/sis/metadata/MetadataVisitor.java   |  28 ++---
 .../apache/sis/metadata/ModifiableMetadata.java    |  20 ++--
 .../org/apache/sis/metadata/TreeTableView.java     |  12 +-
 .../iso/maintenance/DefaultScopeDescription.java   |   2 +-
 .../iso/quality/DefaultEvaluationMethod.java       |   2 +-
 .../org/apache/sis/metadata/sql/Dispatcher.java    |   7 +-
 .../main/org/apache/sis/xml/bind/Context.java      |   7 +-
 .../main/org/apache/sis/portrayal/Canvas.java      |   2 +-
 .../apache/sis/geometry/WraparoundAdjustment.java  |   6 +-
 .../main/org/apache/sis/parameter/IntegerList.java |   2 +-
 .../main/org/apache/sis/parameter/Verifier.java    |   6 +-
 .../main/org/apache/sis/referencing/CRS.java       |  89 ++++++++------
 .../main/org/apache/sis/referencing/CommonCRS.java |   7 +-
 .../referencing/EllipsoidalHeightSeparator.java    |   3 +-
 .../apache/sis/referencing/GeodeticCalculator.java |   4 +-
 .../apache/sis/referencing/crs/AbstractCRS.java    |   2 +-
 .../sis/referencing/crs/AbstractDerivedCRS.java    |  30 +++--
 .../sis/referencing/crs/DefaultProjectedCRS.java   |   2 +-
 .../factory/ConcurrentAuthorityFactory.java        |  26 ++---
 .../referencing/factory/GeodeticObjectFactory.java |   2 +-
 .../factory/IdentifiedObjectFinder.java            |  11 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |  45 ++++---
 .../sis/referencing/factory/sql/EPSGInstaller.java |  14 ++-
 .../factory/sql/InstallationScriptProvider.java    |  10 --
 .../referencing/internal/ParameterizedAffine.java  |  26 +++--
 .../referencing/internal/PositionTransformer.java  |   2 +-
 .../internal/shared/GeodeticObjectBuilder.java     |   7 +-
 .../internal/shared/ReferencingUtilities.java      |  27 -----
 .../internal/shared/TemporalAccessor.java          |   3 +-
 .../internal/shared/WraparoundAxesFinder.java      |   3 +-
 .../operation/AbstractCoordinateOperation.java     |  48 ++++----
 .../operation/CoordinateOperationRegistry.java     |  31 +++--
 .../referencing/operation/DefaultConversion.java   |   4 +-
 .../operation/DefaultPassThroughOperation.java     |  11 +-
 .../operation/transform/ConcatenatedTransform.java |   2 +-
 .../operation/transform/MathTransforms.java        |   2 +-
 .../test/org/apache/sis/io/wkt/CRSParserTest.java  |   2 +-
 .../operation/DefaultConversionTest.java           |   4 +-
 .../sis/storage/geotiff/reader/CRSBuilder.java     |   4 +-
 .../sis/storage/geotiff/reader/XMLMetadata.java    |   4 +-
 .../sis/storage/netcdf/base/GridMapping.java       |   4 +-
 .../sis/storage/netcdf/base/NamedElement.java      |   2 +-
 .../apache/sis/storage/esri/AsciiGridStore.java    |   5 +-
 .../main/org/apache/sis/math/Vector.java           |   2 +-
 .../org/apache/sis/pending/jdk/ScopedValue.java    |  13 ++-
 .../main/org/apache/sis/system/Semaphores.java     | 130 +++++++++++++--------
 optional/src/org.apache.sis.gui/bundle/bin/sisfx   |   1 +
 .../main/org/apache/sis/gui/RecentFiles.java       |   2 +-
 .../org/apache/sis/gui/coverage/GridViewSkin.java  |   2 +-
 .../main/org/apache/sis/gui/dataset/LogViewer.java |   7 +-
 .../sis/gui/metadata/IdentificationInfo.java       |   2 +-
 .../apache/sis/gui/metadata/MetadataSummary.java   |   2 +-
 .../org/apache/sis/gui/referencing/CRSChooser.java |   4 +-
 .../org/apache/sis/gui/referencing/MenuSync.java   |   8 +-
 61 files changed, 365 insertions(+), 371 deletions(-)
 copy incubator/src/org.apache.sis.webapp/main/org/apache/sis/services/Servlet.java => endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/ScopedValue.java (77%)


(sis) 01/02: Replace `Semaphores` bitmask by an `EnumSet` for easier debugging and as anticipation for `ScopedValue` in Java 25. Add a new enumeration value for temporarily disable warnings during the use of EPSG codes that are known to be deprecated.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 8df5c5e20ca1a9867062c7a67b731b34d1c92a44
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Nov 28 10:45:49 2025 +0100

    Replace `Semaphores` bitmask by an `EnumSet` for easier debugging and as anticipation for `ScopedValue` in Java 25.
    Add a new enumeration value for temporarily disable warnings during the use of EPSG codes that are known to be deprecated.
---
 .../org/apache/sis/metadata/MetadataStandard.java  |   9 +-
 .../org/apache/sis/metadata/MetadataVisitor.java   |  28 ++---
 .../apache/sis/metadata/ModifiableMetadata.java    |  20 ++--
 .../org/apache/sis/metadata/TreeTableView.java     |  12 +-
 .../iso/maintenance/DefaultScopeDescription.java   |   2 +-
 .../iso/quality/DefaultEvaluationMethod.java       |   2 +-
 .../org/apache/sis/metadata/sql/Dispatcher.java    |   7 +-
 .../main/org/apache/sis/xml/bind/Context.java      |   7 +-
 .../main/org/apache/sis/parameter/Verifier.java    |   6 +-
 .../main/org/apache/sis/referencing/CommonCRS.java |   7 +-
 .../sis/referencing/crs/AbstractDerivedCRS.java    |  30 +++--
 .../factory/ConcurrentAuthorityFactory.java        |  26 ++---
 .../referencing/factory/GeodeticObjectFactory.java |   2 +-
 .../factory/IdentifiedObjectFinder.java            |  11 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |  45 ++++---
 .../sis/referencing/factory/sql/EPSGInstaller.java |  14 ++-
 .../factory/sql/InstallationScriptProvider.java    |  10 --
 .../referencing/internal/ParameterizedAffine.java  |  26 +++--
 .../operation/AbstractCoordinateOperation.java     |  42 ++++---
 .../operation/CoordinateOperationRegistry.java     |  28 +++--
 .../operation/transform/ConcatenatedTransform.java |   2 +-
 .../org/apache/sis/pending/jdk/ScopedValue.java    |  29 +++++
 .../main/org/apache/sis/system/Semaphores.java     | 130 +++++++++++++--------
 23 files changed, 266 insertions(+), 229 deletions(-)

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 1bed763bfc..f7ec708fae 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
@@ -1083,18 +1083,15 @@ public class MetadataStandard implements Serializable {
         final Set<ObjectPair> inProgress = ObjectPair.CURRENT.get();
         if (inProgress.add(pair)) {
             /*
-             * The NULL_COLLECTION semaphore prevents creation of new empty collections by getter methods
+             * The NULL_FOR_EMPTY_COLLECTION semaphore prevents creation of new empty collections by getter methods
              * (a consequence of lazy instantiation). The intent is to avoid creation of unnecessary objects
              * for all unused properties. Users should not see behavioral difference, except if they override
-             * some getters with an implementation invoking other getters. However in such cases, users would
-             * have been exposed to null values at XML marshalling time anyway.
+             * some getters with an implementation invoking other getters.
              */
-            final boolean allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION);
             try {
-                return accessor.equals(metadata1, metadata2, mode);
+                return Semaphores.NULL_FOR_EMPTY_COLLECTION.execute(() -> accessor.equals(metadata1, metadata2, mode));
             } finally {
                 inProgress.remove(pair);
-                Semaphores.clearIfFalse(Semaphores.NULL_COLLECTION, allowNull);
             }
         } else {
             /*
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 eda12a0a3e..8fd33e2035 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
@@ -28,9 +28,9 @@ import org.apache.sis.util.internal.shared.UnmodifiableArrayList;
 
 /**
  * A visitor of metadata properties with a safety against infinite recursion.
- * The visitor may compute a result, for example a hash code value or a boolean
+ * The visitor may compute a result, for example a hash code value or a Boolean
  * testing whether the metadata is empty. Each {@code MetadataVisitor} instance
- * is used by one thread; this class does not need to be thread-safe.
+ * is used by one thread, so this class does not need to be thread-safe.
  *
  * @author  Martin Desruisseaux (Geomatys)
  *
@@ -71,21 +71,20 @@ abstract class MetadataVisitor<R> {
 
     /**
      * Count of nested calls to {@link #walk(MetadataStandard, Class, Object, boolean)} method.
-     * When this count reach zero, the visitor should be removed from the thread local variable.
+     * When this count reaches zero, the visitor should be removed from the thread local variable.
      *
      * @see #creator()
      */
     private int nestedCount;
 
     /**
-     * Value of the {@link Semaphores#NULL_COLLECTION} flag when we started the walk.
-     * The {@code NULL_COLLECTION} flag prevents creation of new empty collections by getter methods
+     * Whether to clear the {@link Semaphores#NULL_FOR_EMPTY_COLLECTION} flag after the walk of whole tree.
+     * The {@code NULL_FOR_EMPTY_COLLECTION} flag prevents creation of empty collections by getter methods
      * (a consequence of lazy instantiation). The intent is to avoid creation of unnecessary objects
      * for all unused properties. Users should not see behavioral difference, except if they override
-     * some getters with an implementation invoking other getters. However in such cases, users would
-     * have been exposed to null values at XML marshalling time anyway.
+     * some getters with an implementation invoking other getters.
      */
-    private boolean allowNull;
+    private boolean needFlagReset;
 
     /**
      * Creates a new visitor.
@@ -169,14 +168,7 @@ abstract class MetadataVisitor<R> {
                     propertyPath = Arrays.copyOf(propertyPath, nestedCount * 2);
                 }
                 if (nestedCount++ == 0) {
-                    /*
-                     * The NULL_COLLECTION semaphore prevents creation of new empty collections by getter methods
-                     * (a consequence of lazy instantiation). The intent is to avoid creation of unnecessary objects
-                     * for all unused properties. Users should not see behavioral difference, except if they override
-                     * some getters with an implementation invoking other getters. However in such cases, users would
-                     * have been exposed to null values at XML marshalling time anyway.
-                     */
-                    allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION);
+                    needFlagReset = Semaphores.NULL_FOR_EMPTY_COLLECTION.set();
                 }
                 /*
                  * Actual visiting. The `accessor.walk(this, metadata)` method calls below will callback the abstract
@@ -197,9 +189,9 @@ abstract class MetadataVisitor<R> {
                     if (--nestedCount == 0) {
                         /*
                          * We are back to the root metadata (i.e. we finished walking through all children).
-                         * Clear thread local variables, which should restore them to their initial value.
+                         * Restore thread local variables to their initial state.
                          */
-                        Semaphores.clearIfFalse(Semaphores.NULL_COLLECTION, allowNull);
+                        Semaphores.NULL_FOR_EMPTY_COLLECTION.clearIfTrue(needFlagReset);
                         final ThreadLocal<? extends MetadataVisitor<?>> creator = creator();
                         if (creator != null) creator.remove();
                     }
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 c630f9b273..7bef427be6 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
@@ -711,8 +711,8 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
      * performing a {@code equals}, {@code isEmpty} or {@code prune} operation
      * (for avoiding creating unnecessary collections).
      */
-    private static boolean emptyCollectionAsNull() {
-        return Semaphores.query(Semaphores.NULL_COLLECTION);
+    private static boolean nullForEmptyCollection() {
+        return Semaphores.NULL_FOR_EMPTY_COLLECTION.get();
     }
 
     /**
@@ -726,9 +726,9 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
      */
     protected final <E> List<E> nonNullList(final List<E> current, final Class<E> elementType) {
         if (current != null) {
-            return current.isEmpty() && emptyCollectionAsNull() ? null : current;
+            return current.isEmpty() && nullForEmptyCollection() ? null : current;
         }
-        if (emptyCollectionAsNull()) {
+        if (nullForEmptyCollection()) {
             return null;
         }
         if (state < FREEZING) {
@@ -748,9 +748,9 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
      */
     protected final <E> Set<E> nonNullSet(final Set<E> current, final Class<E> elementType) {
         if (current != null) {
-            return current.isEmpty() && emptyCollectionAsNull() ? null : current;
+            return current.isEmpty() && nullForEmptyCollection() ? null : current;
         }
-        if (emptyCollectionAsNull()) {
+        if (nullForEmptyCollection()) {
             return null;
         }
         if (state < FREEZING) {
@@ -775,9 +775,9 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
      */
     protected final <E> Collection<E> nonNullCollection(final Collection<E> current, final Class<E> elementType) {
         if (current != null) {
-            return current.isEmpty() && emptyCollectionAsNull() ? null : current;
+            return current.isEmpty() && nullForEmptyCollection() ? null : current;
         }
-        if (emptyCollectionAsNull()) {
+        if (nullForEmptyCollection()) {
             return null;
         }
         final boolean isModifiable = (state < FREEZING);
@@ -810,9 +810,9 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
      */
     protected final <K,V> Map<K,V> nonNullMap(final Map<K,V> current, final Class<K> keyType) {
         if (current != null) {
-            return current.isEmpty() && emptyCollectionAsNull() ? null : current;
+            return current.isEmpty() && nullForEmptyCollection() ? null : current;
         }
-        if (emptyCollectionAsNull()) {
+        if (nullForEmptyCollection()) {
             return null;
         }
         if (state < FREEZING) {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeTableView.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeTableView.java
index c7994c5064..4018b89b58 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeTableView.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeTableView.java
@@ -141,20 +141,16 @@ final class TreeTableView implements TreeTable, TreeFormatCustomization, Seriali
     @Override
     public String toString() {
         /*
-         * The NULL_COLLECTION semaphore prevents creation of new empty collections by getter methods
+         * The NULL_FOR_EMPTY_COLLECTION semaphore prevents creation of new empty collections by getter methods
          * (a consequence of lazy instantiation). The intent is to avoid creation of unnecessary objects
          * for all unused properties. Users should not see behavioral difference, except if they override
-         * some getters with an implementation invoking other getters. However in such cases, users would
-         * have been exposed to null values at XML marshalling time anyway.
+         * some getters with an implementation invoking other getters.
          */
-        final boolean allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION);
-        try {
+        return Semaphores.NULL_FOR_EMPTY_COLLECTION.execute(() -> {
             synchronized (MetadataFormat.INSTANCE) {
                 return MetadataFormat.INSTANCE.format(this);
             }
-        } finally {
-            Semaphores.clearIfFalse(Semaphores.NULL_COLLECTION, allowNull);
-        }
+        });
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
index c3237e3320..dcab52687c 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
@@ -223,7 +223,7 @@ public class DefaultScopeDescription extends ISOMetadata implements ScopeDescrip
             if (property == code) {
                 return cast(value);
             } else if (!(value instanceof Set) || !((Set<?>) value).isEmpty()) {
-                return Semaphores.query(Semaphores.NULL_COLLECTION)
+                return Semaphores.NULL_FOR_EMPTY_COLLECTION.get()
                        ? null : new ExcludedSet<>(NAMES[code-1], NAMES[property-1]);
             }
         }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
index 1a0b177c10..9e444e196b 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
@@ -438,7 +438,7 @@ public class DefaultEvaluationMethod extends ISOMetadata implements EvaluationMe
     @XmlElement(name = "dateTime")
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
     public Collection<Temporal> getDates() {
-        if (Semaphores.query(Semaphores.NULL_COLLECTION)) {
+        if (Semaphores.NULL_FOR_EMPTY_COLLECTION.get()) {
             return isNullOrEmpty(dates) ? null : dates;
         }
         if (dates == null) {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
index f7129dccaa..298b939394 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
@@ -206,14 +206,15 @@ final class Dispatcher implements InvocationHandler {
         Object value = null;
         final long nullBit = Numerics.bitmask(info.asIndexMap(source.standard).get(method.getName()));     // Okay even if overflow.
         /*
-         * The NULL_COLLECTION semaphore prevents creation of new empty collections by getter methods
+         * The NULL_FOR_EMPTY_COLLECTION flag prevents creation of empty collections by getter methods
          * (a consequence of lazy instantiation). The intent is to avoid creation of unnecessary objects
          * for all unused properties. Users should not see behavioral difference.
          */
         if ((nullValues & nullBit) == 0) {
             final Class<?> type = info.getMetadataType();
-            final boolean allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION);
+            final boolean needFlagReset = Semaphores.NULL_FOR_EMPTY_COLLECTION.set();
             try {
+                @SuppressWarnings("LocalVariableHidesMemberVariable")
                 Object cache = this.cache;
                 if (cache != null) {
                     synchronized (cache) {
@@ -275,7 +276,7 @@ final class Dispatcher implements InvocationHandler {
                     }
                 }
             } finally {
-                Semaphores.clearIfFalse(Semaphores.NULL_COLLECTION, allowNull);
+                Semaphores.NULL_FOR_EMPTY_COLLECTION.clearIfTrue(needFlagReset);
             }
         }
         if (value == null) {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java
index 685f3f9310..91dd7fa3c0 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java
@@ -106,7 +106,8 @@ public final class Context extends MarshalContext {
     public static final int LENIENT_UNMARSHAL = 0x40;
 
     /**
-     * Bit where to store whether {@link #finish()} shall invoke {@code Semaphores.clear(Semaphores.NULL_COLLECTION)}.
+     * Bit where to store whether {@link #finish()} shall invoke
+     * {@code Semaphores.NULL_FOR_EMPTY_COLLECTION.clear()}.
      */
     private static final int CLEAR_SEMAPHORE = 0x80;
 
@@ -319,7 +320,7 @@ public final class Context extends MarshalContext {
              * will not fail with an OutOfMemoryError. This is preferable for allowing the
              * caller to invoke finish() in a finally block.
              */
-            if (!Semaphores.queryAndSet(Semaphores.NULL_COLLECTION)) {
+            if (Semaphores.NULL_FOR_EMPTY_COLLECTION.set()) {
                 bitMasks |= CLEAR_SEMAPHORE;
             }
         }
@@ -890,7 +891,7 @@ public final class Context extends MarshalContext {
      */
     public final void finish() {
         if ((bitMasks & CLEAR_SEMAPHORE) != 0) {
-            Semaphores.clear(Semaphores.NULL_COLLECTION);
+            Semaphores.NULL_FOR_EMPTY_COLLECTION.clear();
         }
         if (previous != null) {
             CURRENT.set(previous);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/Verifier.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/Verifier.java
index 2590583bc3..c1bdb64899 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/Verifier.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/Verifier.java
@@ -201,11 +201,11 @@ final class Verifier {
                 error.convertRange(converter);
                 final String name = getDisplayName(descriptor);
                 final String message = error.message(null, name, value);
-                if (!Semaphores.query(Semaphores.SUSPEND_PARAMETER_CHECK)) {
-                    throw new InvalidParameterValueException(message, name, value);
-                } else {
+                if (Semaphores.SUSPEND_PARAMETER_CHECK.get()) {
                     Logging.completeAndLog(DefaultParameterValue.LOGGER, DefaultParameterValue.class,
                                            "setValue", new LogRecord(Level.WARNING, message));
+                } else {
+                    throw new InvalidParameterValueException(message, name, value);
                 }
             }
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
index d446fd47cb..a70311ecab 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
@@ -73,6 +73,7 @@ import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.referencing.internal.shared.Formulas;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.system.SystemListener;
+import org.apache.sis.system.Semaphores;
 import org.apache.sis.system.Modules;
 import org.apache.sis.util.OptionalCandidate;
 import org.apache.sis.util.ArgumentChecks;
@@ -676,7 +677,8 @@ public enum CommonCRS {
         if (object == null) {
             final GeodeticAuthorityFactory factory = factory();
             if (factory != null) try {
-                cached = object = factory.createGeographicCRS(String.valueOf(geographic));
+                cached = object = Semaphores.FINER_LOG_LEVEL_FOR_DEPRECATION.execute(
+                        () -> factory.createGeographicCRS(String.valueOf(geographic)));
                 return object;
             } catch (FactoryException e) {
                 failure(this, "geographic", e, geographic);
@@ -887,7 +889,8 @@ public enum CommonCRS {
         if (object == null) {
             final GeodeticAuthorityFactory factory = factory();
             if (factory != null) try {
-                cached = object = factory.createGeodeticDatum(String.valueOf(datum));
+                cached = object = Semaphores.FINER_LOG_LEVEL_FOR_DEPRECATION.execute(
+                        () -> factory.createGeodeticDatum(String.valueOf(datum)));
                 return object;
             } catch (FactoryException e) {
                 failure(this, "datum", e, datum);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
index 414b1f751c..0e58748269 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
@@ -215,23 +215,16 @@ abstract class AbstractDerivedCRS extends AbstractCRS implements DerivedCRS {
             return true;
         }
         if (super.equals(object, mode)) {
-            final boolean strict = (mode == ComparisonMode.STRICT);
-            /*
-             * Avoid never-ending recursion: Conversion has a `targetCRS` field (inherited from
-             * the AbstractCoordinateOperation super-class) that is set to this AbstractDerivedCRS.
-             *
-             * Do NOT compare the baseCRS explicitly. This is done implicitely in the comparison of the Conversion
-             * objects, since (this.baseCRS == Conversion.sourceCRS) in Apache SIS.  The reason why we delegate the
-             * comparison of that CRS to the Conversion object is because we want to ignore the baseCRS axes if the
-             * mode said to ignore metadata, but ignoring axis order and units has implication on the MathTransform
-             * instances to compare.  The AbstractCoordinateOperation.equals(…) method implementation handles those
-             * cases.
-             */
-            if (Semaphores.queryAndSet(Semaphores.CONVERSION_AND_CRS)) {
-                return true;
-            } else try {
+            if (Semaphores.COMPARING_CONVERSION_OR_DERIVED_CRS.set()) try {
+                /*
+                 * Do NOT compare the `baseCRS` explicitly. This is done implicitely in the comparison of `Conversion`,
+                 * since `this.baseCRS == Conversion.sourceCRS` in Apache SIS. We delegate the comparison of that CRS
+                 * to the `Conversion` object because we want to ignore the `baseCRS` axes if requested by the `mode`,
+                 * but ignoring axis order and axis units requires special handling during `MathTransform` comparison.
+                 * The `AbstractCoordinateOperation.equals(…)` method implementation handles those cases.
+                 */
                 final Conversion op1, op2;
-                if (strict) {
+                if (mode == ComparisonMode.STRICT) {
                     op1 = conversionFromBase;
                     op2 = ((AbstractDerivedCRS) object).conversionFromBase;
                 } else {
@@ -240,7 +233,10 @@ abstract class AbstractDerivedCRS extends AbstractCRS implements DerivedCRS {
                 }
                 return Utilities.deepEquals(op1, op2, mode);
             } finally {
-                Semaphores.clear(Semaphores.CONVERSION_AND_CRS);
+                Semaphores.COMPARING_CONVERSION_OR_DERIVED_CRS.clear();
+            } else {
+                // Avoid never-ending recursion when the comparison was started by `AbstractCoordinateOperation`.
+                return true;
             }
         }
         return false;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
index 708a43ea86..415f39bc46 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
@@ -388,6 +388,7 @@ public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFa
      * @return Data Access Object (DAO) to use in {@code createFoo(String)} methods.
      * @throws FactoryException if the Data Access Object creation failed.
      */
+    @SuppressWarnings("UseSpecificCatch")
     private DAO getDataAccess() throws FactoryException {
         /*
          * First checks if the current thread is already using a factory. If yes, we will
@@ -479,7 +480,7 @@ public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFa
                 time = usage.timestamp - time;
             }
             /*
-             * Log the event. Note: there is no need to check for `Semaphores.FINER_OBJECT_CREATION_LOGS`
+             * Log the event. Note: there is no need to check for `Semaphores.FINER_LOG_LEVEL_FOR_OBJECTS_CREATION`
              * because this method is not invoked, or is invoked with `type = null`, during execution of
              * `IdentifiedObjectFinder` search operations. The only new information in this log compared
              * to `GeodeticObjectFactory` logs is the creation duration, not useful if too close to zero
@@ -1989,7 +1990,12 @@ public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFa
         private Set<IdentifiedObject> cache(final IdentifiedObject object, Set<IdentifiedObject> result, final int index) {
             final Map<IdentifiedObject, Set<IdentifiedObject>[]> findPool = factory().findPool;
             synchronized (findPool) {
-                final Set<IdentifiedObject>[] entry = findPool.computeIfAbsent(object, Finder::createCacheEntry);
+                /*
+                 * The array is of length `DOMAIN_COUNT × 4`. There is a first ×2 for distinguishing whether axes
+                 * are ignored or not, and another ×2 for whether a singleton is searched instead of the collection.
+                 */
+                @SuppressWarnings({"unchecked", "rawtypes"})            // Generic array creation.
+                final Set<IdentifiedObject>[] entry = findPool.computeIfAbsent(object, (key) -> new Set[DOMAIN_COUNT * 4]);
                 final Set<IdentifiedObject> existing = entry[index];
                 if (existing != null) {
                     return existing;
@@ -2005,22 +2011,6 @@ public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFa
             return result;
         }
 
-        /**
-         * Creates an initially empty cache entry for the given object.
-         * Used in lambda expression and defined as a separated method because of generic type.
-         * The {@code object} argument is present only for having the required method signature.
-         *
-         * <p>The array length is {@value #DOMAIN_COUNT} × 2 for whether axes are ignored or not,
-         * and ×2 again for whether a singleton is searched instead of the collection.</p>
-         *
-         * @param  object  the user-specified object which was searched.
-         * @return a new array to use as a cache for the specified object.
-         */
-        @SuppressWarnings({"unchecked", "rawtypes"})            // Generic array creation.
-        private static Set<IdentifiedObject>[] createCacheEntry(IdentifiedObject object) {
-            return new Set[DOMAIN_COUNT * 4];
-        }
-
         /**
          * Looks up an object from this authority factory which is approximately equal to the specified object.
          * The default implementation performs the same lookup as the Data Access Object and caches the result.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
index c6210c1a8c..c553aca5c4 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
@@ -301,7 +301,7 @@ public class GeodeticObjectFactory extends AbstractFactory implements CRSFactory
     private <T extends AbstractIdentifiedObject> T unique(final String caller, final T object) {
         final T c = pool.unique(object);
         if (c == object) {
-            final Level level = Semaphores.query(Semaphores.FINER_OBJECT_CREATION_LOGS) ? Level.FINER : Level.FINE;
+            final Level level = Semaphores.FINER_LOG_LEVEL_FOR_OBJECTS_CREATION.getLogLevel(Level.FINE);
             if (LOGGER.isLoggable(level)) {
                 final String id = IdentifiedObjects.toString(IdentifiedObjects.getIdentifier(c, null));
                 final LogRecord record = Messages.forLocale(null).createLogRecord(level,
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
index 0bc929e9f7..890c76a9e5 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
@@ -505,7 +505,7 @@ public class IdentifiedObjectFinder {
          */
         @Override
         public IdentifiedObject apply(final String code) {
-            final boolean finer = Semaphores.queryAndSet(Semaphores.FINER_OBJECT_CREATION_LOGS);
+            final boolean needFlagReset = Semaphores.FINER_LOG_LEVEL_FOR_OBJECTS_CREATION.set();
             try {
                 final var candidate = (IdentifiedObject) proxy.createFromAPI(factory, code);
                 if (match(candidate, object, mode, proxy) && existing.add(candidate)) {
@@ -520,7 +520,7 @@ public class IdentifiedObjectFinder {
             } catch (FactoryException e) {
                 exceptionOccurred(e);
             } finally {
-                Semaphores.clearIfFalse(Semaphores.FINER_OBJECT_CREATION_LOGS, finer);
+                Semaphores.FINER_LOG_LEVEL_FOR_OBJECTS_CREATION.clearIfTrue(needFlagReset);
             }
             return null;
         }
@@ -542,13 +542,10 @@ public class IdentifiedObjectFinder {
     final IdentifiedObject createAndFilter(final AuthorityFactory factory, final String code, final IdentifiedObject object)
             throws FactoryException
     {
-        final boolean finer = Semaphores.queryAndSet(Semaphores.FINER_OBJECT_CREATION_LOGS);
-        try {
+        return Semaphores.FINER_LOG_LEVEL_FOR_OBJECTS_CREATION.execute(() -> {
             final var candidate = (IdentifiedObject) proxy.createFromAPI(factory, code);
             return match(candidate, object, getComparisonMode(), proxy) ? candidate : null;
-        } finally {
-            Semaphores.clearIfFalse(Semaphores.FINER_OBJECT_CREATION_LOGS, finer);
-        }
+        });
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 8ab167b668..29184c1e01 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -1341,7 +1341,7 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
                     EPSGDataAccess.class,
                     "create".concat(source.type.getSimpleName()),
                     Resources.forLocale(locale).createLogRecord(
-                            Level.WARNING,
+                            Semaphores.FINER_LOG_LEVEL_FOR_DEPRECATION.getLogLevel(Level.WARNING),
                             Resources.Keys.DeprecatedCode_3,
                             Constants.EPSG + Constants.DEFAULT_SEPARATOR + code,
                             replacedBy,
@@ -1730,7 +1730,7 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
                  * because the `properties` map will be overwritten by calls to `createDatum` and
                  * similar methods. Instead, remember the constructor to invoke later.
                  */
-                final FactoryCall<CRSFactory, CoordinateReferenceSystem> constructor;
+                FactoryCall<CRSFactory, CoordinateReferenceSystem> constructor;
                 /*
                  * The following switch statement should have a case for all "CRS Kind" values enumerated
                  * in the `Prepare.sql` file, except that the values in this Java code are in lower cases.
@@ -1831,28 +1831,27 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
                         final CartesianCS cs = owner.createCartesianCS(csCode);
                         constructor = (factory, metadata) -> {
                             /*
-                             * The crsFactory method calls will indirectly create a parameterized MathTransform.
-                             * Their constructor will try to verify the parameter validity. But some deprecated
-                             * CRS had invalid parameter values (they were deprecated precisely for that reason).
-                             * If and only if we are creating a deprecated CRS, temporarily suspend the parameter
-                             * checks.
+                             * For a ProjectedCRS, the baseCRS is usually geodetic. However, geocentric CRS
+                             * is also allowed, but not yet supported in the code below. We could also have
+                             * a ProjectedCRS derived from another ProjectedCRS.
                              */
-                            final boolean old = !deprecated || Semaphores.queryAndSet(Semaphores.SUSPEND_PARAMETER_CHECK);
-                            try {
-                                /*
-                                 * For a ProjectedCRS, the baseCRS is usually geodetic. However, geocentric CRS
-                                 * is also allowed, but not yet supported in the code below. We could also have
-                                 * a ProjectedCRS derived from another ProjectedCRS.
-                                 */
-                                if (baseCRS instanceof GeodeticCRS) {
-                                    return factory.createProjectedCRS(metadata, (GeodeticCRS) baseCRS, fromBase, cs);
-                                } else {
-                                    return factory.createDerivedCRS(metadata, baseCRS, fromBase, cs);
-                                }
-                            } finally {
-                                Semaphores.clearIfFalse(Semaphores.SUSPEND_PARAMETER_CHECK, old);
+                            if (baseCRS instanceof GeodeticCRS) {
+                                return factory.createProjectedCRS(metadata, (GeodeticCRS) baseCRS, fromBase, cs);
+                            } else {
+                                return factory.createDerivedCRS(metadata, baseCRS, fromBase, cs);
                             }
                         };
+                        /*
+                         * The crsFactory method calls will indirectly create a parameterized MathTransform.
+                         * Their constructor will try to verify the parameter validity. But some deprecated
+                         * CRS had invalid parameter values (they were deprecated precisely for that reason).
+                         * If and only if we are creating a deprecated CRS, temporarily suspend the parameter
+                         * checks.
+                         */
+                        if (deprecated) {
+                            final var c = constructor;
+                            constructor = (factory, metadata) -> Semaphores.SUSPEND_PARAMETER_CHECK.execute(() -> c.create(factory, metadata));
+                        }
                         break;
                     }
                     /* ──────────────────────────────────────────────────────────────────────
@@ -3505,7 +3504,7 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
                  */
                 final OperationMethod     operationMethod;
                 final ParameterValueGroup parameterValues;
-                final boolean isDeferred = Semaphores.query(Semaphores.METADATA_ONLY);
+                final boolean isDeferred = Semaphores.METADATA_ONLY.get();
                 if (methodCode != null && !isDeferred) {
                     final OperationMethod generic = owner.createOperationMethod(methodCode.toString());
                     final List<Parameter> values = createParameterValues(epsg, methodCode);
@@ -3690,7 +3689,7 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
          * Before to return the set, tests the creation of 1 object in order to report early (i.e. now)
          * any problems with SQL statements. Remaining operations will be created only when first needed.
          */
-        if (!Semaphores.query(Semaphores.METADATA_ONLY)) {
+        if (!Semaphores.METADATA_ONLY.get()) {
             set.resolve(1);
         }
         return set;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
index f4f2002d64..b6cd4056be 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
@@ -25,8 +25,10 @@ import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
+import java.util.logging.LogRecord;
 import org.apache.sis.util.internal.shared.Constants;
 import org.apache.sis.util.resources.Messages;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.logging.PerformanceLevel;
 import org.apache.sis.metadata.sql.internal.shared.ScriptRunner;
 import org.apache.sis.metadata.sql.internal.shared.SQLUtilities;
@@ -157,7 +159,7 @@ final class EPSGInstaller extends ScriptRunner {
                 return false;
             }
         }
-        InstallationScriptProvider.log(Messages.forLocale(locale).createLogRecord(
+        log(Messages.forLocale(locale).createLogRecord(
                 Level.INFO,
                 Messages.Keys.CreatingSchema_2,
                 Constants.EPSG,
@@ -173,7 +175,7 @@ final class EPSGInstaller extends ScriptRunner {
             }
         }
         time = System.nanoTime() - time;
-        InstallationScriptProvider.log(Messages.forLocale(locale).createLogRecord(
+        log(Messages.forLocale(locale).createLogRecord(
                 PerformanceLevel.forDuration(time, TimeUnit.NANOSECONDS),
                 Messages.Keys.InsertDuration_2,
                 numRows,
@@ -181,6 +183,14 @@ final class EPSGInstaller extends ScriptRunner {
         return true;
     }
 
+    /**
+     * Logs the given record. This method pretends that the record has been logged by
+     * {@code EPSGFactory.install(…)} because it is the public API using this class.
+     */
+    private static void log(final LogRecord record) {
+        Logging.completeAndLog(EPSGDataAccess.LOGGER, EPSGFactory.class, "install", record);
+    }
+
     /**
      * Creates a message reporting the failure to create EPSG database. This method is invoked when {@link EPSGFactory}
      * caught an exception. This method completes the exception message with the file name and line number where the
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
index eb6606b79b..3dd0e48eeb 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
@@ -18,7 +18,6 @@ package org.apache.sis.referencing.factory.sql;
 
 import java.util.Set;
 import java.util.Objects;
-import java.util.logging.LogRecord;
 import java.sql.Connection;
 import java.io.BufferedReader;
 import java.io.LineNumberReader;
@@ -29,7 +28,6 @@ import java.io.FileNotFoundException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.NoSuchFileException;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.logging.Logging;
 import org.apache.sis.setup.InstallationResources;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.util.internal.shared.Constants;
@@ -215,12 +213,4 @@ public abstract class InstallationScriptProvider extends InstallationResources {
      * @throws IOException if an error occurred while opening the file.
      */
     protected abstract InputStream openStream(final String name) throws IOException;
-
-    /**
-     * Logs the given record. This method pretend that the record has been logged by
-     * {@code EPSGFactory.install(…)} because it is the public API using this class.
-     */
-    static void log(final LogRecord record) {
-        Logging.completeAndLog(EPSGDataAccess.LOGGER, EPSGFactory.class, "install", record);
-    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ParameterizedAffine.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ParameterizedAffine.java
index 29eb24fff0..56a91a957b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ParameterizedAffine.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/ParameterizedAffine.java
@@ -96,6 +96,14 @@ public final class ParameterizedAffine extends AffineTransform2D {
         }
     }
 
+    /**
+     * Whether the {@link #parameters} can be shown to user as an accurate description of this transform.
+     * See {@link #getParameterValues()} for a discussion about when the parameters are shown.
+     */
+    private boolean showParameters() {
+        return isDefinitive || Semaphores.TRANSFORM_ENCLOSED_IN_OPERATION.get();
+    }
+
     /**
      * Returns the parameter descriptors for this map projection.
      *
@@ -104,17 +112,16 @@ public final class ParameterizedAffine extends AffineTransform2D {
      */
     @Override
     public ParameterDescriptorGroup getParameterDescriptors() {
-        return isDefinitive || Semaphores.query(Semaphores.ENCLOSED_IN_OPERATION)  // See comment in getParameterValues().
-               ? parameters.getDescriptor() : super.getParameterDescriptors();
+        return showParameters() ? parameters.getDescriptor() : super.getParameterDescriptors();
     }
 
     /**
      * Returns the parameter values for this map projection.
      *
-     * <p><b>Hack:</b> this method normally returns the matrix parameters in case of doubt. However, if
-     * {@link Semaphores#ENCLOSED_IN_OPERATION} is set, then this method returns the map projection parameters
-     * even if they are not a complete description of this math transform. This internal hack shall be used
-     * only by {@link org.apache.sis.referencing.operation.AbstractCoordinateOperation}.</p>
+     * <p><b>Hack:</b> this method normally returns the matrix parameters in case of doubt.
+     * However, if {@link Semaphores#TRANSFORM_ENCLOSED_IN_OPERATION} is set, then this method returns
+     * the map projection parameters even if they are not a complete description of this math transform.
+     * This hack shall be used only by {@link org.apache.sis.referencing.operation.AbstractCoordinateOperation}.</p>
      *
      * <p><b>Use case of above hack:</b> consider an "Equidistant Cylindrical (Spherical)" map projection
      * from a {@code GeographiCRS} base using (latitude, longitude) axis order. We need to concatenate an
@@ -132,16 +139,15 @@ public final class ParameterizedAffine extends AffineTransform2D {
      *     has been applied.</li>
      * </ul>
      *
-     * The {@code Semaphores.ENCLOSED_IN_OPERATION} flag is SIS internal mechanism for distinguish the two above-cited
-     * cases.
+     * The {@code Semaphores.TRANSFORM_ENCLOSED_IN_OPERATION} flag is <abbr>SIS</abbr> internal mechanism
+     * for distinguish the two above-cited cases.
      *
      * @return the map projection parameters if they are an accurate description of this transform,
      *         or the generic affine parameters in case of doubt.
      */
     @Override
     public ParameterValueGroup getParameterValues() {
-        return isDefinitive || Semaphores.query(Semaphores.ENCLOSED_IN_OPERATION)
-               ? parameters : super.getParameterValues();
+        return showParameters() ? parameters : super.getParameterValues();
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
index 933b98c0ad..9d9a53dee8 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
@@ -720,13 +720,12 @@ check:      for (int isTarget=0; ; isTarget++) {        // 0 == source check; 1
         while (transform != null) {
             if (transform instanceof Parameterized) {
                 final ParameterDescriptorGroup param;
-                if (Semaphores.queryAndSet(Semaphores.ENCLOSED_IN_OPERATION)) {
-                    throw new AssertionError();                                     // Should never happen.
-                }
-                try {
+                if (Semaphores.TRANSFORM_ENCLOSED_IN_OPERATION.set()) try {
                     param = ((Parameterized) transform).getParameterDescriptors();
                 } finally {
-                    Semaphores.clear(Semaphores.ENCLOSED_IN_OPERATION);
+                    Semaphores.TRANSFORM_ENCLOSED_IN_OPERATION.clear();
+                } else {
+                    throw new AssertionError();     // Should never happen.
                 }
                 if (param != null) {
                     return param;
@@ -754,13 +753,12 @@ check:      for (int isTarget=0; ; isTarget++) {        // 0 == source check; 1
         while (mt != null) {
             if (mt instanceof Parameterized) {
                 final ParameterValueGroup param;
-                if (Semaphores.queryAndSet(Semaphores.ENCLOSED_IN_OPERATION)) {
-                    throw new AssertionError();                                     // Should never happen.
-                }
-                try {
+                if (Semaphores.TRANSFORM_ENCLOSED_IN_OPERATION.set()) try {
                     param = ((Parameterized) mt).getParameterValues();
                 } finally {
-                    Semaphores.clear(Semaphores.ENCLOSED_IN_OPERATION);
+                    Semaphores.TRANSFORM_ENCLOSED_IN_OPERATION.clear();
+                } else {
+                    throw new AssertionError();     // Should never happen.
                 }
                 if (param != null) {
                     return param;
@@ -861,13 +859,13 @@ check:      for (int isTarget=0; ; isTarget++) {        // 0 == source check; 1
                     Objects.equals(transform,                   that.transform)        &&
                     Objects.equals(coordinateOperationAccuracy, that.coordinateOperationAccuracy))
                 {
-                    // Check against never-ending recursion with DerivedCRS.
-                    if (Semaphores.queryAndSet(Semaphores.CONVERSION_AND_CRS)) {
-                        return true;
-                    } else try {
+                    if (Semaphores.COMPARING_CONVERSION_OR_DERIVED_CRS.set()) try {
                         return Objects.equals(targetCRS, that.targetCRS);
                     } finally {
-                        Semaphores.clear(Semaphores.CONVERSION_AND_CRS);
+                        Semaphores.COMPARING_CONVERSION_OR_DERIVED_CRS.clear();
+                    } else {
+                        // Avoid never-ending recursion when the comparison was started by `AbstractDerivedCRS`.
+                        return true;
                     }
                 }
             } else {
@@ -899,17 +897,17 @@ check:      for (int isTarget=0; ; isTarget++) {        // 0 == source check; 1
                      * sourceCRS axis order if the mode is ComparisonMode.IGNORE_METADATA.
                      */
                     boolean debug = false;
-                    if (Semaphores.queryAndSet(Semaphores.CONVERSION_AND_CRS)) {
-                        if (mode.isIgnoringMetadata()) {
-                            debug = (mode == ComparisonMode.DEBUG);
-                            mode = ComparisonMode.ALLOW_VARIANT;
-                        }
-                    } else try {
+                    if (Semaphores.COMPARING_CONVERSION_OR_DERIVED_CRS.set()) try {
                         if (!deepEquals(getTargetCRS(), that.getTargetCRS(), mode)) {
                             return false;
                         }
                     } finally {
-                        Semaphores.clear(Semaphores.CONVERSION_AND_CRS);
+                        Semaphores.COMPARING_CONVERSION_OR_DERIVED_CRS.clear();
+                    } else {
+                        if (mode.isIgnoringMetadata()) {
+                            debug = (mode == ComparisonMode.DEBUG);
+                            mode = ComparisonMode.ALLOW_VARIANT;
+                        }
                     }
                     /*
                      * Now compare the sourceCRS, potentially with a relaxed ComparisonMode (see above comment).
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index 879c95ea66..2473b66070 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -602,7 +602,7 @@ class CoordinateOperationRegistry {
                  * The non-public Semaphores.METADATA_ONLY mechanism instructs EPSGDataAccess to
                  * instantiate DeferredCoordinateOperation instead of full coordinate operations.
                  */
-                final boolean mdOnly = Semaphores.queryAndSet(Semaphores.METADATA_ONLY);
+                final boolean needFlagReset = Semaphores.METADATA_ONLY.set();
                 try {
                     Collection<CoordinateOperation> authoritatives;
                     try {
@@ -639,24 +639,22 @@ class CoordinateOperationRegistry {
                      * the first deprecated one (assuming that deprecated operations are sorted last).
                      * Deprecated operations are kept only if there are no non-deprecated operations.
                      */
-                    try {
-                        for (final CoordinateOperation candidate : authoritatives) {
-                            if (candidate != null) {                                    // Paranoiac check.
-                                if ((candidate instanceof Deprecable) && ((Deprecable) candidate).isDeprecated()) {
-                                    if (!useDeprecatedOperations && !operations.isEmpty()) break;
-                                    useDeprecatedOperations = true;
-                                } else if (useDeprecatedOperations) {
-                                    useDeprecatedOperations = false;
-                                    operations.clear();              // Replace deprecated operations by non-deprecated ones.
-                                }
-                                operations.add(candidate);
+                    for (final CoordinateOperation candidate : authoritatives) {
+                        if (candidate != null) {                                    // Paranoiac check.
+                            if ((candidate instanceof Deprecable) && ((Deprecable) candidate).isDeprecated()) {
+                                if (!useDeprecatedOperations && !operations.isEmpty()) break;
+                                useDeprecatedOperations = true;
+                            } else if (useDeprecatedOperations) {
+                                useDeprecatedOperations = false;
+                                operations.clear();              // Replace deprecated operations by non-deprecated ones.
                             }
+                            operations.add(candidate);
                         }
-                    } catch (BackingStoreException exception) {
-                        throw exception.unwrapOrRethrow(FactoryException.class);
                     }
+                } catch (BackingStoreException exception) {
+                    throw exception.unwrapOrRethrow(FactoryException.class);
                 } finally {
-                    Semaphores.clearIfFalse(Semaphores.METADATA_ONLY, mdOnly);
+                    Semaphores.METADATA_ONLY.clearIfTrue(needFlagReset);
                 }
             }
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
index 09561544d0..f9168216a3 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
@@ -506,7 +506,7 @@ class ConcatenatedTransform extends AbstractMathTransform implements Serializabl
     private Parameterized getParameterised() {
         Parameterized param = null;
         final List<Object> transforms = getPseudoSteps();
-        if (transforms.size() == 1 || Semaphores.query(Semaphores.ENCLOSED_IN_OPERATION)) {
+        if (transforms.size() == 1 || Semaphores.TRANSFORM_ENCLOSED_IN_OPERATION.get()) {
             for (final Object candidate : transforms) {
                 /*
                  * Search for non-linear parameters only, ignoring affine transforms and the matrices
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/ScopedValue.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/ScopedValue.java
new file mode 100644
index 0000000000..94c05ad984
--- /dev/null
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/ScopedValue.java
@@ -0,0 +1,29 @@
+/*
+ * 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.pending.jdk;
+
+/**
+ * Place-holder for a class added in JDK25.
+ */
+public final class ScopedValue {
+    private ScopedValue() {
+    }
+
+    public interface CallableOp<T, X extends Throwable> {
+        T call() throws X;
+    }
+}
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Semaphores.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Semaphores.java
index 48f1a19416..b4601e339a 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Semaphores.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Semaphores.java
@@ -16,23 +16,29 @@
  */
 package org.apache.sis.system;
 
+import java.util.EnumSet;
+import java.util.logging.Level;
 import org.apache.sis.util.Workaround;
+import org.apache.sis.pending.jdk.ScopedValue;
 
 
 /**
- * Thread-local booleans that need to be shared across different packages. Each thread has its own set of booleans.
- * The {@link #clear(int)} method <strong>must</strong> be invoked after the {@link #queryAndSet(int)} method in
- * a {@code try ... finally} block.
+ * Thread-local flags that need to be shared across different packages. Each thread has its own set of flags.
+ * The {@link #clear()} method must be invoked after {@link #set()} in a {@code try} … {@code finally} block.
+ * The {@link #execute(Supplier)} method can also be invoked instead.
+ *
+ * <p>This class duplicates a little bit the service provided by {@link ScopedValue}.
+ * We may delete or refactor this class when we will be allowed to target Java 25.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class Semaphores {
+public enum Semaphores {
     /**
      * A flag to indicate that empty collections should be returned as {@code null}. Returning null
      * collections is not a recommended practice, but is useful in some situations like marshalling
      * a XML document with JAXB, when we want to omit empty XML blocks.
      */
-    public static final int NULL_COLLECTION = 1;
+    NULL_FOR_EMPTY_COLLECTION,
 
     /**
      * A flag to indicate that only metadata are desired and that there is no need to create costly objects.
@@ -41,7 +47,7 @@ public final class Semaphores {
      *
      * @see <a href="https://issues.apache.org/jira/browse/SIS-327">SIS-327</a>
      */
-    public static final int METADATA_ONLY = 2;
+    METADATA_ONLY,
 
     /**
      * A lock for avoiding never-ending recursion in the {@code equals} method of {@code AbstractDerivedCRS}
@@ -50,14 +56,14 @@ public final class Semaphores {
      * {@code AbstractDerivedCRS} objects contain a {@code conversionFromBase} field, which contains a
      * {@code DefaultConversion.targetCRS} field referencing back the {@code AbstractDerivedCRS} object.
      */
-    public static final int CONVERSION_AND_CRS = 4;
+    COMPARING_CONVERSION_OR_DERIVED_CRS,
 
     /**
      * A flag to indicate that {@link org.apache.sis.referencing.operation.AbstractCoordinateOperation}
      * is querying parameters of a {@code MathTransform} enclosed in the operation. This is often at the
-     * time of formatting the WKT of a {@code "ProjectedCRS"} element.
+     * time of formatting the <abbr>WKT</abbr> of a {@code "ProjectedCRS"} element.
      */
-    public static final int ENCLOSED_IN_OPERATION = 8;
+    TRANSFORM_ENCLOSED_IN_OPERATION,
 
     /**
      * A flag to indicate that a parameter value outside its domain of validity should not cause an exception
@@ -68,81 +74,109 @@ public final class Semaphores {
      * <p><b>Example:</b> EPSG:3752 was a Mercator (variant A) projection but set the latitude of origin to 41°S.</p>
      */
     @Workaround(library = "EPSG:3752", version = "8.9")        // Deprecated in 2007 but still present in 2016.
-    public static final int SUSPEND_PARAMETER_CHECK = 16;
+    SUSPEND_PARAMETER_CHECK,
 
     /**
      * A flag to indicate that a finer logging level should be used for reporting geodetic object creations.
      * This flag is used during operations that potentially create a large number of CRSs, for example when
      * trying many CRS candidates in search for a CRS compliant with some criteria.
      */
-    public static final int FINER_OBJECT_CREATION_LOGS = 32;
+    FINER_LOG_LEVEL_FOR_OBJECTS_CREATION,
 
     /**
-     * The flags per running thread.
+     * A flag to indicate that a finer logging level should be used for reporting the use of deprecated codes.
+     * This flag is used during operations creating an object which is known to be deprecated.
      */
-    private static final ThreadLocal<Semaphores> FLAGS = new ThreadLocal<>();
+    FINER_LOG_LEVEL_FOR_DEPRECATION;
 
     /**
-     * The bit flags.
+     * The flags per running thread.
      */
-    private int flags;
+    private static final ThreadLocal<EnumSet<Semaphores>> FLAGS = new ThreadLocal<>();
 
     /**
-     * For internal use only.
+     * Returns the log level to use during the creation of an object.
+     * This is used with {@link #FINER_LOG_LEVEL_FOR_OBJECTS_CREATION}
+     * and {@link #FINER_LOG_LEVEL_FOR_DEPRECATION}.
+     *
+     * @param  usual  the log level that would normally be used.
+     * @return the log level to use.
      */
-    private Semaphores() {
+    public final Level getLogLevel(final Level usual) {
+        return get() ? Level.FINER : usual;
     }
 
     /**
-     * Returns {@code true} if the given flag is set.
+     * Returns {@code true} if this flag is set.
      *
-     * @param  flag  one of {@link #CONVERSION_AND_CRS}, {@link #ENCLOSED_IN_OPERATION} or other constants.
-     * @return {@code true} if the given flag is set.
+     * @return {@code true} if this flag is set.
      */
-    public static boolean query(final int flag) {
-        final Semaphores s = FLAGS.get();
-        return (s != null) && (s.flags & flag) != 0;
+    public final boolean get() {
+        final EnumSet<Semaphores> s = FLAGS.get();
+        return (s != null) && s.contains(this);
     }
 
     /**
-     * Sets the given flag.
+     * Sets this flag.
      *
-     * @param  flag  one of {@link #CONVERSION_AND_CRS}, {@link #ENCLOSED_IN_OPERATION} or other constants.
-     * @return {@code true} if the given flag was already set.
+     * @return whether the status of this flag changed as a result of this method call.
      */
-    public static boolean queryAndSet(final int flag) {
-        Semaphores s = FLAGS.get();
-        if (s == null) {
-            s = new Semaphores();
-            FLAGS.set(s);
+    public final boolean set() {
+        final EnumSet<Semaphores> s = FLAGS.get();
+        if (s != null) {
+            return s.add(this);
         }
-        final boolean isSet = ((s.flags & flag) != 0);
-        s.flags |= flag;
-        return isSet;
+        FLAGS.set(EnumSet.of(this));
+        return true;
     }
 
     /**
-     * Clears the given flag.
-     *
-     * @param  flag  one of {@link #CONVERSION_AND_CRS}, {@link #ENCLOSED_IN_OPERATION} or other constants.
+     * Clears this flag.
      */
-    public static void clear(final int flag) {
-        final Semaphores s = FLAGS.get();
-        if (s != null) {
-            s.flags &= ~flag;
+    public final void clear() {
+        final EnumSet<Semaphores> s = FLAGS.get();
+        if (s != null && s.remove(this) && s.isEmpty()) {
+            FLAGS.remove();
         }
     }
 
     /**
-     * Clears the given flag only if it was previously cleared.
-     * This is a convenience method for a common pattern with {@code try … finally} blocks.
+     * Clears this flag if the given value is {@code true}.
+     * This is a convenience method for a common pattern with {@code try} … {@code finally} blocks.
      *
-     * @param  flag      one of {@link #CONVERSION_AND_CRS}, {@link #ENCLOSED_IN_OPERATION} or other constants.
-     * @param  previous  value returned by {@link #queryAndSet(int)}.
+     * @param  reset  value returned by {@link #set()}.
      */
-    public static void clearIfFalse(final int flag, final boolean previous) {
-        if (!previous) {
-            clear(flag);
+    public final void clearIfTrue(final boolean reset) {
+        if (reset) clear();
+    }
+
+    /**
+     * Executes the given method in a block with this flag set.
+     * This is an alternative to the use of {@link #set()} and {@link #clearIfTrue(boolean)}.
+     * In Apache <abbr>SIS</abbr> code, we use this method only for small blocks of code and
+     * the above alternatives for larger blocks.
+     *
+     * @param  <R>     type of value returned by the supplier.
+     * @param  <X>     type of exception thrown by the supplier.
+     * @param  method  the method to invoke for getting the value.
+     * @return value computed by the given method.
+     * @throws X exception thrown by the supplier.
+     */
+    public final <R, X extends Throwable> R execute(final ScopedValue.CallableOp<R, X> method) throws X {
+        EnumSet<Semaphores> s = FLAGS.get();
+        if (s == null) {
+            s = EnumSet.of(this);
+            FLAGS.set(s);
+        } else if (!s.add(this)) {
+            return method.call();
+        }
+        try {
+            return method.call();
+        } finally {
+            s.remove(this);
+            if (s.isEmpty()) {
+                FLAGS.remove();
+            }
         }
     }
 }


(sis) 02/02: Cleanup with one new public method but no other significant code change.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d650ed458d84c7e32bd9ee28a8ace4cd6bfe6d77
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Nov 28 12:37:28 2025 +0100

    Cleanup with one new public method but no other significant code change.
    
    - Move `ReferencingUtilities.getDimension` to `CRS.getDimensionOrZero` for clarity.
    - Define a `CRS.BIDIMENSIONAL` package-private constant for clarity.
    - Replacements of a few codes by standard Java methods.
    - Spelling and javadoc fixes.
---
 .../feature/internal/shared/FeatureProjection.java |  2 +-
 .../apache/sis/filter/DefaultFilterFactory.java    |  4 +-
 .../org/apache/sis/filter/FunctionRegister.java    | 10 +--
 .../geometry/wrapper/SpatialOperationContext.java  |  2 +-
 .../apache/sis/geometry/wrapper/jts/Wrapper.java   |  4 +-
 .../org/apache/sis/image/BandAggregateLayout.java  |  2 +-
 .../main/org/apache/sis/portrayal/Canvas.java      |  2 +-
 .../apache/sis/geometry/WraparoundAdjustment.java  |  6 +-
 .../main/org/apache/sis/parameter/IntegerList.java |  2 +-
 .../main/org/apache/sis/referencing/CRS.java       | 89 ++++++++++++++--------
 .../referencing/EllipsoidalHeightSeparator.java    |  3 +-
 .../apache/sis/referencing/GeodeticCalculator.java |  4 +-
 .../apache/sis/referencing/crs/AbstractCRS.java    |  2 +-
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  2 +-
 .../referencing/internal/PositionTransformer.java  |  2 +-
 .../internal/shared/GeodeticObjectBuilder.java     |  7 +-
 .../internal/shared/ReferencingUtilities.java      | 27 -------
 .../internal/shared/TemporalAccessor.java          |  3 +-
 .../internal/shared/WraparoundAxesFinder.java      |  3 +-
 .../operation/AbstractCoordinateOperation.java     |  6 +-
 .../operation/CoordinateOperationRegistry.java     |  3 +-
 .../referencing/operation/DefaultConversion.java   |  4 +-
 .../operation/DefaultPassThroughOperation.java     | 11 ++-
 .../operation/transform/MathTransforms.java        |  2 +-
 .../test/org/apache/sis/io/wkt/CRSParserTest.java  |  2 +-
 .../operation/DefaultConversionTest.java           |  4 +-
 .../sis/storage/geotiff/reader/CRSBuilder.java     |  4 +-
 .../sis/storage/geotiff/reader/XMLMetadata.java    |  4 +-
 .../sis/storage/netcdf/base/GridMapping.java       |  4 +-
 .../sis/storage/netcdf/base/NamedElement.java      |  2 +-
 .../apache/sis/storage/esri/AsciiGridStore.java    |  5 +-
 .../main/org/apache/sis/math/Vector.java           |  2 +-
 optional/src/org.apache.sis.gui/bundle/bin/sisfx   |  1 +
 .../main/org/apache/sis/gui/RecentFiles.java       |  2 +-
 .../org/apache/sis/gui/coverage/GridViewSkin.java  |  2 +-
 .../main/org/apache/sis/gui/dataset/LogViewer.java |  7 +-
 .../sis/gui/metadata/IdentificationInfo.java       |  2 +-
 .../apache/sis/gui/metadata/MetadataSummary.java   |  2 +-
 .../org/apache/sis/gui/referencing/CRSChooser.java |  4 +-
 .../org/apache/sis/gui/referencing/MenuSync.java   |  8 +-
 40 files changed, 120 insertions(+), 137 deletions(-)

diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureProjection.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureProjection.java
index 297d4257f5..e1ab0ebe1f 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureProjection.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureProjection.java
@@ -347,7 +347,7 @@ public final class FeatureProjection extends Record implements UnaryOperator<Fea
     /**
      * Returns the expression which is executed for fetching the property value at the given index.
      *
-     * @param  index  index of the stored property for which to get the expression for fething the value.
+     * @param  index  index of the stored property for which to get the expression for fetching the value.
      * @return the expression which is executed for fetching the property value at the given index.
      */
     public final Expression<? super Feature, ?> expression(final int index) {
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
index b25e35aa0e..efe4a01e24 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
@@ -76,9 +76,9 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem
     /**
      * All functions identified by a name like {@code "cos"}, {@code "hypot"}, <i>etc</i>.
      * The actual function creations is delegated to an external factory such as SQLMM registry.
-     * The factories are fetched by {@link #function(String, Expression...)} when first needed.
+     * The factories are fetched by {@link #function(String, Expression[])} when first needed.
      *
-     * @see #function(String, Expression...)
+     * @see #function(String, Expression[])
      */
     @LazyCandidate
     private final Capabilities availableFunctions;
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java
index 1589a3e661..3e88d34c6b 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java
@@ -26,18 +26,14 @@ import org.opengis.filter.capability.AvailableFunction;
 /**
  * A factory of {@code org.opengis.filter} functions identified by their names.
  * Each factory can provide an arbitrary number of functions, enumerated by {@link #getNames()}.
- * The {@link org.apache.sis.filter.DefaultFilterFactory#function(String, Expression...)} method
- * delegates to this interface for creating the function implementation for a given name.
- *
- * <p><b>Warning:</b> there is currently no mechanism for avoiding name collision.
- * It is implementer responsibility to keep trace of the whole universe of functions and avoid collision.
- * Checks against name collisions may be added in a future version.</p>
+ * The {@link DefaultFilterFactory#function(String, Expression[])} method delegates to this interface
+ * for creating the function implementation for a given name.
  *
  * @author  Johann Sorel (Geomatys)
  * @version 1.6
  * @since   1.5
  *
- * @see org.opengis.filter.FilterFactory#function(String, Expression...)
+ * @see DefaultFilterFactory#function(String, Expression[])
  */
 public interface FunctionRegister {
     /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
index df9078b625..709d962403 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/SpatialOperationContext.java
@@ -144,7 +144,7 @@ public final class SpatialOperationContext implements Serializable {
      * that {@link ReferencingUtilities#getUnit(CoordinateSystem)} returns {@code null}.
      */
     private static CoordinateReferenceSystem to2D(CoordinateReferenceSystem crs) {
-        if (ReferencingUtilities.getDimension(crs) > BIDIMENSIONAL) {
+        if (CRS.getDimensionOrZero(crs) > BIDIMENSIONAL) {
             crs = CRS.getComponentAt(crs, 0, BIDIMENSIONAL);
         }
         return crs;
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java
index d568096b0c..3b1bc00fb5 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java
@@ -47,7 +47,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.GeneralDirectPosition;
 import org.apache.sis.geometry.GeneralEnvelope;
@@ -219,7 +219,7 @@ final class Wrapper extends GeometryWrapper {
             if (!Double.isNaN(z)) {
                 return new GeneralDirectPosition(c.x, c.y, z);
             }
-        } else if (ReferencingUtilities.getDimension(crs) != Factory.BIDIMENSIONAL) {
+        } else if (CRS.getDimensionOrZero(crs) != Factory.BIDIMENSIONAL) {
             final var point = new GeneralDirectPosition(crs);
             point.setCoordinate(0, c.x);
             point.setCoordinate(1, c.y);
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandAggregateLayout.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandAggregateLayout.java
index c7e6cc30b7..dd7e51c684 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandAggregateLayout.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandAggregateLayout.java
@@ -110,7 +110,7 @@ final class BandAggregateLayout {
     /**
      * Whether to allow the sharing of data buffers (instead of copying) if possible.
      * This flag depends on the {@link ImageProcessor} configuration. Its purpose is
-     * to express <em>user intend</em>, not whether sharing is effectively enabled.
+     * to express <em>user's intent</em>, not whether sharing is effectively enabled.
      *
      * <h4>Design note</h4>
      * This flag is not the result of the modification done in {@code BandAggregateLayout} constructor
diff --git a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java
index a0a1c1e71c..89ea01640d 100644
--- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java
+++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java
@@ -428,7 +428,7 @@ public class Canvas extends Observable implements Localized {
      * Subclasses may override for a little bit more efficiency.
      */
     int getDisplayDimensions() {
-        return ReferencingUtilities.getDimension(getDisplayCRS());
+        return CRS.getDimensionOrZero(getDisplayCRS());
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/WraparoundAdjustment.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/WraparoundAdjustment.java
index 25e84b73fc..5d2574acc4 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/WraparoundAdjustment.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/WraparoundAdjustment.java
@@ -29,7 +29,6 @@ import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.metadata.internal.shared.ReferencingServices;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
 import org.apache.sis.referencing.internal.shared.WraparoundAxesFinder;
 import org.apache.sis.util.logging.Logging;
 
@@ -243,9 +242,8 @@ public class WraparoundAdjustment {
                 if (crs != null && resultCRS != null) {
                     inputToResult = findOperation(crs, resultCRS).getMathTransform();
                 } else {
-                    inputToResult = MathTransforms.identity(
-                            (crs != null) ? ReferencingUtilities.getDimension(crs)
-                                          : domainOfValidity.getDimension());
+                    final int dim = CRS.getDimensionOrZero(crs);
+                    inputToResult = MathTransforms.identity(dim != 0 ? dim : domainOfValidity.getDimension());
                 }
             }
             /*
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/IntegerList.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/IntegerList.java
index f14a2ae2e6..70f3cc223b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/IntegerList.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/parameter/IntegerList.java
@@ -49,7 +49,7 @@ final class IntegerList {
      * @param array the integer values as a Java array.
      */
     public IntegerList(final Object array) {
-        final StringJoiner builder = new StringJoiner(" ");
+        final var builder = new StringJoiner(" ");
         final int length = Array.getLength(array);
         for (int i=0; i<length; i++) {
             builder.add(String.valueOf(Array.get(array, i)));
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
index 7fe88aff7f..8fb46bdba5 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
@@ -158,6 +158,11 @@ public final class CRS {
      */
     static final Logger LOGGER = Logger.getLogger(Modules.REFERENCING);
 
+    /**
+     * The {@value} value, for identifying code that assume two-dimensional objects.
+     */
+    static final int BIDIMENSIONAL = 2;
+
     /**
      * Do not allow instantiation of this class.
      */
@@ -1046,6 +1051,30 @@ public final class CRS {
         return Optional.empty();
     }
 
+    /**
+     * Returns the number of dimensions of the given <abbr>CRS</abbr>, or 0 if {@code null}.
+     * This method also returns 0 if the <abbr>CRS</abbr> is associated to a null coordinate system.
+     *
+     * <p>This is convenience method for cases such as <abbr>CRS</abbr> separated in optional components.
+     * For example, a class may have an optional {@link VerticalCRS} component allowed to be {@code null}.
+     * When computing the total number of dimensions, it is sometime convenient to handle null components
+     * as a component of dimension zero.</p>
+     *
+     * @param  crs  the <abbr>CRS</abbr> from which to get the number of dimensions, or {@code null}.
+     * @return the number of dimensions, or 0 if the given <abbr>CRS</abbr> or its coordinate system is null.
+     *
+     * @since 1.6
+     */
+    public static int getDimensionOrZero(final CoordinateReferenceSystem crs) {
+        if (crs != null) {
+            final CoordinateSystem cs = crs.getCoordinateSystem();
+            if (cs != null) {   // Should never be null, but let be safe.
+                return cs.getDimension();
+            }
+        }
+        return 0;
+    }
+
     /**
      * Creates a compound coordinate reference system from an ordered list of CRS components.
      * A CRS is inferred from the given components and the domain of validity is set to the
@@ -1114,25 +1143,22 @@ public final class CRS {
      *
      * @since 1.3
      */
-    public static CoordinateReferenceSystem selectDimensions(final CoordinateReferenceSystem crs,
-            final int... dimensions) throws FactoryException
+    public static CoordinateReferenceSystem selectDimensions(final CoordinateReferenceSystem crs, final int... dimensions)
+            throws FactoryException
     {
         final var components = selectComponents(crs, dimensions);
-        if (components.isEmpty()) {
-            return null;
-        }
-        return compound(components.toArray(CoordinateReferenceSystem[]::new));
+        return components.isEmpty() ? null : compound(components.toArray(CoordinateReferenceSystem[]::new));
     }
 
     /**
-     * Gets or creates CRS components for a subset of the dimensions of the given CRS.
+     * Gets or creates CRS components for a subset of the dimensions of the given <abbr>CRS</abbr>.
      * The method performs the same work as {@link #selectDimensions(CoordinateReferenceSystem, int...)}
      * except that it does not build new {@link CompoundCRS} instances when the specified dimensions span
      * more than one {@linkplain DefaultCompoundCRS#getComponents() component}.
      * Instead, the components are returned directly.
      *
-     * <p>While this method does not create new {@code CompoundCRS} instances, it still may create other
-     * kinds of CRS for handling ellipsoidal height as documented in the {@code selectDimensions(…)} method.</p>
+     * <p>While this method does not create new {@code CompoundCRS} instances, it may create other kinds
+     * of CRS for handling ellipsoidal height as documented in the {@code selectDimensions(…)} method.</p>
      *
      * @param  crs         the CRS from which to get a subset of the components, or {@code null} if none.
      * @param  dimensions  the dimensions to retain. The dimensions will be taken in increasing order, ignoring duplicated values.
@@ -1144,28 +1170,24 @@ public final class CRS {
      *
      * @since 1.4
      */
-    public static List<CoordinateReferenceSystem> selectComponents(final CoordinateReferenceSystem crs,
-            final int... dimensions) throws FactoryException
+    public static List<CoordinateReferenceSystem> selectComponents(final CoordinateReferenceSystem crs, final int... dimensions)
+            throws FactoryException
     {
         ArgumentChecks.ensureNonNull("dimensions", dimensions);
-        if (crs == null) {
-            return List.of();
-        }
-        final int dimension = ReferencingUtilities.getDimension(crs);
+        final int dimension = getDimensionOrZero(crs);
         long selected = 0;
-        for (final int d : dimensions) {
-            if (d < 0 || d >= dimension) {
-                throw new IndexOutOfBoundsException(Errors.format(Errors.Keys.IndexOutOfBounds_1, d));
+        if (crs != null) {
+            for (final int d : dimensions) {
+                if (Objects.checkIndex(d, dimension) >= Long.SIZE) {
+                    throw new ArithmeticException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, d+1));
+                }
+                selected |= (1L << d);
             }
-            if (d >= Long.SIZE) {
-                throw new ArithmeticException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, d+1));
+            if (selected == 0) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "dimensions"));
             }
-            selected |= (1L << d);
-        }
-        if (selected == 0) {
-            throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "dimensions"));
         }
-        final List<CoordinateReferenceSystem> components = new ArrayList<>(Long.bitCount(selected));
+        final var components = new ArrayList<CoordinateReferenceSystem>(Long.bitCount(selected));
         reduce(0, crs, dimension, selected, components);
         return components;
     }
@@ -1182,7 +1204,8 @@ public final class CRS {
      * @return new bitmask after removal of dimensions of the components added to {@code addTo}.
      */
     private static long reduce(int previous, final CoordinateReferenceSystem crs, int dimension, long selected,
-            final List<CoordinateReferenceSystem> addTo) throws FactoryException
+                               final List<CoordinateReferenceSystem> addTo)
+            throws FactoryException
     {
         final long current = (Numerics.bitmask(dimension) - 1) << previous;
         final long intersect = selected & current;
@@ -1192,7 +1215,7 @@ choice: if (intersect != 0) {
                 selected &= ~current;
             } else if (crs instanceof CompoundCRS) {
                 for (final CoordinateReferenceSystem component : ((CompoundCRS) crs).getComponents()) {
-                    dimension = ReferencingUtilities.getDimension(component);
+                    dimension = getDimensionOrZero(component);
                     selected = reduce(previous, component, dimension, selected, addTo);
                     if ((selected & current) == 0) break;           // Stop if it would be useless to continue.
                     previous += dimension;
@@ -1248,7 +1271,7 @@ choice: if (intersect != 0) {
      * @category information
      */
     public static boolean isHorizontalCRS(final CoordinateReferenceSystem crs) {
-        return horizontalCode(crs) == 2;
+        return horizontalCode(crs) == BIDIMENSIONAL;
     }
 
     /**
@@ -1267,14 +1290,14 @@ choice: if (intersect != 0) {
         if (isGeodetic || crs instanceof ProjectedCRS || (isEngineering = (crs instanceof EngineeringCRS))) {
             final CoordinateSystem cs = crs.getCoordinateSystem();
             final int dim = cs.getDimension();
-            if ((dim & ~1) == 2 && (!isGeodetic || (cs instanceof EllipsoidalCS))) {
+            if ((dim & ~1) == BIDIMENSIONAL && (!isGeodetic || (cs instanceof EllipsoidalCS))) {
                 if (isEngineering) {
                     int n = 0;
                     for (int i=0; i<dim; i++) {
                         if (AxisDirections.isCompass(cs.getAxis(i).getDirection())) n++;
                     }
                     // If we don't have exactly 2 east, north, etc. directions, consider as non-horizontal.
-                    if (n != 2) return 0;
+                    if (n != BIDIMENSIONAL) return 0;
                 }
                 return dim;
             }
@@ -1333,7 +1356,7 @@ choice: if (intersect != 0) {
              * We don't need to check if crs is an instance of SingleCRS since all
              * CRS accepted by horizontalCode(…) are SingleCRS.
              */
-            case 2: {
+            case BIDIMENSIONAL: {
                 return (SingleCRS) crs;
             }
             case 3: {
@@ -1347,7 +1370,7 @@ choice: if (intersect != 0) {
                         return !AxisDirections.isVertical(axis.getDirection());
                     }
                 });
-                if (cs.getDimension() != 2) break;
+                if (cs.getDimension() != BIDIMENSIONAL) break;
                 /*
                  * Most of the time, the CRS to rebuild will be geodetic. In such case we known that the
                  * coordinate system is ellipsoidal because (i.e. the CRS is geographic) because it was
@@ -1578,7 +1601,7 @@ choice: if (intersect != 0) {
     @OptionalCandidate
     public static CoordinateReferenceSystem getComponentAt(CoordinateReferenceSystem crs, int lower, int upper) {
         if (crs == null) return null;     // Skip bounds check.
-        int dimension = ReferencingUtilities.getDimension(crs);
+        int dimension = getDimensionOrZero(crs);
         Objects.checkFromToIndex(lower, upper, dimension);
 check:  while (lower != 0 || upper != dimension) {
             if (crs instanceof CompoundCRS) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
index 73a4a1a107..f5c52cb9f6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
@@ -30,7 +30,6 @@ import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.operation.Conversion;
 import org.apache.sis.referencing.internal.shared.AxisDirections;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.cs.AxisFilter;
 import org.apache.sis.referencing.operation.DefaultConversion;
@@ -141,7 +140,7 @@ final class EllipsoidalHeightSeparator implements AxisFilter {
          */
         if (crs instanceof ProjectedCRS) {
             GeodeticCRS baseCRS = ((ProjectedCRS) crs).getBaseCRS();
-            if (ReferencingUtilities.getDimension(baseCRS) != 2) {
+            if (CRS.getDimensionOrZero(baseCRS) != CRS.BIDIMENSIONAL) {
                 baseCRS = (GeodeticCRS) separate(baseCRS);
             }
             Conversion projection = ((ProjectedCRS) crs).getConversionFromBase();
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java
index 903bf749be..06a0112bc6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/GeodeticCalculator.java
@@ -353,7 +353,7 @@ public class GeodeticCalculator {
     private PositionTransformer geographic(final double φ, final double λ) {
         userToGeodetic.setCoordinate(0, toDegrees(φ));
         userToGeodetic.setCoordinate(1, toDegrees(λ));
-        for (int i=userToGeodetic.getDimension(); --i >= 2;) {
+        for (int i = userToGeodetic.getDimension(); --i >= CRS.BIDIMENSIONAL;) {
             userToGeodetic.setCoordinate(i, 0);                   // Set height to ellipsoid surface.
         }
         return userToGeodetic;
@@ -941,7 +941,7 @@ public class GeodeticCalculator {
          * Creates a builder for the given tolerance at equator in metres.
          */
         PathBuilder(final double εx) {
-            super(ReferencingUtilities.getDimension(userToGeodetic.defaultCRS));
+            super(CRS.getDimensionOrZero(userToGeodetic.defaultCRS));
             φf        = φ2;
             λf        = λ2;
             msinαf    = msinα2;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
index 549fe464af..44709671ac 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
@@ -58,7 +58,7 @@ import org.opengis.coordinate.MismatchedDimensionException;
 /**
  * Coordinate reference system, defined by a {@linkplain AbstractCS coordinate system}
  * and (usually) a {@linkplain org.apache.sis.referencing.datum.AbstractDatum datum}.
- * A coordinate reference system (CRS) consists of an ordered sequence of
+ * A coordinate reference system (<abbr>CRS</abbr>) consists of an ordered sequence of
  * {@linkplain org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis coordinate system axes}
  * that are related to the earth through the datum.
  * Most coordinate reference system do not move relative to the earth, except for
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
index c58440eb67..9201692401 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
@@ -145,7 +145,7 @@ public class DefaultProjectedCRS extends AbstractDerivedCRS implements Projected
      */
     @Workaround(library="JDK", version="1.7")
     private static GeodeticCRS checkDimensions(final GeodeticCRS baseCRS, final CartesianCS derivedCS) {
-        int n = ReferencingUtilities.getDimension(baseCRS);
+        int n = baseCRS.getCoordinateSystem().getDimension();
         if (derivedCS != null) {
             n = Math.max(n, derivedCS.getDimension());
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java
index 40f545525a..e2ca84acd4 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java
@@ -254,7 +254,7 @@ public final class PositionTransformer extends GeneralDirectPosition {
 
     /**
      * Transforms this point to the default CRS and stores the result in the given array, and returns the derivative.
-     * The {@code target} array length should be {@code ReferencingUtilities.getDimension(defaultCRS)}.
+     * The {@code target} array length should be {@code CRS.getDimensionOrZero(defaultCRS)}.
      *
      * @param  target  where to store the transformed coordinates.
      * @return the derivative (Jacobian matrix) at the location of this point.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/GeodeticObjectBuilder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/GeodeticObjectBuilder.java
index e5583b0ce9..40a5a071b9 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/GeodeticObjectBuilder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/GeodeticObjectBuilder.java
@@ -49,6 +49,7 @@ import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.Conversion;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.Builder;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.IdentifiedObjects;
@@ -655,8 +656,8 @@ public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> {
     public CoordinateReferenceSystem replaceComponent(final CoordinateReferenceSystem source,
             final int firstDimension, final CoordinateReferenceSystem replacement) throws FactoryException
     {
-        final int srcDim = ReferencingUtilities.getDimension(source);
-        final int repDim = ReferencingUtilities.getDimension(replacement);
+        final int srcDim = CRS.getDimensionOrZero(source);
+        final int repDim = CRS.getDimensionOrZero(replacement);
         if (firstDimension == 0 && srcDim == repDim) {
             /*
              * conceptually return the replacement. But returning the original instance if applicable
@@ -695,7 +696,7 @@ public class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder> {
                     components[i] = nc;
                     return createCompoundCRS(components);
                 }
-                lower += ReferencingUtilities.getDimension(c);
+                lower += CRS.getDimensionOrZero(c);
             }
         }
         throw new IllegalArgumentException(Resources.forLocale(locale).getString(
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingUtilities.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingUtilities.java
index 0e9e469553..734af0a2a6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingUtilities.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingUtilities.java
@@ -18,7 +18,6 @@ package org.apache.sis.referencing.internal.shared;
 
 import java.util.Map;
 import java.util.HashMap;
-import java.util.OptionalInt;
 import javax.measure.Unit;
 import javax.measure.quantity.Angle;
 import org.opengis.annotation.UML;
@@ -137,32 +136,6 @@ public final class ReferencingUtilities {
         return (crs != null) ? getUnit(crs.getCoordinateSystem()) : null;
     }
 
-    /**
-     * Returns the number of dimensions of the given CRS, or 0 if {@code null}.
-     *
-     * @param  crs  the CRS from which to get the number of dimensions, or {@code null}.
-     * @return the number of dimensions, or 0 if the given CRS or its coordinate system is null.
-     */
-    public static int getDimension(final CoordinateReferenceSystem crs) {
-        return getOptionalDimension(crs).orElse(0);
-    }
-
-    /**
-     * Returns the number of dimensions of the given <abbr>CRS</abbr>.
-     *
-     * @param  crs  the <abbr>CRS</abbr> from which to get the number of dimensions, or {@code null}.
-     * @return the number of dimensions, or empty if the given CRS or its coordinate system is null.
-     */
-    public static OptionalInt getOptionalDimension(final CoordinateReferenceSystem crs) {
-        if (crs != null) {
-            final CoordinateSystem cs = crs.getCoordinateSystem();
-            if (cs != null) {   // Should never be null, but let be safe.
-                return OptionalInt.of(cs.getDimension());
-            }
-        }
-        return OptionalInt.empty();
-    }
-
     /**
      * Returns the GeoAPI interface implemented by the given object, or the implementation class
      * if the interface is unknown. This method can be used when the base type (CRS, CS, Datum…)
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
index bab0c03bf3..632e147f05 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
@@ -21,6 +21,7 @@ import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CompoundCRS;
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.crs.DefaultTemporalCRS;
 import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.geometry.AbstractEnvelope;
@@ -77,7 +78,7 @@ public final class TemporalAccessor {
                 if (accessor != null) {
                     return accessor;
                 }
-                dim += ReferencingUtilities.getDimension(component);
+                dim += CRS.getDimensionOrZero(component);
             }
         }
         return null;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/WraparoundAxesFinder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/WraparoundAxesFinder.java
index a352dd0cfd..57135db41a 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/WraparoundAxesFinder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/WraparoundAxesFinder.java
@@ -20,6 +20,7 @@ import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.operation.MathTransform;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 
 
@@ -54,7 +55,7 @@ public final class WraparoundAxesFinder {
             preferredToSpecified = p.getConversionFromBase().getMathTransform();
         } else {
             // TODO: we should handle the case of CompoundCRS before to fallback on identity.
-            preferredToSpecified = MathTransforms.identity(ReferencingUtilities.getDimension(crs));
+            preferredToSpecified = MathTransforms.identity(CRS.getDimensionOrZero(crs));
         }
         preferredCRS = crs;
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
index 9d9a53dee8..4afc281adb 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
@@ -59,6 +59,7 @@ import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.parameter.Parameterized;
 import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
@@ -66,7 +67,6 @@ import org.apache.sis.referencing.operation.transform.PassThroughTransform;
 import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.referencing.internal.shared.CoordinateOperations;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
 import org.apache.sis.referencing.internal.shared.WKTUtilities;
 import org.apache.sis.referencing.internal.shared.WKTKeywords;
 import org.apache.sis.metadata.internal.shared.ImplementationHelper;
@@ -355,7 +355,7 @@ public class AbstractCoordinateOperation extends AbstractIdentifiedObject implem
         @SuppressWarnings("LocalVariableHidesMemberVariable")
         final MathTransform transform = this.transform;                     // Protect from changes.
         if (transform != null) {
-            final int interpDim = ReferencingUtilities.getDimension(interpolationCRS);
+            final int interpDim = CRS.getDimensionOrZero(interpolationCRS);
 check:      for (int isTarget=0; ; isTarget++) {        // 0 == source check; 1 == target check.
                 final CoordinateReferenceSystem crs;    // Will determine the expected dimensions.
                 int actual;                             // The MathTransform number of dimensions.
@@ -364,7 +364,7 @@ check:      for (int isTarget=0; ; isTarget++) {        // 0 == source check; 1
                     case 1: crs = targetCRS; actual = transform.getTargetDimensions(); break;
                     default: break check;
                 }
-                int expected = ReferencingUtilities.getDimension(crs);
+                int expected = CRS.getDimensionOrZero(crs);
                 if (interpDim != 0) {
                     if (actual == expected || actual < interpDim) {
                         // This check is not strictly necessary as the next check below would catch the error,
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index 2473b66070..af31347efd 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -68,7 +68,6 @@ import org.apache.sis.referencing.internal.DeferredCoordinateOperation;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.referencing.internal.shared.CoordinateOperations;
 import org.apache.sis.referencing.internal.shared.EllipsoidalHeightCombiner;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.system.Semaphores;
@@ -889,7 +888,7 @@ class CoordinateOperationRegistry {
          * (e.g. as in the "geographic 3D to geographic 2D" conversion) because ALLOW_VARIANT mode
          * still requires a matching number of dimensions.
          */
-        assert ReferencingUtilities.getDimension(sourceCRS) != ReferencingUtilities.getDimension(targetCRS)
+        assert CRS.getDimensionOrZero(sourceCRS) != CRS.getDimensionOrZero(targetCRS)
                 || Utilities.deepEquals(sourceCRS, targetCRS, ComparisonMode.ALLOW_VARIANT);
         final Matrix m = CoordinateSystems.swapAndScaleAxes(sourceCRS.getCoordinateSystem(), targetCRS.getCoordinateSystem());
         return (m.isIdentity()) ? null : mtFactory.createAffineTransform(m);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
index 8e3429579a..f3501c4cd6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java
@@ -31,6 +31,7 @@ import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
@@ -39,7 +40,6 @@ import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 import org.apache.sis.referencing.operation.transform.MathTransformProvider;
 import org.apache.sis.referencing.operation.matrix.Matrices;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.resources.Errors;
@@ -229,7 +229,7 @@ public class DefaultConversion extends AbstractSingleOperation implements Conver
                               final MathTransformFactory factory) throws FactoryException
     {
         super(definition);
-        int interpDim = ReferencingUtilities.getDimension(super.getInterpolationCRS().orElse(null));
+        int interpDim = CRS.getDimensionOrZero(super.getInterpolationCRS().orElse(null));
         if (transform == null) {
             /*
              * If the user did not specify explicitly a MathTransform, we will need to create it from the parameters.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
index 05ebe8dfad..79855d7e65 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
@@ -33,7 +33,6 @@ import org.apache.sis.referencing.GeodeticException;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.PassThroughTransform;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
 import org.apache.sis.metadata.internal.shared.ImplementationHelper;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ComparisonMode;
@@ -364,22 +363,22 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
      * of the nested operation from the source/target CRS of the enclosing pass-through operation.
      */
     @Override
+    @SuppressWarnings("LocalVariableHidesMemberVariable")
     final void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
         super.afterUnmarshal(unmarshaller, parent);
         /*
          * State validation. The `missing` string will be used in exception message
          * at the end of this method if a required component is reported missing.
          */
-        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final int[] modifiedCoordinates = this.modifiedCoordinates;
         FactoryException cause = null;
         String missing = "modifiedCoordinate";
         if (modifiedCoordinates.length != 0) {
             missing = "sourceCRS";
-            final CoordinateReferenceSystem sourceCRS = super.getSourceCRS();
+            final CoordinateReferenceSystem sourceCRS = super.sourceCRS;
             if (sourceCRS != null) {
                 missing = "targetCRS";
-                final CoordinateReferenceSystem targetCRS = super.getTargetCRS();
+                final CoordinateReferenceSystem targetCRS = super.targetCRS;
                 if (targetCRS != null) {
                     missing = "coordOperation";
                     if (operation != null) {
@@ -401,8 +400,8 @@ public class DefaultPassThroughOperation extends AbstractCoordinateOperation imp
                             }
                         }
                         if (subTransform != null) {
-                            transform = MathTransforms.passThrough(modifiedCoordinates, subTransform,
-                                                        ReferencingUtilities.getDimension(sourceCRS));
+                            final int resultDim = sourceCRS.getCoordinateSystem().getDimension();
+                            transform = MathTransforms.passThrough(modifiedCoordinates, subTransform, resultDim);
                             return;
                         }
                     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java
index 5d776ca4a3..86e777f0c0 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/MathTransforms.java
@@ -403,7 +403,7 @@ public final class MathTransforms {
      */
     public static MathTransform passThrough(final int[] modifiedCoordinates, final MathTransform subTransform, final int resultDim) {
         ArgumentChecks.ensureNonNull("modifiedCoordinates", modifiedCoordinates);
-        final BitSet bitset = new BitSet();
+        final var bitset = new BitSet();
         int previous = -1;
         for (int i=0; i < modifiedCoordinates.length; i++) {
             final int dim = modifiedCoordinates[i];
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/CRSParserTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/CRSParserTest.java
index 5da7217039..c73cf2bd1f 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/CRSParserTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/CRSParserTest.java
@@ -50,7 +50,7 @@ public final class CRSParserTest extends WKTParserTest {
      * This is done for avoiding race conditions logging the same message many times.
      */
     static {
-        // Will do nothing, the intend is only to force class initialization.
+        // Will do nothing, the intent is only to force class initialization.
         TestCase.out.flush();
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConversionTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConversionTest.java
index afec45c053..eac21492dd 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConversionTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConversionTest.java
@@ -29,9 +29,9 @@ import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.Conversion;
 import org.opengis.referencing.operation.OperationMethod;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.internal.shared.CoordinateOperations;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
 import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
 import org.apache.sis.referencing.crs.DefaultGeographicCRS;
 import org.apache.sis.referencing.operation.matrix.Matrix3;
@@ -142,7 +142,7 @@ public final class DefaultConversionTest extends TestCase {
          * from the parameters. But we don't do the normal steps here because this class is a unit test:
          * we want to test DefaultConversion in isolation of MathTransformFactory.
          */
-        final int interpDim = ReferencingUtilities.getDimension(interpolationCRS);
+        final int interpDim = CRS.getDimensionOrZero(interpolationCRS);
         final int sourceDim = sourceCRS.getCoordinateSystem().getDimension();
         final int targetDim = targetCRS.getCoordinateSystem().getDimension();
         final OperationMethod method = DefaultOperationMethodTest.create(
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
index 4604c24a3a..6c72ab4a9d 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
@@ -283,7 +283,7 @@ public final class CRSBuilder extends ReferencingFactoryContainer {
         if (value != null) {
             if (value.getClass().isArray()) {
                 final int length = Array.getLength(value);
-                final StringJoiner buffer = new StringJoiner(", ");
+                final var buffer = new StringJoiner(", ");
                 for (int i=0; i<length; i++) {
                     buffer.add(String.valueOf(Array.get(value, i)));
                 }
@@ -574,7 +574,7 @@ public final class CRSBuilder extends ReferencingFactoryContainer {
          * Emits a warning for unprocessed GeoTIFF tags. A single warning is emitted for all ignored tags.
          */
         if (!geoKeys.isEmpty()) {
-            final StringJoiner joiner = new StringJoiner(", ");
+            final var joiner = new StringJoiner(", ");
             final Short[] keys = geoKeys.keySet().toArray(Short[]::new);
             Arrays.sort(keys);
             for (final short key : keys) {
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
index 9daaeb1969..d4c8376956 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
@@ -339,7 +339,7 @@ public final class XMLMetadata implements Filter {
                 }
             }
         }
-        final StringJoiner buffer = new StringJoiner("");
+        final var buffer = new StringJoiner("");
         while (reader.hasNext()) {
             final XMLEvent event = reader.nextEvent();
             if (event.isStartElement()) {
@@ -470,7 +470,7 @@ public final class XMLMetadata implements Filter {
                 final Attribute a = start.getAttributeByName(name);
                 if (a != null) attribute = a.getValue();
             }
-            final StringJoiner buffer = new StringJoiner("");
+            final var buffer = new StringJoiner("");
             while (reader.hasNext()) {
                 final XMLEvent event = reader.nextEvent();
                 if (event.isEndElement()) {
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
index 355f2077ab..732e1b21a7 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java
@@ -68,7 +68,6 @@ import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.referencing.operation.provider.PseudoPlateCarree;
 import org.apache.sis.referencing.internal.shared.AxisDirections;
 import org.apache.sis.referencing.internal.shared.AffineTransform2D;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.netcdf.internal.Resources;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -791,7 +790,8 @@ final class GridMapping {
         MathTransform implicitG2C = gridToCRS(variable);
         CoordinateReferenceSystem implicitCRS = crs;
         if (implicitG2C != null) {
-            final int tgtDim = ReferencingUtilities.getOptionalDimension(implicitCRS).orElse(srcDim);
+            int tgtDim = CRS.getDimensionOrZero(implicitCRS);
+            if (tgtDim == 0) tgtDim = srcDim;
             MathTransform step1 = changeOfDimension(srcDim, implicitG2C.getSourceDimensions());
             MathTransform step3 = changeOfDimension(implicitG2C.getTargetDimensions(), tgtDim);
             implicitG2C = MathTransforms.concatenate(step1, implicitG2C, step3);
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/NamedElement.java b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/NamedElement.java
index 76cc09263e..baa3a5bb52 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/NamedElement.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/NamedElement.java
@@ -70,7 +70,7 @@ public abstract class NamedElement {
      * @return a name for an object composed of the given components.
      */
     protected static String listNames(final NamedElement[] components, final int count, final String delimiter) {
-        final StringJoiner joiner = new StringJoiner(delimiter);
+        final var joiner = new StringJoiner(delimiter);
         for (int i=0; i<count; i++) {
             joiner.add(components[i].getName());
         }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/AsciiGridStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/AsciiGridStore.java
index 543d0b5fd7..fda499dda2 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/AsciiGridStore.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/AsciiGridStore.java
@@ -18,7 +18,6 @@ package org.apache.sis.storage.esri;
 
 import java.util.Map;
 import java.util.List;
-import java.util.StringJoiner;
 import java.io.IOException;
 import java.nio.file.StandardOpenOption;
 import java.awt.image.RenderedImage;
@@ -319,9 +318,7 @@ cellsize:       if (value != null) {
              * We list all properties in a single message.
              */
             if (!header.isEmpty()) {
-                final StringJoiner joiner = new StringJoiner(", ");
-                header.keySet().forEach(joiner::add);
-                listeners.warning(messageForProperty(Errors.Keys.UnexpectedProperty_2, joiner.toString()));
+                listeners.warning(messageForProperty(Errors.Keys.UnexpectedProperty_2, String.join(", ", header.keySet())));
             }
         } catch (DataStoreException e) {
             closeOnError(e);
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java
index e4dce03219..e3d9f87d1d 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java
@@ -1604,7 +1604,7 @@ search:     for (;;) {
      */
     @Override
     public String toString() {
-        final StringJoiner buffer = new StringJoiner(", ", "[", "]");
+        final var buffer = new StringJoiner(", ", "[", "]");
         final int length = size();
         for (int i=0; i<length; i++) {
             buffer.add(stringValue(i));
diff --git a/optional/src/org.apache.sis.gui/bundle/bin/sisfx b/optional/src/org.apache.sis.gui/bundle/bin/sisfx
index d4ab463fd5..4c3054a447 100755
--- a/optional/src/org.apache.sis.gui/bundle/bin/sisfx
+++ b/optional/src/org.apache.sis.gui/bundle/bin/sisfx
@@ -89,6 +89,7 @@ shopt -u nullglob
 # Execute SIS with any optional JAR that the user may put in the `lib` directory.
 #
 java -splash:"$BASE_DIR/lib/logo.jpg" \
+     --enable-native-access javafx.graphics \
      --module-path "$PATH_TO_FX:$BASE_DIR/lib:$BASE_DIR/lib/app/org.apache.sis.gui.jar" \
      -Djava.util.logging.config.class="org.apache.sis.util.logging.Initializer" \
      -Djava.util.logging.config.file="$BASE_DIR/conf/logging.properties" \
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/RecentFiles.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/RecentFiles.java
index dda531c721..e1a4894eb0 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/RecentFiles.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/RecentFiles.java
@@ -155,7 +155,7 @@ final class RecentFiles implements EventHandler<ActionEvent> {
         } while (++i < MAX_COUNT-1);
         System.arraycopy(allFiles, 0, allFiles, 1, i);
         allFiles[0] = file;
-        final StringJoiner s = new StringJoiner(System.lineSeparator());
+        final var s = new StringJoiner(System.lineSeparator());
         for (final File f : allFiles) {
             if (f == null) break;
             s.add(f.getPath());
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridViewSkin.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridViewSkin.java
index a4fceba2a4..e2e6047f8c 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridViewSkin.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridViewSkin.java
@@ -475,7 +475,7 @@ final class GridViewSkin extends SkinBase<GridView> implements EventHandler<Mous
 
     /**
      * Resizes the given array of cells. If the array become longer, new labels are created.
-     * This is an helper method for {@link #layoutChildren(double, double, double, double)}.
+     * This is a helper method for {@link #layoutChildren(double, double, double, double)}.
      *
      * @param  cells   the array to resize.
      * @param  count   the desired number of elements.
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/LogViewer.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/LogViewer.java
index 5fdb1e8feb..9e4612ab17 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/LogViewer.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/LogViewer.java
@@ -21,7 +21,6 @@ import java.util.Date;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.ArrayList;
-import java.util.StringJoiner;
 import java.util.function.Predicate;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
@@ -603,11 +602,7 @@ public class LogViewer extends Widget {
                 path.add(item.getValue());
                 item = item.getParent();
             }
-            final var joiner = new StringJoiner(".");
-            for (int i = path.size(); --i >= 0;) {
-                joiner.add(path.get(i));
-            }
-            return joiner.toString();
+            return String.join(".", path.reversed());
         });
         dialog.setResizable(true);
         final DialogPane pane = dialog.getDialogPane();
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/IdentificationInfo.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/IdentificationInfo.java
index c92f390c2c..263afc1df2 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/IdentificationInfo.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/IdentificationInfo.java
@@ -290,7 +290,7 @@ final class IdentificationInfo extends Section<Identification> {
          * is formatted as "codespace:code" or only "code" if there is no codespace.
          */
         if (citation != null) {
-            final StringJoiner buffer = new StringJoiner(", ");
+            final var buffer = new StringJoiner(", ");
             for (final Identifier id : citation.getIdentifiers()) {
                 buffer.add(IdentifiedObjects.toString(id));
             }
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java
index 49dec24f81..de8c591e6c 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java
@@ -353,7 +353,7 @@ public class MetadataSummary extends Widget {
      * Returns all code lists in a comma-separated list.
      */
     final String string(final Collection<? extends ControlledVocabulary> codes) {
-        final StringJoiner buffer = new StringJoiner(", ");
+        final var buffer = new StringJoiner(", ");
         for (final ControlledVocabulary c : codes) {
             final String text = string(Types.getCodeTitle(c));
             if (text != null) buffer.add(text);
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java
index 5d01defd3e..9b49ea947b 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/CRSChooser.java
@@ -56,8 +56,8 @@ import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.Conversion;
 import org.opengis.referencing.operation.OperationMethod;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
 import org.apache.sis.gui.internal.BackgroundThreads;
 import org.apache.sis.gui.internal.ExceptionReporter;
 import org.apache.sis.gui.internal.IdentityValueFactory;
@@ -387,7 +387,7 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem> {
             expected = 0;
         }
         String text = Vocabulary.forLocale(locale).getString(key);
-        final int     dimension = ReferencingUtilities.getDimension(crs);
+        final int     dimension = CRS.getDimensionOrZero(crs);
         final boolean addDimension = (dimension != expected && expected != 0);
         final boolean isProjection = (crs instanceof DerivedCRS);
         if (addDimension | isProjection) {
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/MenuSync.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/MenuSync.java
index 5bcb63d1f6..409812c2c7 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/MenuSync.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/MenuSync.java
@@ -33,12 +33,12 @@ import javafx.scene.control.ToggleGroup;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.crs.DerivedCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
-import org.apache.sis.gui.internal.GUIUtilities;
-import org.apache.sis.gui.internal.Resources;
 import org.apache.sis.referencing.gazetteer.GazetteerFactory;
 import org.apache.sis.referencing.gazetteer.GazetteerException;
+import org.apache.sis.gui.internal.GUIUtilities;
+import org.apache.sis.gui.internal.Resources;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Utilities;
 
@@ -166,7 +166,7 @@ final class MenuSync extends SimpleObjectProperty<ReferenceSystem> implements Ev
     private void initialize() {
         for (final ReferenceSystem system : recentSystems) {
             if (system instanceof CoordinateReferenceSystem) {
-                if (ReferencingUtilities.getDimension((CoordinateReferenceSystem) system) == BIDIMENSIONAL) {
+                if (CRS.getDimensionOrZero((CoordinateReferenceSystem) system) == BIDIMENSIONAL) {
                     set(system);
                     break;
                 }