This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 3123aa404b0a6c489b08e45c95829ded05caa423 Author: Martin Desruisseaux <[email protected]> AuthorDate: Fri May 15 15:34:37 2026 +0200 Replace the internal `TreeFormatCustomization` interface by a public `TreeTable.Node.isVisible()` method. --- .../main/org/apache/sis/metadata/TreeNode.java | 84 ++++++++++++++++++---- .../org/apache/sis/metadata/TreeNodeChildren.java | 9 ++- .../org/apache/sis/metadata/TreeTableView.java | 55 +------------- .../apache/sis/metadata/TreeTableFormatTest.java | 15 ++-- .../org/apache/sis/util/collection/TreeTable.java | 18 ++++- .../sis/util/collection/TreeTableFormat.java | 40 ++++------- .../internal/shared/TreeFormatCustomization.java | 51 ------------- 7 files changed, 118 insertions(+), 154 deletions(-) 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 6c44ff7bb4..a4abb46eba 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 @@ -26,7 +26,10 @@ import java.util.NoSuchElementException; import java.util.ConcurrentModificationException; import java.util.function.Function; import org.opengis.annotation.Obligation; +import org.opengis.metadata.citation.Citation; import org.apache.sis.xml.NilReason; +import org.apache.sis.xml.bind.NonMarshalledAuthority; +import org.apache.sis.xml.bind.SpecializedIdentifier; import org.apache.sis.xml.bind.lan.LocaleAndCharset; import org.apache.sis.util.Debug; import org.apache.sis.util.Classes; @@ -403,7 +406,7 @@ class TreeNode implements Node { * as a {@link java.util.Locale} node with a {@link java.nio.charset.Charset} child. This separation is done by * {@link LocaleAndCharset}. */ - final Function<TreeNode,Node> decorator; + final Function<TreeNode, Node> decorator; /** * Creates a new child for a property of the given metadata at the given index. @@ -543,7 +546,7 @@ class TreeNode implements Node { * Gets whether the property is mandatory, optional or conditional, or {@code null} if unspecified. */ @Override - Obligation getObligation() { + final Obligation getObligation() { return accessor.obligation(indexInData); } @@ -605,7 +608,7 @@ class TreeNode implements Node { /** * A node for an element in a collection. This class needs the iteration order to be stable. */ - static final class CollectionElement extends Element { + static class CollectionElement extends Element { /** * Index of the element in the collection, in iteration order. */ @@ -633,7 +636,7 @@ class TreeNode implements Node { */ @Debug @Override - void appendIdentifier(final StringBuilder buffer) { + final void appendIdentifier(final StringBuilder buffer) { super.appendIdentifier(buffer); buffer.append('[').append(indexInList).append(']'); } @@ -642,7 +645,7 @@ class TreeNode implements Node { * Returns the zero-based index of this node in the metadata property. */ @Override - Integer getIndex() { + final Integer getIndex() { return indexInList; } @@ -651,7 +654,7 @@ class TreeNode implements Node { * Index numbering begins at 1, since this name if for human reading. */ @Override - CharSequence getName() { + final CharSequence getName() { CharSequence name = super.getName(); final int size = PropertyAccessor.size(super.getUserObject()); if (size >= 2) { @@ -665,7 +668,7 @@ class TreeNode implements Node { * then fetch the element at the index represented by this node. */ @Override - public Object getUserObject() { + public final Object getUserObject() { final Object collection = super.getUserObject(); final Collection<?> values; if (collection instanceof Collection<?>) { @@ -709,7 +712,7 @@ class TreeNode implements Node { * Sets the property value for this node. */ @Override - void setUserObject(Object value) { + final void setUserObject(Object value) { cachedValue = null; canUseCache = false; final Collection<?> values = (Collection<?>) super.getUserObject(); @@ -748,7 +751,7 @@ class TreeNode implements Node { * This is a bit unusual, since nil reasons usually apply to the whole property. */ @Override - NilReason getNilReason() { + final NilReason getNilReason() { // Do not check `canUseCache` because it applies to TableColumn.VALUE. if (cachedValue == null) cachedValue = getUserObject(); return NilReason.forObject(cachedValue); @@ -760,7 +763,7 @@ class TreeNode implements Node { * This is a bit unusual, since nil reasons usually apply to the whole property. */ @Override - void setNilReason(final NilReason value) { + final void setNilReason(final NilReason value) { setUserObject(value != null ? value.createNilObject(baseType) : null); } @@ -769,7 +772,7 @@ class TreeNode implements Node { * should be the same for both nodes. */ @Override - public boolean equals(final Object other) { + public final boolean equals(final Object other) { return super.equals(other) && ((CollectionElement) other).indexInList == indexInList; } @@ -777,13 +780,68 @@ class TreeNode implements Node { * Returns a hash code value for this node. */ @Override - public int hashCode() { + public final int hashCode() { return super.hashCode() ^ indexInList; } } - // -------- Final methods (defined in terms of above methods only) ---------------------------- + + + /** + * Special case for a node for which the parent is a {@link Citation}. + * This node hides the <abbr>ISBN</abbr> and <abbr>ISSN</abbr> identifiers, + * because they will be formatted in the {@code ISBN} and {@code ISSN} properties instead. + * We apply this filtering for avoiding redundancies in the tree representation. + */ + static final class CitationElement extends CollectionElement { + /** + * Creates a new node for the given collection element. + * + * @param parent the parent of this node. Shall be for a {@link Citation}. + * @param metadata the metadata object for which this node will be a value. + * @param accessor accessor to use for fetching the name, type and collection. + * @param indexInData index to be given to the accessor of fetching the collection. + * @param indexInList index of the element in the collection, in iteration order. + */ + CitationElement(final TreeNode parent, final Object metadata, + final PropertyAccessor accessor, final int indexInData, final int indexInList) + { + super(parent, metadata, accessor, indexInData, indexInList); + } + + /** + * Returns {@code false} if the value is an <abbr>ISBN</abbr> or <abbr>ISSN</abbr> identifier. + * Those identifiers will be formatted in the {@code ISBN} and {@code ISSN} properties instead. + * + * <p>Since this method is invoked (indirectly) during iteration over the children, the value + * may have been cached by {@link org.apache.sis.metadata.TreeNodeChildren.Iter#next()}. + * This method tries to use the cached value instead of computing it again.</p> + */ + @Override + public boolean isVisible() { + Object value = cachedValue; + if (value == null) { + value = getUserObject(); + } + if (value instanceof SpecializedIdentifier) { + final Citation authority = ((SpecializedIdentifier) value).getAuthority(); + if (authority instanceof NonMarshalledAuthority && ((NonMarshalledAuthority) authority).isBookOrSerialNumber()) { + return false; + } + } + return super.isVisible(); + } + } + + + + + // ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐ + // │ Final methods (defined in terms of above methods only) │ + // └───────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + /** 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 d3b480119a..95d0fce1ab 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 @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.AbstractCollection; import java.util.NoSuchElementException; import java.util.ConcurrentModificationException; +import org.opengis.metadata.citation.Citation; import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.collection.TableColumn; import org.apache.sis.util.collection.TreeTable; @@ -269,7 +270,11 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> { * A ClassCastException below would be a logical error in this class. */ if (node == null || ((TreeNode.CollectionElement) node).indexInList != subIndex) { - node = new TreeNode.CollectionElement(parent, metadata, accessor, index, subIndex); + if (Citation.class.isAssignableFrom(parent.baseType)) { + node = new TreeNode.CitationElement(parent, metadata, accessor, index, subIndex); + } else { + node = new TreeNode.CollectionElement(parent, metadata, accessor, index, subIndex); + } } } else { /* @@ -322,7 +327,7 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> { */ @Override public void clear() { - for (int i=childCount(); --i>=0;) { + for (int i = childCount(); --i >= 0;) { clearAt(i); } } 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 f6a1419ef1..681e8c7f8e 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 @@ -21,16 +21,11 @@ import java.io.Serializable; import java.io.IOException; 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; import org.apache.sis.util.collection.Containers; -import org.apache.sis.util.internal.shared.TreeFormatCustomization; -import org.apache.sis.xml.bind.SpecializedIdentifier; -import org.apache.sis.xml.bind.NonMarshalledAuthority; import org.apache.sis.system.Semaphores; @@ -51,7 +46,7 @@ import org.apache.sis.system.Semaphores; * * @author Martin Desruisseaux (Geomatys) */ -final class TreeTableView implements TreeTable, TreeFormatCustomization, Serializable { +final class TreeTableView implements TreeTable, Serializable { /** * For cross-version compatibility. */ @@ -153,54 +148,6 @@ final class TreeTableView implements TreeTable, TreeFormatCustomization, Seriali }); } - /** - * Returns the filter to use when formatting an instance of this {@code TreeTable}. - * This filter will be combined with the filter that the user may specify by a call - * to {@link TreeTableFormat#setNodeFilter(Predicate)}. - */ - @Override - public Predicate<TreeTable.Node> filter() { - return TreeTableView::filter; - } - - /** - * Invoked during the formatting of a tree node for hiding the ISBN and ISSN identifiers of a {@link Citation}. - * Those identifiers will be formatted in the {@code ISBN} and {@code ISSN} properties instead. We apply this - * filtering for avoiding redundancies in the tree representation. - */ - private static boolean filter(final TreeTable.Node node) { - /* - * The special case implemented in this method applies only to two attributes in the Citation interface. - * We test for this condition first because the call to TreeNode.getParent() is cheap and allow to detect - * soon the metadata instances that do not need further examination. - */ - final Node parent = node.getParent(); - if (parent instanceof TreeNode && Citation.class.isAssignableFrom(((TreeNode) parent).baseType)) { - Object value = null; - if (node instanceof TreeNode) { - /* - * Since this method is invoked (indirectly) during iteration over the children, the value may have - * been cached by TreeNodeChildren.Iter.next(). Try to use this value instead of computing it again. - */ - value = ((TreeNode) node).cachedValue; - } - if (value == null) { - value = node.getUserObject(); - } - /* - * Filter out the ISBN and ISSN identifiers if they are inside a Citation object. - * We keep them if the user added them to other kinds of objects. - */ - if (value instanceof SpecializedIdentifier) { - final Citation authority = ((SpecializedIdentifier) value).getAuthority(); - if (authority instanceof NonMarshalledAuthority && ((NonMarshalledAuthority) authority).isBookOrSerialNumber()) { - return false; - } - } - } - return true; - } - /** * Invoked on serialization. Write the metadata object instead of the {@linkplain #root} node. * diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableFormatTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableFormatTest.java index ce8f2b02f9..ad562b03ac 100644 --- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableFormatTest.java +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeTableFormatTest.java @@ -46,6 +46,7 @@ import org.apache.sis.metadata.iso.citation.DefaultResponsibility; * * @author Martin Desruisseaux (Geomatys) */ +@SuppressWarnings("exports") public final class TreeTableFormatTest extends TestCase { /** * The formatter to use. @@ -64,7 +65,7 @@ public final class TreeTableFormatTest extends TestCase { * Creates a band for the given minimum and maximum wavelengths, in centimetres. */ private static DefaultBand createBand(final double min, final double max) { - final DefaultBand band = new DefaultBand(); + final var band = new DefaultBand(); band.setMinValue(min); band.setMaxValue(max); band.setUnits(Units.CENTIMETRE); @@ -106,13 +107,13 @@ public final class TreeTableFormatTest extends TestCase { */ @Test public void testProcessing() { - final DefaultCitation titled = new DefaultCitation("Some specification"); - final DefaultCitation coded = new DefaultCitation(); - final DefaultCitation untitled = new DefaultCitation(); + final var titled = new DefaultCitation("Some specification"); + final var coded = new DefaultCitation(); + final var untitled = new DefaultCitation(); titled .setPresentationForms(Set.of(PresentationForm.DOCUMENT_HARDCOPY)); coded .setPresentationForms(Set.of(PresentationForm.IMAGE_HARDCOPY)); untitled.setCitedResponsibleParties(Set.of(new DefaultResponsibility(Role.AUTHOR, null, null))); - final DefaultProcessing processing = new DefaultProcessing(); + final var processing = new DefaultProcessing(); processing.setDocumentations(List.of(titled, coded, untitled)); final String text = format.format(processing.asTreeTable()); assertMultilinesEquals( @@ -131,7 +132,7 @@ public final class TreeTableFormatTest extends TestCase { */ @Test public void testImageDescription() { - final DefaultImageDescription image = new DefaultImageDescription(); + final var image = new DefaultImageDescription(); image.setAttributeGroups(List.of( new DefaultAttributeGroup(null, createBand(0.25, 0.26)), new DefaultAttributeGroup(null, createBand(0.28, 0.29)) @@ -156,7 +157,7 @@ public final class TreeTableFormatTest extends TestCase { */ @Test public void testTreeWithCustomElements() { - final DefaultCitation citation = new DefaultCitation(); + final var citation = new DefaultCitation(); citation.setAlternateTitles(List.of( new SimpleInternationalString("Apple"), new SimpleInternationalString("Orange"), diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TreeTable.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TreeTable.java index 372fe26d16..a3a400ef4d 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TreeTable.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TreeTable.java @@ -18,6 +18,7 @@ package org.apache.sis.util.collection; import java.util.Collection; import java.util.List; +import java.util.function.Predicate; /** @@ -119,7 +120,7 @@ public interface TreeTable { * The methods providing a default implementations are suitable for unmodifiable tree nodes. * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 1.5 + * @version 1.7 * @since 0.3 */ public interface Node { @@ -227,6 +228,21 @@ public interface TreeTable { return false; } + /** + * Determines whether this node should be shown in a string or <abbr>GUI</abbr> representation of the tree. + * Overriding this method produces the same effect as specifying a {@linkplain TreeTableFormat#setNodeFilter + * node filter at formatting time}, but this method is more convenient when the decision to show the node or + * not depends on the {@code Node} implementation. + * + * @return whether this node should be shown. + * + * @see TreeTableFormat#setNodeFilter(Predicate) + * @since 1.7 + */ + default boolean isVisible() { + return true; + } + /** * Returns the user object associated to this node. * The user object is for information purpose only and does not appear in the rendered tree. diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TreeTableFormat.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TreeTableFormat.java index 5146de0a14..54018b3535 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TreeTableFormat.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/TreeTableFormat.java @@ -43,7 +43,6 @@ import org.apache.sis.math.NumberType; import org.apache.sis.util.internal.Acyclic; import org.apache.sis.util.internal.shared.PropertyFormat; import org.apache.sis.util.internal.shared.LocalizedParseException; -import org.apache.sis.util.internal.shared.TreeFormatCustomization; import static org.apache.sis.util.Characters.NO_BREAK_SPACE; @@ -92,7 +91,7 @@ import static org.apache.sis.util.Characters.NO_BREAK_SPACE; * than the user object of a parent node <var>A</var>, then the children of the <var>C</var> node will not be formatted. * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 1.1 + * @version 1.7 * @since 0.3 */ public class TreeTableFormat extends TabularFormat<TreeTable> { @@ -325,11 +324,16 @@ public class TreeTableFormat extends TabularFormat<TreeTable> { /** * Sets a filter specifying whether a node should be formatted or ignored. - * Filters are tested at formatting time for all children of the root node (but not for the root node itself). + * Filters are tested at formatting time for all children of the root node, but not for the root node itself. * Filters are ignored at parsing time. * + * <p>This method can be used for the same purpose as overriding the {@link TreeTable.Node#isVisible()} method, + * except that this {@code setNodeFilter(…)} method does not require control over the {@code TreeTable.Node} + * implementation.</p> + * * @param filter filter for specifying whether a node should be formatted, or {@code null} for no filtering. * + * @see TreeTable.Node#isVisible() * @since 1.0 */ public void setNodeFilter(final Predicate<TreeTable.Node> filter) { @@ -625,12 +629,6 @@ public class TreeTableFormat extends TabularFormat<TreeTable> { * of a node, and discarded when the formatting is finished.</p> */ private final class Writer extends PropertyFormat { - /** - * Combination of {@link #nodeFilter} with other filter that may be specified by the tree table to format. - * The {@code TreeTable}-specific filter is specified by {@link TreeFormatCustomization}. - */ - private final Predicate<TreeTable.Node> filter; - /** * The columns to write. */ @@ -666,9 +664,9 @@ public class TreeTableFormat extends TabularFormat<TreeTable> { /** * Creates a new instance which will write to the given appendable. * - * @param out where to format the tree. - * @param tree the tree table to format. - * @param columns the columns of the tree table to format. + * @param out where to format the tree. + * @param tree the tree table to format. + * @param columns the columns of the tree table to format. * @param recursionGuard an initially empty set. */ Writer(final Appendable out, final TreeTable tree, final TableColumn<?>[] columns, @@ -681,17 +679,6 @@ public class TreeTableFormat extends TabularFormat<TreeTable> { this.values = new Object[columns.length]; this.isLast = new boolean[8]; this.recursionGuard = recursionGuard; - - @SuppressWarnings("LocalVariableHidesMemberVariable") // To be stored in the field if successful. - Predicate<TreeTable.Node> filter = nodeFilter; - if (tree instanceof TreeFormatCustomization) { - final var custom = (TreeFormatCustomization) tree; - final Predicate<TreeTable.Node> more = custom.filter(); - if (more != null) { - filter = (filter != null) ? more.and(filter) : more; - } - } - this.filter = filter; setTabulationExpanded(true); setLineSeparator(multiLineCells ? TreeTableFormat.this.getLineSeparator() : " ¶ "); } @@ -841,13 +828,14 @@ public class TreeTableFormat extends TabularFormat<TreeTable> { /** * Returns the next filtered element from the given iterator, or {@code null} if none. - * The filter applied by this method combines {@link #getNodeFilter()} with the filter - * returned by {@link TreeFormatCustomization#filter()}. + * The filter applied by this method combines {@link #getNodeFilter()} with the value + * returned by {@link TreeTable.Node#isVisible()}. */ private TreeTable.Node next(final Iterator<? extends TreeTable.Node> it) { + final Predicate<TreeTable.Node> filter = nodeFilter; while (it.hasNext()) { final TreeTable.Node next = it.next(); - if (next != null) { + if (next != null && next.isVisible()) { if (filter == null || filter.test(next)) { return next; } diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/TreeFormatCustomization.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/TreeFormatCustomization.java deleted file mode 100644 index 98347569b5..0000000000 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/TreeFormatCustomization.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.util.internal.shared; - -import java.util.function.Predicate; -import org.apache.sis.util.collection.TreeTable; -import org.apache.sis.util.collection.TreeTableFormat; - - -/** - * Customization of {@link TreeTable} formatting on a per-instance basis. Methods in this interface - * are invoked by {@link TreeTableFormat#format(TreeTable, Appendable)} before to format the tree. - * Non-null return values are merged with the {@code TreeTableFormat} configuration. - * - * <h2>Design note</h2> - * methods in this class are invoked for configuring the formatter before to write the tree. - * We do not use this interface as callbacks invoked for individual rows during formatting. - * The reason is that functions provided by this interface may need to manage a state - * (for example {@linkplain #filter() filtering} may depend on previous rows) but we do not want - * to force implementations to store such state in {@code TreeFormatCustomization} instances - * since objects implementing this interface may be immutable. - * - * <p>This class is not yet in public API. We are waiting for more experience before to decide if it should be - * committed API.</p> - * - * @author Martin Desruisseaux (Geomatys) - */ -public interface TreeFormatCustomization { - /** - * Returns the tree node filter to use when formatting instances of the {@code TreeTable}. - * If non-null, then the filter is combined with {@link TreeTableFormat#getNodeFilter()} - * by a "and" operation. - * - * @return the tree node filter to use for the {@code TreeTable} instance being formatted. - */ - Predicate<TreeTable.Node> filter(); -}
