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 74c479f028189dd1d041dd40dc33ef58d0d2a80c Author: Martin Desruisseaux AuthorDate: Sat Nov 3 17:05:15 2018 +0100 Add a remarks columns in the metadata tree. The main intent is to notify user when the geographic bounding box crosses the antimeridian, since it is often a source of confusion. --- .../apache/sis/internal/metadata/Resources.java | 33 +++++++++++ .../sis/internal/metadata/Resources.properties | 1 + .../sis/internal/metadata/Resources_fr.properties | 1 + .../org/apache/sis/metadata/AbstractMetadata.java | 5 ++ .../org/apache/sis/metadata/MetadataFormat.java | 65 ++++++++++++++++++++++ .../org/apache/sis/metadata/MetadataStandard.java | 5 ++ .../org/apache/sis/metadata/PropertyAccessor.java | 11 +++- .../java/org/apache/sis/metadata/SpecialCases.java | 21 ++++++- .../java/org/apache/sis/metadata/TreeNode.java | 19 ++++++- .../org/apache/sis/metadata/TreeTableView.java | 50 ++++++----------- .../org/apache/sis/metadata/TreeTableViewTest.java | 30 ++++++++-- .../DefaultDataIdentificationTest.java | 2 +- .../apache/sis/util/collection/TableColumn.java | 11 ++++ .../sis/util/collection/TreeTableFormat.java | 48 +++++++++++----- .../java/org/apache/sis/test/TestUtilities.java | 9 +-- 15 files changed, 253 insertions(+), 58 deletions(-) diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java index 9e4af18..2687c4e 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java @@ -20,8 +20,10 @@ import java.net.URL; import java.util.Locale; import java.util.MissingResourceException; import javax.annotation.Generated; +import org.opengis.util.InternationalString; import org.apache.sis.util.resources.KeyConstants; import org.apache.sis.util.resources.IndexedResourceBundle; +import org.apache.sis.util.resources.ResourceInternationalString; /** @@ -59,6 +61,11 @@ public final class Resources extends IndexedResourceBundle { } /** + * Bounding box crosses the antimeridian. + */ + public static final short BoxCrossesAntiMeridian = 3; + + /** * This metadata element is already initialized with value “{0}”. */ public static final short ElementAlreadyInitialized_1 = 2; @@ -125,4 +132,30 @@ public final class Resources extends IndexedResourceBundle { { return forLocale(null).getString(key, arg0); } + + /** + * The international string to be returned by {@link formatInternational}. + */ + private static final class International extends ResourceInternationalString { + private static final long serialVersionUID = 7465539282825054584L; + + International(short key) {super(key);} + International(short key, Object args) {super(key, args);} + @Override protected KeyConstants getKeyConstants() {return Resources.Keys.INSTANCE;} + @Override protected IndexedResourceBundle getBundle(final Locale locale) { + return forLocale(locale); + } + } + + /** + * Gets an international string for the given key. This method does not check for the key + * validity. If the key is invalid, then a {@link MissingResourceException} may be thrown + * when a {@link InternationalString#toString(Locale)} method is invoked. + * + * @param key the key for the desired string. + * @return an international string for the given key. + */ + public static InternationalString formatInternational(final short key) { + return new International(key); + } } diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.properties b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.properties index 5e5cfe5..1e5dda0 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.properties +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.properties @@ -19,5 +19,6 @@ # Resources in this file are for "sis-metadata" usage only and should not be used by any other module. # For resources shared by all modules in the Apache SIS project, see "org.apache.sis.util.resources" package. # +BoxCrossesAntiMeridian = Bounding box crosses the antimeridian. ElementAlreadyInitialized_1 = This metadata element is already initialized with value \u201c{0}\u201d. UnmodifiableMetadata = This metadata is not modifiable. diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources_fr.properties b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources_fr.properties index 7b917c2..1184335 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources_fr.properties +++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources_fr.properties @@ -24,5 +24,6 @@ # U+202F NARROW NO-BREAK SPACE before ; ! and ? # U+00A0 NO-BREAK SPACE before : # +BoxCrossesAntiMeridian = La bo\u00eete englobante traverse l\u2019antim\u00e9ridien. ElementAlreadyInitialized_1 = Cet \u00e9l\u00e9ment de m\u00e9ta-donn\u00e9e est d\u00e9j\u00e0 initialis\u00e9 avec la valeur \u00ab\u202f{0}\u202f\u00bb. UnmodifiableMetadata = Cette m\u00e9ta-donn\u00e9e n\u2019est pas modifiable. diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java index 2f1c7b3..f1b83b8 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java @@ -227,6 +227,11 @@ public abstract class AbstractMetadata implements LenientComparable, Emptiable { *
  • {@link org.apache.sis.util.collection.TableColumn#VALUE}
    * 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.
  • + * + *
  • {@link org.apache.sis.util.collection.TableColumn#REMARKS}
    + * 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 spanning the anti-meridian.
  • * * *
    Write operations
    diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataFormat.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataFormat.java new file mode 100644 index 0000000..e8b7ee9 --- /dev/null +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataFormat.java @@ -0,0 +1,65 @@ +/* + * 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.util.Locale; +import java.util.TimeZone; +import org.apache.sis.util.collection.TableColumn; +import org.apache.sis.util.collection.TreeTableFormat; +import org.apache.sis.internal.system.LocalizedStaticObject; +import org.apache.sis.io.TableAppender; + + +/** + * Default format for {@link AbstractMetadata} objects. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.0 + * @since 1.0 + * @module + */ +@SuppressWarnings({"CloneableClassWithoutClone", "serial"}) // Not intended to be cloned or serialized. +final class MetadataFormat extends TreeTableFormat { + /** + * The shared instance to use for the {@link TreeTableView#toString()} method implementation. + * Would need to be reset to {@code null} on locale or timezone changes, but we do not yet have + * any listener for such information. + */ + @LocalizedStaticObject + static final MetadataFormat INSTANCE = new MetadataFormat(); + + /** + * Creates a new format. + */ + private MetadataFormat() { + super(Locale.getDefault(Locale.Category.FORMAT), TimeZone.getDefault()); + setColumns(TableColumn.NAME, TableColumn.VALUE, TableColumn.REMARKS); + } + + /** + * Override the default behavior for not moving to next column before writing remarks. + * Doing so put too many spaces for large metadata tree. Instead we add spaces in the current column. + */ + @Override + protected void writeColumnSeparator(final int nextColumn, final TableAppender out) { + if (nextColumn == 1) { + super.writeColumnSeparator(nextColumn, out); + } else { + out.append(" ! "); + } + } +} diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java index 493fd04..daf4667 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java @@ -890,6 +890,11 @@ public class MetadataStandard implements Serializable { *
  • {@link org.apache.sis.util.collection.TableColumn#VALUE}
    * 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.
  • + * + *
  • {@link org.apache.sis.util.collection.TableColumn#REMARKS}
    + * 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 spanning the anti-meridian.
  • * * *
    Write operations
    diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java index 7f98d1a..e3cbd58 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java @@ -667,6 +667,15 @@ class PropertyAccessor { } /** + * Returns a remark or warning to format with the value at the given index, or {@code null} if none. + * This is provided when the value may look surprising, for example the longitude values in a geographic + * bounding box spanning the anti-meridian. + */ + CharSequence remarks(int index, Object metadata) { + return null; + } + + /** * Returns {@code true} if the {@link #implementation} class has at least one setter method. */ final boolean isWritable() { @@ -1100,7 +1109,7 @@ class PropertyAccessor { * * @see #count() */ - public int count(final Object metadata, final ValueExistencePolicy valuePolicy, final int mode) + final int count(final Object metadata, final ValueExistencePolicy valuePolicy, final int mode) throws BackingStoreException { assert type.isInstance(metadata) : metadata; diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java index baabcd9..edcd984 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java @@ -20,6 +20,7 @@ import org.opengis.metadata.citation.Citation; import org.opengis.metadata.extent.GeographicBoundingBox; import org.apache.sis.measure.Latitude; import org.apache.sis.measure.Longitude; +import org.apache.sis.internal.metadata.Resources; import org.apache.sis.util.collection.BackingStoreException; @@ -30,7 +31,7 @@ import org.apache.sis.util.collection.BackingStoreException; * {@link Latitude} instances instead of {@link Double}. * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.0 * @since 0.4 * @module */ @@ -91,6 +92,24 @@ final class SpecialCases extends PropertyAccessor { } /** + * Returns a remark or warning to format with the value at the given index, or {@code null} if none. + * This is used for notifying the user that a geographic box is spanning the anti-meridian. + */ + @Override + CharSequence remarks(final int index, final Object metadata) { + if (index == eastBoundLongitude) { + Object east = super.get(index, metadata); + if (east != null) { + Object west = super.get(westBoundLongitude, metadata); + if (west != null && (Double) east < (Double) west) { + return Resources.formatInternational(Resources.Keys.BoxCrossesAntiMeridian); + } + } + } + return super.remarks(index, metadata); + } + + /** * Delegates to {@link PropertyAccessor#get(int, Object)}, then substitutes the value for the properties * handled in a special way. */ diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java index 735e484..1cfa971 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java @@ -59,7 +59,7 @@ import org.apache.sis.util.resources.Vocabulary; * depends on the instantiation order). * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.0 * @since 0.3 * @module */ @@ -232,6 +232,13 @@ class TreeNode implements Node { } /** + * Gets remarks about the value in this node, or {@code null} if none. + */ + CharSequence getRemarks() { + return null; + } + + /** * 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. @@ -411,6 +418,14 @@ class TreeNode implements Node { } /** + * Gets remarks about the value in this node, or {@code null} if none. + */ + @Override + CharSequence getRemarks() { + return accessor.remarks(indexInData, metadata); + } + + /** * Fetches the node value from the metadata object. */ @Override @@ -841,6 +856,8 @@ class TreeNode implements Node { value = children.getParentTitle(); } } + } else if (column == TableColumn.REMARKS) { + value = getRemarks(); } return column.getElementType().cast(value); } diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java index 6e61c71..3e6cca4 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java @@ -17,9 +17,6 @@ package org.apache.sis.metadata; import java.util.List; -import java.util.Locale; -import java.util.TimeZone; -import java.text.Format; import java.io.Serializable; import java.io.IOException; import java.io.ObjectInputStream; @@ -33,7 +30,6 @@ import org.apache.sis.internal.util.UnmodifiableArrayList; import org.apache.sis.internal.jaxb.SpecializedIdentifier; import org.apache.sis.internal.jaxb.NonMarshalledAuthority; import org.apache.sis.internal.util.TreeFormatCustomization; -import org.apache.sis.internal.system.LocalizedStaticObject; import org.apache.sis.internal.system.Semaphores; @@ -47,6 +43,7 @@ import org.apache.sis.internal.system.Semaphores; *
  • {@link TableColumn#NAME} - the human-readable property name, inferred from the identifier and index.
  • *
  • {@link TableColumn#TYPE} - the base interface of property values.
  • *
  • {@link TableColumn#VALUE} - the property value.
  • + *
  • {@link TableColumn#REMARKS} - remarks on the property value.
  • * * * @author Martin Desruisseaux (Geomatys) @@ -68,18 +65,11 @@ final class TreeTableView implements TreeTable, TreeFormatCustomization, Seriali TableColumn.INDEX, TableColumn.NAME, TableColumn.TYPE, - TableColumn.VALUE + TableColumn.VALUE, + TableColumn.REMARKS }); /** - * The {@link TreeTableFormat} to use for the {@link #toString()} method implementation. - * Created when first needed. Would need to be reset to {@code null} on locale or timezone - * changes, but we do not yet have any listener for such information. - */ - @LocalizedStaticObject - private static Format format; - - /** * The root of the metadata tree. * Consider this field as final - it is modified only on * deserialization by {@link #readObject(ObjectInputStream)}. @@ -140,27 +130,21 @@ final class TreeTableView implements TreeTable, TreeFormatCustomization, Seriali */ @Override public String toString() { - synchronized (TreeTableView.class) { - if (format == null) { - final TreeTableFormat f = new TreeTableFormat( - Locale.getDefault(Locale.Category.FORMAT), TimeZone.getDefault()); - f.setColumns(TableColumn.NAME, TableColumn.VALUE); - format = f; + /* + * The NULL_COLLECTION semaphore prevents creation of new empty collections by getter methods + * (a consequence of lazy instantiation). The intent is to avoid creation of unnecessary objects + * for all unused properties. Users should not see behavioral difference, except if they override + * some getters with an implementation invoking other getters. However in such cases, users would + * have been exposed to null values at XML marshalling time anyway. + */ + final boolean allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION); + try { + synchronized (MetadataFormat.INSTANCE) { + return MetadataFormat.INSTANCE.format(this); } - /* - * The NULL_COLLECTION semaphore prevents creation of new empty collections by getter methods - * (a consequence of lazy instantiation). The intent is to avoid creation of unnecessary objects - * for all unused properties. Users should not see behavioral difference, except if they override - * some getters with an implementation invoking other getters. However in such cases, users would - * have been exposed to null values at XML marshalling time anyway. - */ - final boolean allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION); - try { - return format.format(this); - } finally { - if (!allowNull) { - Semaphores.clear(Semaphores.NULL_COLLECTION); - } + } finally { + if (!allowNull) { + Semaphores.clear(Semaphores.NULL_COLLECTION); } } } diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java index 9d0c6e9..93e1454 100644 --- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java +++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java @@ -21,6 +21,7 @@ import java.io.ObjectOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import org.opengis.metadata.citation.Citation; +import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; import org.apache.sis.test.DependsOnMethod; import org.apache.sis.test.DependsOn; import org.apache.sis.test.TestCase; @@ -28,7 +29,7 @@ import org.junit.Test; import static org.apache.sis.test.Assert.*; import static org.apache.sis.test.TestUtilities.toTreeStructure; -import static org.apache.sis.test.TestUtilities.formatNameAndValue; +import static org.apache.sis.test.TestUtilities.formatMetadata; /** @@ -73,13 +74,13 @@ public final strictfp class TreeTableViewTest extends TestCase { /** * Tests {@link TreeTableView#toString()}. - * Since the result is locale-dependant, we can not compare against an exact string. + * Since the result is locale-dependent, we can not compare against an exact string. * We will only compare the beginning of each line. */ @Test public void testToString() { final TreeTableView metadata = create(ValueExistencePolicy.COMPACT); - assertMultilinesEquals(EXPECTED, formatNameAndValue(metadata)); // Locale-independent + assertMultilinesEquals(EXPECTED, formatMetadata(metadata)); // Locale-independent assertArrayEquals(toTreeStructure(EXPECTED), toTreeStructure(metadata.toString())); // Locale-dependent. } @@ -102,6 +103,27 @@ public final strictfp class TreeTableViewTest extends TestCase { try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data))) { deserialized = in.readObject(); } - assertMultilinesEquals(EXPECTED, formatNameAndValue((TreeTableView) deserialized)); + assertMultilinesEquals(EXPECTED, formatMetadata((TreeTableView) deserialized)); + } + + /** + * Tests formatting a tree containing a remark. We use a geographic bounding box spanning the anti-meridian. + * In this test the longitude value and the remarks and separated by "……" characters, but this is because we + * use the default {@link org.apache.sis.util.collection.TreeTableFormat}. When using {@link MetadataFormat} + * specialization, the formatting is a little bit different + * + * @since 1.0 + */ + @Test + public void testRemarks() { + final DefaultGeographicBoundingBox bbox = new DefaultGeographicBoundingBox(170, -160, -30, 40); + final String text = formatMetadata(bbox.asTreeTable()); + assertMultilinesEquals( + "Geographic bounding box\n" + + "  ├─West bound longitude…… 170°E\n" + + "  ├─East bound longitude…… 160°W…… Bounding box crosses the antimeridian.\n" + // See method javadoc. + "  ├─South bound latitude…… 30°S\n" + + "  ├─North bound latitude…… 40°N\n" + + "  └─Extent type code……………… true\n", text); } } diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentificationTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentificationTest.java index 52f6d46..2a2d621 100644 --- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentificationTest.java +++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentificationTest.java @@ -157,7 +157,7 @@ public final strictfp class DefaultDataIdentificationTest extends TestCase { "  ├─Language (1 of 2)………………………………… en_US\n" + "  ├─Language (2 of 2)………………………………… en\n" + "  └─Character set…………………………………………… US-ASCII\n", - TestUtilities.formatNameAndValue(create().asTreeTable())); + TestUtilities.formatMetadata(create().asTreeTable())); } /** diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/collection/TableColumn.java b/core/sis-utility/src/main/java/org/apache/sis/util/collection/TableColumn.java index 304fff5..3f2da9d 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/util/collection/TableColumn.java +++ b/core/sis-utility/src/main/java/org/apache/sis/util/collection/TableColumn.java @@ -161,6 +161,17 @@ public class TableColumn implements CheckedContainer { Number.class, Vocabulary.Keys.Value); /** + * Frequently-used constant for a column of remarks. + * The column {@linkplain #getHeader() header} is "Remarks" (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. + * + * @since 1.0 + */ + public static final TableColumn REMARKS = new Constant<>("REMARKS", + CharSequence.class, Vocabulary.Keys.Remarks); + + /** * A map containing only the {@link #NAME} column. * This is the default set of columns when parsing a tree table. */ diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java b/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java index a0328be..8578a7b 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java +++ b/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java @@ -631,18 +631,6 @@ public class TreeTableFormat extends TabularFormat { } /** - * Writes the column separator to the given appendable. This is a helper method for the - * {@link Writer} inner class, defined here because it uses many protected fields from - * the superclass. Accessing those fields from the inner class generate many synthetic - * methods, so we are better to define only one method here doing the work. - */ - final void writeColumnSeparator(final Appendable out) throws IOException { - // We have a TableAppender instance if and only if there is 2 or more columns. - ((TableAppender) out.append(beforeFill)).nextColumn(fillCharacter); - out.append(columnSeparator); - } - - /** * Creates string representation of the node values. Tabulations are replaced by spaces, * and line feeds are replaced by the Pilcrow character. This is necessary in order to * avoid conflict with the characters expected by {@link TableAppender}. @@ -881,7 +869,8 @@ public class TreeTableFormat extends TabularFormat { } for (int i=0; i<=n; i++) { if (i != 0) { - writeColumnSeparator(out); + // We have a TableAppender instance if and only if there is 2 or more columns. + writeColumnSeparator(i, (TableAppender) out); } columnFormat = formats[i]; formatValue(values[i], false); @@ -1014,6 +1003,39 @@ public class TreeTableFormat extends TabularFormat { } /** + * Writes characters between columns. The default implementation applies the configuration + * specified by {@link #setColumnSeparatorPattern(String)} as below: + * + *
    + * out.append({@linkplain #beforeFill beforeFill}); + * out.nextColumn({@linkplain #fillCharacter fillCharacter}); + * out.append({@linkplain #columnSeparator columnSeparator}); + *
    + * + * The output with default values is like below: + * + * {@preformat text + * root + * └─column0…… column1…… column2…… column3 + * } + * + * Subclasses can override this method if different column separators are desired. + * Note however that doing so may prevent the {@link #parse parse(…)} method to work. + * + * @param nextColumn zero-based index of the column to be written after the separator. + * @param out where to write the column separator. + * + * @see TableAppender#nextColumn(char) + * + * @since 1.0 + */ + protected void writeColumnSeparator(final int nextColumn, final TableAppender out) { + out.append(beforeFill); + out.nextColumn(fillCharacter); + out.append(columnSeparator); + } + + /** * Returns a clone of this format. * * @return a clone of this format. diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java b/core/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java index e2a4212..5008a2d 100644 --- a/core/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java +++ b/core/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java @@ -53,7 +53,7 @@ import static org.apache.sis.internal.util.StandardDateFormat.UTC; * Miscellaneous utility methods for test cases. * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.0 * @since 0.3 * @module */ @@ -268,18 +268,19 @@ public final strictfp class TestUtilities extends Static { } /** - * Returns a unlocalized string representation of {@code NAME} and {@code VALUE} columns of the given tree table. + * Returns a unlocalized string representation of {@code NAME}, {@code VALUE} and {@code REMARKS} columns + * of the given tree table. They are the columns included in default string representation of metadata. * Dates and times, if any, will be formatted using the {@code "yyyy-MM-dd HH:mm:ss"} pattern in UTC timezone. * This method is used mostly as a convenient way to verify the content of an ISO 19115 metadata object. * * @param table the table for which to get a string representation. * @return a unlocalized string representation of the given tree table. */ - public static String formatNameAndValue(final TreeTable table) { + public static String formatMetadata(final TreeTable table) { synchronized (TestUtilities.class) { if (tableFormat == null) { final TreeTableFormat f = new TreeTableFormat(null, null); - f.setColumns(TableColumn.NAME, TableColumn.VALUE); + f.setColumns(TableColumn.NAME, TableColumn.VALUE, TableColumn.REMARKS); tableFormat = f; } return tableFormat.format(table);