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 fad33a63ee36b5c28b12f9c677ac78e3d40e3c0a
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Mon Nov 20 12:14:54 2023 +0100

    `TreeTableView` delegates the handling of `NilReason` to `NilReasonMap`.
    This is necessary for objects that cannot be represented as `NilObject`.
---
 .../org/apache/sis/metadata/AbstractMetadata.java  |  7 ++
 .../main/org/apache/sis/metadata/NilReasonMap.java |  9 ++-
 .../main/org/apache/sis/metadata/TreeNode.java     | 89 ++++++++++++++++++++--
 .../main/org/apache/sis/metadata/package-info.java |  4 +
 .../org/apache/sis/metadata/TreeTableViewTest.java | 28 ++++++-
 5 files changed, 126 insertions(+), 11 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java
index f3333c9a39..6b9c0ed5e0 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java
@@ -83,6 +83,13 @@ public abstract class AbstractMetadata implements 
LenientComparable, Emptiable {
      * The reasons why some mandatory properties are absent. This map is used 
only for values of
      * classes that cannot be represented as instances of {@link 
org.apache.sis.xml.NilObject}.
      *
+     * <h4>Mutability</h4>
+     * We do not make this map unmodifiable when the enclosing metadata object 
is made unmodifiable.
+     * It should not be necessary because all {@code put(…)} operations on 
this map are done only after
+     * the corresponding {@code set(…)} operations on this metadata. So if the 
metadata is unmodifiable,
+     * an exception should have been thrown before. On the other hand, {@code 
remove(…)} operations may
+     * still be done for removing entries that shouldn't be there.
+     *
      * @see NilReasonMap
      */
     HashMap<Integer,NilReason> nilReasons;
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/NilReasonMap.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/NilReasonMap.java
index 9ceed8c7aa..0645f92f18 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/NilReasonMap.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/NilReasonMap.java
@@ -127,7 +127,14 @@ final class NilReasonMap extends PropertyMap<NilReason> {
      */
     @Override
     final NilReason getReflectively(final int index) {
-        final Object value = accessor.get(index, metadata);
+        return getNilReason(index, accessor.get(index, metadata));
+    }
+
+    /**
+     * Returns the nil reason for the property at the specified index, using a 
value already read.
+     * This method can be invoked when the value is already available in a 
cache.
+     */
+    final NilReason getNilReason(final int index, final Object value) {
         if (value != null) {
             nilReasons.remove(index);
             return NilReason.forObject(value);
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java
index 41c4b0c3f8..5bc0c1164e 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java
@@ -277,13 +277,11 @@ class TreeNode implements Node {
      * Gets the reason why the value is missing, or {@code null} if 
unspecified.
      * Note that this method is expected to always return {@code null} if
      * {@link ValueExistencePolicy#acceptNilValues()} is {@code false}.
+     *
+     * @see #setNilReason(NilReason)
      */
-    private NilReason getNilReason() {
-        // Do not check `canUseCache` because it applies to TableColumn.VALUE.
-        if (cachedValue == null) {
-            cachedValue = getUserObject();
-        }
-        return NilReason.forObject(cachedValue);
+    NilReason getNilReason() {
+        return null;
     }
 
     /**
@@ -324,6 +322,15 @@ class TreeNode implements Node {
         throw new 
UnsupportedOperationException(unmodifiableCellValue(TableColumn.VALUE));
     }
 
+    /**
+     * Sets the value to nil with a reason explaining why the value is nil.
+     *
+     * @throws UnsupportedOperationException if the metadata value is not 
writable.
+     */
+    void setNilReason(final NilReason value) {
+        throw new 
UnsupportedOperationException(unmodifiableCellValue(MetadataColumn.NIL_REASON));
+    }
+
     /**
      * Returns {@code true} if the metadata value can be set.
      * Subclasses must override this method.
@@ -372,6 +379,14 @@ class TreeNode implements Node {
          */
         private final PropertyAccessor accessor;
 
+        /**
+         * The reasons why some mandatory property values are missing.
+         * Created only if cell values in the "Nil reason" column are 
requested.
+         *
+         * @see #nilReasons()
+         */
+        private transient NilReasonMap nilReasons;
+
         /**
          * Index of the value in the {@link #metadata} object to be fetched 
with the {@link #accessor}.
          */
@@ -492,6 +507,38 @@ class TreeNode implements Node {
             return type;
         }
 
+        /**
+         * Returns the map of reasons why a mandatory value is missing.
+         * The map is created only when first needed.
+         */
+        @SuppressWarnings("ReturnOfCollectionOrArrayField")
+        private NilReasonMap nilReasons() {
+            if (nilReasons == null) {
+                nilReasons = new NilReasonMap(metadata, accessor, 
KeyNamePolicy.UML_IDENTIFIER);
+            }
+            return nilReasons;
+        }
+
+        /**
+         * Sets the value to nil with a reason explaining why the value is nil.
+         */
+        @Override
+        void setNilReason(final NilReason value) {
+            cachedValue = null;
+            canUseCache = false;
+            nilReasons().setReflectively(indexInData, value);
+        }
+
+        /**
+         * Gets the reason why the value is missing, or {@code null} if 
unspecified.
+         */
+        @Override
+        NilReason getNilReason() {
+            // Do not check `canUseCache` because it applies to 
TableColumn.VALUE.
+            if (cachedValue == null) cachedValue = getUserObject();
+            return nilReasons().getNilReason(indexInData, cachedValue);
+        }
+
         /**
          * Gets whether the property is mandatory, optional or conditional, or 
{@code null} if unspecified.
          */
@@ -521,6 +568,8 @@ class TreeNode implements Node {
          */
         @Override
         void setUserObject(final Object value) {
+            cachedValue = null;
+            canUseCache = false;
             accessor.set(indexInData, metadata, value, 
PropertyAccessor.RETURN_NULL);
         }
 
@@ -661,6 +710,8 @@ class TreeNode implements Node {
          */
         @Override
         void setUserObject(Object value) {
+            cachedValue = null;
+            canUseCache = false;
             final Collection<?> values = (Collection<?>) super.getUserObject();
             if (!(values instanceof List<?>)) {
                 // `setValue(…)` is the public method which invoked this one.
@@ -692,6 +743,28 @@ class TreeNode implements Node {
             }
         }
 
+        /**
+         * Gets the reason why the value is missing, or {@code null} if 
unspecified.
+         * Note that this method gets the nil reason of a specific collection 
element.
+         * This is a bit unusual, since nil reasons usually apply to the whole 
property.
+         */
+        @Override
+        NilReason getNilReason() {
+            // Do not check `canUseCache` because it applies to 
TableColumn.VALUE.
+            if (cachedValue == null) cachedValue = getUserObject();
+            return NilReason.forObject(cachedValue);
+        }
+
+        /**
+         * Sets the value to nil with a reason explaining why the value is nil.
+         * Note that this method sets the nil reason of a specific collection 
element.
+         * This is a bit unusual, since nil reasons usually apply to the whole 
property.
+         */
+        @Override
+        void setNilReason(final NilReason value) {
+            setUserObject(value != null ? value.createNilObject(baseType) : 
null);
+        }
+
         /**
          * Returns {@code true} if the value returned by {@link 
#getUserObject()}
          * should be the same for both nodes.
@@ -964,12 +1037,12 @@ class TreeNode implements Node {
         ArgumentChecks.ensureNonNull("column", column);
         if (column == TableColumn.VALUE) {
             ArgumentChecks.ensureNonNull("value", value);                      
 // See javadoc.
-            cachedValue = null;
-            canUseCache = false;
             final TreeNodeChildren children = getCompactChildren();
             if (children == null || !(children.setParentTitle(value))) {
                 setUserObject(value);
             }
+        } else if (column == MetadataColumn.NIL_REASON) {
+            setNilReason((NilReason) value);
         } else if (table.getColumns().contains(column)) {
             throw new 
UnsupportedOperationException(unmodifiableCellValue(column));
         } else {
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
index 2b5308b6f6..b49daea01c 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/package-info.java
@@ -123,4 +123,8 @@
  * @version 1.5
  * @since   0.3
  */
+@XmlAccessorType(XmlAccessType.NONE)
 package org.apache.sis.metadata;
+
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableViewTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableViewTest.java
index b129fc86f9..33369314db 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableViewTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableViewTest.java
@@ -123,6 +123,7 @@ public final class TreeTableViewTest extends TestCase {
 
     /**
      * Verifies the values of the given root node and some of its children.
+     * This method modifies the metadata for also testing some nil values.
      *
      * @param  node     root node to verify.
      * @param  title    expected citation title, or {@code null} if it is 
expected to be missing.
@@ -136,7 +137,10 @@ public final class TreeTableViewTest extends TestCase {
         assertNull  (                node.getValue(TableColumn.OBLIGATION));
         assertNull  (                node.getValue(TableColumn.VALUE));
         assertNull  (                node.getValue(MetadataColumn.NIL_REASON));
-
+        /*
+         * The first child of a Citation object should be the title.
+         * Verify the title value, type, obligation, etc.
+         */
         Iterator<TreeTable.Node> it = node.getChildren().iterator();
         node = it.next();
         assertEquals("title",                      
node.getValue(TableColumn.IDENTIFIER));
@@ -146,7 +150,16 @@ public final class TreeTableViewTest extends TestCase {
         assertEquals(Obligation.MANDATORY,         
node.getValue(TableColumn.OBLIGATION));
         assertI18nEq(title,                        
node.getValue(TableColumn.VALUE));
         assertEquals(titleNR,                      
node.getValue(MetadataColumn.NIL_REASON));
-
+        /*
+         * Declare the title as missing and verify that the change has been 
applied.
+         */
+        node.setValue(MetadataColumn.NIL_REASON, NilReason.MISSING);
+        assertEquals(NilReason.MISSING, 
node.getValue(MetadataColumn.NIL_REASON));
+        assertNull(node.getValue(TableColumn.VALUE));
+        /*
+         * The second child of the Citation use in this test should be an 
alternate title.
+         * This property is a collection with two elements. Check the first 
one.
+         */
         node = it.next();
         assertEquals("alternateTitle",              
node.getValue(TableColumn.IDENTIFIER));
         assertEquals(0,                             
node.getValue(TableColumn.INDEX));
@@ -155,6 +168,17 @@ public final class TreeTableViewTest extends TestCase {
         assertEquals(Obligation.OPTIONAL,           
node.getValue(TableColumn.OBLIGATION));
         assertI18nEq("First alternate title",       
node.getValue(TableColumn.VALUE));
         assertNull  (                               
node.getValue(MetadataColumn.NIL_REASON));
+        /*
+         * Set the first element to nil, then check that the second element 
has not been impacted.
+         * Contrarily to the previous test, this test modifies a collection 
elements instead of the
+         * property as a whole.
+         */
+        node.setValue(MetadataColumn.NIL_REASON, NilReason.INAPPLICABLE);
+        assertEquals(NilReason.INAPPLICABLE, 
node.getValue(MetadataColumn.NIL_REASON));
+        assertNull(node.getValue(TableColumn.VALUE));
+        node = it.next();
+        assertI18nEq("Second alternate title", 
node.getValue(TableColumn.VALUE));
+        assertNull(node.getValue(MetadataColumn.NIL_REASON));
     }
 
     /**

Reply via email to