sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/04: 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.
Date Sat, 03 Nov 2018 16:42:22 GMT
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 <martin.desruisseaux@geomatys.com>
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 {
      *   <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>
+     *
+     *   <li>{@link org.apache.sis.util.collection.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 spanning the anti-meridian.</li>
      * </ul>
      *
      * <div class="section">Write operations</div>
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 <strong>not</strong> 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 {
      *   <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>
+     *
+     *   <li>{@link org.apache.sis.util.collection.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 spanning the anti-meridian.</li>
      * </ul>
      *
      * <div class="section">Write operations</div>
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).</div>
  *
  * @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;
  *   <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>
  * </ul>
  *
  * @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<V> implements CheckedContainer<V>
{
             Number.class, Vocabulary.Keys.Value);
 
     /**
+     * Frequently-used constant for a column of remarks.
+     * The column {@linkplain #getHeader() header} is <cite>"Remarks"</cite>
(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<CharSequence> 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<TreeTable> {
     }
 
     /**
-     * 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<TreeTable> {
             }
             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<TreeTable>
{
     }
 
     /**
+     * Writes characters between columns. The default implementation applies the configuration
+     * specified by {@link #setColumnSeparatorPattern(String)} as below:
+     *
+     * <blockquote><code>
+     * out.append({@linkplain #beforeFill beforeFill});
+     * out.nextColumn({@linkplain #fillCharacter fillCharacter});
+     * out.append({@linkplain #columnSeparator columnSeparator});
+     * </code></blockquote>
+     *
+     * 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);


Mime
View raw message