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 3725429e48acd348ede5fd55e8caf6d14d37e049
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Tue Jul 18 16:59:09 2023 +0200

    Abandon NilObject support of primitive wrappers, except for the types 
supporting NaN values.
    This feature depended on primitive wrapper constructors that are deprecated 
for removal.
    
    https://issues.apache.org/jira/browse/SIS-586
---
 ...peProperties.java => FinalClassExtensions.java} |  72 ++++----
 .../apache/sis/internal/jaxb/gco/GO_Boolean.java   |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_Integer.java   |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_Integer64.java |   2 +-
 .../apache/sis/internal/jaxb/gco/PropertyType.java |   7 +-
 .../DefaultFeatureCatalogueDescription.java        |   5 +-
 .../iso/content/DefaultImageDescription.java       |   5 +-
 .../iso/extent/AbstractGeographicExtent.java       |   6 +-
 .../iso/extent/DefaultGeographicBoundingBox.java   |  21 +--
 .../main/java/org/apache/sis/xml/NilReason.java    | 196 +++++++++++++++------
 .../apache/sis/xml/NilReasonMarshallingTest.java   |  67 +------
 .../java/org/apache/sis/xml/NilReasonTest.java     | 109 ++----------
 12 files changed, 214 insertions(+), 280 deletions(-)

diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/PrimitiveTypeProperties.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/FinalClassExtensions.java
similarity index 53%
rename from 
core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/PrimitiveTypeProperties.java
rename to 
core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/FinalClassExtensions.java
index a27bf1f424..fefa0b5305 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/PrimitiveTypeProperties.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/FinalClassExtensions.java
@@ -19,32 +19,41 @@ package org.apache.sis.internal.jaxb;
 import java.util.Map;
 import java.util.IdentityHashMap;
 import java.lang.reflect.Modifier;
+import org.apache.sis.util.Numbers;
 import org.apache.sis.xml.NilReason;
 
 
 /**
- * A workaround for attaching properties ({@code nilreason}, {@code href}, 
<i>etc.</i>) to primitive type wrappers.
- * The normal approach in SIS is to implement the {@link 
org.apache.sis.xml.NilObject} interface. However, we cannot
- * do so when the object is a final Java class like {@link Boolean}, {@link 
Integer}, {@link Double} or {@link String}.
+ * A workaround for attaching properties ({@code nilreason}, {@code href}, 
<i>etc.</i>) to final classes.
+ * The normal approach in SIS is to implement the {@link 
org.apache.sis.xml.NilObject} interface.
+ * However, we cannot do so when the object is a final Java class such as 
{@link String}.
  * This class provides a workaround using specific instances of those wrappers.
  *
+ * <h2>Historical note</h2>
+ * In previous Apache SIS releases (before 1.4), this class was used mostly 
for primitive type wrappers such as
+ * {@link Boolean}, {@link Byte}, {@link Short}, {@link Integer}, {@link 
Long}, {@link Float} and {@link Double}.
+ * Support for those types has been removed because it depends on {@code 
java.lang} constructors now marked as
+ * deprecated for removal.
+ *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.4
+ * @version 1.4
  *
  * @see NilReason#createNilObject(Class)
+ * @see <a href="https://issues.apache.org/jira/browse/SIS-586";>SIS-586</a>
  *
  * @since 0.4
  */
-public final class PrimitiveTypeProperties {
+public final class FinalClassExtensions {
     /**
-     * The map where to store specific instances. Keys are instances of the 
primitive wrappers considered as nil.
-     * Values are the {@code NilReason} why the primitive is missing, or any 
other property we may want to attach.
+     * The map where to store specific instances. Keys are instances 
considered as nil.
+     * Values are the {@code NilReason} why the instance is missing,
+     * or any other property we may want to attach.
      *
      * <h4>Identity comparisons</h4>
      * We really need an identity hash map; using the {@code 
Object.equals(Object)} method is not allowed here.
-     * This is because "nil values" are real values. For example if the type 
is {@link Integer}, then the nil value
-     * is an {@code Integer} instance having the value 0. We don't want to 
consider every 0 integer value as nil,
-     * but only the specific {@code Integer} instance used as sentinel value 
for nil.
+     * This is because "nil values" are real values. For example if the type 
is {@link String}, then the nil value
+     * is a {@code String} instance having the value "". We don't want to 
consider every empty string value as nil,
+     * but only the specific {@code String} instance used as sentinel value 
for nil.
      *
      * <h4>Weak references</h4>
      * We cannot use weak value references, because we don't want the {@link 
NilReason} (the map value) to be lost
@@ -52,7 +61,7 @@ public final class PrimitiveTypeProperties {
      * does not provides any map implementation which is both an {@code 
IdentityHashMap} and a {@code WeakHashMap}.
      *
      * For now we do not use weak references. This means that if a user 
creates a custom {@code NilReason} by a call
-     * to {@link NilReason#valueOf(String)}, and if he uses that nil reason 
for a primitive type, then that custom
+     * to {@link NilReason#valueOf(String)} and if (s)he uses that nil reason 
for a primitive type, then that custom
      * {@code NilReason} instance and its sentinel values will never be 
garbage-collected.
      * We presume that such cases will be rare enough for not being an issue 
in practice.
      *
@@ -64,50 +73,51 @@ public final class PrimitiveTypeProperties {
     /**
      * Do not allow instantiation of this class.
      */
-    private PrimitiveTypeProperties() {
+    private FinalClassExtensions() {
     }
 
     /**
-     * Returns {@code true} if the given type is a valid key. This {@code 
PrimitiveTypeProperties}
+     * Returns {@code true} if the given type is a valid key. This {@code 
FinalClassExtensions}
      * class is a workaround to be used only for final classes on which we 
have no control.
      * Non-final classes shall implement {@link org.apache.sis.xml.NilObject} 
instead.
+     * Primitive wrappers are not allowed neither because they will become 
value objects.
      */
-    private static boolean isValidKey(final Object primitive) {
-        return Modifier.isFinal(primitive.getClass().getModifiers());
+    private static boolean isValidKey(final Class<?> type) {
+        return Modifier.isFinal(type.getModifiers()) && 
Numbers.getEnumConstant(type) == Numbers.OTHER;
     }
 
     /**
-     * Associates the given property to the given primitive.
-     * The {@code primitive} argument shall be a specific instance created by 
the {@code new} keyword, not
-     * a shared instance link {@link Boolean#FALSE} or the values returned by 
{@link Integer#valueOf(int)}.
+     * Associates the given property to the given instance.
+     * The {@code instance} argument shall be a specific instance created by 
the {@code new} keyword,
+     * not a shared instance like static constants or instances created by 
static factory methods.
      *
-     * @param  primitive  the {@link Boolean}, {@link Integer}, {@link Double} 
or {@link String} specific instance.
-     * @param  property   the {@link NilReason} or other property to associate 
to the given instance.
+     * @param  instance  the {@link String} (or other final class) specific 
instance.
+     * @param  property  the {@link NilReason} or other property to associate 
to the given instance.
      */
-    public static void associate(final Object primitive, final Object 
property) {
-        assert isValidKey(primitive) : primitive;
+    public static void associate(final Object instance, final Object property) 
{
+        assert isValidKey(instance.getClass()) : instance;
         synchronized (SENTINEL_VALUES) {
-            final Object old = SENTINEL_VALUES.put(primitive, property);
+            final Object old = SENTINEL_VALUES.put(instance, property);
             if (old != null) {                          // Should never happen 
- this is rather debugging check.
-                SENTINEL_VALUES.put(primitive, old);
-                throw new AssertionError(primitive);
+                SENTINEL_VALUES.put(instance, old);
+                throw new AssertionError(instance);
             }
         }
     }
 
     /**
-     * Returns the property of the given primitive type, or {@code null} if 
none.
+     * Returns the property of the given final type, or {@code null} if none.
      *
-     * @param  primitive  the {@link Boolean}, {@link Integer}, {@link Double} 
or {@link String} specific instance.
+     * @param  instance  the {@link String} (or other final class) specific 
instance.
      * @return the property associated to the given instance, or {@code null} 
if none.
      */
-    public static Object property(final Object primitive) {
+    public static Object property(final Object instance) {
         /*
-         * No 'assert isValidKey(primitive)' because this method is sometimes 
invoked
-         * only after a brief inspection (e.g. 'NilReason.mayBeNil(Object)' 
method).
+         * No `assert isValidKey(instance)` because this method is sometimes 
invoked
+         * only after a brief inspection (e.g. `NilReason.mayBeNil(Object)` 
method).
          */
         synchronized (SENTINEL_VALUES) {
-            return SENTINEL_VALUES.get(primitive);
+            return SENTINEL_VALUES.get(instance);
         }
     }
 }
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Boolean.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Boolean.java
index 44a66f1dde..23bab631a6 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Boolean.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Boolean.java
@@ -47,7 +47,7 @@ public final class GO_Boolean extends 
PropertyType<GO_Boolean, Boolean> {
      */
     @SuppressWarnings("NumberEquality")
     private GO_Boolean(final Boolean value) {
-        super(value, !value && value != Boolean.FALSE);
+        super(value, false);
     }
 
     /**
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Integer.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Integer.java
index 118efa01fa..6d80c8628c 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Integer.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Integer.java
@@ -50,7 +50,7 @@ public class GO_Integer extends PropertyType<GO_Integer, 
Integer> {
      * @param  value  the value.
      */
     private GO_Integer(final Integer value) {
-        super(value, value == 0);
+        super(value, false);
     }
 
     /**
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Integer64.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Integer64.java
index 95132ef9b3..9b8b73c70a 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Integer64.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_Integer64.java
@@ -43,7 +43,7 @@ public final class GO_Integer64 extends 
PropertyType<GO_Integer64, Long> {
      * @param  value  the value.
      */
     private GO_Integer64(final Long value) {
-        super(value, value == 0L);
+        super(value, false);
     }
 
     /**
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/PropertyType.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/PropertyType.java
index 03389f4272..fec1d09474 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/PropertyType.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/PropertyType.java
@@ -33,7 +33,6 @@ import org.apache.sis.xml.ReferenceResolver;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.jaxb.FilterByVersion;
-import org.apache.sis.internal.jaxb.PrimitiveTypeProperties;
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.util.resources.Errors;
 
@@ -146,7 +145,7 @@ public abstract class PropertyType<ValueType extends 
PropertyType<ValueType,Boun
     }
 
     /**
-     * Builds a {@code PropertyType} wrapper for the given primitive type 
wrapper.
+     * Builds a {@code PropertyType} wrapper for an instance of a final class.
      * This constructor checks for nil reasons only if {@code check} is {@code 
true}.
      *
      * @param  value     the primitive type wrapper.
@@ -155,8 +154,8 @@ public abstract class PropertyType<ValueType extends 
PropertyType<ValueType,Boun
     protected PropertyType(final BoundType value, final boolean mayBeNil) {
         metadata = value;
         if (mayBeNil) {
-            final Object property = PrimitiveTypeProperties.property(value);
-            if (property instanceof NilReason) {
+            final NilReason property = NilReason.forObject(value);
+            if (property != null) {
                 reference = property.toString();
                 metadata  = null;
             }
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
index 6523501792..331a127a5e 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
@@ -83,10 +83,7 @@ public class DefaultFeatureCatalogueDescription extends 
AbstractContentInformati
 
     /**
      * Whether or not the cited feature catalogue complies with ISO 19110.
-     *
-     * <p>Implementation note: we need to store the reference to the {@code 
Boolean} instance instead
-     * than using bitmask because {@link 
org.apache.sis.internal.jaxb.PrimitiveTypeProperties} may
-     * associate some properties to that particular instance.</p>
+     * May be {@code null} is unspecified.
      */
     private Boolean compliant;
 
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultImageDescription.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultImageDescription.java
index 8fb228cbb9..37132a80eb 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultImageDescription.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultImageDescription.java
@@ -111,10 +111,7 @@ public class DefaultImageDescription extends 
DefaultCoverageDescription implemen
 
     /**
      * Indication of whether or not triangulation has been performed upon the 
image.
-     *
-     * <p>Implementation note: we need to store the reference to the {@code 
Boolean} instance instead
-     * than using bitmask because {@link 
org.apache.sis.internal.jaxb.PrimitiveTypeProperties} may
-     * associate some properties to that particular instance.</p>
+     * May be {@code null} is unspecified.
      */
     private Boolean triangulationIndicator;
 
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/AbstractGeographicExtent.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/AbstractGeographicExtent.java
index f0406057e7..d5ae75b890 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/AbstractGeographicExtent.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/AbstractGeographicExtent.java
@@ -61,10 +61,7 @@ public class AbstractGeographicExtent extends ISOMetadata 
implements GeographicE
     /**
      * Indication of whether the bounding polygon encompasses an area covered 
by the data
      * (<cite>inclusion</cite>) or an area where data is not present 
(<cite>exclusion</cite>).
-     *
-     * <p>Implementation note: we need to store the reference to the {@code 
Boolean} instance instead
-     * than using bitmask because {@link 
org.apache.sis.internal.jaxb.PrimitiveTypeProperties} may
-     * associate some properties to that particular instance.</p>
+     * May be {@code null} is unspecified.
      */
     private Boolean inclusion;
 
@@ -144,6 +141,7 @@ public class AbstractGeographicExtent extends ISOMetadata 
implements GeographicE
      * (<cite>inclusion</cite>) or an area where data is not present 
(<cite>exclusion</cite>).
      *
      * @return {@code true} for inclusion, or {@code false} for exclusion.
+     *         May be {@code null} is unspecified.
      */
     @Override
     @XmlElement(name = "extentTypeCode")
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
index cbc3871e7d..b82f5e9308 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
@@ -33,9 +33,7 @@ import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.internal.metadata.ReferencingServices;
-import org.apache.sis.internal.jaxb.PrimitiveTypeProperties;
 import org.apache.sis.metadata.InvalidMetadataException;
-import org.apache.sis.xml.NilReason;
 
 import static java.lang.Double.doubleToLongBits;
 
@@ -248,27 +246,14 @@ public class DefaultGeographicBoundingBox extends 
AbstractGeographicExtent imple
     }
 
     /**
-     * Makes sure that the given inclusion is non-nil, then returns its value.
+     * Makes sure that the given inclusion is non-null, then returns its value.
      * If the given inclusion is {@code null}, then the default value is 
{@code true}.
      *
-     * @param  value  the {@link 
org.opengis.metadata.extent.GeographicBoundingBox#getInclusion()} value.
+     * @param  value  the {@link GeographicBoundingBox#getInclusion()} value.
      * @return the given value as a primitive type.
-     * @throws InvalidMetadataException if the given value is nil.
      */
-    @SuppressWarnings("NumberEquality")
     static boolean getInclusion(final Boolean value) throws 
InvalidMetadataException {
-        if (value == null) {
-            return true;
-        }
-        final boolean p = value;
-        /*
-         * (value == Boolean.FALSE) is an optimization for a common case 
avoiding PrimitiveTypeProperties check.
-         * DO NOT REPLACE BY `equals` OR `booleanValue()` — the exact 
reference value matter.
-         */
-        if (p || (value == Boolean.FALSE) || 
!(PrimitiveTypeProperties.property(value) instanceof NilReason)) {
-            return p;
-        }
-        throw new 
InvalidMetadataException(Errors.format(Errors.Keys.MissingValueForProperty_1, 
"inclusion"));
+        return (value == null) || value;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/NilReason.java 
b/core/sis-metadata/src/main/java/org/apache/sis/xml/NilReason.java
index 72cde921b4..ccfb945061 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/xml/NilReason.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/NilReason.java
@@ -30,7 +30,8 @@ import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.collection.WeakHashSet;
 import org.apache.sis.util.collection.WeakValueHashMap;
-import org.apache.sis.internal.jaxb.PrimitiveTypeProperties;
+import org.apache.sis.internal.jaxb.FinalClassExtensions;
+import org.apache.sis.math.MathFunctions;
 
 
 /**
@@ -139,9 +140,38 @@ public final class NilReason implements Serializable {
 
     /**
      * The pool of other nil reasons created up to date.
+     *
+     * @see #unique()
      */
     private static final WeakHashSet<NilReason> POOL = new 
WeakHashSet<>(NilReason.class);
 
+    /**
+     * The last {@linkplain #ordinal} value used in a {@code NilReason} 
instance.
+     * This is used for choosing the next value to assign to {@link #ordinal}.
+     * This value is usually equal to {@code PREDEFINED.length + POOL.size()}
+     * but may be different if some pool entries have been garbage collected.
+     * Accesses to this field shall be synchronized on {@link #POOL}.
+     */
+    private static int lastOrdinal;
+
+    /**
+     * A numerical identifier of this {@code NilReason}, or 0 if not yet 
assigned.
+     * This field is used for choosing a NaN value for floating point numbers.
+     * We want the same NaN to be used for {@code float} and {@code double} 
types.
+     * This value shall not be modified after {@code NilReason} publication.
+     *
+     * @see #ordinal()
+     * @see #forNumber(Number)
+     */
+    private transient int ordinal;
+    static {
+        int i = 0;
+        while (i < PREDEFINED.length) {
+            PREDEFINED[i].ordinal = ++i;
+        }
+        lastOrdinal = i;
+    }
+
     /**
      * Either the XML value as a {@code String} (including the explanation if 
the prefix
      * is "{@code other}", or a {@link URI}. Those types are serializable.
@@ -254,10 +284,33 @@ public final class NilReason implements Serializable {
                 if (result.equals(reason)) {
                     result = reason;                            // Use the 
existing instance.
                 }
-                return POOL.unique(new NilReason(result));
+                return new NilReason(result).unique();
+            }
+        }
+        return new NilReason(new URI(reason)).unique();
+    }
+
+    /**
+     * Returns a unique instance of this nil reason.
+     * If another instance has been created previously for the same reason,
+     * that other instance is returned. Otherwise {@code this} is returned.
+     *
+     * @return a unique instance of this nil reason.
+     */
+    private NilReason unique() {
+        synchronized (POOL) {
+            final NilReason instance = POOL.unique(this);
+            if (instance == this) {
+                if ((instance.ordinal = ++lastOrdinal) < 0) {
+                    /*
+                     * Overflow, but still accept to create this nil reason.
+                     * Maybe it will not be used for floating point numbers.
+                     */
+                    instance.ordinal = lastOrdinal = Integer.MIN_VALUE;     // 
Really MIN, not MAX.
+                }
             }
+            return instance;
         }
-        return POOL.unique(new NilReason(new URI(reason)));
     }
 
     /**
@@ -274,7 +327,21 @@ public final class NilReason implements Serializable {
                 }
             }
         }
-        return POOL.unique(this);
+        return unique();
+    }
+
+    /**
+     * Returns an ordinal value for this nil reason.
+     * Numbers start at 1.
+     *
+     * @return an ordinal value for this nil reason, numbered from 1.
+     * @throws ArithmeticException if there is too many {@code NilReason} 
instances.
+     *
+     * @see #forNumber(Number)
+     */
+    final int ordinal() {
+        if (ordinal > 0) return ordinal;
+        throw new 
ArithmeticException(Errors.format(Errors.Keys.IntegerOverflow_1, Integer.SIZE));
     }
 
     /**
@@ -360,11 +427,17 @@ public final class NilReason implements Serializable {
      *             {@code 0} or {@code false}, in this preference order, 
depending on the method return type.</li>
      *       </ul>
      *   </li>
-     *   <li>One of {@code Boolean}, {@link Byte}, {@link Short}, {@code 
Integer}, {@link Long}, {@link Float},
-     *       {@code Double} or {@code String} types: in such case, this method 
returns a specific instance which
-     *       will be recognized as "nil" by the XML marshaller.</li>
+     *   <li>One of {@link Float}, {@link Double} or {@link String} types.
+     *       In such case, this method returns an instance which will be 
recognized as "nil" by the XML marshaller.</li>
      * </ul>
      *
+     * <h4>Historical note</h4>
+     * In previous Apache SIS releases, this method recognized also {@code 
Boolean}, {@link Byte}, {@link Short},
+     * {@code Integer}, {@link Long}, {@link Float} and {@code Double} types 
in the same way as {@code String}.
+     * The support for those types has been removed in Apache SIS 1.4 (except 
for types supporting NaN values)
+     * because it depends on {@code java.lang} constructors now marked as 
deprecated for removal.
+     * See <a href="https://issues.apache.org/jira/browse/SIS-586";>SIS-586</a> 
on JIRA issue tracker.
+     *
      * @param  <T>   the compile-time type of the {@code type} argument.
      * @param  type  the object type as an <strong>interface</strong>
      *         (usually a <a href="http://www.geoapi.org";>GeoAPI</a> one) or 
one of the special types.
@@ -376,7 +449,7 @@ public final class NilReason implements Serializable {
         ArgumentChecks.ensureNonNull("type", type);
         /*
          * Check for existing instance in the cache before to create a new 
object. Returning a unique
-         * instance is mandatory for the types handled by 
`createNilPrimitive(Class)`. Since we have
+         * instance is mandatory for the types handled by 
`createNilInstance(Class)`. Since we have
          * to cache those values anyway, we opportunistically extend the 
caching to other types too.
          *
          * Implementation note: we have two synchronizations here: one lock on 
`this` because of the
@@ -412,11 +485,10 @@ public final class NilReason implements Serializable {
                 }
             } else {
                 /*
-                 * If the requested type is not an interface, this is usually 
an error except for some
-                 * special cases: Boolean, Byte, Short, Integer, Long, Float, 
Double or String.
+                 * If the requested type is not an interface, this is usually 
an error except for some special cases:
+                 * Float, Double and String (Apache SIS 1.3 supported also: 
Boolean, Byte, Short, Integer and Long).
                  */
-                object = createNilPrimitive(type);
-                PrimitiveTypeProperties.associate(object, this);
+                object = createNilInstance(type);
             }
             if (nilObjects.put(type, object) != null) {
                 throw new AssertionError(type);                                
 // Should never happen.
@@ -426,11 +498,9 @@ public final class NilReason implements Serializable {
     }
 
     /**
-     * Returns a new {@code Boolean}, {@link Byte}, {@link Short}, {@code 
Integer}, {@link Long},
-     * {@link Float}, {@code Double} or {@code String} instance to be 
considered as a nil value.
-     * The caller is responsible for registering the value in {@link 
PrimitiveTypeProperties}.
+     * Returns a new {@code Float}, {@code Double} or {@code String} instance 
to be considered as a nil value.
      *
-     * <p><b>Reminder:</b> If more special cases are added, do not forget to 
update the {@link #mayBeNil(Object)}
+     * <p><b>Reminder:</b> If more special cases are added, do not forget to 
update the {@link #forObject(Object)}
      * method and to update the {@link #createNilObject(Class)} and {@link 
#forObject(Object)} javadoc.</p>
      *
      * <h4>Implementation note</h4>
@@ -440,41 +510,21 @@ public final class NilReason implements Serializable {
      * encoding in the XML files, since many files use another encoding than 
UTF-16 anyway.
      *
      * @throws IllegalArgumentException if the given type is not a supported 
type.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-586";>SIS-586</a>
      */
-    @SuppressWarnings({"deprecation", "RedundantStringConstructorCall", 
"BooleanConstructorCall", "UnnecessaryBoxing"})
-    private static Object createNilPrimitive(final Class<?> type) {
-        if (type == String .class) return new String("");         // REALLY 
need a new instance.
-        if (type == Boolean.class) return new Boolean(false);     // REALLY 
need a new instance, not Boolean.FALSE.
-        if (type == Byte   .class) return new Byte((byte) 0);     // REALLY 
need a new instance, not Byte.valueOf(0).
-        if (type == Short  .class) return new Short((byte) 0);    // REALLY 
need a new instance, not Short.valueOf(0).
-        if (type == Integer.class) return new Integer(0);         // REALLY 
need a new instance, not Integer.valueOf(0).
-        if (type == Long   .class) return new Long(0);            // REALLY 
need a new instance, not Long.valueOf(0).
-        if (type == Float  .class) return new Float(Float.NaN);   // REALLY 
need a new instance, not Float.valueOf(…).
-        if (type == Double .class) return new Double(Double.NaN); // REALLY 
need a new instance, not Double.valueOf(…).
-        throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, 
"type", type));
-    }
-
-    /**
-     * Returns {@code true} if the given object may be one of the sentinel 
values
-     * created by {@link #createNilPrimitive(Class)}. This method only checks 
the value.
-     * If this method returns {@code true}, then the caller still needs to 
check the actual instance using the
-     * {@link PrimitiveTypeProperties} class. The purpose of this method is to 
filter the values that cannot
-     * be sentinel values, in order to avoid the synchronization done by 
{@code PrimitiveTypeProperties}.
-     */
-    private static boolean mayBeNil(final Object object) {
-        // `instanceof` checks on instances of final classes are expected to 
be very fast.
-        if (object instanceof String)  return ((String) object).isEmpty();
-        if (object instanceof Boolean) return !((Boolean) object) && (object 
!= Boolean.FALSE);
-        if (object instanceof Number) {
-            /*
-             * Following test may return false positives for Long, Float and 
Double types, but this is okay
-             * since the real check will be done by PrimitiveTypeProperties.  
The purpose of this method is
-             * only to perform a cheap filtering. Note that this method relies 
on the fact that casting NaN
-             * to `int` produces 0.
-             */
-            return ((Number) object).intValue() == 0;
+    @SuppressWarnings({"RedundantStringConstructorCall", "UnnecessaryBoxing"})
+    private Object createNilInstance(final Class<?> type) {
+        if (type == Double .class) return 
Double.valueOf(MathFunctions.toNanFloat(ordinal()));
+        if (type == Float  .class) return Float 
.valueOf(MathFunctions.toNanFloat(ordinal()));
+        final Object object;
+        if (type == String .class) {
+            object = new String("");         // REALLY need a new instance.
+        } else {
+            throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, 
"type", type));
         }
-        return false;
+        FinalClassExtensions.associate(object, this);
+        return object;
     }
 
     /**
@@ -484,9 +534,8 @@ public final class NilReason implements Serializable {
      * <ul>
      *   <li>If the given object implements the {@link NilObject} interface, 
then this method delegates
      *       to the {@link NilObject#getNilReason()} method.</li>
-     *   <li>Otherwise if the given object is one of the {@code Boolean}, 
{@link Byte}, {@link Short}, {@code Integer},
-     *       {@link Long}, {@link Float}, {@code Double} or {@code String} 
instances returned by
-     *       {@link #createNilObject(Class)}, then this method returns the 
associated reason.</li>
+     *   <li>Otherwise if the given object is one of the {@link Float}, {@link 
Double} or {@link String} instances
+     *       returned by {@link #createNilObject(Class)}, then this method 
returns the associated reason.</li>
      *   <li>Otherwise this method returns {@code null}.</li>
      * </ul>
      *
@@ -501,10 +550,49 @@ public final class NilReason implements Serializable {
         if (object != null) {
             if (object instanceof NilObject) {
                 return ((NilObject) object).getNilReason();
+            } else if (object instanceof Double) {
+                final Double value = (Double) object;
+                if (value.isNaN()) {
+                    return forNumber(value);
+                }
+            } else if (object instanceof Float) {
+                final Float value = (Float) object;
+                if (value.isNaN()) {
+                    return forNumber(value);
+                }
+            } else if (object instanceof String) {
+                return (NilReason) FinalClassExtensions.property(object);
             }
-            if (mayBeNil(object)) {
-                return (NilReason) PrimitiveTypeProperties.property(object);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the nil reason for the ordinal value extracted from the given 
floating point value.
+     * Caller should have verified that the value is NaN before to invoke this 
method.
+     *
+     * @param  value  the floating point value for which to search the nil 
reason.
+     * @return the nil reason, or {@code null} if none has been found for the 
given ordinal.
+     *
+     * @see #ordinal()
+     */
+    private static NilReason forNumber(final Number value) {
+        try {
+            final int ordinal = MathFunctions.toNanOrdinal(value.floatValue());
+            if (ordinal >= 1) {
+                if (ordinal <= PREDEFINED.length) {
+                    return PREDEFINED[ordinal - 1];
+                }
+                synchronized (POOL) {
+                    for (final NilReason reason : POOL) {       // Should be a 
very small set, usually empty.
+                        if (reason.ordinal == ordinal) {
+                            return reason;
+                        }
+                    }
+                }
             }
+        } catch (IllegalArgumentException e) {
+            // Unsupported bit pattern. Ignore.
         }
         return null;
     }
diff --git 
a/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonMarshallingTest.java
 
b/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonMarshallingTest.java
index 4e087fc105..6f509f575a 100644
--- 
a/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonMarshallingTest.java
+++ 
b/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonMarshallingTest.java
@@ -20,8 +20,6 @@ import jakarta.xml.bind.JAXBException;
 import org.opengis.metadata.content.Band;
 import org.opengis.metadata.citation.Series;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.spatial.Dimension;
-import org.opengis.metadata.quality.ConformanceResult;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.xml.TestCase;
 import org.junit.Test;
@@ -81,71 +79,10 @@ public final class NilReasonMarshallingTest extends 
TestCase {
         assertEquals(citation, unmarshal(Citation.class, actual));
     }
 
-    /**
-     * Tests a missing boolean value. The {@link Boolean}, {@link Integer}, 
{@link Double} and {@link String}
-     * values are implemented as special cases in {@link NilReason}, because 
they are final classes on which
-     * we have no control.
-     *
-     * @throws JAXBException if an error occurred during (un)marshalling.
-     */
-    @Test
-    @DependsOnMethod("testMissing")
-    public void testMissingBoolean() throws JAXBException {
-        final String expected =
-                "<mdq:DQ_ConformanceResult xmlns:mdq=\"" + Namespaces.MDQ + 
'"' +
-                                         " xmlns:gco=\"" + Namespaces.GCO + 
"\">\n" +
-                "  <mdq:explanation>\n" +
-                "    <gco:CharacterString>An 
explanation</gco:CharacterString>\n" +
-                "  </mdq:explanation>\n" +
-                "  <mdq:pass gco:nilReason=\"missing\"/>\n" +
-                "</mdq:DQ_ConformanceResult>";
-
-        final ConformanceResult result = unmarshal(ConformanceResult.class, 
expected);
-        assertEquals("explanation", "An explanation", 
result.getExplanation().toString());
-
-        final Boolean pass = result.pass();
-        assertNotNull("Expected a sentinel value.", pass);
-        assertEquals ("Nil value shall be false.",  Boolean.FALSE, pass);
-        assertNotSame("Expected a sentinel value.", Boolean.FALSE, pass);
-        assertSame("nilReason", NilReason.MISSING, NilReason.forObject(pass));
-
-        final String actual = marshal(result);
-        assertXmlEquals(expected, actual, "xmlns:*");
-        assertEquals(result, unmarshal(ConformanceResult.class, actual));
-    }
-
-    /**
-     * Tests a missing integer value. The {@link Boolean}, {@link Integer}, 
{@link Double} and {@link String}
-     * values are implemented as special cases in {@link NilReason}, because 
they are final classes on which
-     * we have no control.
-     *
-     * @throws JAXBException if an error occurred during (un)marshalling.
-     */
-    @Test
-    @DependsOnMethod("testMissing")
-    @SuppressWarnings("UnnecessaryBoxing")
-    public void testMissingInteger() throws JAXBException {
-        final String expected =
-                "<msr:MD_Dimension xmlns:msr=\"" + Namespaces.MSR + '"' +
-                                 " xmlns:gco=\"" + Namespaces.GCO + "\">\n" +
-                "  <msr:dimensionSize gco:nilReason=\"unknown\"/>\n" +
-                "</msr:MD_Dimension>";
-
-        final Dimension result = unmarshal(Dimension.class, expected);
-
-        final Integer size = result.getDimensionSize();
-        assertNotNull("Expected a sentinel value.", size);
-        assertEquals ("Nil value shall be 0.",      Integer.valueOf(0), size);
-        assertNotSame("Expected a sentinel value.", Integer.valueOf(0), size);
-        assertSame("nilReason", NilReason.UNKNOWN, NilReason.forObject(size));
-
-        final String actual = marshal(result);
-        assertXmlEquals(expected, actual, "xmlns:*");
-        assertEquals(result, unmarshal(Dimension.class, actual));
-    }
-
     /**
      * Tests a missing double value.
+     * The {@link Float}, {@link Double} and {@link String} values are 
implemented as special
+     * cases in {@link NilReason}, because they are final classes on which we 
have no control.
      *
      * @throws JAXBException if an error occurred during (un)marshalling.
      */
diff --git 
a/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java 
b/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java
index 7bdf76eb5e..f91c765adc 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java
@@ -34,7 +34,7 @@ import static org.opengis.test.Assert.assertInstanceOf;
  * Tests the {@link NilReason}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.4
+ * @version 1.4
  * @since   0.3
  */
 public final class NilReasonTest extends TestCase {
@@ -44,6 +44,18 @@ public final class NilReasonTest extends TestCase {
     public NilReasonTest() {
     }
 
+    /**
+     * Verifies {@link NilReason#ordinal()} values.
+     */
+    @Test
+    public void verifyOrdinal() {
+        assertEquals(3, NilReason.TEMPLATE    .ordinal());
+        assertEquals(2, NilReason.MISSING     .ordinal());
+        assertEquals(1, NilReason.INAPPLICABLE.ordinal());
+        assertEquals(4, NilReason.UNKNOWN     .ordinal());
+        assertEquals(5, NilReason.WITHHELD    .ordinal());
+    }
+
     /**
      * Tests the {@link NilReason#valueOf(String)} method on constants.
      *
@@ -100,95 +112,6 @@ public final class NilReasonTest extends TestCase {
         assertTrue(ArraysExt.contains(reasons, other));
     }
 
-    /**
-     * Tests {@link NilReason#createNilObject(Class)} for a boolean type.
-     * Opportunistically tests {@link NilReason#forObject(Object)} with the 
created object.
-     *
-     * @since 0.4
-     */
-    @Test
-    public void testCreateNilBoolean() {
-        final Boolean value = NilReason.MISSING.createNilObject(Boolean.class);
-        assertEquals (Boolean.FALSE, value);
-        assertNotSame(Boolean.FALSE, value);
-        assertSame("NilReason.forObject(…)", NilReason.MISSING, 
NilReason.forObject(value));
-        assertNull("NilReason.forObject(…)", 
NilReason.forObject(Boolean.FALSE));
-        assertNull("NilReason.forObject(…)", 
NilReason.forObject(Boolean.TRUE));
-        assertSame("Expected cached value.", value, 
NilReason.MISSING.createNilObject(Boolean.class));
-    }
-
-    /**
-     * Tests {@link NilReason#createNilObject(Class)} for an integer type.
-     * Opportunistically tests {@link NilReason#forObject(Object)} with the 
created object.
-     *
-     * @since 0.4
-     */
-    @Test
-    public void testCreateNilInteger() {
-        final Integer zero  = 0;
-        final Integer value = NilReason.MISSING.createNilObject(Integer.class);
-        assertEquals (zero, value);
-        assertNotSame(zero, value);
-        assertSame("NilReason.forObject(…)", NilReason.MISSING, 
NilReason.forObject(value));
-        assertNull("NilReason.forObject(…)", NilReason.forObject(zero));
-        assertNull("NilReason.forObject(…)", NilReason.forObject(1));
-        assertSame("Expected cached value.", value, 
NilReason.MISSING.createNilObject(Integer.class));
-    }
-
-    /**
-     * Tests {@link NilReason#createNilObject(Class)} for a byte type.
-     * Opportunistically tests {@link NilReason#forObject(Object)} with the 
created object.
-     *
-     * @since 0.4
-     */
-    @Test
-    public void testCreateNilByte() {
-        final Byte zero  = 0;
-        final Byte value = NilReason.MISSING.createNilObject(Byte.class);
-        assertEquals (zero, value);
-        assertNotSame(zero, value);
-        assertSame("NilReason.forObject(…)", NilReason.MISSING, 
NilReason.forObject(value));
-        assertNull("NilReason.forObject(…)", NilReason.forObject(zero));
-        assertNull("NilReason.forObject(…)", NilReason.forObject(1));
-        assertSame("Expected cached value.", value, 
NilReason.MISSING.createNilObject(Byte.class));
-    }
-
-    /**
-     * Tests {@link NilReason#createNilObject(Class)} for a short type.
-     * Opportunistically tests {@link NilReason#forObject(Object)} with the 
created object.
-     *
-     * @since 0.4
-     */
-    @Test
-    public void testCreateNilShort() {
-        final Short zero  = 0;
-        final Short value = NilReason.MISSING.createNilObject(Short.class);
-        assertEquals (zero, value);
-        assertNotSame(zero, value);
-        assertSame("NilReason.forObject(…)", NilReason.MISSING, 
NilReason.forObject(value));
-        assertNull("NilReason.forObject(…)", NilReason.forObject(zero));
-        assertNull("NilReason.forObject(…)", NilReason.forObject(1));
-        assertSame("Expected cached value.", value, 
NilReason.MISSING.createNilObject(Short.class));
-    }
-
-    /**
-     * Tests {@link NilReason#createNilObject(Class)} for a long type.
-     * Opportunistically tests {@link NilReason#forObject(Object)} with the 
created object.
-     *
-     * @since 0.4
-     */
-    @Test
-    public void testCreateNilLong() {
-        final Long zero  = 0L;
-        final Long value = NilReason.MISSING.createNilObject(Long.class);
-        assertEquals (zero, value);
-        assertNotSame(zero, value);
-        assertSame("NilReason.forObject(…)", NilReason.MISSING, 
NilReason.forObject(value));
-        assertNull("NilReason.forObject(…)", NilReason.forObject(zero));
-        assertNull("NilReason.forObject(…)", NilReason.forObject(1L));
-        assertSame("Expected cached value.", value, 
NilReason.MISSING.createNilObject(Long.class));
-    }
-
     /**
      * Tests {@link NilReason#createNilObject(Class)} for a float type.
      * Opportunistically tests {@link NilReason#forObject(Object)} with the 
created object.
@@ -216,13 +139,13 @@ public final class NilReasonTest extends TestCase {
     @Test
     public void testCreateNilDouble() {
         final Double nan  = Double.NaN;
-        final Double value = NilReason.MISSING.createNilObject(Double.class);
+        final Double value = NilReason.TEMPLATE.createNilObject(Double.class);
         assertEquals (nan, value);
         assertNotSame(nan, value);
-        assertSame("NilReason.forObject(…)", NilReason.MISSING, 
NilReason.forObject(value));
+        assertSame("NilReason.forObject(…)", NilReason.TEMPLATE, 
NilReason.forObject(value));
         assertNull("NilReason.forObject(…)", NilReason.forObject(nan));
         assertNull("NilReason.forObject(…)", NilReason.forObject(0.0));
-        assertSame("Expected cached value.", value, 
NilReason.MISSING.createNilObject(Double.class));
+        assertSame("Expected cached value.", value, 
NilReason.TEMPLATE.createNilObject(Double.class));
     }
 
     /**


Reply via email to