You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/11/18 15:48:11 UTC
(sis) 02/02: Add "Obligation" and "Nil reason" columns in metadata `TreeTableView`.
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 49818875864c8f3d94acfb2309c33eaa1aac0b7e
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Nov 18 16:18:32 2023 +0100
Add "Obligation" and "Nil reason" columns in metadata `TreeTableView`.
---
.../org/apache/sis/metadata/AbstractMetadata.java | 33 ++---
.../org/apache/sis/metadata/MetadataColumn.java | 67 +++++++++
.../org/apache/sis/metadata/MetadataStandard.java | 39 ++++--
.../org/apache/sis/metadata/PropertyAccessor.java | 17 +++
.../main/org/apache/sis/metadata/TreeNode.java | 155 +++++++++++++++------
.../org/apache/sis/metadata/TreeNodeChildren.java | 64 +++++----
.../org/apache/sis/metadata/TreeTableView.java | 47 ++++---
.../apache/sis/metadata/ValueExistencePolicy.java | 12 ++
.../main/org/apache/sis/metadata/package-info.java | 23 +--
.../org/apache/sis/metadata/TreeTableViewTest.java | 79 ++++++++++-
.../apache/sis/util/collection/TableColumn.java | 29 ++--
.../org/apache/sis/util/resources/Vocabulary.java | 5 +
.../sis/util/resources/Vocabulary.properties | 1 +
.../sis/util/resources/Vocabulary_fr.properties | 1 +
netbeans-project/nbproject/project.xml | 3 +
15 files changed, 435 insertions(+), 140 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 49517f0c0c..5e5ac1ffcc 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
@@ -69,7 +69,7 @@ import org.apache.sis.util.collection.TreeTable;
* use a single lock for the whole metadata tree (including children).
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
*
* @see MetadataStandard
*
@@ -150,13 +150,10 @@ public abstract class AbstractMetadata implements LenientComparable, Emptiable {
}
/**
- * Returns a view of the property values in a {@link Map}. The map is backed by this metadata
- * object, so changes in the underlying metadata object are immediately reflected in the map
- * and conversely.
- *
- * <h4>Supported operations</h4>
- * The map supports the {@link Map#put(Object, Object) put(…)} and {@link Map#remove(Object)
- * remove(…)} operations if the underlying metadata object contains setter methods.
+ * Returns a view of the property values in a {@link Map}. The map is backed by this metadata object,
+ * so changes in the underlying metadata object are immediately reflected in the map and conversely.
+ * The map supports the {@link Map#put(Object, Object) put(…)} and {@link Map#remove(Object) remove(…)}
+ * operations if the underlying metadata object contains setter methods.
* The {@code remove(…)} method is implemented by a call to {@code put(…, null)}.
*
* <h4>Keys and values</h4>
@@ -179,9 +176,7 @@ public abstract class AbstractMetadata implements LenientComparable, Emptiable {
* The default implementation is equivalent to the following:
*
* {@snippet lang="java" :
- * return getStandard().asValueMap(this, null,
- * KeyNamePolicy.JAVABEANS_PROPERTY,
- * ValueExistencePolicy.NON_EMPTY);
+ * return getStandard().asValueMap(this, null, KeyNamePolicy.JAVABEANS_PROPERTY, ValueExistencePolicy.NON_EMPTY);
* }
*
* @return a view of this metadata object as a map.
@@ -197,7 +192,11 @@ public abstract class AbstractMetadata implements LenientComparable, Emptiable {
* The tree table is backed by the metadata object using Java reflection, so changes in the
* underlying metadata object are immediately reflected in the tree table and conversely.
*
- * <p>The returned {@code TreeTable} instance contains the following columns:</p>
+ * <p>The returned {@code TreeTable} instance contains the columns listed below.
+ * The {@code (IDENTIFIER, INDEX)} pair of columns can be used as a primary key for uniquely identifying
+ * a node in a list of children. That uniqueness is guaranteed only for the children of a given node.
+ * The same keys may appear in the children of any other nodes.</p>
+ *
* <ul class="verbose">
* <li>{@link org.apache.sis.util.collection.TableColumn#IDENTIFIER}<br>
* The {@linkplain org.opengis.annotation.UML#identifier() UML identifier} if any,
@@ -209,12 +208,7 @@ public abstract class AbstractMetadata implements LenientComparable, Emptiable {
* If the metadata property is a collection, then the zero-based index of the element in that collection.
* Otherwise {@code null}. For example, in a tree table view of {@code DefaultCitation}, if the
* {@code "alternateTitle"} collection contains two elements, then there is a node with index 0
- * for the first element and another node with index 1 for the second element.
- *
- * <div class="note"><b>Note:</b>
- * The {@code (IDENTIFIER, INDEX)} pair can be used as a primary key for uniquely identifying a node
- * in a list of children. That uniqueness is guaranteed only for the children of a given node;
- * the same keys may appear in the children of any other nodes.</div></li>
+ * for the first element and another node with index 1 for the second element.</li>
*
* <li>{@link org.apache.sis.util.collection.TableColumn#NAME}<br>
* A human-readable name for the node, derived from the identifier and the index.
@@ -224,6 +218,9 @@ public abstract class AbstractMetadata implements LenientComparable, Emptiable {
* <li>{@link org.apache.sis.util.collection.TableColumn#TYPE}<br>
* The base type of the value (usually an interface).</li>
*
+ * <li>{@link org.apache.sis.util.collection.TableColumn#OBLIGATION}<br>
+ * Whether the property is mandatory, optional or conditional.</li>
+ *
* <li>{@link org.apache.sis.util.collection.TableColumn#VALUE}<br>
* The metadata value for the node. Values in this column are writable if the underlying
* metadata class have a setter method for the property represented by the node.</li>
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataColumn.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataColumn.java
new file mode 100644
index 0000000000..1264826941
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataColumn.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.metadata;
+
+import java.io.Serializable;
+import java.io.ObjectStreamException;
+import org.apache.sis.xml.NilReason;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.collection.TableColumn;
+
+
+/**
+ * A tree table column specific to the metadata module.
+ * Defined as a class for allowing serialization.
+ *
+ * @param <V> base type of all values in the column identified by this instance.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @see TreeTableView
+ */
+final class MetadataColumn<V> extends TableColumn<V> implements Serializable {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 8256073324266678871L;
+
+ /**
+ * Table column for the reason why a mandatory property value is absent.
+ */
+ public static final MetadataColumn<NilReason> NIL_REASON =
+ new MetadataColumn<>(NilReason.class, Vocabulary.Keys.NilReason);
+
+ /**
+ * Creates a new column header.
+ *
+ * @param type base type of all values in the column identified by this instance.
+ * @param key resource key of the localized text to use as the column header.
+ */
+ private MetadataColumn(final Class<V> type, final short key) {
+ super(type, Vocabulary.formatInternational(key));
+ }
+
+ /**
+ * Invoked on deserialization for resolving this instance to one of the predefined constants.
+ *
+ * @return one of the predefined constants.
+ * @throws InvalidObjectException if this instance cannot be resolved.
+ */
+ private Object readResolve() throws ObjectStreamException {
+ return NIL_REASON; // For now this is the only column.
+ }
+}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java
index 43952c8d15..cd0d10aeea 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java
@@ -32,13 +32,15 @@ import org.opengis.metadata.citation.Citation;
import org.apache.sis.util.Classes;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.util.collection.TableColumn;
import org.apache.sis.util.collection.CheckedContainer;
+import org.apache.sis.util.internal.Strings;
import org.apache.sis.system.Configuration;
import org.apache.sis.system.Modules;
import org.apache.sis.system.Semaphores;
import org.apache.sis.system.SystemListener;
import org.apache.sis.metadata.simple.SimpleCitation;
-import org.apache.sis.util.internal.Strings;
+import org.apache.sis.xml.NilReason;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
@@ -89,7 +91,7 @@ import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
* by a large amount of {@link ModifiableMetadata}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
*
* @see AbstractMetadata
*
@@ -898,38 +900,45 @@ public class MetadataStandard implements Serializable {
* The tree table is backed by the metadata object using Java reflection, so changes in the
* underlying metadata object are immediately reflected in the tree table and conversely.
*
- * <p>The returned {@code TreeTable} instance contains the following columns:</p>
+ * <p>The returned {@code TreeTable} instance contains the columns listed below.
+ * The {@code (IDENTIFIER, INDEX)} pair of columns can be used as a primary key for uniquely identifying
+ * a node in a list of children. That uniqueness is guaranteed only for the children of a given node.
+ * The same keys may appear in the children of any other nodes.</p>
+ *
* <ul class="verbose">
- * <li>{@link org.apache.sis.util.collection.TableColumn#IDENTIFIER}<br>
+ * <li>{@link TableColumn#IDENTIFIER}<br>
* The {@linkplain org.opengis.annotation.UML#identifier() UML identifier} if any,
* or the Java Beans property name otherwise, of a metadata property. For example
* in a tree table view of {@link org.apache.sis.metadata.iso.citation.DefaultCitation},
* there is a node having the {@code "title"} identifier.</li>
*
- * <li>{@link org.apache.sis.util.collection.TableColumn#INDEX}<br>
+ * <li>{@link TableColumn#INDEX}<br>
* If the metadata property is a collection, then the zero-based index of the element in that collection.
* Otherwise {@code null}. For example, in a tree table view of {@code DefaultCitation}, if the
* {@code "alternateTitle"} collection contains two elements, then there is a node with index 0
- * for the first element and another node with index 1 for the second element.
+ * for the first element and another node with index 1 for the second element.</li>
*
- * <div class="note"><b>Note:</b>
- * The {@code (IDENTIFIER, INDEX)} pair can be used as a primary key for uniquely identifying a node
- * in a list of children. That uniqueness is guaranteed only for the children of a given node;
- * the same keys may appear in the children of any other nodes.</div></li>
- *
- * <li>{@link org.apache.sis.util.collection.TableColumn#NAME}<br>
+ * <li>{@link TableColumn#NAME}<br>
* A human-readable name for the node, derived from the identifier and the index.
* This is the column shown in the default {@link #toString()} implementation and
* may be localizable.</li>
*
- * <li>{@link org.apache.sis.util.collection.TableColumn#TYPE}<br>
+ * <li>{@link TableColumn#TYPE}<br>
* The base type of the value (usually an interface).</li>
*
- * <li>{@link org.apache.sis.util.collection.TableColumn#VALUE}<br>
+ * <li>{@link TableColumn#OBLIGATION}<br>
+ * Whether the property is mandatory, optional or conditional.</li>
+ *
+ * <li>{@link TableColumn#VALUE}<br>
* The metadata value for the node. Values in this column are writable if the underlying
* metadata class have a setter method for the property represented by the node.</li>
*
- * <li>{@link org.apache.sis.util.collection.TableColumn#REMARKS}<br>
+ * <li>{@code NIL_REASON}<br>
+ * If the property is mandatory and nevertheless absent, the reason why.
+ * This column is included only if {@code valuePolicy} accepts nil values.
+ * Values are instances of {@link NilReason}.</li>
+ *
+ * <li>{@link TableColumn#REMARKS}<br>
* Remarks or warning on the property value. This is rarely present.
* It is provided when the value may look surprising, for example the longitude values
* in a geographic bounding box crossing the anti-meridian.</li>
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyAccessor.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyAccessor.java
index 5f30786d4e..f753bcbd3b 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyAccessor.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyAccessor.java
@@ -26,6 +26,7 @@ import java.util.Collection;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import org.opengis.annotation.UML;
+import org.opengis.annotation.Obligation;
import org.opengis.metadata.ExtendedElementInformation;
import org.opengis.metadata.citation.Citation;
import org.apache.sis.util.Classes;
@@ -518,6 +519,22 @@ class PropertyAccessor {
return index;
}
+ /**
+ * Returns whether the property at the given index is mandatory, optional or conditional.
+ *
+ * @param index the index of the property for which to get the obligation.
+ * @return the obligation at the given index, or {@code null} if none or if the index is out of bounds.
+ */
+ final Obligation obligation(final int index) {
+ if (index >= 0 && index < names.length) {
+ final UML uml = getters[index].getAnnotation(UML.class);
+ if (uml != null) {
+ return uml.obligation();
+ }
+ }
+ return null;
+ }
+
/**
* Returns the name of the property at the given index, or {@code null} if none.
*
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 b6c29f80f6..41c4b0c3f8 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
@@ -25,7 +25,10 @@ import java.util.Objects;
import java.util.NoSuchElementException;
import java.util.ConcurrentModificationException;
import java.util.function.Function;
+import org.opengis.annotation.Obligation;
+import org.apache.sis.xml.NilReason;
import org.apache.sis.xml.bind.lan.LocaleAndCharset;
+import org.apache.sis.util.Debug;
import org.apache.sis.util.Classes;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ArgumentChecks;
@@ -40,13 +43,12 @@ import org.apache.sis.util.resources.Vocabulary;
/**
- * A node in a {@link TreeTableView} view. The {@code TreeTableView} class is used directly
- * only for the root node, or for nodes containing a fixed value instead of a value fetched from
- * the metadata object. For all other nodes, the actual node class shall be either {@link Element}
- * or {@link CollectionElement}.
+ * A node in a {@link TreeTableView} view. The {@code TreeNode} base class is used directly only for the root node,
+ * or for nodes containing a fixed value instead of a value fetched from the metadata object. For all other nodes,
+ * the actual node class shall be either {@link Element} or {@link CollectionElement}.
*
* <p>The value of a node is extracted from the {@linkplain #metadata} object by {@link #getUserObject()}.
- * For each instance of {@code TreeTableView}, that value is always a singleton, never a collection.
+ * For each instance of {@code TreeNode}, that value is always a singleton, never a collection.
* If a metadata property is a collection, then there is an instance of the {@link CollectionElement}
* subclass for each element in the collection.</p>
*
@@ -54,7 +56,7 @@ import org.apache.sis.util.resources.Vocabulary;
* set the identifier and the value, in that order, before any other operation on the new child.
* See {@code newChild()} javadoc for an example.</p>
*
- * <div class="note"><b>API note:</b>
+ * <h2>API note</h2>
* This class is not serializable because the values of the {@link Element#indexInData}
* and {@link CollectionElement#indexInList} fields may not be stable.
* The former may be invalid if the node is serialized and deserialized by two different versions of Apache SIS
@@ -66,8 +68,8 @@ import org.apache.sis.util.resources.Vocabulary;
*/
class TreeNode implements Node {
/**
- * The collection of {@linkplain #children} to return when the node does not allow children
- * (i.e. is a leaf). This constant is also used as a sentinel value by {@link #isLeaf()}.
+ * The collection of {@linkplain #children} to return when the node does not allow children.
+ * This constant is also used as a sentinel value by {@link #isLeaf()}.
*
* <p>We choose an empty set instead of an empty list because {@link TreeNodeChildren}
* does not implement the {@link List} interface. So we are better to never give to the user
@@ -76,8 +78,8 @@ class TreeNode implements Node {
private static final Collection<Node> LEAF = Set.of();
/**
- * The table for which this node is an element. Contains information like
- * the metadata standard and the value existence policy.
+ * The table for which this node is an element.
+ * Contains information like the metadata standard and the value existence policy.
*
* <p>All {@code TreeNode} instances in the same tree have
* a reference to the same {@code TreeTableView} instance.</p>
@@ -148,9 +150,20 @@ class TreeNode implements Node {
* twice in common situations like the {@link TreeTableView#toString()} implementation or in
* Graphical User Interface. However, we may remove this field in any future SIS version if
* experience shows that it is more problematic than helpful.</p>
+ *
+ * @see #getNonNilValue()
*/
transient Object cachedValue;
+ /**
+ * Whether {@link #cachedValue} can be used for the value of {@link TableColumn#VALUE}.
+ * This flag is set to {@code true} only by the {@link TreeNodeChildren} iterator,
+ * thus allowing the use of cached value in the {@code VALUE} column only after
+ * a call to {@link Iterator#next()} (for opportunistic reason), and only once.
+ * This restriction does not apply to {@link MetadataColumn#NIL_REASON}.
+ */
+ transient boolean canUseCache;
+
/**
* Creates the root node of a new metadata tree table.
*
@@ -200,6 +213,20 @@ class TreeNode implements Node {
return new CacheKey(metadata.getClass(), baseType);
}
+ /**
+ * Appends an identifier for this node in the given buffer, for {@link #toString()} implementation.
+ * The appended value is similar to the value returned by {@link #getIdentifier()} (except for the
+ * root node), but may contains additional information like the index in a collection.
+ *
+ * <p>The default implementation is suitable only for the root node - subclasses must override.</p>
+ *
+ * @param buffer the buffer where to complete the {@link #toString()} representation.
+ */
+ @Debug
+ void appendIdentifier(final StringBuilder buffer) {
+ buffer.append(Classes.getShortClassName(metadata));
+ }
+
/**
* Returns the UML identifier defined by the standard. The default implementation is suitable
* only for the root node, since it returns the class identifier. Subclasses must override in
@@ -232,6 +259,13 @@ class TreeNode implements Node {
table.standard.getInterface(key()))).toString();
}
+ /**
+ * Gets whether the property is mandatory, optional or conditional, or {@code null} if unspecified.
+ */
+ Obligation getObligation() {
+ return null;
+ }
+
/**
* Gets remarks about the value in this node, or {@code null} if none.
*/
@@ -240,14 +274,36 @@ class TreeNode implements Node {
}
/**
- * Appends an identifier for this node in the given buffer, for {@link #toString()} implementation.
- * The appended value is similar to the value returned by {@link #getIdentifier()} (except for the
- * root node), but may contains additional information like the index in a collection.
- *
- * <p>The default implementation is suitable only for the root node - subclasses must override.</p>
+ * 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}.
*/
- void appendIdentifier(final StringBuilder buffer) {
- buffer.append(Classes.getShortClassName(metadata));
+ private NilReason getNilReason() {
+ // Do not check `canUseCache` because it applies to TableColumn.VALUE.
+ if (cachedValue == null) {
+ cachedValue = getUserObject();
+ }
+ return NilReason.forObject(cachedValue);
+ }
+
+ /**
+ * Returns the property value, excluding nil value and using the cached value if available.
+ * Nil value are excluded because the reason why they are nil is reported in a separated column.
+ *
+ * <h4>Caching</h4>
+ * The cached value is set by {@link TreeNodeChildren} iterator and used only once for
+ * the value in {@link TableColumn#VALUE}. However, the cached value may be reused for
+ * the value in {@link MetadataColumn#NIL_REASON}.
+ */
+ private Object getNonNilValue() {
+ if (!canUseCache) {
+ cachedValue = getUserObject();
+ }
+ canUseCache = false; // Use the cached value only once after iteration.
+ if (table.valuePolicy.acceptNilValues() && NilReason.forObject(cachedValue) != null) {
+ return null;
+ }
+ return cachedValue;
}
/**
@@ -356,20 +412,22 @@ class TreeNode implements Node {
}
/**
- * The property identifier to be returned in the {@link TableColumn#IDENTIFIER} cells.
+ * Appends an identifier for this node in the given buffer, for {@link #toString()} implementation.
+ * This method is mostly for debugging purposes and is not used for the tree table node values.
*/
+ @Debug
@Override
- final String getIdentifier() {
- return accessor.name(indexInData, KeyNamePolicy.UML_IDENTIFIER);
+ void appendIdentifier(final StringBuilder buffer) {
+ super.appendIdentifier(buffer);
+ buffer.append('.').append(accessor.name(indexInData, KeyNamePolicy.JAVABEANS_PROPERTY));
}
/**
- * Appends an identifier for this node in the given buffer, for {@link #toString()} implementation.
+ * The property identifier to be returned in the {@link TableColumn#IDENTIFIER} cells.
*/
@Override
- void appendIdentifier(final StringBuilder buffer) {
- super.appendIdentifier(buffer);
- buffer.append('.').append(accessor.name(indexInData, KeyNamePolicy.JAVABEANS_PROPERTY));
+ final String getIdentifier() {
+ return accessor.name(indexInData, KeyNamePolicy.UML_IDENTIFIER);
}
/**
@@ -434,6 +492,14 @@ class TreeNode implements Node {
return type;
}
+ /**
+ * Gets whether the property is mandatory, optional or conditional, or {@code null} if unspecified.
+ */
+ @Override
+ Obligation getObligation() {
+ return accessor.obligation(indexInData);
+ }
+
/**
* Gets remarks about the value in this node, or {@code null} if none.
*/
@@ -514,7 +580,9 @@ class TreeNode implements Node {
/**
* Appends an identifier for this node in the given buffer, for {@link #toString()} implementation.
+ * This method is mostly for debugging purposes and is not used for the tree table node values.
*/
+ @Debug
@Override
void appendIdentifier(final StringBuilder buffer) {
super.appendIdentifier(buffer);
@@ -675,25 +743,21 @@ class TreeNode implements Node {
* in which case we do not need to check for changes in the underlying metadata.
*/
if (!isLeaf()) {
- Object value = cachedValue;
+ Object value = getNonNilValue();
if (value == null) {
- value = getUserObject();
- if (value == null) {
- /*
- * If there is no value, returns an empty set but *do not* set `children`
- * to that set, in order to allow this method to check again the next time
- * that this method is invoked.
- */
- children = null; // Let GC do its work.
- return LEAF;
- }
+ /*
+ * If there is no value, returns an empty set but *do not* set `children`
+ * to that set, in order to allow this method to check again the next time
+ * that this method is invoked.
+ */
+ children = null; // Let GC do its work.
+ return LEAF;
}
- cachedValue = null; // Use the cached value only once after iteration.
/*
* If there is a value, check if the cached collection is still applicable.
* We verify that the collection is a wrapper for the same metadata object.
* If we need to create a new collection, we know that the property accessor
- * exists otherwise the call to `isLeaf()` above would have returned 'true'.
+ * exists otherwise the call to `isLeaf()` above would have returned `true`.
*/
if (children == null || ((TreeNodeChildren) children).metadata != value) {
PropertyAccessor accessor = table.standard.getAccessor(new CacheKey(value.getClass(), baseType), true);
@@ -807,7 +871,7 @@ class TreeNode implements Node {
}
delegate = siblings.childAt(indexInData, indexInList);
/*
- * Do not set 'delegate.cachedValue = value', since `value` may
+ * Do not set `delegate.cachedValue = value`, since `value` may
* have been converted by the setter method to another value.
*/
return;
@@ -869,17 +933,17 @@ class TreeNode implements Node {
}
} else if (column == TableColumn.VALUE) {
if (isLeaf()) {
- value = cachedValue;
- cachedValue = null; // Use the cached value only once after iteration.
- if (value == null) {
- value = getUserObject();
- }
+ value = getNonNilValue();
} else {
final TreeNodeChildren children = getCompactChildren();
if (children != null) {
value = children.getParentTitle();
}
}
+ } else if (column == MetadataColumn.OBLIGATION) {
+ value = getObligation();
+ } else if (column == MetadataColumn.NIL_REASON) {
+ value = getNilReason();
} else if (column == TableColumn.REMARKS) {
value = getRemarks();
}
@@ -901,11 +965,12 @@ class TreeNode implements Node {
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 (TreeTableView.COLUMNS.contains(column)) {
+ } else if (table.getColumns().contains(column)) {
throw new UnsupportedOperationException(unmodifiableCellValue(column));
} else {
throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "column", column));
@@ -942,7 +1007,9 @@ class TreeNode implements Node {
/**
* Implementation of {@link #toString()} appending the string representation in the given buffer.
+ * This method is mostly for debugging purposes and is not used for the tree table node values.
*/
+ @Debug
final void appendStringTo(final StringBuilder buffer) {
appendIdentifier(buffer.append("Node["));
buffer.append(" : ").append(Classes.getShortName(baseType)).append(']');
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java
index 2a30fe6987..98e45d8108 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java
@@ -66,18 +66,17 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
* This is typically an {@link AbstractMetadata} instance, but not necessarily.
* Any type for which {@link MetadataStandard#isMetadata(Class)} returns {@code true} is okay.
*
- * <p>This field is a snapshot of the {@linkplain #parent} {@link TreeNode#getUserObject()} at
- * creation time. This collection does not track changes in the reference returned by the above-cited
- * {@code getUserObject()}. In other words, changes in the {@code metadata} object will be reflected
- * in this collection, but if {@code parent.getUserObject()} returns a reference to another object,
- * this change will not be reflected in this collection.
+ * <p>This field is a snapshot of the {@linkplain #parent} {@link TreeNode#getUserObject()} at creation time.
+ * This collection does not track changes in the reference returned by the above-cited {@code getUserObject()}.
+ * In other words, changes in the {@code metadata} object will be reflected in this collection,
+ * but if {@code parent.getUserObject()} returns a reference to another object,
+ * then this change will not be reflected in this collection.
*/
final Object metadata;
/**
- * The accessor to use for accessing the property names, types and values of the
- * {@link #metadata} object. This is given at construction time and shall be the
- * same than the following code:
+ * The accessor to use for accessing the property names, types and values of the {@link #metadata} object.
+ * This is given at construction time and shall be the same than the following code:
*
* {@snippet lang="java" :
* accessor = parent.table.standard.getAccessor(metadata.getClass(), true);
@@ -121,7 +120,7 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
* is done on a <cite>best effort basis</cite> only, since we cannot not track the changes which
* are done independently in the {@linkplain #metadata} object.
*/
- int modCount;
+ private int modCount;
/**
* Creates a collection of children for the specified metadata.
@@ -200,8 +199,8 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
* This method sets the property to {@code null}. This is not strictly correct for collections,
* since we should rather set the property to an empty collection. However, this approach would
* force us to check if the expected collection type is actually a list, a set or any other type.
- * Passing null avoid the type check and is safe at least with SIS implementation. We may revisit
- * later if this appears to be a problem with other implementations.
+ * Passing null avoid the type check and is safe at least with SIS implementation.
+ * We may revisit later if this appears to be a problem with other implementations.
*
* @param index the index in the accessor (<em>not</em> the index in this collection).
*/
@@ -374,8 +373,8 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
/**
* The value of the node to be returned by the {@link #next()} method. This value is computed
- * ahead of time by {@link #hasNext()} since we need that information in order to determine
- * if the value needs to be skipped or not.
+ * ahead of time by {@link #hasNext()} because we need that information in order to determine
+ * if the value should be skipped or not.
*
* <h4>Implementation note</h4>
* Actually we don't really need to keep this value, since it is not used outside the {@link #hasNext()}
@@ -384,6 +383,12 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
*/
private Object nextValue;
+ /**
+ * The node returned by the last call to {@link #next()}. This is used for clearing the
+ * {@link TreeNode#cachedValue} field when the iterator moves to the next element.
+ */
+ private TreeNode current;
+
/**
* If the call to {@link #next()} found a collection, the iterator over the elements in that collection.
* Otherwise {@code null}. A non-null value (even if that sub-iterator has no next elements) means that
@@ -417,8 +422,15 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
/**
* Throws {@link ConcurrentModificationException} if an unexpected change has been detected.
+ * Also opportunistically clears the cached value of the previous node, since this method is
+ * invoked either before moving to the next node or for removing the current node.
*/
- final void checkConcurrentModification() {
+ private void checkConcurrentModification() {
+ if (current != null) {
+ current.canUseCache = false;
+ current.cachedValue = null;
+ current = null;
+ }
if (modCountCheck != modCount) {
throw new ConcurrentModificationException();
}
@@ -438,7 +450,7 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
}
/*
* If we were iterating over the elements of a sub-collection, move to the next element
- * in that iteration. We do not check for 'isSkipped(value)' here because null or empty
+ * in that iteration. We do not check for `isSkipped(value)` here because null or empty
* elements in collections are probably mistakes, and we want to see them.
*/
if (subIterator != null) {
@@ -477,8 +489,8 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
/*
* If the property is a collection, unconditionally get the first element
* even if absent (null) in order to comply with the ValueExistencePolicy.
- * if we were expected to ignore empty collections, 'isSkipped(nextValue)'
- * would have returned 'true'.
+ * if we were expected to ignore empty collections, `isSkipped(nextValue)`
+ * would have returned `true`.
*/
subIndex = 0;
if (subIterator.hasNext()) {
@@ -486,7 +498,7 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
} else {
nextValue = null;
/*
- * Do not set 'childIterator' to null, since the above 'nextValue'
+ * Do not set `childIterator` to null, because the above `nextValue`
* is considered as part of the child iteration.
*/
}
@@ -502,26 +514,28 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
/**
* Returns the node for the metadata property at the current {@link #nextInAccessor}.
- * The value of this property is initially {@link #nextValue}, but this may change at
- * any time if the user modifies the underlying metadata object.
+ * The value in `TableColumn.VALUE` is initially set to {@link #nextValue},
+ * but may change later if the user modifies the underlying metadata object.
*/
@Override
public TreeTable.Node next() {
if (hasNext()) {
final TreeNode.Element node = childAt(nextInAccessor, subIndex);
- node.cachedValue = nextValue;
+ node.canUseCache = true;
+ node.cachedValue = nextValue;
previousInAccessor = nextInAccessor;
if (subIterator == null) {
/*
* If we are iterating over the elements in a collection, the PropertyAccessor index
- * still the same and will be incremented by 'hasNext()' only when the iteration is
+ * still the same and will be incremented by `hasNext()` only when the iteration is
* over. Otherwise (not iterating in a collection), move to the next property. The
- * 'hasNext()' method will determine later if this property is non-empty, or if we
+ * `hasNext()` method will determine later if this property is non-empty, or if we
* need to move forward again.
*/
nextInAccessor++;
}
isNextVerified = false;
+ current = node;
return (node.decorator == null) ? node : node.decorator.apply(node);
}
throw new NoSuchElementException();
@@ -530,8 +544,8 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
/**
* Clears the element returned by the last call to {@link #next()}.
* Whether the cleared element is considered removed or not depends
- * on the value policy and on the element type. With the default
- * {@code NON_EMPTY} policy, the effect is a removal.
+ * on the value policy and on the element type.
+ * With the default {@code NON_EMPTY} policy, the effect is a removal.
*/
@Override
public void remove() {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeTableView.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeTableView.java
index c1ce0f9c6a..5a1e0d5d71 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeTableView.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeTableView.java
@@ -23,6 +23,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.function.Predicate;
import org.opengis.metadata.citation.Citation;
+import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.collection.TreeTable;
import org.apache.sis.util.collection.TableColumn;
import org.apache.sis.util.collection.TreeTableFormat;
@@ -38,12 +39,14 @@ import org.apache.sis.system.Semaphores;
* The tree table is made of the following columns:
*
* <ul>
- * <li>{@link TableColumn#IDENTIFIER} - the property identifier as defined by the UML (if any).</li>
- * <li>{@link TableColumn#INDEX} - the index in the collection, or null if the property is not a collection.</li>
- * <li>{@link TableColumn#NAME} - the human-readable property name, inferred from the identifier and index.</li>
- * <li>{@link TableColumn#TYPE} - the base interface of property values.</li>
- * <li>{@link TableColumn#VALUE} - the property value.</li>
- * <li>{@link TableColumn#REMARKS} - remarks on the property value.</li>
+ * <li>{@link MetadataColumn#IDENTIFIER} - the property identifier as defined by the UML (if any).</li>
+ * <li>{@link MetadataColumn#INDEX} - the index in the collection, or null if the property is not a collection.</li>
+ * <li>{@link MetadataColumn#NAME} - the human-readable property name, inferred from the identifier and index.</li>
+ * <li>{@link MetadataColumn#TYPE} - the base interface of property values.</li>
+ * <li>{@link MetadataColumn#OBLIGATION} - whether the property is mandatory, optional or conditional.</li>
+ * <li>{@link MetadataColumn#VALUE} - the property value.</li>
+ * <li>{@link MetadataColumn#NIL_REASON} - if the property is mandatory and nevertheless absent, the reason why.</li>
+ * <li>{@link MetadataColumn#REMARKS} - remarks on the property value.</li>
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
@@ -56,15 +59,25 @@ final class TreeTableView implements TreeTable, TreeFormatCustomization, Seriali
/**
* The columns to be returned by {@link #getColumns()}.
+ * The filtered columns are the columns without the nil reason.
+ * The latter column is useless if {@link ValueExistencePolicy} is excluding nil values.
*/
- static final List<TableColumn<?>> COLUMNS = UnmodifiableArrayList.wrap(new TableColumn<?>[] {
- TableColumn.IDENTIFIER,
- TableColumn.INDEX,
- TableColumn.NAME,
- TableColumn.TYPE,
- TableColumn.VALUE,
- TableColumn.REMARKS
- });
+ private static final List<TableColumn<?>> COLUMNS, FILTERED_COLUMNS;
+ static {
+ var columns = new TableColumn<?>[] {
+ MetadataColumn.IDENTIFIER,
+ MetadataColumn.INDEX,
+ MetadataColumn.NAME,
+ MetadataColumn.TYPE,
+ MetadataColumn.OBLIGATION,
+ MetadataColumn.VALUE,
+ MetadataColumn.NIL_REASON,
+ MetadataColumn.REMARKS
+ };
+ COLUMNS = UnmodifiableArrayList.wrap(columns);
+ columns = ArraysExt.remove(columns, 6, 1);
+ FILTERED_COLUMNS = UnmodifiableArrayList.wrap(columns);
+ }
/**
* The root of the metadata tree.
@@ -100,12 +113,12 @@ final class TreeTableView implements TreeTable, TreeFormatCustomization, Seriali
}
/**
- * Returns the columns included in this tree table.
+ * {@return the columns included in this tree table}.
*/
@Override
- @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ @SuppressWarnings("ReturnOfCollectionOrArrayField") // Because the returned collection is unmodifiable.
public List<TableColumn<?>> getColumns() {
- return COLUMNS; // Unmodifiable
+ return valuePolicy.acceptNilValues() ? COLUMNS : FILTERED_COLUMNS;
}
/**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ValueExistencePolicy.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ValueExistencePolicy.java
index 677381447a..0939e7e1bc 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ValueExistencePolicy.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ValueExistencePolicy.java
@@ -45,6 +45,11 @@ import org.apache.sis.xml.NilReason;
* @since 0.3
*/
public enum ValueExistencePolicy {
+ /*
+ * Implementation note: enumeration order matter.
+ * The `acceptNilValues()` method relies on it.
+ */
+
/**
* Includes all entries in the map, including those having a null value or an empty collection.
*/
@@ -180,6 +185,13 @@ public enum ValueExistencePolicy {
}
};
+ /**
+ * {@return whether this policy accepts nil values}.
+ */
+ final boolean acceptNilValues() {
+ return ordinal() < NON_NIL.ordinal();
+ }
+
/**
* Returns {@code true} if the given value shall be skipped for this policy.
*/
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 24c207efe6..2b5308b6f6 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
@@ -19,21 +19,22 @@
* Root package for various metadata implementations.
*
* <h2>Foreword</h2>
- * Many metadata standards exist, including <cite>Dublin core</cite>, <cite>ISO 19115</cite> and the Image I/O
- * metadata defined in {@link javax.imageio.metadata}. The SIS implementation focuses on ISO 19115 (including
- * its ISO 19115-2 extension), but the classes are designed in a way that allow the usage of different standards.
+ * Many metadata standards exist, including Dublin core, ISO 19115
+ * and the Image I/O metadata defined in {@link javax.imageio.metadata} package.
+ * The SIS implementation focuses on the ISO 19115 series of standards,
+ * but the classes are designed in a way that allow the usage of different standards.
* This genericity goal should be keep in mind in the discussion below.
*
* <h2>How Metadata are defined</h2>
- * A metadata standard is defined by a set of Java interfaces belonging to a specific package and its sub-packages.
- * For example, the ISO 19115 standard is defined by the <a href="http://www.geoapi.org">GeoAPI</a> interfaces
+ * A metadata standard is reified by a set of Java interfaces belonging to a specific package and its sub-packages.
+ * For example, the ISO 19115 standard is reified by the <a href="http://www.geoapi.org">GeoAPI</a> interfaces
* defined in the {@link org.opengis.metadata} package and sub-packages. That standard is identified in SIS by the
* {@link org.apache.sis.metadata.MetadataStandard#ISO_19115} constant. Other standards are defined as well,
* for example the {@link org.apache.sis.metadata.MetadataStandard#ISO_19123} constant stands for the standards
* defined by the interfaces in the {@link org.opengis.coverage} package and sub-packages.
*
- * <p>For each interface, the collection of declared getter methods defines its <cite>properties</cite>
- * (or <cite>attributes</cite>). If a {@link org.opengis.annotation.UML} annotation is attached to the getter method,
+ * <p>For each interface, the collection of declared getter methods defines its <dfn>properties</dfn>
+ * (or <dfn>attributes</dfn>). If a {@link org.opengis.annotation.UML} annotation is attached to the getter method,
* the identifier declared in that annotation is taken as the property name. This is typically the name defined by the
* International Standard from which the interface is derived. Otherwise (if there is no {@code UML} annotation)
* the property name is inferred from the method name like what the <cite>Java Beans</cite> framework does.</p>
@@ -48,7 +49,7 @@
* <ul class="verbose">
* <li>The {@code Abstract} prefix means that the class is abstract in the sense of the implemented standard.
* It it not necessarily abstract in the sense of Java. Because incomplete metadata are common in practice,
- * sometimes we wish to instantiate an "abstract" class despite the lack of knowledge about the exact sub-type.</li>
+ * sometimes we wish to instantiate an "abstract" class because of the lack of knowledge about the exact sub-type.</li>
* <li>The properties are determined by the getter methods declared in the interfaces.
* Getter methods declared in the implementation classes are ignored.</li>
* <li>Setter methods, if any, can be declared in the implementation classes without the need for declarations
@@ -66,10 +67,10 @@
*
* <p>In addition, the metadata modules provide support methods for handling the metadata objects through Java Reflection.
* This is an approach similar to <cite>Java Beans</cite>, in that users are encouraged to use directly the API of
- * <cite>Plain Old Java</cite> objects (actually interfaces) every time their type is known at compile time,
+ * Plain Old Java objects (actually interfaces) every time their type is known at compile time,
* and fallback on the reflection technic when the type is known only at runtime.</p>
*
- * <p>Using Java reflection, a metadata can be viewed in many different ways:</p>
+ * <p>Using Java reflection, a metadata can be viewed in different ways:</p>
* <ul class="verbose">
* <li><b>As a {@link java.util.Map}</b><br>
* The {@link org.apache.sis.metadata.MetadataStandard} class provides various methods returning a view
@@ -119,7 +120,7 @@
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Adrian Custer (Geomatys)
- * @version 1.4
+ * @version 1.5
* @since 0.3
*/
package org.apache.sis.metadata;
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 644acb683a..b129fc86f9 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
@@ -16,12 +16,20 @@
*/
package org.apache.sis.metadata;
+import java.util.Locale;
+import java.util.Iterator;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import org.opengis.annotation.Obligation;
+import org.opengis.util.InternationalString;
import org.opengis.metadata.citation.Citation;
+import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.xml.NilReason;
// Test dependencies
import org.junit.Test;
@@ -29,7 +37,7 @@ import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.DependsOn;
import org.apache.sis.test.TestCase;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
import static org.apache.sis.test.Assertions.assertMultilinesEquals;
import static org.apache.sis.test.TestUtilities.toTreeStructure;
import static org.apache.sis.test.TestUtilities.formatMetadata;
@@ -86,10 +94,79 @@ public final class TreeTableViewTest extends TestCase {
@Test
public void testToString() {
final TreeTableView metadata = create(ValueExistencePolicy.COMPACT);
+ assertFalse(metadata.getColumns().contains(MetadataColumn.NIL_REASON));
assertMultilinesEquals(EXPECTED, formatMetadata(metadata)); // Locale-independent
assertArrayEquals(toTreeStructure(EXPECTED), toTreeStructure(metadata.toString())); // Locale-dependent.
}
+ /**
+ * Verifies most columns in the tree table. All nil reasons are null.
+ */
+ @Test
+ public void testGetValues() {
+ final TreeTableView metadata = create(ValueExistencePolicy.NON_NULL);
+ assertTrue(metadata.getColumns().contains(MetadataColumn.NIL_REASON));
+ verify(metadata.getRoot(), "Some title", null);
+ }
+
+ /**
+ * Verifies columns in the tree table with some non-null nil reasons.
+ */
+ @Test
+ public void testNilReasons() {
+ final TreeTableView metadata = create(ValueExistencePolicy.NON_NULL);
+ assertTrue(metadata.getColumns().contains(MetadataColumn.NIL_REASON));
+ final var citation = (DefaultCitation) metadata.getRoot().getUserObject();
+ citation.setTitle(NilReason.TEMPLATE.createNilObject(InternationalString.class));
+ verify(metadata.getRoot(), null, NilReason.TEMPLATE);
+ }
+
+ /**
+ * Verifies the values of the given root node and some of its children.
+ *
+ * @param node root node to verify.
+ * @param title expected citation title, or {@code null} if it is expected to be missing.
+ * @param titleNR if the title is missing, the expected reason why.
+ */
+ private void verify(TreeTableView.Node node, final String title, final NilReason titleNR) {
+ assertEquals("CI_Citation", node.getValue(TableColumn.IDENTIFIER));
+ assertNull ( node.getValue(TableColumn.INDEX));
+ assertEquals("Citation", node.getValue(TableColumn.NAME));
+ assertEquals(Citation.class, node.getValue(TableColumn.TYPE));
+ assertNull ( node.getValue(TableColumn.OBLIGATION));
+ assertNull ( node.getValue(TableColumn.VALUE));
+ assertNull ( node.getValue(MetadataColumn.NIL_REASON));
+
+ Iterator<TreeTable.Node> it = node.getChildren().iterator();
+ node = it.next();
+ assertEquals("title", node.getValue(TableColumn.IDENTIFIER));
+ assertNull ( node.getValue(TableColumn.INDEX));
+ assertEquals("Title", node.getValue(TableColumn.NAME));
+ assertEquals(InternationalString.class, node.getValue(TableColumn.TYPE));
+ assertEquals(Obligation.MANDATORY, node.getValue(TableColumn.OBLIGATION));
+ assertI18nEq(title, node.getValue(TableColumn.VALUE));
+ assertEquals(titleNR, node.getValue(MetadataColumn.NIL_REASON));
+
+ node = it.next();
+ assertEquals("alternateTitle", node.getValue(TableColumn.IDENTIFIER));
+ assertEquals(0, node.getValue(TableColumn.INDEX));
+ assertI18nEq("Alternate title (1 of 2)", node.getValue(TableColumn.NAME));
+ assertEquals(InternationalString.class, node.getValue(TableColumn.TYPE));
+ assertEquals(Obligation.OPTIONAL, node.getValue(TableColumn.OBLIGATION));
+ assertI18nEq("First alternate title", node.getValue(TableColumn.VALUE));
+ assertNull ( node.getValue(MetadataColumn.NIL_REASON));
+ }
+
+ /**
+ * Verifies the value of the given international string in English.
+ */
+ private static void assertI18nEq(final String expected, Object text) {
+ if (text instanceof InternationalString) {
+ text = ((InternationalString) text).toString(Locale.ENGLISH);
+ }
+ assertEquals(expected, text);
+ }
+
/**
* Tests serialization.
*
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TableColumn.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TableColumn.java
index b3e441ddfa..f4d8418d51 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TableColumn.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TableColumn.java
@@ -20,6 +20,7 @@ import java.util.Map;
import java.io.Serializable;
import java.io.ObjectStreamException;
import java.io.InvalidObjectException;
+import org.opengis.annotation.Obligation;
import org.opengis.util.InternationalString;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.SimpleInternationalString;
@@ -91,7 +92,7 @@ import org.apache.sis.util.resources.Vocabulary;
* The constants defined in this class use a similar approach for providing serialization support.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.5
*
* @param <V> base type of all values in the column identified by this instance.
*
@@ -99,7 +100,7 @@ import org.apache.sis.util.resources.Vocabulary;
*/
public class TableColumn<V> implements CheckedContainer<V> {
/**
- * Frequently-used constant for a column of object names.
+ * Predefined constant for a column of object names.
* The column {@linkplain #getHeader() header} is <q>Name</q> (eventually localized) and
* the column elements are typically instances of {@link String} or {@link InternationalString},
* depending on whether the data provide localization support or not.
@@ -108,7 +109,7 @@ public class TableColumn<V> implements CheckedContainer<V> {
CharSequence.class, Vocabulary.Keys.Name);
/**
- * Frequently-used constant for a column of object identifiers.
+ * Predefined constant for a column of object identifiers.
* The column {@linkplain #getHeader() header} is <q>Identifier</q> (eventually localized)
* and the column elements are instances of {@link String}.
*/
@@ -116,7 +117,7 @@ public class TableColumn<V> implements CheckedContainer<V> {
String.class, Vocabulary.Keys.Identifier);
/**
- * Frequently-used constant for a column of index values.
+ * Predefined constant for a column of index values.
* The column {@linkplain #getHeader() header} is <q>Index</q> (eventually localized)
* and the column elements are instances of {@link Integer}.
*/
@@ -124,7 +125,7 @@ public class TableColumn<V> implements CheckedContainer<V> {
Integer.class, Vocabulary.Keys.Index);
/**
- * Frequently-used constant for a column of object types.
+ * Predefined constant for a column of object types.
* The column {@linkplain #getHeader() header} is <q>Type</q> (eventually localized).
*/
@SuppressWarnings("unchecked")
@@ -132,7 +133,17 @@ public class TableColumn<V> implements CheckedContainer<V> {
(Class) Class.class, Vocabulary.Keys.Type);
/**
- * Frequently-used constant for a column of object values.
+ * Predefined constant for a column of obligation (mandatory, optional, conditional).
+ * The column {@linkplain #getHeader() header} is <q>Obligation</q> (eventually localized)
+ * and the column elements are instances of {@link Obligation}.
+ *
+ * @since 1.5
+ */
+ public static final TableColumn<Obligation> OBLIGATION = new Constant<>("OBLIGATION",
+ Obligation.class, Vocabulary.Keys.Obligation);
+
+ /**
+ * Predefined constant for a column of object values.
* The column {@linkplain #getHeader() header} is <q>Value</q> (eventually localized) and
* the column elements can be instance of any kind of objects.
*
@@ -143,7 +154,7 @@ public class TableColumn<V> implements CheckedContainer<V> {
Object.class, Vocabulary.Keys.Value);
/**
- * Frequently-used constant for a column of object textual values.
+ * Predefined constant for a column of object textual values.
* The column {@linkplain #getHeader() header} is <q>Value</q> (eventually localized) and
* the column elements are typically instances of {@link String} or {@link InternationalString},
* depending on whether the data provide localization support or not.
@@ -152,14 +163,14 @@ public class TableColumn<V> implements CheckedContainer<V> {
CharSequence.class, Vocabulary.Keys.Value);
/**
- * Frequently-used constant for a column of object numerical values.
+ * Predefined constant for a column of object numerical values.
* The column {@linkplain #getHeader() header} is <q>Value</q> (eventually localized).
*/
public static final TableColumn<Number> VALUE_AS_NUMBER = new Constant<>("VALUE_AS_NUMBER",
Number.class, Vocabulary.Keys.Value);
/**
- * Frequently-used constant for a column of remarks.
+ * Predefined constant for a column of remarks.
* The column {@linkplain #getHeader() header} is <q>Remarks</q> (eventually localized) and
* the column elements are typically instances of {@link String} or {@link InternationalString},
* depending on whether the data provide localization support or not.
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 c56c3ff649..9a581e9c82 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
@@ -869,6 +869,11 @@ public class Vocabulary extends IndexedResourceBundle {
*/
public static final short NearestNeighbor = 232;
+ /**
+ * Nil reason
+ */
+ public static final short NilReason = 274;
+
/**
* No data
*/
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
index 3c760931aa..4e60a35b3c 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
@@ -178,6 +178,7 @@ More_1 = \u2026 {0} more\u2026
Multiplicity = Multiplicity
Name = Name
NearestNeighbor = Nearest neighbor
+NilReason = Nil reason
Nodata = No data
None = None
Note = Note
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
index 0061fa151a..5c2178784e 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -185,6 +185,7 @@ More_1 = \u2026 {0} de plus\u2026
Multiplicity = Multiplicit\u00e9
Name = Nom
NearestNeighbor = Plus proche voisin
+NilReason = Raison de l\u2019absence
Nodata = Absence de donn\u00e9es
None = Aucun
Note = Note
diff --git a/netbeans-project/nbproject/project.xml b/netbeans-project/nbproject/project.xml
index 7c5ceb94b7..8fce63d8e7 100644
--- a/netbeans-project/nbproject/project.xml
+++ b/netbeans-project/nbproject/project.xml
@@ -27,5 +27,8 @@
<root id="incubator.test.dir" pathref="incubator.test.dir.path"/>
</test-roots>
</data>
+ <spellchecker-wordlist xmlns="http://www.netbeans.org/ns/spellchecker-wordlist/1">
+ <word>genericity</word>
+ </spellchecker-wordlist>
</configuration>
</project>