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(); + } } } }
