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 <[email protected]>
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();
+            }
         }
     }
 }

Reply via email to