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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 193b493363 Improve `DefaultObjectDomain` with the use of `NilObject` 
for telling when the value is missing.
193b493363 is described below

commit 193b4933634c42eb835be176246eb54d12fbf358
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Aug 30 12:30:03 2023 +0200

    Improve `DefaultObjectDomain` with the use of `NilObject` for telling when 
the value is missing.
---
 .../metadata/internal/ImplementationHelper.java    |  17 ++-
 .../metadata/simple/SimpleIdentifiedObject.java    |   6 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      |  10 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |  51 ++++---
 .../sis/referencing/DefaultObjectDomain.java       | 164 +++++++++++++++------
 .../referencing/datum/DefaultGeodeticDatum.java    |  38 ++---
 .../org/apache/sis/util/resources/Vocabulary.java  |  20 ++-
 7 files changed, 200 insertions(+), 106 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/ImplementationHelper.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/ImplementationHelper.java
index f0516b2311..48750b280d 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/ImplementationHelper.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/ImplementationHelper.java
@@ -21,6 +21,8 @@ import java.util.List;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.Objects;
+import org.apache.sis.xml.NilObject;
 import org.apache.sis.xml.NilReason;
 import org.apache.sis.xml.IdentifierSpace;
 import org.apache.sis.xml.IdentifiedObject;
@@ -37,7 +39,7 @@ import org.apache.sis.util.internal.CollectionsExt;
  * This is not an helper class for <em>usage</em> of metadata.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  * @since   0.3
  */
 public final class ImplementationHelper extends Static {
@@ -74,6 +76,19 @@ public final class ImplementationHelper extends Static {
         return (value != Long.MIN_VALUE) ? new Date(value) : null;
     }
 
+    /**
+     * Returns {@code true} if the given object is non-null and not an 
instance of {@link NilObject}.
+     * This is a helper method for use in lambda expressions.
+     *
+     * @param  value  the value to test.
+     * @return whether the given value is non-null and non-nil.
+     *
+     * @see Objects#nonNull(Object)
+     */
+    public static boolean nonNil(final Object value) {
+        return (value != null) && !(value instanceof NilObject);
+    }
+
     /**
      * Returns the given collection if non-null and non-empty, or {@code null} 
otherwise.
      * This method is used for calls to {@code checkWritePermission(Object)}.
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java
index d1a948204e..68ccb00b6a 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleIdentifiedObject.java
@@ -97,7 +97,10 @@ public class SimpleIdentifiedObject implements 
IdentifiedObject, LenientComparab
      * revisit {@link #equals(Object, ComparisonMode)} in subclasses.</p>
      *
      * @return the domain of validity, or {@code null} if none.
+     *
+     * @deprecated Removed from ISO 19111:2019 (moved to {@code ObjectDomain}).
      */
+    @Deprecated
     public final Extent getDomainOfValidity() {
         return null;
     }
@@ -123,10 +126,7 @@ public class SimpleIdentifiedObject implements 
IdentifiedObject, LenientComparab
      * The default implementation returns {@link Identifier#getDescription()}.
      *
      * @return a narrative explanation of the role of this object, or {@code 
null} if none.
-     *
-     * @deprecated Removed from ISO 19111:2019 (moved to {@code ObjectDomain}).
      */
-    @Deprecated
     public InternationalString getDescription() {
         final Identifier name = this.name;
         return (name != null) ? name.getDescription() : null;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
index f27c538f41..3431b25267 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
@@ -70,7 +70,6 @@ import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.collection.IntegerList;
-import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.util.internal.X364;
 import org.apache.sis.util.internal.Numerics;
 import org.apache.sis.util.internal.Constants;
@@ -78,14 +77,15 @@ import org.apache.sis.util.internal.StandardDateFormat;
 import org.apache.sis.system.Configuration;
 import org.apache.sis.metadata.simple.SimpleExtent;
 import org.apache.sis.metadata.internal.Resources;
+import org.apache.sis.metadata.iso.extent.Extents;
+import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.ImmutableIdentifier;
-import org.apache.sis.referencing.DefaultObjectDomain;
 import org.apache.sis.referencing.util.WKTKeywords;
 import org.apache.sis.referencing.util.WKTUtilities;
 import org.apache.sis.geometry.AbstractDirectPosition;
 import org.apache.sis.geometry.AbstractEnvelope;
-import org.apache.sis.metadata.iso.extent.Extents;
+import org.apache.sis.xml.NilObject;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.util.ControlledVocabulary;
@@ -918,10 +918,10 @@ public class Formatter implements Localized {
      * @since 1.4
      */
     public void append(final InternationalString scope, final Extent area) {
-        if (DefaultObjectDomain.isDefined(scope)) {
+        if (scope != null && !(scope instanceof NilObject)) {
             appendOnNewLine(WKTKeywords.Scope, scope, ElementKind.SCOPE);
         }
-        if (DefaultObjectDomain.isDefined(area)) {
+        if (area != null && !(area instanceof NilObject)) {
             appendOnNewLine(WKTKeywords.Area, area.getDescription(), 
ElementKind.EXTENT);
             append(Extents.getGeographicBoundingBox(area), BBOX_ACCURACY);
             appendVerticalExtent(Extents.getVerticalRange(area));
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
index 0814928ad3..9740d40462 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java
@@ -27,7 +27,6 @@ import java.util.Objects;
 import java.util.Formattable;
 import java.util.FormattableFlags;
 import java.util.function.Function;
-import java.util.function.Predicate;
 import java.io.Serializable;
 import jakarta.xml.bind.annotation.XmlID;
 import jakarta.xml.bind.annotation.XmlType;
@@ -135,7 +134,7 @@ import org.opengis.referencing.ObjectDomain;
     "identifier",
     "names",
     "remarks",
-    "domain",       // GML defines "domainOfValidity" in Datum, AbstractCRS 
and AbstractCoordinateOperation.
+    "domainExtent", // GML defines "domainOfValidity" in Datum, AbstractCRS 
and AbstractCoordinateOperation.
     "domainScope"   // GML defines "scope" in same classes than above.
 })
 @XmlSeeAlso({
@@ -1062,7 +1061,7 @@ public class AbstractIdentifiedObject extends 
FormattableObject implements Ident
      */
     private void setIdentifier(final Code identifier) {
         if (identifiers != null) {
-            
ImplementationHelper.propertyAlreadySet(AbstractIdentifiedObject.class, 
"setIdentifier", "identifier");
+            propertyAlreadySet("setIdentifier", "identifier");
         } else if (identifier != null) {
             final Identifier id = identifier.getIdentifier();
             if (id != null) {
@@ -1180,14 +1179,13 @@ public class AbstractIdentifiedObject extends 
FormattableObject implements Ident
     /**
      * Finds the first non-null domain element.
      *
-     * @param  <T>        type of domain element to get.
-     * @param  getter     {@link ObjectDomain} getter method to invoke.
-     * @param  predicate  the non-null and non-undefined check.
+     * @param  <T>     type of domain element to get.
+     * @param  getter  {@link ObjectDomain} getter method to invoke.
      * @return first non-null value, or {@code null} if none.
      */
-    private <T> T findFirst(final Function<ObjectDomain,T> getter, final 
Predicate<T> isDefined) {
+    private <T> T findFirst(final Function<ObjectDomain,T> getter) {
         if (domains == null) return null;
-        return 
domains.stream().map(getter).filter(isDefined).findFirst().orElse(null);
+        return 
domains.stream().map(getter).filter(ImplementationHelper::nonNil).findFirst().orElse(null);
     }
 
     /**
@@ -1200,8 +1198,8 @@ public class AbstractIdentifiedObject extends 
FormattableObject implements Ident
     // For an unknown reason, JAXB does not take the adapter declared in 
package-info for this particular property.
     @Workaround(library = "JDK", version = "1.8")
     @XmlJavaTypeAdapter(EX_Extent.class)
-    private Extent getDomain() {
-        return findFirst(ObjectDomain::getDomainOfValidity, 
DefaultObjectDomain::isDefined);
+    private Extent getDomainExtent() {
+        return findFirst(ObjectDomain::getDomainOfValidity);
     }
 
     /**
@@ -1214,21 +1212,21 @@ public class AbstractIdentifiedObject extends 
FormattableObject implements Ident
      */
     @XmlElement(name ="scope")
     private InternationalString getDomainScope() {
-        return findFirst(ObjectDomain::getScope, 
DefaultObjectDomain::isDefined);
+        return findFirst(ObjectDomain::getScope);
     }
 
     /**
      * Invoked by JAXB only at unmarshalling time.
      */
-    private void setDomain(final Extent value) {
+    private void setDomainExtent(final Extent value) {
         InternationalString scope = null;
-        final ObjectDomain domain = CollectionsExt.first(domains);
+        final DefaultObjectDomain domain = 
DefaultObjectDomain.castOrCopy(CollectionsExt.first(domains));
         if (domain != null) {
-            if (DefaultObjectDomain.isDefined(domain.getDomainOfValidity())) {
-                
ImplementationHelper.propertyAlreadySet(AbstractReferenceSystem.class, 
"setDomainOfValidity", "domainOfValidity");
+            if (domain.domainOfValidity != null) {
+                propertyAlreadySet("setDomain", "domainOfValidity");
                 return;
             }
-            scope = domain.getScope();
+            scope = domain.scope;
         }
         domains = Collections.singleton(new DefaultObjectDomain(scope, value));
     }
@@ -1238,13 +1236,13 @@ public class AbstractIdentifiedObject extends 
FormattableObject implements Ident
      */
     private void setDomainScope(final InternationalString value) {
         Extent area = null;
-        final ObjectDomain domain = CollectionsExt.first(domains);
+        final DefaultObjectDomain domain = 
DefaultObjectDomain.castOrCopy(CollectionsExt.first(domains));
         if (domain != null) {
-            if (DefaultObjectDomain.isDefined(domain.getScope())) {
-                
ImplementationHelper.propertyAlreadySet(AbstractReferenceSystem.class, 
"setScope", "scope");
+            if (domain.scope != null) {
+                propertyAlreadySet("setDomainScope", "scope");
                 return;
             }
-            area = domain.getDomainOfValidity();
+            area = domain.domainOfValidity;
         }
         domains = Collections.singleton(new DefaultObjectDomain(value, area));
     }
@@ -1258,7 +1256,18 @@ public class AbstractIdentifiedObject extends 
FormattableObject implements Ident
         if (remarks == null) {
             remarks = value;
         } else {
-            
ImplementationHelper.propertyAlreadySet(AbstractIdentifiedObject.class, 
"setRemarks", "remarks");
+            propertyAlreadySet("setRemarks", "remarks");
         }
     }
+
+    /**
+     * Logs a warning saying that an unmarshalled property was already set.
+     *
+     * @param  method  the caller method, used for logging.
+     * @param  name    the property name, used for logging and exception 
message.
+     * @throws IllegalStateException if we are not unmarshalling an object.
+     */
+    private static void propertyAlreadySet(final String method, final String 
name) {
+        
ImplementationHelper.propertyAlreadySet(AbstractIdentifiedObject.class, method, 
name);
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java
index 07ad21a943..b44848b058 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java
@@ -18,6 +18,7 @@ package org.apache.sis.referencing;
 
 import java.util.Objects;
 import java.io.Serializable;
+import java.io.ObjectStreamException;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.ComparisonMode;
@@ -27,6 +28,8 @@ import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.referencing.util.WKTKeywords;
 import org.apache.sis.io.wkt.FormattableObject;
 import org.apache.sis.io.wkt.Formatter;
+import org.apache.sis.xml.NilObject;
+import org.apache.sis.xml.NilReason;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -34,7 +37,16 @@ import org.opengis.referencing.ObjectDomain;
 
 
 /**
- * Default implementation of scope and domain of validity of a CRS-related 
object.
+ * Scope and domain of validity of a CRS-related object.
+ * Those two properties are mandatory according ISO 19111.
+ * If a property is unspecified (by passing {@code null} to the constructor),
+ * then this class substitutes the null value by a <i>"not known"</i> text in 
an
+ * object implementing the {@link NilObject} interface with {@link 
NilReason#UNKNOWN}.
+ * The use of <i>"not known"</i> text is an ISO 19111 recommendation.
+ *
+ * <h2>Immutability and thread safety</h2>
+ * This class is immutable and thus thread-safe if the property values
+ * given to the constructor are also immutable.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.4
@@ -48,40 +60,94 @@ public class DefaultObjectDomain extends FormattableObject 
implements ObjectDoma
 
     /**
      * The text used by default when the scope was not specified.
-     * The <i>"not known"</i> text is standardized by ISO 19111.
+     * The <i>"not known"</i> text is recommended by the ISO 19111 standard.
      * The text may be localized.
-     *
-     * @see #isDefined(InternationalString)
      */
-    public static final InternationalString UNKNOWN_SCOPE = 
Vocabulary.formatInternational(Vocabulary.Keys.NotKnown);
+    private static final class UnknownScope extends Vocabulary.International 
implements NilObject {
+        /** For cross-version interoperability. */
+        private static final long serialVersionUID = 7235301883912422934L;
+
+        /** The singleton instance. */
+        static final UnknownScope INSTANCE = new UnknownScope();
+
+        /** Creates the singleton instance. */
+        private UnknownScope() {
+            super(Vocabulary.Keys.NotKnown);
+        }
+
+        /**
+         * {@return a reason saying that the extent is unknown}.
+         */
+        @Override
+        public NilReason getNilReason() {
+            return NilReason.UNKNOWN;
+        }
+
+        /**
+         * Returns the unique instance on deserialization.
+         *
+         * @return the object to use after deserialization.
+         * @throws ObjectStreamException if the serialized object contains 
invalid data.
+         */
+        private Object readResolve() throws ObjectStreamException {
+            return INSTANCE;
+        }
+    }
 
     /**
-     * The extent used by default when the domain of validity was not 
specified.
-     *
-     * @see #isDefined(Extent)
+     * The extent used by default when the scope was not specified.
      */
-    public static final Extent UNKNOWN_EXTENT;
-    static {
-        final var domainOfValidity = new DefaultExtent(UNKNOWN_SCOPE, null, 
null, null);
-        domainOfValidity.transitionTo(DefaultExtent.State.FINAL);
-        UNKNOWN_EXTENT = domainOfValidity;
+    private static final class UnknownExtent extends DefaultExtent implements 
NilObject {
+        /** For cross-version interoperability. */
+        private static final long serialVersionUID = 662383891780679068L;
+
+        /** The singleton instance. */
+        static final UnknownExtent INSTANCE = new UnknownExtent();
+
+        /** Creates the singleton instance. */
+        private UnknownExtent() {
+            super(UnknownScope.INSTANCE, null, null, null);
+            transitionTo(DefaultExtent.State.FINAL);
+        }
+
+        /**
+         * {@return a reason saying that the extent is unknown}.
+         */
+        @Override
+        public NilReason getNilReason() {
+            return NilReason.UNKNOWN;
+        }
+
+        /**
+         * Returns the unique instance on deserialization.
+         *
+         * @return the object to use after deserialization.
+         * @throws ObjectStreamException if the serialized object contains 
invalid data.
+         */
+        private Object readResolve() throws ObjectStreamException {
+            return INSTANCE;
+        }
     }
 
     /**
      * Description of domain of usage, or limitations of usage, for which the 
object is valid.
+     * This is {@code null} (i.e. is not replaced by the <i>"not known"</i> 
text) if the value given
+     * to the {@linkplain #DefaultObjectDomain(InternationalString, Extent) 
constructor} was null.
      *
      * @see #getScope()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are 
serializable.
-    private final InternationalString scope;
+    protected final InternationalString scope;
 
     /**
      * Area for which the object is valid.
+     * This is {@code null} (i.e. is not replaced by the <i>"not known"</i> 
text) if the value given
+     * to the {@linkplain #DefaultObjectDomain(InternationalString, Extent) 
constructor} was null.
      *
      * @see #getDomainOfValidity()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are 
serializable.
-    private final Extent domainOfValidity;
+    protected final Extent domainOfValidity;
 
     /**
      * Creates a new domain with the given scope and extent.
@@ -91,55 +157,64 @@ public class DefaultObjectDomain extends FormattableObject 
implements ObjectDoma
      * @param scope             description of domain of usage, or limitations 
of usage.
      * @param domainOfValidity  area for which the object is valid.
      */
-    public DefaultObjectDomain(InternationalString scope, Extent 
domainOfValidity) {
-        if (scope == null) {
-            scope = UNKNOWN_SCOPE;
-        }
-        if (domainOfValidity == null) {
-            domainOfValidity = UNKNOWN_EXTENT;
-        }
+    public DefaultObjectDomain(final InternationalString scope, final Extent 
domainOfValidity) {
         this.scope = scope;
         this.domainOfValidity = domainOfValidity;
     }
 
     /**
-     * Returns a description of usage, or limitations of usage, for which this 
object is valid.
+     * Creates a new domain with the same values than the specified one.
+     * This copy constructor provides a way to convert an arbitrary 
implementation into a SIS one
+     * or a user-defined one (as a subclass), usually in order to leverage 
some implementation-specific API.
      *
-     * @return the domain of usage.
+     * <p>This constructor performs a shallow copy, i.e. the properties are 
not cloned.</p>
+     *
+     * @param  domain  the domain to copy.
+     *
+     * @see #castOrCopy(ObjectDomain)
      */
-    @Override
-    public InternationalString getScope() {
-        return scope;
+    public DefaultObjectDomain(final ObjectDomain domain) {
+        scope = domain.getScope();
+        domainOfValidity = domain.getDomainOfValidity();
     }
 
     /**
-     * Returns the spatial and temporal extent in which this object is valid.
+     * Returns a SIS datum implementation with the same values than the given 
arbitrary implementation.
+     * If the given object is {@code null}, then this method returns {@code 
null}.
+     * Otherwise if the given object is already a SIS implementation, then the 
given object is returned unchanged.
+     * Otherwise a new SIS implementation is created and initialized to the 
attribute values of the given object.
      *
-     * @return the area or time frame of usage.
+     * @param  object  the object to get as a SIS implementation, or {@code 
null} if none.
+     * @return a SIS implementation containing the values of the given object 
(may be the
+     *         given object itself), or {@code null} if the argument was null.
      */
-    @Override
-    public Extent getDomainOfValidity() {
-        return domainOfValidity;
+    public static DefaultObjectDomain castOrCopy(final ObjectDomain object) {
+        return (object == null) || (object instanceof DefaultObjectDomain)
+                ? (DefaultObjectDomain) object : new 
DefaultObjectDomain(object);
     }
 
     /**
-     * Returns {@code true} if the given scope is neither null or not known.
+     * Returns a description of usage, or limitations of usage, for which this 
object is valid.
+     * If no scope was specified to the constructor, then this method returns 
<i>"not known"</i>
+     * in an instance implementing the {@link NilObject} interface with {@link 
NilReason#UNKNOWN}.
      *
-     * @param  scope  the scope to test.
-     * @return {@code true} if the given scope is not null and not {@link 
#UNKNOWN_SCOPE}.
+     * @return the domain of usage.
      */
-    public static boolean isDefined(final InternationalString scope) {
-        return (scope != null) && (scope != UNKNOWN_SCOPE);
+    @Override
+    public InternationalString getScope() {
+        return (scope != null) ? scope : UnknownScope.INSTANCE;
     }
 
     /**
-     * Returns {@code true} if the given extent is neither null or not known.
+     * Returns the spatial and temporal extent in which this object is valid.
+     * If no extent was specified to the constructor, then this method returns 
<i>"not known"</i>
+     * in an instance implementing the {@link NilObject} interface with {@link 
NilReason#UNKNOWN}.
      *
-     * @param  domainOfValidity  the extent to test.
-     * @return {@code true} if the given extent is not null and not {@link 
#UNKNOWN_EXTENT}.
+     * @return the area or time frame of usage.
      */
-    public static boolean isDefined(final Extent domainOfValidity) {
-        return (domainOfValidity != null) && (domainOfValidity != 
UNKNOWN_EXTENT);
+    @Override
+    public Extent getDomainOfValidity() {
+        return (domainOfValidity != null) ? domainOfValidity : 
UnknownExtent.INSTANCE;
     }
 
     /**
@@ -204,8 +279,8 @@ public class DefaultObjectDomain extends FormattableObject 
implements ObjectDoma
      * The default implementation writes the following elements:
      *
      * <ul>
-     *   <li>The object {@linkplain #getScope() scope}.</li>
-     *   <li>The geographic description of the {@linkplain 
#getDomainOfValidity() domain of validity}.</li>
+     *   <li>The object {@linkplain #scope}.</li>
+     *   <li>The geographic description of the {@linkplain #domainOfValidity 
domain of validity}.</li>
      *   <li>The geographic bounding box of the domain of validity.</li>
      * </ul>
      *
@@ -215,6 +290,7 @@ public class DefaultObjectDomain extends FormattableObject 
implements ObjectDoma
      */
     @Override
     protected String formatTo(final Formatter formatter) {
+        // Use the fields directly in order to keep null values.
         formatter.append(scope, domainOfValidity);
         return WKTKeywords.Usage;
     }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
index 2c38c567a2..c4b6ef8c56 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
@@ -206,55 +206,41 @@ public class DefaultGeodeticDatum extends AbstractDatum 
implements GeodeticDatum
      *     <th>Property name</th>
      *     <th>Value type</th>
      *     <th>Returned by</th>
-     *   </tr>
-     *   <tr>
+     *   </tr><tr>
      *     <td>{@value #BURSA_WOLF_KEY}</td>
      *     <td>{@link BursaWolfParameters} (optionally as array)</td>
      *     <td>{@link #getBursaWolfParameters()}</td>
-     *   </tr>
-     *   <tr>
+     *   </tr><tr>
      *     <th colspan="3" class="hsep">Defined in parent classes 
(reminder)</th>
-     *   </tr>
-     *   <tr>
+     *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
      *     <td>{@link Identifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
-     *   </tr>
-     *   <tr>
+     *   </tr><tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
      *     <td>{@link GenericName} or {@link CharSequence} (optionally as 
array)</td>
      *     <td>{@link #getAlias()}</td>
-     *   </tr>
-     *   <tr>
+     *   </tr><tr>
      *     <td>{@value 
org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
      *     <td>{@link Identifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
-     *   </tr>
-     *   <tr>
+     *   </tr><tr>
+     *     <td>{@value 
org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td>
+     *     <td>{@link org.opengis.referencing.ObjectDomain} (optionally as 
array)</td>
+     *     <td>{@link #getDomains()}</td>
+     *   </tr><tr>
      *     <td>{@value 
org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getRemarks()}</td>
-     *   </tr>
-     *   <tr>
+     *   </tr><tr>
      *     <td>{@value 
org.opengis.referencing.datum.Datum#ANCHOR_POINT_KEY}</td>
      *     <td>{@link InternationalString} or {@link String}</td>
      *     <td>{@link #getAnchorPoint()}</td>
-     *   </tr>
-     *   <tr>
+     *   </tr><tr>
      *     <td>{@value 
org.opengis.referencing.datum.Datum#REALIZATION_EPOCH_KEY}</td>
      *     <td>{@link Date}</td>
      *     <td>{@link #getRealizationEpoch()}</td>
      *   </tr>
-     *   <tr>
-     *     <td>{@value 
org.opengis.referencing.datum.Datum#DOMAIN_OF_VALIDITY_KEY}</td>
-     *     <td>{@link Extent}</td>
-     *     <td>{@link #getDomainOfValidity()}</td>
-     *   </tr>
-     *   <tr>
-     *     <td>{@value org.opengis.referencing.datum.Datum#SCOPE_KEY}</td>
-     *     <td>{@link InternationalString} or {@link String}</td>
-     *     <td>{@link #getScope()}</td>
-     *   </tr>
      * </table>
      *
      * If Bursa-Wolf parameters are specified, then the prime meridian of their
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
index 4d26c8a2fb..6e0bea56bf 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
@@ -1484,15 +1484,23 @@ public class Vocabulary extends IndexedResourceBundle {
     }
 
     /**
-     * The international string to be returned by {@link formatInternational}.
+     * The international string to be returned by {@code 
formatInternational(…)} methods.
+     * This implementation details is made public for allowing the creation of 
subclasses
+     * implementing some additional interfaces.
      */
-    private static final class International extends 
ResourceInternationalString {
+    public static class International extends ResourceInternationalString {
+        /** For cross-version compatibility. */
         private static final long serialVersionUID = -5423999784169092823L;
 
-        International(short key)                           {super(key);}
-        International(short key, Object args)              {super(key, args);}
-        @Override protected KeyConstants getKeyConstants() {return 
Keys.INSTANCE;}
-        @Override protected IndexedResourceBundle getBundle(final Locale 
locale) {
+        /**
+         * Creates a new instance for the given vocabulary resource key.
+         *
+         * @param  key  one of the {@link Keys} values.
+         */
+        protected International(short key)                       {super(key);}
+        International(short key, Object args)                    {super(key, 
args);}
+        @Override protected final KeyConstants getKeyConstants() {return 
Keys.INSTANCE;}
+        @Override protected final IndexedResourceBundle getBundle(final Locale 
locale) {
             return getResources(locale);
         }
     }

Reply via email to