sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/02: Replace hard-coded citations by entries in the SpatialMetadata database. https://issues.apache.org/jira/browse/SIS-338
Date Tue, 24 Jul 2018 14:29:03 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 45515122b47d59144bf84c3272e51b906c681dec
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Jul 24 16:13:09 2018 +0200

    Replace hard-coded citations by entries in the SpatialMetadata database.
    https://issues.apache.org/jira/browse/SIS-338
---
 .../sis/internal/jaxb/NonMarshalledAuthority.java  |   9 +
 .../sis/internal/jaxb/SpecializedIdentifier.java   |   2 +-
 .../sis/internal/metadata/ServicesForUtility.java  | 158 ----------------
 .../sis/internal/metadata/sql/ScriptRunner.java    |  43 ++++-
 .../sis/internal/simple/CitationConstant.java      |  58 +++++-
 .../org/apache/sis/metadata/TreeTableView.java     |  57 +++++-
 .../sis/metadata/iso/citation/Citations.java       | 109 +++++------
 .../org/apache/sis/metadata/sql/Installer.java     |  27 ++-
 .../apache/sis/metadata/sql/MetadataFallback.java  | 134 ++++++++++++-
 .../apache/sis/metadata/sql/MetadataSource.java    |   3 +-
 .../apache/sis/metadata/sql/MetadataWriter.java    |   2 +-
 .../apache/sis/metadata/sql/TableHierarchy.java    |   8 +
 .../org/apache/sis/metadata/sql/Create.sql         | 208 +++++++++++++++++----
 .../apache/sis/metadata/TreeTableFormatTest.java   |   4 -
 .../sis/metadata/iso/citation/CitationsTest.java   | 151 ++++++++-------
 .../sis/metadata/sql/MetadataFallbackVerifier.java | 141 ++++++++++++++
 .../sis/metadata/sql/MetadataSourceTest.java       |  20 ++
 .../apache/sis/referencing/IdentifiedObjects.java  |   2 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |   3 +-
 .../operation/CoordinateOperationRegistry.java     |   2 +-
 .../sis/referencing/NamedIdentifierTest.java       |   2 +-
 .../org/apache/sis/test/ReferencingAssert.java     |   2 +-
 .../sis/internal/util/TreeFormatCustomization.java |  46 +++++
 .../sis/util/collection/TreeTableFormat.java       |  62 ++++--
 ide-project/NetBeans/nbproject/genfiles.properties |   2 +-
 25 files changed, 873 insertions(+), 382 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java
index f4eb0c0..5633da9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java
@@ -108,6 +108,15 @@ public final class NonMarshalledAuthority<T> extends CitationConstant.Authority<
     }
 
     /**
+     * Returns {@code true} if this authority is for ISBN or ISSN identifiers.
+     *
+     * @return whether this authority is for ISBN or ISSN identifiers.
+     */
+    public boolean isBookOrSerialNumber() {
+        return ordinal == ISBN || ordinal == ISSN;
+    }
+
+    /**
      * Returns the first marshallable identifier from the given collection. This method omits
      * "special" identifiers (ISO 19115-3 attributes, ISBN codes…), which are recognized by
      * the implementation class of their authority.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
index 593275a..2b89294 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
@@ -277,7 +277,7 @@ public final class SpecializedIdentifier<T> implements Identifier, Cloneable, Se
      * Formats the given (authority, code) par value in the given buffer.
      */
     static void format(final StringBuilder buffer, final Citation authority, final String code) {
-        buffer.append(Citations.getIdentifier(authority)).append('=');
+        buffer.append(Citations.toCodeSpace(authority)).append('=');
         final boolean quote = (code != null) && (code.indexOf('[') < 0);
         if (quote) buffer.append('“');
         buffer.append(code);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java
index 5a6117a..60ed74b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java
@@ -17,33 +17,19 @@
 package org.apache.sis.internal.metadata;
 
 import java.text.Format;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Locale;
 import java.util.TimeZone;
 import javax.sql.DataSource;
-import org.opengis.metadata.Identifier;
-import org.opengis.metadata.citation.Role;
-import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.PresentationForm;
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.util.ControlledVocabulary;
-import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.MetadataServices;
 import org.apache.sis.internal.metadata.sql.Initializer;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.jaxb.Context;
-import org.apache.sis.metadata.iso.ImmutableIdentifier;
-import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.metadata.iso.citation.DefaultCitation;
-import org.apache.sis.metadata.iso.citation.DefaultOrganisation;
-import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Classes;
 
-import static java.util.Collections.singleton;
 
 
 /**
@@ -87,150 +73,6 @@ public final class ServicesForUtility extends MetadataServices {
     }
 
     /**
-     * Returns the build-in citation for the given primary key, or {@code null}.
-     *
-     * @param  key  the primary key of the desired citation.
-     * @return the requested citation, or {@code null} if unknown.
-     *
-     * @todo The content is hard-coded for now. But the plan for a future version is to fetch richer information
-     *       from a database, including for example the responsible party and the URL. However that method would
-     *       need to make sure that the given key is present in the alternate titles, since we rely on that when
-     *       checking for code spaces.
-     */
-    public static Citation createCitation(final String key) {
-        CharSequence     title;
-        CharSequence     alternateTitle        = null;
-        CharSequence     edition               = null;
-        String           code                  = null;
-        String           codeSpace             = null;
-        String           version               = null;
-        Identifier[]     alternateIdentifiers  = null;
-        CharSequence     citedResponsibleParty = null;
-        PresentationForm presentationForm      = null;
-        Citation[]       copyFrom              = null;      // Copy citedResponsibleParty from those citations.
-        switch (key) {
-            case "ISO 19115-1": {
-                title     = "Geographic Information — Metadata Part 1: Fundamentals";
-                edition   = "ISO 19115-1:2014(E)";
-                code      = "19115-1";
-                codeSpace = "ISO";
-                version   = "2014(E)";
-                citedResponsibleParty = "International Organization for Standardization";
-                presentationForm = PresentationForm.DOCUMENT_DIGITAL;
-                break;
-            }
-            case "ISO 19115-2": {
-                title     = "Geographic Information — Metadata Part 2: Extensions for imagery and gridded data";
-                edition   = "ISO 19115-2:2009(E)";
-                code      = "19115-2";
-                codeSpace = "ISO";
-                version   = "2009(E)";
-                copyFrom  = new Citation[] {Citations.ISO_19115.get(0)};
-                presentationForm = PresentationForm.DOCUMENT_DIGITAL;
-                break;
-            }
-            case "WMS": {
-                title                = "Web Map Server";                                      // OGC title
-                alternateTitle       = "Geographic Information — Web map server interface";   // ISO title
-                alternateIdentifiers = new Identifier[] {
-                    new ImmutableIdentifier(null, "OGC", "06-042",  null, null),
-                    new ImmutableIdentifier(null, "ISO", "19128", "2005", null)
-                };
-                edition          = "1.3";
-                code             = "WMS";
-                codeSpace        = "OGC";
-                copyFrom         = new Citation[] {Citations.OGC, Citations.ISO_19115.get(0)};
-                presentationForm = PresentationForm.DOCUMENT_DIGITAL;
-                break;
-            }
-            case Constants.OGC: {
-                title = "Identifiers in OGC namespace";
-                code = Constants.OGC;
-                citedResponsibleParty = "Open Geospatial Consortium";
-                presentationForm = PresentationForm.DOCUMENT_DIGITAL;
-                break;
-            }
-            case Constants.IOGP: {                                      // Not in public API (see Citations.IOGP javadoc)
-                title = "Using the EPSG Geodetic Parameter Dataset";    // Geomatics Guidance Note number 7, part 1
-                code = Constants.IOGP;
-                copyFrom = new Citation[] {Citations.EPSG};
-                presentationForm = PresentationForm.DOCUMENT_DIGITAL;
-                break;
-            }
-            case Constants.EPSG: {
-                title     = "EPSG Geodetic Parameter Dataset";
-                code      = Constants.EPSG;
-                codeSpace = Constants.IOGP;
-                citedResponsibleParty = "International Association of Oil & Gas producers";
-                presentationForm = PresentationForm.TABLE_DIGITAL;
-                /*
-                 * More complete information is provided as an ISO 19115 structure
-                 * in EPSG Surveying and Positioning Guidance Note Number 7, part 1.
-                 * EPSGDataAccess.getAuthority() also add more information.
-                 * After we moved the content of this citation in a database,
-                 * EPSGDataAccess.getAuthority() should use this citation as a template.
-                 */
-                break;
-            }
-            case Constants.SIS: {
-                title = "Apache Spatial Information System";
-                code  = key;
-                break;
-            }
-            case "ISBN": {
-                title = "International Standard Book Number";
-                alternateTitle = key;
-                break;
-            }
-            case "ISSN": {
-                title = "International Standard Serial Number";
-                alternateTitle = key;
-                break;
-            }
-            case Constants.PROJ4: {
-                title = "Proj.4";
-                break;
-            }
-            case "S57": {
-                title = "S-57";
-                break;
-            }
-            default: return null;
-        }
-        /*
-         * Do not use the 'c.getFoo().add(foo)' pattern below. Use the 'c.setFoo(singleton(foo))' pattern instead.
-         * This is because this method may be invoked during XML serialization, in which case some getter methods
-         * may return null (for preventing JAXB to marshal some empty elements).
-         */
-        final DefaultCitation c = new DefaultCitation(title);
-        if (alternateTitle        != null) c.setAlternateTitles(singleton(Types.toInternationalString(alternateTitle)));
-        if (edition               != null) c.setEdition(Types.toInternationalString(edition));
-        if (code                  != null) c.setIdentifiers(singleton(new ImmutableIdentifier(null, codeSpace, code, version, null)));
-        if (presentationForm      != null) c.setPresentationForms(singleton(presentationForm));
-        if (citedResponsibleParty != null) {
-            c.setCitedResponsibleParties(singleton(new DefaultResponsibility(Role.PRINCIPAL_INVESTIGATOR, null,
-                    new DefaultOrganisation(citedResponsibleParty, null, null, null))));
-        }
-        if (copyFrom != null) {
-            for (final Citation other : copyFrom) {
-                final Collection<? extends Responsibility> parties = other.getCitedResponsibleParties();
-                final Collection<Responsibility> current = c.getCitedResponsibleParties();
-                if (current != null) {
-                    current.addAll(parties);
-                } else {
-                    c.setCitedResponsibleParties(parties);
-                }
-            }
-        }
-        if (alternateIdentifiers != null) {
-            // getIdentifiers() should not return null at this point.
-            c.getIdentifiers().addAll(Arrays.asList(alternateIdentifiers));
-        }
-        c.freeze();
-        return c;
-    }
-
-    /**
      * Returns information about the Apache SIS configuration.
      * See super-class for a list of keys.
      *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/ScriptRunner.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/ScriptRunner.java
index 963178e..2125ac3 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/ScriptRunner.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/ScriptRunner.java
@@ -50,7 +50,7 @@ import org.apache.sis.util.resources.Errors;
  * functionalities other than what we need for those scripts.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.7
  * @module
  */
@@ -646,6 +646,38 @@ parseLine:  while (pos < length) {
     }
 
     /**
+     * Returns {@code true} if the given fragment seems outside identifier quotes or text quotes.
+     * The given fragment must be the beginning or the end of an SQL statement, or be bounded by
+     * indices that are known to be outside quotes. The implementation counts the occurrences of
+     * {@value #IDENTIFIER_QUOTE} and {@value #QUOTE} and verifies that both of them are even.
+     *
+     * @param  sql   the SQL statement for which to test if a fragment is outside quotes.
+     * @param  from  index of the first character of the fragment.
+     * @param  to    index after the last character of the fragment.
+     * @return whether the given fragment seems outside quotes.
+     */
+    private static boolean isOutsideQuotes(final CharSequence sql, int from, final int to) {
+        int nq = 0, ni = 0;
+        while (from < to) {
+            switch (sql.charAt(from++)) {
+                case IDENTIFIER_QUOTE: {
+                    ni++;
+                    break;
+                }
+                case QUOTE: {
+                    if ((nq & 1) != 0 && from < to && sql.charAt(from) == QUOTE) {
+                        from++;
+                    } else {
+                        nq++;
+                    }
+                    break;
+                }
+            }
+        }
+        return ((nq | ni) & 1) == 0;
+    }
+
+    /**
      * Returns {@code true} if the given SQL statements is supported by the database engine,
      * or {@code false} if this statement should be ignored. The default implementation checks
      * if the given query matches the regular expressions given to {@link #addStatementToSkip(String)}.
@@ -675,6 +707,8 @@ parseLine:  while (pos < length) {
      *
      * <ul>
      *   <li>If {@link #isSupported(CharSequence)} returns {@code false}, then this method does nothing.</li>
+     *   <li>If the statement is {@code CREATE TABLE ... INHERITS ...} but the database does not support
+     *       table inheritance, then this method drops the {@code INHERITS ...} part.</li>
      *   <li>If the {@code maxRowsPerInsert} argument given at construction time was zero,
      *       then this method skips {@code "INSERT INTO"} statements but executes all other.</li>
      *   <li>Otherwise this method executes the given statement with the following modification:
@@ -696,6 +730,13 @@ parseLine:  while (pos < length) {
             return 0;
         }
         String subSQL = currentSQL = CharSequences.trimWhitespaces(sql).toString();
+        if (!dialect.isTableInheritanceSupported && subSQL.startsWith("CREATE TABLE")) {
+            final int s = sql.lastIndexOf("INHERITS");
+            if (s >= 0 && isOutsideQuotes(sql, s+8, sql.length())) {             // 8 is the length of "INHERITS".
+                sql.setLength(CharSequences.skipTrailingWhitespaces(sql, 0, s));
+                subSQL = currentSQL = sql.toString();
+            }
+        }
         int count = 0;
         /*
          * The scripts usually do not contain any SELECT statement. One exception is the creation
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
index 654c2fc..55a15e1 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
@@ -29,8 +29,11 @@ import org.opengis.metadata.citation.Series;
 import org.opengis.metadata.identification.BrowseGraphic;
 import org.opengis.util.InternationalString;
 import org.apache.sis.xml.IdentifierSpace;
+import org.apache.sis.metadata.sql.MetadataSource;
+import org.apache.sis.metadata.sql.MetadataStoreException;
 import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.internal.metadata.ServicesForUtility;
+import org.apache.sis.internal.system.Loggers;
+import org.apache.sis.util.logging.Logging;
 
 
 /**
@@ -43,7 +46,7 @@ import org.apache.sis.internal.metadata.ServicesForUtility;
  * @version 1.0
  *
  * @see IdentifierSpace
- * @see org.apache.sis.metadata.iso.citation.Citations
+ * @see Citations
  *
  * @since 0.6
  * @module
@@ -52,7 +55,7 @@ public class CitationConstant extends SimpleCitation {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 7812463864874105717L;
+    private static final long serialVersionUID = -8429121584437634107L;
 
     /**
      * Class of {@code public static final Citation} constants which are also used as namespace for identifiers.
@@ -69,6 +72,7 @@ public class CitationConstant extends SimpleCitation {
 
         /**
          * Creates a new citation for an authority managing codes in the given namespace.
+         * This constructor assumes that the namespace is the same as the abbreviation given as citation title.
          *
          * @param  namespace  the namespace of codes managed by this authority (e.g. "EPSG").
          */
@@ -77,6 +81,16 @@ public class CitationConstant extends SimpleCitation {
         }
 
         /**
+         * Creates a new citation for an authority managing codes in the given namespace.
+         *
+         * @param  name       a human-understandable primary key for fetching more information.
+         * @param  namespace  the namespace of codes managed by this authority (e.g. "EPSG").
+         */
+        public Authority(final String name, final String namespace) {
+            super(name, namespace);
+        }
+
+        /**
          * Returns the name space given at construction time. Can be one of the following:
          * <ul>
          *   <li>Abbreviation of the authority managing the codes (e.g. {@code "EPSG"} or {@code "ISBN"}).</li>
@@ -84,8 +98,8 @@ public class CitationConstant extends SimpleCitation {
          * </ul>
          */
         @Override
-        public String getName() {
-            return title;
+        public final String getName() {
+            return namespace;
         }
 
         /**
@@ -98,7 +112,17 @@ public class CitationConstant extends SimpleCitation {
     }
 
     /**
+     * The name by which this citation is known to {@link Citations#fromName(String)}.
+     * Often the same than the abbreviation that {@link CitationConstant} uses as the title.
+     * If this {@code CitationConstant} is a {@link Authority} subtype, then this is also
+     * the authority namespace.
+     */
+    public final String namespace;
+
+    /**
      * The citation which contain the "real" data, or {@code null} if not yet created.
+     * This is usually an instance created by {@link MetadataSource}. Those instances
+     * manage their own caching, so accesses to the database should not occur often.
      */
     private transient volatile Citation delegate;
 
@@ -110,6 +134,19 @@ public class CitationConstant extends SimpleCitation {
      */
     public CitationConstant(final String name) {
         super(name);
+        this.namespace = name;
+    }
+
+    /**
+     * Creates a new proxy for the given primary key but a different programmatic name.
+     * The key should be readable enough for being usable as a fallback if the database is not available.
+     *
+     * @param  name       a human-understandable primary key for fetching more information.
+     * @param  namespace  the name by which this citation is known to {@link Citations#fromName(String)}.
+     */
+    public CitationConstant(final String name, final String namespace) {
+        super(name);
+        this.namespace = namespace;
     }
 
     /**
@@ -135,12 +172,15 @@ public class CitationConstant extends SimpleCitation {
             synchronized (this) {
                 c = delegate;
                 if (c == null) {
-                    c = ServicesForUtility.createCitation(title);
-                    if (c == null) {
+                    try {
+                        c = MetadataSource.getProvided().lookup(Citation.class, title);
+                    } catch (MetadataStoreException e) {
                         /*
-                         * 'sis-metadata' module not on the classpath (should be very rare)
-                         * or no citation defined for the given primary key.
+                         * If no database was available, MetadataSource.getProvided() was supposed to fallback on
+                         * the MetadataFallback class. So if we get this exception, a more serious error occurred.
+                         * It is still not fatal however, since most of Citation content is informative.
                          */
+                        Logging.unexpectedException(Logging.getLogger(Loggers.SQL), CitationConstant.class, "delegate", e);
                         c = new SimpleCitation(title);
                     }
                     delegate = c;
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 7965d25..6e61c71 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
@@ -24,10 +24,15 @@ 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.collection.TreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.TreeTableFormat;
 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;
 
@@ -45,11 +50,11 @@ import org.apache.sis.internal.system.Semaphores;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
-final class TreeTableView implements TreeTable, Serializable {
+final class TreeTableView implements TreeTable, TreeFormatCustomization, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -161,6 +166,54 @@ final class TreeTableView implements TreeTable, Serializable {
     }
 
     /**
+     * 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 than 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 kind 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.
      *
      * @param  out  the output stream where to serialize this object.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
index 6eee404..ba4874a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
@@ -17,6 +17,7 @@
 package org.apache.sis.metadata.iso.citation;
 
 import java.util.List;
+import java.util.Arrays;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;                // For javadoc
 import org.apache.sis.util.Static;
@@ -127,7 +128,7 @@ public final class Citations extends Static {
      * @see org.apache.sis.internal.jaxb.referencing.Code#getIdentifier()
      * @see <a href="http://issues.apache.org/jira/browse/SIS-200">SIS-200</a>
      */
-    static final Citation IOGP = new CitationConstant(Constants.IOGP);
+    static final CitationConstant IOGP = new CitationConstant(Constants.IOGP);
 
     /**
      * The authority for identifiers of objects defined by the
@@ -203,23 +204,7 @@ public final class Citations extends Static {
      *
      * @since 0.7
      */
-    public static final IdentifierSpace<Integer> WMS = new WMS();
-
-    /**
-     * Special case for the {@link Citations#WMS} constant
-     * since it uses the same codespace than {@link Citations#OGC}.
-     */
-    private static final class WMS extends CitationConstant.Authority<Integer> {
-        private static final long serialVersionUID = -8490156477724003085L;
-
-        WMS() {
-            super("WMS");
-        }
-
-        @Override public String getName() {
-            return Constants.OGC;
-        }
-    }
+    public static final IdentifierSpace<Integer> WMS = new CitationConstant.Authority<>("WMS", Constants.OGC);
 
     /**
      * The authority for identifiers found in specifications from the
@@ -301,7 +286,7 @@ public final class Citations extends Static {
      *
      * @since 0.4
      */
-    public static final IdentifierSpace<String> ESRI = new CitationConstant.Authority<>("ESRI");
+    public static final IdentifierSpace<String> ESRI = new CitationConstant.Authority<>("ArcGIS", "ESRI");
 
     /**
      * The authority for identifiers of objects defined by the
@@ -388,7 +373,7 @@ public final class Citations extends Static {
      *
      * @since 0.6
      */
-    public static final IdentifierSpace<Integer> S57 = new CitationConstant.Authority<>("S57");
+    public static final IdentifierSpace<Integer> S57 = new CitationConstant.Authority<>("IHO S-57", "S57");
 
     /**
      * The <cite>International Standard Book Number</cite> (ISBN) defined by ISO-2108.
@@ -431,16 +416,17 @@ public final class Citations extends Static {
      *
      * @since 0.4
      */
-    public static final Citation SIS = new CitationConstant(Constants.SIS);
+    public static final Citation SIS = new CitationConstant.Authority<String>(Constants.SIS);
 
     /**
-     * List of citations declared in this class.
+     * List of <em>public</em> citations declared in this class.
      * Most frequently used citations (at least in SIS) should be first.
+     * Non-public citations like {@link #IOGP} are handled separately.
      */
     private static final CitationConstant[] CITATIONS = {
         (CitationConstant) EPSG,
-        (CitationConstant) WMS,
         (CitationConstant) OGC,
+        (CitationConstant) WMS,                 // Must be after OGC because it declares the same namespace.
         (CitationConstant) ESRI,
         (CitationConstant) NETCDF,
         (CitationConstant) GEOTIFF,
@@ -451,8 +437,7 @@ public final class Citations extends Static {
         (CitationConstant) ISSN,
         (CitationConstant) SIS,
         (CitationConstant) ISO_19115.get(0),
-        (CitationConstant) ISO_19115.get(1),
-        (CitationConstant) IOGP
+        (CitationConstant) ISO_19115.get(1)
     };
 
     static {  // Must be after CITATIONS array construction.
@@ -476,6 +461,18 @@ public final class Citations extends Static {
         for (final CitationConstant citation : CITATIONS) {
             citation.refresh();
         }
+        IOGP.refresh();
+    }
+
+    /**
+     * Returns the values declared in this {@code Citations} class.
+     *
+     * @return the value declared in this {@code Citations} class.
+     *
+     * @since 1.0
+     */
+    public static Citation[] values() {
+        return Arrays.copyOf(CITATIONS, CITATIONS.length, Citation[].class);
     }
 
     /**
@@ -496,16 +493,17 @@ public final class Citations extends Static {
             return null;
         }
         for (final CitationConstant citation : CITATIONS) {
-            if (equalsFiltered(identifier, citation.title)) {
+            if (equalsFiltered(identifier, citation.namespace)) {
                 return citation;
             }
         }
-        if (equalsFiltered(identifier, "OGP")) {        // Old name of "IOGP" organization.
-            return IOGP;
-        }
-        if (equalsFiltered(identifier, Constants.CRS)) {
+        if (equalsFiltered(identifier, Constants.CRS) || equalsFiltered(identifier, "WMS")) {
             return WMS;
         }
+        // "OGP" is the old name of "IOGP" organization.
+        if (equalsFiltered(identifier, "IOGP") || equalsFiltered(identifier, "OGP")) {
+            return IOGP;
+        }
         /*
          * If we found no match, org.apache.sis.internal.metadata.ServicesForUtility expects
          * that we return anything that is not an instance of CitationConstant.
@@ -648,11 +646,35 @@ public final class Citations extends Static {
 
     /**
      * Infers a valid Unicode identifier from the given citation, or returns {@code null} if none.
+     *
+     * @param  citation  the citation for which to get the Unicode identifier, or {@code null}.
+     * @return a non-empty Unicode identifier for the given citation without leading or trailing whitespaces,
+     *         or {@code null} if the given citation is null or does not have any Unicode identifier or title.
+     *
+     * @see org.apache.sis.metadata.iso.ImmutableIdentifier
+     * @see org.apache.sis.referencing.IdentifiedObjects#getSimpleNameOrIdentifier(IdentifiedObject)
+     * @see org.apache.sis.util.CharSequences#isUnicodeIdentifier(CharSequence)
+     *
+     * @since 0.6
+     *
+     * @deprecated Replaced by {@link #toCodeSpace(Citation)} in order to reduce the risk of inconsistent
+     *             behavior if those two methods are mixed.
+     */
+    @Deprecated
+    public static String getUnicodeIdentifier(final Citation citation) {
+        return org.apache.sis.internal.util.Citations.removeIgnorableCharacters(
+               org.apache.sis.internal.util.Citations.getIdentifier(citation, true));
+    }
+
+    /**
+     * Infers a code space from the given citation, or returns {@code null} if none.
      * This method is useful for extracting a short designation of an authority (e.g. {@code "EPSG"})
      * for processing purpose. This method performs the following actions:
      *
      * <ul class="verbose">
-     *   <li>First, performs the same work than {@link #getIdentifier(Citation)} except that {@code '_'}
+     *   <li>If the given citation is an instance of {@code IdentifierSpace},
+     *       returns {@link IdentifierSpace#getName()}.</li>
+     *   <li>Otherwise, performs the same work than {@link #getIdentifier(Citation)} except that {@code '_'}
      *       is used instead of {@link org.apache.sis.util.iso.DefaultNameSpace#DEFAULT_SEPARATOR ':'}
      *       as the separator between the codespace and the code.</li>
      *   <li>If the result of above method call is {@code null} or is not a
@@ -679,28 +701,6 @@ public final class Citations extends Static {
      *   <li>{@code ⁔}</li>
      * </ul></div>
      *
-     * @param  citation  the citation for which to get the Unicode identifier, or {@code null}.
-     * @return a non-empty Unicode identifier for the given citation without leading or trailing whitespaces,
-     *         or {@code null} if the given citation is null or does not have any Unicode identifier or title.
-     *
-     * @see org.apache.sis.metadata.iso.ImmutableIdentifier
-     * @see org.apache.sis.referencing.IdentifiedObjects#getSimpleNameOrIdentifier(IdentifiedObject)
-     * @see org.apache.sis.util.CharSequences#isUnicodeIdentifier(CharSequence)
-     *
-     * @since 0.6
-     */
-    public static String getUnicodeIdentifier(final Citation citation) {
-        return org.apache.sis.internal.util.Citations.removeIgnorableCharacters(
-               org.apache.sis.internal.util.Citations.getIdentifier(citation, true));
-    }
-
-    /**
-     * Infers a code space from the given citation, or returns {@code null} if none.
-     * This method is very close to {@link #getUnicodeIdentifier(Citation)}, except that it looks for
-     * {@link IdentifierSpace#getName()} before to scan the identifiers and titles. The result should
-     * be the same in most cases, except some cases like the {@link org.apache.sis.metadata.iso.citation.Citations}
-     * constant for {@code "Proj.4"} in which case this method returns {@code "Proj4"} instead of {@code null}.
-     *
      * @param  citation  the citation for which to infer the code space, or {@code null}.
      * @return a non-empty code space for the given citation without leading or trailing whitespaces,
      *         or {@code null} if the given citation is null or does not have any Unicode identifier or title.
@@ -711,7 +711,8 @@ public final class Citations extends Static {
         if (citation instanceof IdentifierSpace<?>) {
             return ((IdentifierSpace<?>) citation).getName();
         } else {
-            return getUnicodeIdentifier(citation);
+            return org.apache.sis.internal.util.Citations.removeIgnorableCharacters(
+                   org.apache.sis.internal.util.Citations.getIdentifier(citation, true));
         }
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java
index 626222c..6a0c53f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java
@@ -34,6 +34,12 @@ import org.apache.sis.util.StringBuilders;
  */
 final class Installer extends ScriptRunner {
     /**
+     * List of enumeration types to replace by {@code VARCHAR}
+     * on implementations that do not support {@code ENUM} type.
+     */
+    private final String[] enumTypes;
+
+    /**
      * Creates a new installer for the metadata database.
      *
      * @param  connection  connection to the metadata database.
@@ -41,6 +47,16 @@ final class Installer extends ScriptRunner {
      */
     Installer(final Connection connection) throws SQLException {
         super(connection, 100);
+        if (isEnumTypeSupported) {
+            enumTypes = null;
+        } else {
+            enumTypes = new String[] {
+                "RoleCode", "DateTypeCode", "PresentationFormCode", "OnLineFunctionCode"
+            };
+            for (int i=0; i<enumTypes.length; i++) {
+                enumTypes[i] = "metadata.\"" + enumTypes[i] + '"';
+            }
+        }
     }
 
     /**
@@ -52,17 +68,14 @@ final class Installer extends ScriptRunner {
 
     /**
      * Invoked for each line of the SQL installation script to execute.
-     * If the database does not support enumerations, replace enumeration columns by {@code VARCHAR}.
-     *
-     * @todo The hard-coded checks performed in this method should disappear after we replaced the
-     *       "CREATE TABLE" statement by usage of {@code MetadataWriter}.
+     * If the database does not support enumerations, replaces enumeration columns by {@code VARCHAR}.
      */
     @Override
     protected int execute(final StringBuilder sql) throws SQLException, IOException {
         if (!isEnumTypeSupported && CharSequences.startsWith(sql, "CREATE TABLE", true)) {
-            StringBuilders.replace(sql, "metadata.\"RoleCode\"", "VARCHAR(25)");
-            StringBuilders.replace(sql, "metadata.\"DateTypeCode\"", "VARCHAR(25)");
-            StringBuilders.replace(sql, "metadata.\"PresentationFormCode\"", "VARCHAR(25)");
+            for (final String type : enumTypes) {
+                StringBuilders.replace(sql, type, "VARCHAR(25)");
+            }
         }
         return super.execute(sql);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java
index 7a8ec06..4c3ef6a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java
@@ -17,12 +17,21 @@
 package org.apache.sis.metadata.sql;
 
 import org.opengis.util.ControlledVocabulary;
+import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.Citation;
-import org.apache.sis.internal.metadata.ServicesForUtility;
+import org.opengis.metadata.citation.PresentationForm;
+import org.apache.sis.metadata.iso.ImmutableIdentifier;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.citation.DefaultOrganisation;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+import org.apache.sis.internal.util.Constants;
+import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.WarningListener;
 import org.apache.sis.xml.NilReason;
 
+import static java.util.Collections.singleton;
+
 
 /**
  * A fallback providing hard-coded values of metadata entities.
@@ -76,8 +85,7 @@ final class MetadataFallback extends MetadataSource {
         } else {
             value = null;
             if (type == Citation.class) {
-                // TODO: move ServicesForUtility code here.
-                value = ServicesForUtility.createCitation(identifier);
+                value = createCitation(identifier);
             }
             if (value == null) {
                 return NilReason.MISSING.createNilObject(type);
@@ -87,6 +95,126 @@ final class MetadataFallback extends MetadataSource {
     }
 
     /**
+     * Returns the build-in citation for the given primary key, or {@code null}.
+     * The content in this method should be consistent with the content provided
+     * in the {@code "Create.sql"} script (this is verified by JUnit tests).
+     *
+     * @param  key  the primary key of the desired citation.
+     * @return the requested citation, or {@code null} if unknown.
+     */
+    static Citation createCitation(final String key) {
+        CharSequence     title;
+        CharSequence     alternateTitle        = null;
+        CharSequence     edition               = null;
+        String           code                  = null;
+        String           codeSpace             = null;
+        String           version               = null;
+        CharSequence     citedResponsibleParty = null;
+        PresentationForm presentationForm      = null;
+        String           copyFrom              = null;      // Copy citedResponsibleParty from those citations.
+        switch (key) {
+            case "ISO 19115-1": {
+                title     = "Geographic Information — Metadata Part 1: Fundamentals";
+                edition   = "ISO 19115-1:2014(E)";
+                code      = "19115-1";
+                codeSpace = "ISO";
+                version   = "2014(E)";
+                citedResponsibleParty = "International Organization for Standardization";
+                presentationForm = PresentationForm.DOCUMENT_DIGITAL;
+                break;
+            }
+            case "ISO 19115-2": {
+                title     = "Geographic Information — Metadata Part 2: Extensions for imagery and gridded data";
+                edition   = "ISO 19115-2:2009(E)";
+                code      = "19115-2";
+                codeSpace = "ISO";
+                version   = "2009(E)";
+                copyFrom  = "ISO 19115-1";
+                presentationForm = PresentationForm.DOCUMENT_DIGITAL;
+                break;
+            }
+            case "WMS": {
+                title             = "Web Map Server";
+                alternateTitle    = "WMS";
+                edition = version = "1.3";
+                code              = "WMS";      // Note: OGC internal code is 06-042.
+                codeSpace         = "OGC";
+                copyFrom          = "OGC";
+                presentationForm  = PresentationForm.DOCUMENT_DIGITAL;
+                break;
+            }
+            case "OGC": {
+                title                 = "OGC Naming Authority";
+                code                  = Constants.OGC;
+                citedResponsibleParty = "Open Geospatial Consortium";
+                presentationForm      = PresentationForm.DOCUMENT_DIGITAL;
+                break;
+            }
+            case "IOGP": {          // Not in public API (see Citations.IOGP javadoc)
+                title = "IOGP Surveying and Positioning Guidance Note 7";
+                code             = Constants.IOGP;
+                copyFrom         = Constants.EPSG;
+                presentationForm = PresentationForm.DOCUMENT_DIGITAL;
+                break;
+            }
+            case Constants.EPSG: {
+                title                 = "EPSG Geodetic Parameter Dataset";
+                alternateTitle        = "EPSG Dataset";
+                code                  = Constants.EPSG;
+                codeSpace             = Constants.IOGP;
+                citedResponsibleParty = "International Association of Oil & Gas producers";
+                presentationForm      = PresentationForm.TABLE_DIGITAL;
+                break;
+            }
+            case Constants.SIS: {
+                title     = "Apache Spatial Information System";
+                code      = key;
+                codeSpace = "Apache";
+                break;
+            }
+            case "ISBN": {
+                title = "International Standard Book Number";
+                alternateTitle = key;
+                break;
+            }
+            case "ISSN": {
+                title = "International Standard Serial Number";
+                alternateTitle = key;
+                break;
+            }
+            case Constants.PROJ4: {
+                title = "Proj";
+                break;
+            }
+            case "IHO S-57": {
+                title = "S-57";
+                presentationForm = PresentationForm.DOCUMENT_DIGITAL;
+                break;
+            }
+            default: return null;
+        }
+        /*
+         * Do not use the 'c.getFoo().add(foo)' pattern below. Use the 'c.setFoo(singleton(foo))' pattern instead.
+         * This is because this method may be invoked during XML serialization, in which case some getter methods
+         * may return null (for preventing JAXB to marshal some empty elements).
+         */
+        final DefaultCitation c = new DefaultCitation(title);
+        if (alternateTitle        != null) c.setAlternateTitles(singleton(Types.toInternationalString(alternateTitle)));
+        if (edition               != null) c.setEdition(Types.toInternationalString(edition));
+        if (code                  != null) c.setIdentifiers(singleton(new ImmutableIdentifier(null, codeSpace, code, version, null)));
+        if (presentationForm      != null) c.setPresentationForms(singleton(presentationForm));
+        if (citedResponsibleParty != null) {
+            c.setCitedResponsibleParties(singleton(new DefaultResponsibility(Role.PRINCIPAL_INVESTIGATOR, null,
+                    new DefaultOrganisation(citedResponsibleParty, null, null, null))));
+        }
+        if (copyFrom != null) {
+            c.setCitedResponsibleParties(createCitation(copyFrom).getCitedResponsibleParties());
+        }
+        c.apply(DefaultCitation.State.FINAL);
+        return c;
+    }
+
+    /**
      * Ignored.
      */
     @Override
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
index 81262a7..86768eb 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
@@ -121,8 +121,7 @@ public class MetadataSource implements AutoCloseable {
     static final KeyNamePolicy NAME_POLICY = KeyNamePolicy.UML_IDENTIFIER;
 
     /**
-     * The column name used for the identifiers. We do not quote this identifier;
-     * we will let the database uses its own lower-case / upper-case convention.
+     * The column name used for the identifiers.
      */
     static final String ID_COLUMN = "ID";
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
index 4b96b98..582edbb 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
@@ -714,7 +714,7 @@ public class MetadataWriter extends MetadataSource {
             }
         }
         if (identifier == null && metadata instanceof Citation) {
-            identifier = nonEmpty(Citations.getIdentifier((Citation) metadata));
+            identifier = nonEmpty(Citations.toCodeSpace((Citation) metadata));
         }
         if (identifier == null) {
             final TitleProperty tp = metadata.getClass().getAnnotation(TitleProperty.class);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java
index 7b435e8..bd8c757 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java
@@ -25,6 +25,14 @@ import org.apache.sis.util.iso.Types;
  * Utility methods for handling the inheritance between tables.
  * This features is partially supported in PostgreSQL database.
  *
+ * <p>This class is a work around for databases that support table inheritances,
+ * but not yet index inheritance. For example in PostgreSQL 9.5.13, we can not yet declare
+ * a foreigner key to the super table and find the entries in inherited tables that way.</p>
+ *
+ * <p>An alternative to current workaround would be to repeat a search in all child tables.
+ * We could use {@link java.sql.DatabaseMetaData#getSuperTables(String, String, String)} for
+ * getting the list of child tables.</p>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
  * @since   1.0
diff --git a/core/sis-metadata/src/main/resources/org/apache/sis/metadata/sql/Create.sql b/core/sis-metadata/src/main/resources/org/apache/sis/metadata/sql/Create.sql
index 3aff648..57bfa75 100644
--- a/core/sis-metadata/src/main/resources/org/apache/sis/metadata/sql/Create.sql
+++ b/core/sis-metadata/src/main/resources/org/apache/sis/metadata/sql/Create.sql
@@ -32,9 +32,13 @@ CREATE TYPE metadata."DateTypeCode" AS ENUM (
   'unavailable', 'inForce', 'adopted', 'deprecated', 'superseded', 'validityBegins', 'validityExpires',
   'released', 'distribution');
 
-CREATE CAST (VARCHAR AS metadata."PresentationFormCode") WITH INOUT AS ASSIGNMENT;
-CREATE CAST (VARCHAR AS metadata."RoleCode")             WITH INOUT AS ASSIGNMENT;
-CREATE CAST (VARCHAR AS metadata."DateTypeCode")         WITH INOUT AS ASSIGNMENT;
+CREATE TYPE metadata."OnLineFunctionCode" AS ENUM (
+  'download', 'information', 'offlineAccess', 'order', 'search', 'completeMetadata', 'browseGraphic',
+  'upload', 'emailService', 'browsing', 'fileAccess');
+
+CREATE TYPE metadata."TransferFunctionTypeCode" AS ENUM (
+  'linear', 'logarithmic', 'exponential');
+
 
 
 --
@@ -46,77 +50,205 @@ CREATE CAST (VARCHAR AS metadata."DateTypeCode")         WITH INOUT AS ASSIGNMEN
 -- VARCHAR(120) are for character sequences.
 --
 CREATE TABLE metadata."Identifier" (
-  ID          VARCHAR(15) NOT NULL PRIMARY KEY,
+  "ID"        VARCHAR(15) NOT NULL PRIMARY KEY,
   "authority" VARCHAR(15),
   "code"      VARCHAR(120),
   "codeSpace" VARCHAR(120),
   "version"   VARCHAR(120));
 
+
+
 CREATE TABLE metadata."Party" (
-  ID     VARCHAR(15) NOT NULL PRIMARY KEY,
+  "ID"   VARCHAR(15) NOT NULL PRIMARY KEY,
   "name" VARCHAR(120));
 
+
+
+--
+-- "ID" and "name" columns are inherited from the "Party" parent table.
+-- But we nevertheless repeat them for databases that do not support table inheritance.
+-- On PostgreSQL, those duplicated declarations are merged in single columns.
+--
+CREATE TABLE metadata."Organisation" (
+  "ID"   VARCHAR(15) NOT NULL PRIMARY KEY,
+  "name" VARCHAR(120))
+INHERITS (metadata."Party");
+
+
+
+--
+-- Foreigner key should reference the "Party" parent table, but it does not yet work on PostgreSQL
+-- (tested on 9.5.13). For the purpose of this file, we need to reference "Organisation" only.
+-- This constraint can be dropped at end of this script.
+--
 CREATE TABLE metadata."Responsibility" (
-  ID      VARCHAR(15) NOT NULL PRIMARY KEY,
+  "ID"    VARCHAR(15) NOT NULL PRIMARY KEY,
   "role"  metadata."RoleCode",
-  "party" VARCHAR(15) REFERENCES metadata."Party" (ID) ON UPDATE RESTRICT ON DELETE RESTRICT);
+  "party" VARCHAR(15) REFERENCES metadata."Organisation" ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT);
+
+
 
 CREATE TABLE metadata."Date" (
-  ID         VARCHAR(15) NOT NULL PRIMARY KEY,
+  "ID"       VARCHAR(15) NOT NULL PRIMARY KEY,
   "date"     TIMESTAMP,
   "dateType" metadata."DateTypeCode");
 
+
+
+CREATE TABLE metadata."OnlineResource" (
+  "ID"       VARCHAR(15) NOT NULL PRIMARY KEY,
+  "linkage"  VARCHAR(120),
+  "function" metadata."OnLineFunctionCode");
+
+
+
 CREATE TABLE metadata."Citation" (
-  ID                      VARCHAR(15) NOT NULL PRIMARY KEY,
+  "ID"                    VARCHAR(15) NOT NULL PRIMARY KEY,
   "title"                 VARCHAR(120),
   "alternateTitle"        VARCHAR(120),
-  "date"                  VARCHAR(15) REFERENCES metadata."Date" (ID) ON UPDATE RESTRICT ON DELETE RESTRICT,
+  "date"                  VARCHAR(15) REFERENCES metadata."Date" ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT,
   "edition"               VARCHAR(120),
   "editionDate"           TIMESTAMP,
-  "identifier"            VARCHAR(15) REFERENCES metadata."Identifier"     (ID) ON UPDATE RESTRICT ON DELETE RESTRICT,
-  "citedResponsibleParty" VARCHAR(15) REFERENCES metadata."Responsibility" (ID) ON UPDATE RESTRICT ON DELETE RESTRICT,
-  "presentationForm"      metadata."PresentationFormCode");
+  "identifier"            VARCHAR(15) REFERENCES metadata."Identifier"     ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT,
+  "citedResponsibleParty" VARCHAR(15) REFERENCES metadata."Responsibility" ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT,
+  "presentationForm"      metadata."PresentationFormCode",
+  "onlineResource"        VARCHAR(15) REFERENCES metadata."OnlineResource" ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT);
 
 ALTER TABLE metadata."Identifier" ADD CONSTRAINT fk_identifier_citation
-FOREIGN KEY ("authority") REFERENCES metadata."Citation" (ID) ON UPDATE RESTRICT ON DELETE RESTRICT;
+FOREIGN KEY ("authority") REFERENCES metadata."Citation" ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT;
+
+
 
 CREATE TABLE metadata."Format" (
-  ID                            VARCHAR(15) NOT NULL PRIMARY KEY,
-  "formatSpecificationCitation" VARCHAR(15) REFERENCES metadata."Citation" (ID) ON UPDATE RESTRICT ON DELETE RESTRICT,
+  "ID"                          VARCHAR(15) NOT NULL PRIMARY KEY,
+  "formatSpecificationCitation" VARCHAR(15) REFERENCES metadata."Citation" ("ID") ON UPDATE RESTRICT ON DELETE RESTRICT,
   "amendmentNumber"             VARCHAR(120),
   "fileDecompressionTechnique"  VARCHAR(120));
 
 
+
+--
+-- All URLs referenced in this SQL file. Unless otherwise specified, the function of all those URLs
+-- is 'information'. URLs may appear in citations or in contact information of responsible parties.
+--
+INSERT INTO metadata."OnlineResource" ("ID", "linkage") VALUES
+  ('EPSG',    'http://www.epsg.org/'),
+  ('ESRI',    'http://www.esri.com/'),
+  ('GeoTIFF', 'https://trac.osgeo.org/geotiff/'),
+  ('IHO',     'https://www.iho.int/'),
+  ('IOGP',    'http://www.iogp.org/'),
+  ('ISBN',    'https://www.isbn-international.org/'),
+  ('ISSN',    'http://www.issn.org/'),
+  ('ISO',     'http://www.iso.org/'),
+  ('NetCDF',  'https://www.unidata.ucar.edu/software/netcdf/'),
+  ('OGC',     'http://www.opengeospatial.org/'),
+  ('OGCNA',   'http://www.opengeospatial.org/ogcna'),
+  ('Oracle',  'http://www.oracle.com/'),
+  ('OSGeo',   'https://www.osgeo.org/'),
+  ('PostGIS', 'https://postgis.net/'),
+  ('Proj4',   'https://proj4.org/'),
+  ('SIS',     'http://sis.apache.org/'),
+  ('WMS',     'http://www.opengeospatial.org/standards/wms');
+
+UPDATE metadata."OnlineResource" SET "function" = 'information';
+
+
+--
+-- All parties referenced in this SQL file. We currently have only organisations, no individuals.
+-- This SQL file has a one-to-one relationship between "Party" (organisation) and "Responsibility"
+-- but sometime with different identifiers for emphasising on the product rather than the company.
+--
+INSERT INTO metadata."Organisation" ("ID", "name") VALUES
+  ('{org}Apache', 'The Apache Software Foundation'),
+  ('{org}ESRI',   'ESRI'),
+  ('{org}IHO',    'International Hydrographic Organization'),
+  ('{org}IOGP',   'International Association of Oil & Gas producers'),
+  ('{org}ISBN',   'International ISBN Agency'),
+  ('{org}ISSN',   'The International Centre for the registration of serial publications'),
+  ('{org}ISO',    'International Organization for Standardization'),
+  ('{org}NATO',   'North Atlantic Treaty Organization'),
+  ('{org}OGC',    'Open Geospatial Consortium'),
+  ('{org}OSGeo',  'The Open Source Geospatial Foundation'),
+  ('{org}PBI',    'Pitney Bowes Inc.');
+
+INSERT INTO metadata."Responsibility" ("ID", "party", "role") VALUES
+  ('Apache',  '{org}Apache', 'resourceProvider'),
+  ('ESRI',    '{org}ESRI',   'principalInvestigator'),
+  ('IHO',     '{org}IHO',    'principalInvestigator'),
+  ('IOGP',    '{org}IOGP',   'principalInvestigator'),
+  ('ISBN',    '{org}ISBN',   'principalInvestigator'),
+  ('ISSN',    '{org}ISSN',   'principalInvestigator'),
+  ('ISO',     '{org}ISO',    'principalInvestigator'),
+  ('MapInfo', '{org}PBI',    'principalInvestigator'),
+  ('NATO',    '{org}NATO',   'principalInvestigator'),
+  ('OGC',     '{org}OGC',    'principalInvestigator'),
+  ('OSGeo',   '{org}OSGeo',  'resourceProvider');
+
+
+
+--
+-- Specifications, data or softwares referenced by the "Citations" class.
+-- Those citations are not organizations; they are resources published by
+-- organizations. Each identifier codespace identifies the organization.
+--
+-- Some citations are used as "authority" for defining codes in a codespace
+-- (for example EPSG codes). The authority codespace is not necessarily the
+-- code or codespace of the citation's identifier. The authority codespaces
+-- are hard-coded in the Citations class and do not appear here.
+--
+-- Rows below are sorted in specifications first, then tables, then softwares.
+-- There is almost a one-to-one relationship between identifiers and citations.
+--
+INSERT INTO metadata."Identifier" ("ID", "code", "codeSpace", "version") VALUES
+  ('ISO 19115-1', '19115-1', 'ISO',         '2014(E)'),
+  ('ISO 19115-2', '19115-2', 'ISO',         '2009(E)'),
+  ('IHO S-57',    'S-57',    'IHO',         '3.1'),
+  ('WMS',         'WMS',     'OGC',         '1.3'),
+  ('EPSG',        'EPSG',    'IOGP',         NULL),
+  ('ArcGIS',      'ArcGIS',  'ESRI',         NULL),
+  ('MapInfo',     'MapInfo', 'Pitney Bowes', NULL),
+  ('Proj4',       'Proj4',   'OSGeo',        NULL),
+  ('SIS',         'SIS',     'Apache',       NULL);
+
+INSERT INTO metadata."Citation" ("ID", "onlineResource", "edition", "citedResponsibleParty", "presentationForm", "alternateTitle" , "title") VALUES
+  ('ISBN',       'ISBN',  NULL,                 'ISBN',    NULL,             'ISBN',         'International Standard Book Number'),
+  ('ISSN',       'ISSN',  NULL,                 'ISSN',    NULL,             'ISSN',         'International Standard Serial Number'),
+  ('ISO 19115-1', NULL,  'ISO 19115-1:2014(E)', 'ISO',    'documentDigital', 'ISO 19115-1',  'Geographic Information — Metadata Part 1: Fundamentals'),
+  ('ISO 19115-2', NULL,  'ISO 19115-2:2009(E)', 'ISO',    'documentDigital', 'ISO 19115-2',  'Geographic Information — Metadata Part 2: Extensions for imagery and gridded data'),
+  ('IHO S-57',    NULL,  '3.1',                 'IHO',    'documentDigital', 'S-57',         'IHO transfer standard for digital hydrographic data'),
+  ('MGRS',        NULL,   NULL,                 'NATO',   'documentDigital',  NULL,          'Military Grid Reference System'),
+  ('WMS',        'WMS',  '1.3',                 'OGC',    'documentDigital', 'WMS',          'Web Map Server'),
+  ('EPSG',       'EPSG',  NULL,                 'IOGP',   'tableDigital',    'EPSG Dataset', 'EPSG Geodetic Parameter Dataset'),
+  ('ArcGIS',     'ESRI',  NULL,                 'ESRI',    NULL,              NULL,          'ArcGIS'),
+  ('MapInfo',     NULL,   NULL,                 'MapInfo', NULL,             'MapInfo',      'MapInfo Pro'),
+  ('Proj4',      'Proj4', NULL,                 'OSGeo',   NULL,             'Proj',         'PROJ coordinate transformation software library'),
+  ('SIS',        'SIS',   NULL,                 'Apache',  NULL,             'Apache SIS',   'Apache Spatial Information System');
+
+--
+-- Citations for organizations. They should not be citations; they are "responsible parties" instead.
+-- But we have to declare some organizations as "citations" because this is the kind of object required
+-- by the "authority" attribute of factories.
 --
--- Metadata about organizations.
+-- Instead than repeating the organization name, the title should reference some naming authority
+-- in that organization. The identifier should have no codespace, and the identifier code should be
+-- the codespace of objects created by the authority represented by that organisation.
 --
+INSERT INTO metadata."Identifier" ("ID", "code") VALUES
+  ('OGC',  'OGC'),
+  ('IOGP', 'IOGP');
 
-INSERT INTO metadata."Party" (ID, "name") VALUES
-  ('Apache', 'The Apache Software Foundation'),
-  ('OGC',    'Open Geospatial Consortium'),
-  ('ISO',    'International Organization for Standardization'),
-  ('IOGP',   'International Association of Oil & Gas producers'),
-  ('NATO',   'North Atlantic Treaty Organization');
+INSERT INTO metadata."Citation" ("ID", "onlineResource", "citedResponsibleParty", "presentationForm", "title") VALUES
+  ('OGC',  'OGCNA', 'OGC',  'documentDigital', 'OGC Naming Authority'),
+  ('IOGP', 'IOGP',  'IOGP', 'documentDigital', 'IOGP Surveying and Positioning Guidance Note 7');
 
-INSERT INTO metadata."Responsibility" (ID, "party", "role") VALUES
-  ('Apache', 'Apache', 'principalInvestigator'),
-  ('OGC',    'OGC',    'principalInvestigator'),
-  ('ISO',    'ISO',    'principalInvestigator'),
-  ('IOGP',   'IOGP',   'principalInvestigator'),
-  ('NATO',   'NATO',   'principalInvestigator');
+UPDATE metadata."Citation" SET "identifier" = "ID" WHERE "ID"<>'ISBN' AND "ID"<>'ISSN' AND "ID"<>'MGRS';
 
-INSERT INTO metadata."Citation" (ID, "edition", "citedResponsibleParty", "title") VALUES
-  ('SIS',         NULL,                  'Apache',  'Apache Spatial Information System'),
-  ('ISO 19115-1', 'ISO 19115-1:2014(E)', 'ISO',     'Geographic Information — Metadata Part 1: Fundamentals'),
-  ('ISO 19115-2', 'ISO 19115-2:2009(E)', 'ISO',     'Geographic Information — Metadata Part 2: Extensions for imagery and gridded data'),
-  ('EPSG',        NULL,                  'IOGP',    'EPSG Geodetic Parameter Dataset'),
-  ('MGRS',        NULL,                  'NATO',    'Military Grid Reference System');
 
 
 --
--- Metadata about file formats.
+-- File formats. Those entries are referenced by DataStore implementations.
 --
-INSERT INTO metadata."Citation" (ID, "alternateTitle", "title") VALUES
+INSERT INTO metadata."Citation" ("ID", "alternateTitle", "title") VALUES
   ('GeoTIFF', 'GeoTIFF', 'GeoTIFF Coverage Encoding Profile'),
   ('NetCDF',  'NetCDF',  'NetCDF Classic and 64-bit Offset Format'),
   ('PNG',     'PNG',     'PNG (Portable Network Graphics) Specification'),
@@ -124,7 +256,7 @@ INSERT INTO metadata."Citation" (ID, "alternateTitle", "title") VALUES
   ('CSV-MF',  'CSV',     'OGC Moving Features Encoding Extension: Simple Comma-Separated Values (CSV)'),
   ('GPX',     'GPX',     'GPS Exchange Format');
 
-INSERT INTO metadata."Format" (ID, "formatSpecificationCitation") VALUES
+INSERT INTO metadata."Format" ("ID", "formatSpecificationCitation") VALUES
   ('GeoTIFF', 'GeoTIFF'),
   ('NetCDF',  'NetCDF'),
   ('PNG',     'PNG'),
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
index 2c77b1c..dee83e6 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
@@ -86,10 +86,6 @@ public final strictfp class TreeTableFormatTest extends TestCase {
         assertMultilinesEquals(
             "Citation……………………………………………………………………………… Undercurrent\n" +
             "  ├─Alternate title………………………………………………… Andākarento\n" +
-            "  ├─Identifier……………………………………………………………… 9782505004509\n" +
-            "  │   ├─Authority……………………………………………………… International Standard Book Number\n" +
-            "  │   │   └─Alternate title…………………………… ISBN\n" +
-            "  │   └─Code space…………………………………………………… ISBN\n"+
             "  ├─Cited responsible party (1 of 2)\n" +
             "  │   ├─Role…………………………………………………………………… Author\n" +
             "  │   └─Individual…………………………………………………… Testsuya Toyoda\n" +
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java
index 1a60769..776eb4b 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java
@@ -16,8 +16,11 @@
  */
 package org.apache.sis.metadata.iso.citation;
 
+import java.util.Set;
 import java.util.List;
 import java.util.Locale;
+import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.lang.reflect.Field;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
@@ -38,12 +41,37 @@ import static org.apache.sis.test.MetadataAssert.*;
  * Tests {@link Citations}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.0
  * @since   0.6
  * @module
  */
 public final strictfp class CitationsTest extends TestCase {
     /**
+     * Verifies that {@link Citations#values()} is complete by comparing with the list
+     * of constants given by reflection.
+     *
+     * @throws IllegalAccessException should never happen since we asked only for public fields.
+     */
+    @Test
+    public void verifyValues() throws IllegalAccessException {
+        final Set<Citation> citations = Collections.newSetFromMap(new IdentityHashMap<>());
+        for (final Citation c : Citations.values()) {
+            final String name = ((CitationConstant) c).title;
+            assertTrue(name, citations.add(c));                             // Fail if duplicated instances.
+        }
+        for (final Field field : Citations.class.getFields()) {
+            final String name  = field.getName();
+            final Object value = field.get(null);
+            if (Citation.class.isAssignableFrom(field.getType())) {
+                assertTrue(name, citations.remove((Citation) value));       // Fail if that instance is missing.
+            } else for (final Object element : (List<?>) value) {
+                assertTrue(name, citations.remove((Citation) element));     // Fail if that instance is missing.
+            }
+        }
+        assertTrue(citations.isEmpty());
+    }
+
+    /**
      * Tests {@link Citations#fromName(String)}.
      *
      * @throws IllegalAccessException should never happen since we asked only for public fields.
@@ -87,49 +115,45 @@ public final strictfp class CitationsTest extends TestCase {
      */
     @Test
     public void testGetIdentifier() {
-        assertEquals("SIS",         getIdentifier(SIS));
-        assertEquals("OGC",         getIdentifier(OGC));
-        assertEquals("IOGP",        getIdentifier(IOGP));
-        assertEquals("EPSG",        getIdentifier(EPSG));
-        assertEquals("ESRI",        getIdentifier(ESRI));
-        assertEquals("NetCDF",      getIdentifier(NETCDF));
-        assertEquals("GeoTIFF",     getIdentifier(GEOTIFF));
-        assertEquals("MapInfo",     getIdentifier(MAP_INFO));
-        assertEquals("ISBN",        getIdentifier(ISBN));
-        assertEquals("ISSN",        getIdentifier(ISSN));
-        assertEquals("Proj.4",      getIdentifier(PROJ4));              // Not a valid Unicode identifier.
-        assertEquals("S-57",        getIdentifier(S57));                // Not a valid Unicode identifier.
-        assertEquals("ISO:19115-1", getIdentifier(ISO_19115.get(0)));   // The ':' separator is not usual in ISO references
-        assertEquals("ISO:19115-2", getIdentifier(ISO_19115.get(1)));   // and could be changed in future SIS versions.
-        assertEquals("OGC:WMS",     getIdentifier(WMS));
-        assertIdentifierEquals("OGC:06-042", null, "OGC", null, "06-042",
-                ((List<? extends Identifier>) WMS.getIdentifiers()).get(1));
-        assertIdentifierEquals("ISO:19128", null, "ISO", "2005", "19128",
-                ((List<? extends Identifier>) WMS.getIdentifiers()).get(2));
+        assertEquals("Apache:SIS",           getIdentifier(SIS));
+        assertEquals("OGC",                  getIdentifier(OGC));
+        assertEquals("IOGP",                 getIdentifier(IOGP));
+        assertEquals("EPSG",                 getIdentifier(EPSG));
+        assertEquals("ESRI:ArcGIS",          getIdentifier(ESRI));
+        assertEquals("NetCDF",               getIdentifier(NETCDF));
+        assertEquals("GeoTIFF",              getIdentifier(GEOTIFF));
+        assertEquals("Pitney Bowes:MapInfo", getIdentifier(MAP_INFO));
+        assertEquals("ISBN",                 getIdentifier(ISBN));
+        assertEquals("ISSN",                 getIdentifier(ISSN));
+        assertEquals("OSGeo:Proj4",          getIdentifier(PROJ4));              // Not a valid Unicode identifier.
+        assertEquals("IHO:S-57",             getIdentifier(S57));                // Not a valid Unicode identifier.
+        assertEquals("ISO:19115-1",          getIdentifier(ISO_19115.get(0)));   // The ':' separator is not usual in ISO references
+        assertEquals("ISO:19115-2",          getIdentifier(ISO_19115.get(1)));   // and could be changed in future SIS versions.
+        assertEquals("OGC:WMS",              getIdentifier(WMS));
     }
 
     /**
-     * Tests {@link Citations#getUnicodeIdentifier(Citation)} on the constants declared in the {@link Citations} class.
-     * All values shall be valid Unicode identifiers or {@code null}.
+     * Tests {@link Citations#toCodeSpace(Citation)} on the constants
+     * declared in the {@link Citations} class.
      */
     @Test
     @DependsOnMethod("testGetIdentifier")
-    public void testGetUnicodeIdentifier() {
-        assertEquals("SIS",         getUnicodeIdentifier(SIS));
-        assertEquals("OGC",         getUnicodeIdentifier(OGC));
-        assertEquals("IOGP",        getUnicodeIdentifier(IOGP));
-        assertEquals("EPSG",        getUnicodeIdentifier(EPSG));
-        assertEquals("ESRI",        getUnicodeIdentifier(ESRI));
-        assertEquals("NetCDF",      getUnicodeIdentifier(NETCDF));
-        assertEquals("GeoTIFF",     getUnicodeIdentifier(GEOTIFF));
-        assertEquals("MapInfo",     getUnicodeIdentifier(MAP_INFO));
-        assertEquals("ISBN",        getUnicodeIdentifier(ISBN));
-        assertEquals("ISSN",        getUnicodeIdentifier(ISSN));
-        assertNull  ("Proj4",       getUnicodeIdentifier(PROJ4));      // Not yet publicly declared as an identifier.
-        assertNull  ("S57",         getUnicodeIdentifier(S57));        // Not yet publicly declared as an identifier.
-        assertEquals("OGC_WMS",     getUnicodeIdentifier(WMS));
-        assertNull  ("ISO_19115-1", getUnicodeIdentifier(ISO_19115.get(0)));  // Not a valid Unicode identifier.
-        assertNull  ("ISO_19115-2", getUnicodeIdentifier(ISO_19115.get(1)));
+    public void testToCodeSpaceFromConstant() {
+        assertEquals("SIS",         toCodeSpace(SIS));
+        assertEquals("OGC",         toCodeSpace(WMS));
+        assertEquals("OGC",         toCodeSpace(OGC));
+        assertEquals("IOGP",        toCodeSpace(IOGP));
+        assertEquals("EPSG",        toCodeSpace(EPSG));
+        assertEquals("ESRI",        toCodeSpace(ESRI));
+        assertEquals("NetCDF",      toCodeSpace(NETCDF));
+        assertEquals("GeoTIFF",     toCodeSpace(GEOTIFF));
+        assertEquals("MapInfo",     toCodeSpace(MAP_INFO));
+        assertEquals("ISBN",        toCodeSpace(ISBN));
+        assertEquals("ISSN",        toCodeSpace(ISSN));
+        assertEquals("Proj4",       toCodeSpace(PROJ4));
+        assertEquals("S57",         toCodeSpace(S57));
+        assertNull  ("ISO_19115-1", toCodeSpace(ISO_19115.get(0)));
+        assertNull  ("ISO_19115-2", toCodeSpace(ISO_19115.get(1)));
     }
 
     /**
@@ -143,7 +167,7 @@ public final strictfp class CitationsTest extends TestCase {
      */
     @Test
     @DependsOnMethod("testGetIdentifier")
-    public void testGetCodeSpace() {
+    public void testToCodeSpace() {
         final SimpleCitation citation = new SimpleCitation(" Valid\u2060Id\u200Bentifier ");
         assertEquals("ValidIdentifier", Citations.toCodeSpace(citation));
 
@@ -154,7 +178,7 @@ public final strictfp class CitationsTest extends TestCase {
     }
 
     /**
-     * A citation which is also an {@link IdentifierSpace}, for {@link #testGetCodeSpace()} purpose.
+     * A citation which is also an {@link IdentifierSpace}, for {@link #testToCodeSpace()} purpose.
      */
     @SuppressWarnings("serial")
     private static final class Proj4 extends SimpleCitation implements IdentifierSpace<Integer> {
@@ -169,47 +193,22 @@ public final strictfp class CitationsTest extends TestCase {
     }
 
     /**
-     * Tests {@link Citations#toCodeSpace(Citation)} on the constants
-     * declared in the {@link Citations} class.
-     */
-    @Test
-    @DependsOnMethod({"testGetUnicodeIdentifier", "testGetIdentifier"})
-    public void testGetConstantCodeSpace() {
-        assertEquals("SIS",         Citations.toCodeSpace(SIS));
-        assertEquals("OGC",         Citations.toCodeSpace(WMS));
-        assertEquals("OGC",         Citations.toCodeSpace(OGC));
-        assertEquals("IOGP",        Citations.toCodeSpace(IOGP));
-        assertEquals("EPSG",        Citations.toCodeSpace(EPSG));
-        assertEquals("ESRI",        Citations.toCodeSpace(ESRI));
-        assertEquals("NetCDF",      Citations.toCodeSpace(NETCDF));
-        assertEquals("GeoTIFF",     Citations.toCodeSpace(GEOTIFF));
-        assertEquals("MapInfo",     Citations.toCodeSpace(MAP_INFO));
-        assertEquals("ISBN",        Citations.toCodeSpace(ISBN));
-        assertEquals("ISSN",        Citations.toCodeSpace(ISSN));
-        assertEquals("Proj4",       Citations.toCodeSpace(PROJ4));
-        assertEquals("S57",         Citations.toCodeSpace(S57));
-        assertNull  ("ISO_19115-1", Citations.toCodeSpace(ISO_19115.get(0)));
-        assertNull  ("ISO_19115-2", Citations.toCodeSpace(ISO_19115.get(1)));
-    }
-
-    /**
      * Tests {@code getTitle()} on some {@code Citation} constants.
      */
     @Test
     public void testGetTitles() {
-        assertTitleEquals("SIS",     "Apache Spatial Information System",    SIS);
-        assertTitleEquals("WMS",     "Web Map Server",                       WMS);
-        assertTitleEquals("OGC",     "Identifiers in OGC namespace",         OGC);
-        assertTitleEquals("EPSG",    "EPSG Geodetic Parameter Dataset",      EPSG);
-        assertTitleEquals("ISBN",    "International Standard Book Number",   ISBN);
-        assertTitleEquals("ISSN",    "International Standard Serial Number", ISSN);
-        assertTitleEquals("GEOTIFF", "GeoTIFF",                              GEOTIFF);
-        assertTitleEquals("NETCDF",  "NetCDF",                               NETCDF);
-        assertTitleEquals("PROJ4",   "Proj.4",                               PROJ4);
-        assertTitleEquals("S57",     "S-57",                                 S57);
+        assertTitleEquals("SIS",       "Apache Spatial Information System",                      SIS);
+        assertTitleEquals("WMS",       "Web Map Server",                                         WMS);
+        assertTitleEquals("OGC",       "OGC Naming Authority",                                   OGC);
+        assertTitleEquals("EPSG",      "EPSG Geodetic Parameter Dataset",                        EPSG);
+        assertTitleEquals("ISBN",      "International Standard Book Number",                     ISBN);
+        assertTitleEquals("ISSN",      "International Standard Serial Number",                   ISSN);
+        assertTitleEquals("GEOTIFF",   "GeoTIFF Coverage Encoding Profile",                      GEOTIFF);
+        assertTitleEquals("NETCDF",    "NetCDF Classic and 64-bit Offset Format",                NETCDF);
+        assertTitleEquals("PROJ4",     "PROJ coordinate transformation software library",        PROJ4);
+        assertTitleEquals("S57",       "IHO transfer standard for digital hydrographic data",    S57);
         assertTitleEquals("ISO_19115", "Geographic Information — Metadata Part 1: Fundamentals", ISO_19115.get(0));
         assertTitleEquals("ISO_19115", "Geographic Information — Metadata Part 2: Extensions for imagery and gridded data", ISO_19115.get(1));
-        assertEquals     ("ISO_19128", "Geographic Information — Web map server interface", getSingleton(WMS.getAlternateTitles()).toString());
     }
 
     /**
@@ -241,7 +240,7 @@ public final strictfp class CitationsTest extends TestCase {
     @Test
     public void testEPSG() {
         final Identifier identifier = getSingleton(EPSG.getIdentifiers());
-        assertEquals("EPSG", getUnicodeIdentifier(EPSG));
+        assertEquals("EPSG", toCodeSpace(EPSG));
         assertEquals("IOGP", identifier.getCodeSpace());
         assertEquals("EPSG", identifier.getCode());
     }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
new file mode 100644
index 0000000..3353cf3
--- /dev/null
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
@@ -0,0 +1,141 @@
+/*
+ * 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.sql;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.HashSet;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.Party;
+import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.citation.Responsibility;
+import org.apache.sis.internal.simple.CitationConstant;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.metadata.MetadataStandard;
+import org.apache.sis.test.sql.TestDatabase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.apache.sis.internal.util.CollectionsExt.first;
+
+
+/**
+ * Compares the {@link MetadataFallback} hard-coded values with the {@code Create.sql} content.
+ * This test is actually invoked by {@link MetadataSourceTest} in order to opportunistically use
+ * the database created by the later (i.e. for avoiding to recreate the same database many times).
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   0.8
+ * @module
+ */
+public final strictfp class MetadataFallbackVerifier {
+    /**
+     * Identifier for which {@link MetadataFallback} does not provide hard-coded values.
+     */
+    private static final Set<String> EXCLUDES = new HashSet<>(Arrays.asList("NetCDF", "GeoTIFF", "ArcGIS", "MapInfo"));
+
+    /**
+     * Creates a temporary database for comparing {@link MetadataFallback} content with database content.
+     * This method is provided for allowing to execute this class individually. In a complete Maven build,
+     * of {@code sis-metadata} module, the test will rather be executed by {@link MetadataSourceTest} for
+     * opportunistic reasons.
+     *
+     * @throws Exception if an exception occurred while creating or comparing the database.
+     */
+    @Test
+    public void compare() throws Exception {
+        try (TestDatabase db = TestDatabase.create("MetadataSource");
+             MetadataSource source = new MetadataSource(MetadataStandard.ISO_19115, db.source, "metadata", null))
+        {
+            source.install();
+            compare(source);
+        }
+    }
+
+    /**
+     * Compares {@link MetadataFallback} content with database content using the given source.
+     * This method is invoked by {@link MetadataSourceTest} for opportunistically reusing the
+     * available database.
+     */
+    static void compare(final MetadataSource source) throws MetadataStoreException {
+        for (final Citation c : Citations.values()) {
+            final String name = ((CitationConstant) c).title;
+            final boolean exclude = EXCLUDES.contains(name);
+            final Citation fromFB = MetadataFallback.createCitation(name);
+            assertEquals(name, exclude, fromFB == null);        // Verify that missing fallbacks are known ones.
+            if (!exclude) {
+                compare(name, source.lookup(Citation.class, name), fromFB);
+            }
+        }
+        compare("IOGP", source.lookup(Citation.class, "IOGP"), MetadataFallback.createCitation("IOGP"));
+    }
+
+    /**
+     * Compares a fallback citation from the citation declared in the database.
+     *
+     * @param  name    identifier used in assertions for identifying which citation failed.
+     * @param  fromDB  citation read from the database.
+     * @param  fromFB  citation created by {@link MetadataFallback}.
+     */
+    private static void compare(final String name, final Citation fromDB, final Citation fromFB) {
+        /*
+         * The database may contain more verbose title than the one declared in MetadataFallback,
+         * in which case the shorter title appears as alternate title.
+         */
+        final InternationalString expectedAltTitle = first(fromDB.getAlternateTitles());
+        final InternationalString actualAltTitle   = first(fromFB.getAlternateTitles());
+        if (fromFB.getTitle().equals(expectedAltTitle)) {
+            assertNull(name, actualAltTitle);
+        } else {
+            assertEquals(name, fromDB.getTitle(), fromFB.getTitle());
+            if (actualAltTitle != null) {
+                assertEquals(name, expectedAltTitle, actualAltTitle);
+            }
+        }
+        /*
+         * The fallback may not declare all identifiers (but it should not declare more).
+         * If it declares an identifier, it should be equal.
+         */
+        final Identifier expectedID = first(fromDB.getIdentifiers());
+        final Identifier actualID   = first(fromFB.getIdentifiers());
+        if (expectedID == null) {
+            assertNull(name, actualID);
+        } else if (actualID != null) {
+            assertEquals(name, expectedID.getCode(),      actualID.getCode());
+            assertEquals(name, expectedID.getCodeSpace(), actualID.getCodeSpace());
+            assertEquals(name, expectedID.getVersion(),   actualID.getVersion());
+        }
+        /*
+         * The fallback may not declare all responsible parties.
+         * If it declares a party, the name and role shall be equal.
+         */
+        final Responsibility expectedResp = first(fromDB.getCitedResponsibleParties());
+        final Responsibility actualResp   = first(fromFB.getCitedResponsibleParties());
+        if (expectedResp == null) {
+            assertNull(name, actualResp);
+        } else if (actualResp != null) {
+            assertEquals(name, expectedResp.getRole(), actualResp.getRole());
+            final Party expectedParty = first(expectedResp.getParties());
+            final Party actualParty = first(actualResp.getParties());
+            assertEquals(name, expectedParty.getName(), actualParty.getName());
+        }
+        assertEquals(name, first(fromDB.getPresentationForms()),
+                           first(fromFB.getPresentationForms()));
+    }
+}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
index e8697b6..dcca10d 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
@@ -57,6 +57,26 @@ public final strictfp class MetadataSourceTest extends TestCase {
             source.install();
             verifyFormats(source);
             testSearch(source);
+
+            // Opportunistic verification using the database we have at hand.
+            MetadataFallbackVerifier.compare(source);
+        }
+    }
+
+    /**
+     * Tests {@link MetadataSource} with a PostgreSQL database if available.
+     *
+     * @throws Exception if an error occurred while executing the script runner.
+     */
+    @Test
+    public void testOnPostgreSQL() throws Exception {
+        try (TestDatabase db = TestDatabase.createOnPostgreSQL("metadata", false);
+             MetadataSource source = new MetadataSource(MetadataStandard.ISO_19115, db.source, "metadata", null))
+        {
+            source.install();
+            verifyFormats(source);
+            testSearch(source);
+            MetadataFallbackVerifier.compare(source);
         }
     }
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
index 24f2e27..e86b14e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
@@ -774,7 +774,7 @@ public final class IdentifiedObjects extends Static {
         final String code = identifier.getCode();
         String cs = identifier.getCodeSpace();
         if (cs == null || cs.isEmpty()) {
-            cs = Citations.getUnicodeIdentifier(identifier.getAuthority());
+            cs = Citations.toCodeSpace(identifier.getAuthority());
         }
         if (cs != null) {
             return cs + DefaultNameSpace.DEFAULT_SEPARATOR + code;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index d3d465c..a7c40eb 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -453,7 +453,6 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho
              *    Description:  EPSG dataset version 9.1 on “Apache Derby Embedded JDBC Driver” version 10.14.
              *
              * TODO: A future version should use Citations.EPSG as a template.
-             *       See the "EPSG" case in ServiceForUtility.createCitation(String).
              */
             final DatabaseMetaData metadata  = connection.getMetaData();
 addURIs:    for (int i=0; ; i++) {
@@ -487,7 +486,7 @@ addURIs:    for (int i=0; ; i++) {
         } catch (SQLException exception) {
             unexpectedException("getAuthority", exception);
         } finally {
-            c.freeze();
+            c.apply(DefaultCitation.State.FINAL);
         }
         return c;
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index dedb2eb..f78bde3 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -227,7 +227,7 @@ class CoordinateOperationRegistry {
             if (registry instanceof GeodeticAuthorityFactory) {
                 codeFinder = ((GeodeticAuthorityFactory) registry).newIdentifiedObjectFinder();
             } else try {
-                codeFinder = IdentifiedObjects.newFinder(Citations.getIdentifier(registry.getAuthority()));
+                codeFinder = IdentifiedObjects.newFinder(Citations.toCodeSpace(registry.getAuthority()));
             } catch (NoSuchAuthorityFactoryException e) {
                 Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
                         CoordinateOperationRegistry.class, "<init>", e);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java
index 5a31bdb..b43ac5b 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java
@@ -81,7 +81,7 @@ public final strictfp class NamedIdentifierTest extends TestCase {
         // ImmutableIdentifier properties
         assertEquals("code",      "4326", identifier.getCode());
         assertEquals("codeSpace", "EPSG", identifier.getCodeSpace());
-        assertEquals("authority", "IOGP", Citations.getIdentifier(identifier.getAuthority()));
+        assertEquals("authority", "IOGP", Citations.toCodeSpace(identifier.getAuthority()));
         assertNull  ("version",           identifier.getVersion());
         assertNull  ("description",       identifier.getDescription());
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java b/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java
index 2dd2152..b9bd6f4 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java
@@ -95,7 +95,7 @@ public strictfp class ReferencingAssert extends MetadataAssert {
         assertNotNull(actual);
         assertEquals("code",       expected,        actual.getCode());
         assertEquals("codeSpace",  Constants.EPSG,  actual.getCodeSpace());
-        assertEquals("authority",  Constants.EPSG,  Citations.getIdentifier(actual.getAuthority()));
+        assertEquals("authority",  Constants.EPSG,  Citations.toCodeSpace(actual.getAuthority()));
         assertEquals("identifier", Constants.EPSG + DefaultNameSpace.DEFAULT_SEPARATOR + expected,
                 IdentifiedObjects.toString(actual));
     }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TreeFormatCustomization.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TreeFormatCustomization.java
new file mode 100644
index 0000000..966b998
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TreeFormatCustomization.java
@@ -0,0 +1,46 @@
+/*
+ * 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.internal.util;
+
+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.
+ *
+ * <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)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+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();
+}
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 0d8b8f4..2f771fd 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
@@ -50,6 +50,7 @@ import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.internal.util.Acyclic;
 import org.apache.sis.internal.util.MetadataServices;
 import org.apache.sis.internal.util.LocalizedParseException;
+import org.apache.sis.internal.util.TreeFormatCustomization;
 
 import static org.apache.sis.util.Characters.NO_BREAK_SPACE;
 
@@ -113,7 +114,8 @@ public class TreeTableFormat extends TabularFormat<TreeTable> {
 
     /**
      * Shared {@code TreeTableFormat} instance for {@link DefaultTreeTable#toString()} implementation.
-     * Usage of this instance shall be done in a synchronized block.
+     * Usage of this instance shall be done in a synchronized block. Note that metadata objects defined
+     * as {@link org.apache.sis.metadata.AbstractMetadata} subclasses use their own format instance.
      */
     static final TreeTableFormat INSTANCE = new TreeTableFormat(null, null);
 
@@ -622,9 +624,18 @@ public class TreeTableFormat extends TabularFormat<TreeTable> {
      * 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}.
+     *
+     * <p>Instances of {@link Writer} are created temporarily before to begin the formatting
+     * of a node, and discarded when the formatting is finished.</p>
      */
     private final class Writer extends LineAppender {
         /**
+         * 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.
          */
         private final TableColumn<?>[] columns;
@@ -660,16 +671,27 @@ 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  recursivityGuard  an initially empty set.
          */
-        Writer(final Appendable out, final TableColumn<?>[] columns, final Set<TreeTable.Node> recursivityGuard) {
+        Writer(final Appendable out, final TreeTable tree, final TableColumn<?>[] columns,
+                final Set<TreeTable.Node> recursivityGuard)
+        {
             super(columns.length >= 2 ? new TableAppender(out, "") : out);
             this.columns = columns;
             this.formats = getFormats(columns, false);
             this.values  = new Object[columns.length];
             this.isLast  = new boolean[8];
             this.recursivityGuard = recursivityGuard;
+            Predicate<TreeTable.Node> filter = nodeFilter;
+            if (tree instanceof TreeFormatCustomization) {
+                final Predicate<TreeTable.Node> more = ((TreeFormatCustomization) tree).filter();
+                if (more != null) {
+                    filter = (filter != null) ? more.and(filter) : more;
+                }
+            }
+            this.filter = filter;
             setTabulationExpanded(true);
             setLineSeparator(" ¶ ");
         }
@@ -858,7 +880,24 @@ public class TreeTableFormat extends TabularFormat<TreeTable> {
                    .append(')').append(lineSeparator);
             }
         }
-    }
+
+        /**
+         * 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()}.
+         */
+        private TreeTable.Node next(final Iterator<? extends TreeTable.Node> it) {
+            while (it.hasNext()) {
+                final TreeTable.Node next = it.next();
+                if (next != null) {
+                    if (filter == null || filter.test(next)) {
+                        return next;
+                    }
+                }
+            }
+            return null;
+        }
+   }
 
     /**
      * Writes a graphical representation of the specified tree table in the given stream or buffer.
@@ -890,7 +929,7 @@ public class TreeTableFormat extends TabularFormat<TreeTable> {
             recursivityGuard = new HashSet<>();
         }
         try {
-            final Writer out = new Writer(toAppendTo, columns, recursivityGuard);
+            final Writer out = new Writer(toAppendTo, tree, columns, recursivityGuard);
             out.format(tree.getRoot(), 0);
             out.flush();
         } finally {
@@ -899,21 +938,6 @@ public class TreeTableFormat extends TabularFormat<TreeTable> {
     }
 
     /**
-     * Returns the next filtered element from the given iterator, or {@code null} if none.
-     */
-    private TreeTable.Node next(final Iterator<? extends TreeTable.Node> it) {
-        while (it.hasNext()) {
-            final TreeTable.Node next = it.next();
-            if (next != null) {
-                if (nodeFilter == null || nodeFilter.test(next)) {
-                    return next;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
      * Returns a clone of this format.
      *
      * @return a clone of this format.
diff --git a/ide-project/NetBeans/nbproject/genfiles.properties b/ide-project/NetBeans/nbproject/genfiles.properties
index f1aa966..04f889d 100644
--- a/ide-project/NetBeans/nbproject/genfiles.properties
+++ b/ide-project/NetBeans/nbproject/genfiles.properties
@@ -3,6 +3,6 @@
 build.xml.data.CRC32=58e6b21c
 build.xml.script.CRC32=462eaba0
 build.xml.stylesheet.CRC32=28e38971@1.53.1.46
-nbproject/build-impl.xml.data.CRC32=fe2883d9
+nbproject/build-impl.xml.data.CRC32=f62bab13
 nbproject/build-impl.xml.script.CRC32=b7ab89c5
 nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48


Mime
View raw message