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

Reply via email to