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)); } /**