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 <[email protected]> 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>
