sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Make native (implementation-dependent) NetCDF metadata available.
Date Mon, 03 Feb 2020 19:27:48 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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 55f5813  Make native (implementation-dependent) NetCDF metadata available.
55f5813 is described below

commit 55f58134462e0619a21fe05dd692c64eb8e34d5a
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Feb 3 20:26:23 2020 +0100

    Make native (implementation-dependent) NetCDF metadata available.
---
 .../apache/sis/gui/dataset/ResourceExplorer.java   |  32 ++-
 .../apache/sis/gui/metadata/MetadataSummary.java   |  41 +++-
 .../org/apache/sis/gui/metadata/MetadataTree.java  | 206 ++---------------
 ...MetadataTree.java => StandardMetadataTree.java} | 253 +--------------------
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 .../org/apache/sis/internal/netcdf/Decoder.java    |  10 +-
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |  22 ++
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   |  46 ++++
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |  25 +-
 .../apache/sis/storage/netcdf/package-info.java    |   2 +-
 .../java/org/apache/sis/storage/DataStore.java     |  31 ++-
 13 files changed, 228 insertions(+), 447 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
index 68a8e7c..d7672b8 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.gui.dataset;
 
+import java.util.Objects;
 import java.util.Collection;
 import javafx.beans.property.ReadOnlyProperty;
 import javafx.beans.property.SimpleObjectProperty;
@@ -32,10 +33,13 @@ import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.gui.metadata.MetadataSummary;
 import org.apache.sis.gui.metadata.MetadataTree;
+import org.apache.sis.gui.metadata.StandardMetadataTree;
 import org.apache.sis.gui.coverage.GridView;
 import org.apache.sis.gui.coverage.ImageRequest;
 import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.util.resources.Vocabulary;
 
 
 /**
@@ -59,12 +63,6 @@ public class ResourceExplorer extends WindowManager {
     private final ResourceTree resources;
 
     /**
-     * The tab where to show {@link #features} or {@link #coverage}, depending on the kind of resource.
-     * The data will be set only if this tab is visible, because their loading may be costly.
-     */
-    private final Tab dataTab;
-
-    /**
      * The data as a table, created when first needed.
      */
     private FeatureTable features;
@@ -88,6 +86,12 @@ public class ResourceExplorer extends WindowManager {
     private final SplitPane content;
 
     /**
+     * The tab where to show {@link #features} or {@link #coverage}, depending on the kind of resource.
+     * The data will be set only if this tab is visible, because their loading may be costly.
+     */
+    private final Tab dataTab;
+
+    /**
      * The currently selected resource.
      */
     public final ReadOnlyProperty<Resource> selectedResourceProperty;
@@ -110,9 +114,21 @@ public class ResourceExplorer extends WindowManager {
         final Resources localized = localized();
         dataTab = new Tab(localized.getString(Resources.Keys.Data));
         dataTab.setContextMenu(new ContextMenu(createNewWindowMenu()));
+
+        final String nativeTabText = Vocabulary.getResources(localized.getLocale()).getString(Vocabulary.Keys.Format);
+        final MetadataTree nativeMetadata = new MetadataTree(metadata);
+        final Tab nativeTab = new Tab(nativeTabText, nativeMetadata);
+        nativeTab.setDisable(true);
+        nativeMetadata.contentProperty.addListener((p,o,n) -> {
+            nativeTab.setDisable(n == null);
+            Object label = (n != null) ? n.getRoot().getValue(TableColumn.NAME) : null;
+            nativeTab.setText(Objects.toString(label, nativeTabText));
+        });
+
         final TabPane tabs = new TabPane(
             new Tab(localized.getString(Resources.Keys.Summary),  metadata.getView()), dataTab,
-            new Tab(localized.getString(Resources.Keys.Metadata), new MetadataTree(metadata)));
+            new Tab(localized.getString(Resources.Keys.Metadata), new StandardMetadataTree(metadata)),
+            nativeTab);
 
         tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
         tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
@@ -121,7 +137,7 @@ public class ResourceExplorer extends WindowManager {
         resources.getSelectionModel().getSelectedItems().addListener(this::selectResource);
         SplitPane.setResizableWithParent(resources, Boolean.FALSE);
         SplitPane.setResizableWithParent(tabs, Boolean.TRUE);
-        content.setDividerPosition(0, 300);
+        resources.setPrefWidth(400);
 
         selectedResourceProperty = new SimpleObjectProperty<>(this, "selectedResource");
         dataTab.selectedProperty().addListener(this::dataTabShown);
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java
index a90e99d..f4f3f42 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java
@@ -19,6 +19,7 @@ package org.apache.sis.gui.metadata;
 import java.text.DateFormat;
 import java.text.NumberFormat;
 import java.util.Collection;
+import java.util.ArrayList;
 import java.util.Locale;
 import java.util.StringJoiner;
 import javafx.application.Platform;
@@ -43,9 +44,11 @@ import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.internal.gui.Styles;
 import org.apache.sis.internal.util.Strings;
+import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.iso.Types;
 
 
@@ -134,6 +137,13 @@ public class MetadataSummary {
     private final TitledPane[] information;
 
     /**
+     * The listeners to notify about native metadata. We define those listeners in this {@link MetadataSummary}
+     * for taking advantage of the background loading mechanism. We do not provide public API for such listeners
+     * because a future version may want to provide those listeners in a more appropriate (not yet defined) class.
+     */
+    final ArrayList<MetadataTree> nativeMetadataViews;
+
+    /**
      * Creates an initially empty metadata overview.
      */
     public MetadataSummary() {
@@ -147,6 +157,7 @@ public class MetadataSummary {
         content.setFitToWidth(true);
         metadataProperty = new SimpleObjectProperty<>(this, "metadata");
         metadataProperty.addListener(MetadataSummary::applyChange);
+        nativeMetadataViews = new ArrayList<>();
     }
 
     /**
@@ -202,19 +213,31 @@ public class MetadataSummary {
             setMetadata((Metadata) null);
         } else {
             final class Getter extends Task<Metadata> {
-                /**
-                 * Invoked in a background thread for fetching metadata,
-                 * eventually with other information like grid geometry.
-                 */
+                /** The native metadata, or {@code null} if none or not requested. */
+                private TreeTable nativeMetadata;
+
+                /** Invoked in a background thread for fetching metadata. */
                 @Override protected Metadata call() throws DataStoreException {
+                    if (resource instanceof DataStore && !nativeMetadataViews.isEmpty()) {
+                        nativeMetadata = ((DataStore) resource).getNativeMetadata().orElse(null);
+                    }
                     return resource.getMetadata();
                 }
 
-                /**
-                 * Shows the result, unless another {@link #setMetadata(Resource)} has been invoked.
-                 */
-                @Override protected void succeeded() {super.succeeded(); setMetadata(getValue());}
-                @Override protected void failed()    {super.failed();    setError(getException());}
+                /** Shows the result in JavaFX thread. */
+                @Override protected void succeeded() {
+                    super.succeeded();
+                    setMetadata(getValue());
+                    for (final MetadataTree view : nativeMetadataViews) {
+                        view.setContent(nativeMetadata);
+                    }
+                }
+
+                /** Invoked in JavaFX thread if metadata loading failed. */
+                @Override protected void failed() {
+                    super.failed();
+                    setError(getException());
+                }
             }
             BackgroundThreads.execute(new Getter());
         }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
index 397303c..9b964b2 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
@@ -20,9 +20,6 @@ import java.util.Locale;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.io.StringWriter;
-import javax.xml.transform.stream.StreamResult;
 import javafx.beans.DefaultProperty;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyObjectWrapper;
@@ -30,40 +27,23 @@ import javafx.beans.property.ReadOnlyStringWrapper;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ObservableValue;
 import javafx.collections.ObservableList;
-import javafx.event.ActionEvent;
-import javafx.event.EventHandler;
-import javafx.scene.control.ContextMenu;
-import javafx.scene.control.Menu;
-import javafx.scene.control.MenuItem;
 import javafx.scene.control.TreeItem;
 import javafx.scene.control.TreeTableView;
 import javafx.scene.control.TreeTableColumn;
 import javafx.scene.control.TreeTableColumn.CellDataFeatures;
-import javafx.scene.control.TreeTableRow;
-import javafx.scene.input.Clipboard;
-import javafx.scene.input.ClipboardContent;
-import org.opengis.metadata.Metadata;
 import org.opengis.util.InternationalString;
 import org.opengis.util.ControlledVocabulary;
 import org.opengis.referencing.IdentifiedObject;
-import org.apache.sis.metadata.AbstractMetadata;
-import org.apache.sis.metadata.MetadataStandard;
-import org.apache.sis.metadata.ValueExistencePolicy;
 import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.internal.xml.LegacyNamespaces;
-import org.apache.sis.internal.gui.ExceptionReporter;
-import org.apache.sis.internal.gui.DataFormats;
-import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.iso.Types;
-import org.apache.sis.io.wkt.WKTFormat;
-import org.apache.sis.xml.XML;
 
 
 /**
- * A view of {@link Metadata} properties organized as a tree table.
- * The content of each row in this tree table is represented by a {@link TreeTable.Node}.
+ * A view of metadata represented by a {@link TreeTable}. This base class can represent
+ * {@linkplain org.apache.sis.storage.DataStore#getNativeMetadata() native metadata} or
+ * {@linkplain org.apache.sis.storage.DataStore#getMetadata() standard metadata}.
  * The tree table shows the following columns:
  *
  * <ul>
@@ -71,8 +51,8 @@ import org.apache.sis.xml.XML;
  *   <li>{@link TableColumn#VALUE} — the property value typically as a string, number or date.</li>
  * </ul>
  *
- * While this view is designed mostly for metadata, it can actually be used
- * for other kinds of data provided by the {@link TreeTable} interface.
+ * In the particular case of metadata from ISO 19115 standard,
+ * the {@link StandardMetadataTree} specialization should be used instead than this base class.
  *
  * <h2>Limitations</h2>
  * <ul>
@@ -80,9 +60,7 @@ import org.apache.sis.xml.XML;
  *       For changing content, use the {@link #contentProperty} instead.</li>
  * </ul>
  *
- * @todo Add contextual menu for showing a node in the summary pane (we would store in memory the path,
- *       including sequence number for multi-values property, and apply it to all opened resources).
- *       Add a panel for controlling the number/date/angle format pattern.
+ * @todo Add a panel for controlling the number/date/angle format pattern.
  *
  * @author  Siddhesh Rane (GSoC)
  * @author  Martin Desruisseaux (Geomatys)
@@ -137,7 +115,7 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
     /**
      * The locale to use for texts.
      */
-    private final Locale textLocale;
+    final Locale textLocale;
 
     /**
      * The locale to use for dates/numbers.
@@ -146,26 +124,30 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
     private final Locale dataLocale;
 
     /**
-     * The "copy" and "copy as" localized string, used for contextual menus.
-     */
-    private final String copy, copyAs;
-
-    /**
      * Creates a new initially empty metadata tree.
      */
     public MetadataTree() {
-        this(null);
+        this(null, false);
     }
 
     /**
      * Creates a new initially empty metadata tree which will be automatically updated
-     * when the given widget shows new metadata. This constructor registers a listener
-     * to {@link MetadataSummary#metadataProperty} which forwards the metadata changes
-     * to {@link #setContent(Metadata)}.
+     * when the given widget is given a {@linkplain org.apache.sis.storage.DataStore}.
      *
      * @param  controller  the widget to watch, or {@code null} if none.
      */
     public MetadataTree(final MetadataSummary controller) {
+        this(controller, false);
+    }
+
+    /**
+     * For {@link MetadataTree} and {@link StandardMetadataTree} constructors.
+     *
+     * @param  controller  the widget to watch, or {@code null} if none.
+     * @param  standard    {@code true} for showing standard metadata, or {@code false} for native metadata.
+     */
+    @SuppressWarnings("ThisEscapedInObjectConstruction")
+    MetadataTree(final MetadataSummary controller, final boolean standard) {
         if (controller != null) {
             textLocale = controller.localized.getLocale();
             dataLocale = controller.dataLocale;
@@ -173,47 +155,24 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
             textLocale = Locale.getDefault(Locale.Category.DISPLAY);
             dataLocale = Locale.getDefault(Locale.Category.FORMAT);
         }
-        final Resources localized = Resources.forLocale(textLocale);
-        copy   = localized.getString(Resources.Keys.Copy);
-        copyAs = localized.getString(Resources.Keys.CopyAs);
-
         contentProperty = new ContentProperty(this);
         nameColumn      = new TreeTableColumn<>(TableColumn.NAME .getHeader().toString(textLocale));
         valueColumn     = new TreeTableColumn<>(TableColumn.VALUE.getHeader().toString(textLocale));
         nameColumn .setCellValueFactory(MetadataTree::getPropertyName);
         valueColumn.setCellValueFactory(MetadataTree::getPropertyValue);
-        setRowFactory(Row::new);
 
         setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY);
         getColumns().setAll(nameColumn, valueColumn);
         contentProperty.addListener(MetadataTree::applyChange);
-        if (controller != null) {
-            controller.metadataProperty.addListener((p,o,n) -> setContent(n));
-        }
-    }
-
-    /**
-     * Returns the given metadata as a tree table.
-     */
-    private static TreeTable toTree(final Object metadata) {
-        if (metadata instanceof AbstractMetadata) {
-            return ((AbstractMetadata) metadata).asTreeTable();
-        } else {
-            return MetadataStandard.ISO_19115.asTreeTable(metadata, null, ValueExistencePolicy.COMPACT);
+        if (!standard) {
+            setShowRoot(false);
+            if (controller != null) {
+                controller.nativeMetadataViews.add(this);
+            }
         }
     }
 
     /**
-     * Sets the metadata to show in this tree table. This method gets a {@link TreeTable} view
-     * of the given metadata, then delegates to {@link #setContent(TreeTable)}.
-     *
-     * @param  metadata  the metadata to show in this tree table view, or {@code null} if none.
-     */
-    public void setContent(final Metadata metadata) {
-        setContent(metadata == null ? null : toTree(metadata));
-    }
-
-    /**
      * Sets the data to show.
      * This is a convenience method for setting {@link #contentProperty} value.
      * The given {@link TreeTable} shall contain at least the following columns:
@@ -263,121 +222,6 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
     }
 
     /**
-     * A row in a metadata tree view, used for adding contextual menu on a row-by-row basis.
-     */
-    private static final class Row extends TreeTableRow<TreeTable.Node> implements EventHandler<ActionEvent> {
-        /**
-         * The context menu, to be added only if this row is non-empty.
-         */
-        private final ContextMenu menu;
-
-        /**
-         * The menu items for XML or WKT formats.
-         */
-        private final MenuItem copyAsXML, copyAsLegacy, copyAsWKT;
-
-        /**
-         * The menu items for copying in XML formats, to be disabled if we can not do this export.
-         */
-        private final Menu copyAs;
-
-        /**
-         * Creates a new row for the given tree table.
-         */
-        @SuppressWarnings("ThisEscapedInObjectConstruction")
-        Row(final TreeTableView<TreeTable.Node> view) {
-            final MetadataTree md = (MetadataTree) view;
-            final MenuItem copy;
-            copy         = new MenuItem(md.copy);
-            copyAsXML    = new MenuItem();
-            copyAsWKT    = new MenuItem("WKT — Well Known Text");
-            copyAsLegacy = new MenuItem("XML — Metadata (2007)");
-            copyAs       = new Menu(md.copyAs, null, copyAsWKT, copyAsXML, copyAsLegacy);
-            menu         = new ContextMenu(copy, copyAs);
-            copyAsLegacy.setOnAction(this);
-            copyAsXML   .setOnAction(this);
-            copy        .setOnAction(this);
-        }
-
-        /**
-         * Invoked when a new row is selected. This method enable or disable the "copy as" menu
-         * depending on whether or not we can format XML document for currently selected row.
-         */
-        @Override
-        protected void updateItem​(final TreeTable.Node item, final boolean empty) {
-            super.updateItem(item, empty);
-            if (!empty) {
-                boolean disabled = true;
-                final TreeTable.Node node = getItem();
-                if (node != null) {
-                    final Object obj = node.getUserObject();
-                    if (obj != null) {
-                        if (MetadataStandard.ISO_19115.isMetadata(obj.getClass())) {
-                            copyAsXML.setText("XML — Metadata (2016)");
-                            copyAsWKT.setDisable(true);
-                            copyAsLegacy.setDisable(false);
-                            disabled = false;
-                        } else if (obj instanceof IdentifiedObject) {
-                            copyAsXML.setText("XML — Geographic Markup Language");
-                            copyAsWKT.setDisable(false);
-                            copyAsLegacy.setDisable(true);
-                            disabled = false;
-                        }
-                    }
-                }
-                copyAs.setDisable(disabled);
-            }
-            setContextMenu(empty ? null : menu);
-        }
-
-        /**
-         * Invoked when user requested to copy metadata. The requested format (ISO 19115 versus ISO 19139)
-         * will be determined by comparing the event source with {@link #copyAsLegacy} and {@link #copyAsXML}
-         * menu items.
-         */
-        @Override
-        public void handle(final ActionEvent event) {
-            final TreeTable.Node node = getItem();
-            if (node != null) {
-                final Object obj = node.getUserObject();
-                if (obj != null) {
-                    final Object source = event.getSource();
-                    final ClipboardContent content = new ClipboardContent();
-                    final String text;
-                    try {
-                        if (source == copyAsWKT) {                              // Well Known Text.
-                            final WKTFormat f = new WKTFormat(null, null);
-                            text = f.format(obj);
-                        } else if (source == copyAsXML) {                       // GML or ISO 19115-3:2016.
-                            text = XML.marshal(obj);
-                            content.put(DataFormats.XML, text);
-                        } else if (source == copyAsLegacy) {                    // ISO 19139:2007.
-                            final StringWriter output = new StringWriter();
-                            XML.marshal(obj, new StreamResult(output),
-                                    Collections.singletonMap(XML.METADATA_VERSION, LegacyNamespaces.VERSION_2007));
-                            text = output.toString();
-                            content.put(DataFormats.ISO_19139, text);
-                        } else if (MetadataStandard.ISO_19115.isMetadata(obj.getClass())) {
-                            text = toTree(obj).toString();
-                        } else {
-                            final Object value = node.getValue(TableColumn.VALUE);
-                            if (value == null) return;
-                            text = value.toString();
-                        }
-                    } catch (Exception e) {
-                        final Resources localized = Resources.forLocale(((MetadataTree) getTreeTableView()).textLocale);
-                        ExceptionReporter.show(localized.getString(Resources.Keys.ErrorExportingData),
-                                               localized.getString(Resources.Keys.CanNotCreateXML), e);
-                        return;
-                    }
-                    content.putString(text);
-                    Clipboard.getSystemClipboard().setContent(content);
-                }
-            }
-        }
-    }
-
-    /**
      * A simple node encapsulating a {@link TreeTable.Node} in a view.
      * The list of children is fetched when first needed.
      */
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/StandardMetadataTree.java
similarity index 50%
copy from application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
copy to application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/StandardMetadataTree.java
index 397303c..77d280d 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/StandardMetadataTree.java
@@ -16,47 +16,29 @@
  */
 package org.apache.sis.gui.metadata;
 
-import java.util.Locale;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.io.StringWriter;
 import javax.xml.transform.stream.StreamResult;
-import javafx.beans.DefaultProperty;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.ReadOnlyObjectWrapper;
-import javafx.beans.property.ReadOnlyStringWrapper;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.value.ObservableValue;
-import javafx.collections.ObservableList;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
 import javafx.scene.control.ContextMenu;
 import javafx.scene.control.Menu;
 import javafx.scene.control.MenuItem;
-import javafx.scene.control.TreeItem;
 import javafx.scene.control.TreeTableView;
-import javafx.scene.control.TreeTableColumn;
-import javafx.scene.control.TreeTableColumn.CellDataFeatures;
 import javafx.scene.control.TreeTableRow;
 import javafx.scene.input.Clipboard;
 import javafx.scene.input.ClipboardContent;
 import org.opengis.metadata.Metadata;
-import org.opengis.util.InternationalString;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.referencing.IdentifiedObject;
 import org.apache.sis.metadata.AbstractMetadata;
 import org.apache.sis.metadata.MetadataStandard;
 import org.apache.sis.metadata.ValueExistencePolicy;
-import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.DataFormats;
 import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.TableColumn;
-import org.apache.sis.util.iso.Types;
 import org.apache.sis.io.wkt.WKTFormat;
 import org.apache.sis.xml.XML;
 
@@ -71,8 +53,8 @@ import org.apache.sis.xml.XML;
  *   <li>{@link TableColumn#VALUE} — the property value typically as a string, number or date.</li>
  * </ul>
  *
- * While this view is designed mostly for metadata, it can actually be used
- * for other kinds of data provided by the {@link TreeTable} interface.
+ * This class still supports the {@link #setContent(TreeTable)} method,
+ * but {@link #setContent(Metadata)} should be used instead.
  *
  * <h2>Limitations</h2>
  * <ul>
@@ -82,7 +64,6 @@ import org.apache.sis.xml.XML;
  *
  * @todo Add contextual menu for showing a node in the summary pane (we would store in memory the path,
  *       including sequence number for multi-values property, and apply it to all opened resources).
- *       Add a panel for controlling the number/date/angle format pattern.
  *
  * @author  Siddhesh Rane (GSoC)
  * @author  Martin Desruisseaux (Geomatys)
@@ -90,61 +71,7 @@ import org.apache.sis.xml.XML;
  * @since   1.1
  * @module
  */
-@DefaultProperty("content")
-public class MetadataTree extends TreeTableView<TreeTable.Node> {
-    /**
-     * The column for metadata property name.
-     */
-    private final TreeTableColumn<TreeTable.Node, String> nameColumn;
-
-    /**
-     * The column for metadata property value.
-     * Values are typically {@link InternationalString}, {@link Number} or dates.
-     */
-    private final TreeTableColumn<TreeTable.Node, Object> valueColumn;
-
-    /**
-     * The data shown in this tree table. The {@link ObjectProperty#set(Object)} method requires
-     * that the given value obey to the constraints documented in {@link #setContent(TreeTable)}.
-     *
-     * @see #getContent()
-     * @see #setContent(TreeTable)
-     */
-    public final ObjectProperty<TreeTable> contentProperty;
-
-    /**
-     * Implementation of {@link MetadataTree#contentProperty} as a named class for more readable stack trace.
-     * This class verifies the constraints documented in {@link MetadataTree#setContent(TreeTable)}.
-     */
-    private static final class ContentProperty extends SimpleObjectProperty<TreeTable> {
-        /** Creates a new property. */
-        ContentProperty(final MetadataTree bean) {
-            super(bean, "content");
-        }
-
-        /** Invoked when the user wants to set new data. */
-        @Override public void set(final TreeTable data) {
-            if (data != null) {
-                final List<TableColumn<?>> columns = data.getColumns();
-                if (!(columns.contains(TableColumn.NAME) && columns.contains(TableColumn.VALUE))) {
-                    throw new IllegalArgumentException();
-                }
-            }
-            super.set(data);
-        }
-    }
-
-    /**
-     * The locale to use for texts.
-     */
-    private final Locale textLocale;
-
-    /**
-     * The locale to use for dates/numbers.
-     * This is often the same than {@link #textLocale}.
-     */
-    private final Locale dataLocale;
-
+public class StandardMetadataTree extends MetadataTree {
     /**
      * The "copy" and "copy as" localized string, used for contextual menus.
      */
@@ -153,7 +80,7 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
     /**
      * Creates a new initially empty metadata tree.
      */
-    public MetadataTree() {
+    public StandardMetadataTree() {
         this(null);
     }
 
@@ -165,28 +92,12 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
      *
      * @param  controller  the widget to watch, or {@code null} if none.
      */
-    public MetadataTree(final MetadataSummary controller) {
-        if (controller != null) {
-            textLocale = controller.localized.getLocale();
-            dataLocale = controller.dataLocale;
-        } else {
-            textLocale = Locale.getDefault(Locale.Category.DISPLAY);
-            dataLocale = Locale.getDefault(Locale.Category.FORMAT);
-        }
+    public StandardMetadataTree(final MetadataSummary controller) {
+        super(controller, true);
         final Resources localized = Resources.forLocale(textLocale);
         copy   = localized.getString(Resources.Keys.Copy);
         copyAs = localized.getString(Resources.Keys.CopyAs);
-
-        contentProperty = new ContentProperty(this);
-        nameColumn      = new TreeTableColumn<>(TableColumn.NAME .getHeader().toString(textLocale));
-        valueColumn     = new TreeTableColumn<>(TableColumn.VALUE.getHeader().toString(textLocale));
-        nameColumn .setCellValueFactory(MetadataTree::getPropertyName);
-        valueColumn.setCellValueFactory(MetadataTree::getPropertyValue);
         setRowFactory(Row::new);
-
-        setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY);
-        getColumns().setAll(nameColumn, valueColumn);
-        contentProperty.addListener(MetadataTree::applyChange);
         if (controller != null) {
             controller.metadataProperty.addListener((p,o,n) -> setContent(n));
         }
@@ -214,55 +125,6 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
     }
 
     /**
-     * Sets the data to show.
-     * This is a convenience method for setting {@link #contentProperty} value.
-     * The given {@link TreeTable} shall contain at least the following columns:
-     *
-     * <ul>
-     *   <li>{@link TableColumn#NAME}</li>
-     *   <li>{@link TableColumn#VALUE}</li>
-     * </ul>
-     *
-     * @param  data  the data to show, or {@code null} if none.
-     * @throws IllegalArgumentException if the data is non-null but does not contains the required columns.
-     */
-    public final void setContent(final TreeTable data) {
-        contentProperty.setValue(data);
-    }
-
-    /**
-     * Returns the data currently shown, or {@code null} if none.
-     * This is a convenience method for fetching {@link #contentProperty} value.
-     *
-     * @return the table currently shown, or {@code null} if none.
-     *
-     * @see #contentProperty
-     * @see #setContent(TreeTable)
-     */
-    public final TreeTable getContent() {
-        return contentProperty.getValue();
-    }
-
-    /**
-     * Invoked when {@link #contentProperty} value changed.
-     *
-     * @param  property  the property which has been modified.
-     * @param  oldValue  the old tree table.
-     * @param  content   the tree table to use for building new content.
-     */
-    private static void applyChange(final ObservableValue<? extends TreeTable> property,
-                                    final TreeTable oldValue, final TreeTable  content)
-    {
-        final MetadataTree s = (MetadataTree) ((ContentProperty) property).getBean();
-        TreeItem<TreeTable.Node> root = null;
-        if (content != null) {
-            root = new Item(content.getRoot());
-            root.setExpanded(true);
-        }
-        s.setRoot(root);
-    }
-
-    /**
      * A row in a metadata tree view, used for adding contextual menu on a row-by-row basis.
      */
     private static final class Row extends TreeTableRow<TreeTable.Node> implements EventHandler<ActionEvent> {
@@ -286,7 +148,7 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
          */
         @SuppressWarnings("ThisEscapedInObjectConstruction")
         Row(final TreeTableView<TreeTable.Node> view) {
-            final MetadataTree md = (MetadataTree) view;
+            final StandardMetadataTree md = (StandardMetadataTree) view;
             final MenuItem copy;
             copy         = new MenuItem(md.copy);
             copyAsXML    = new MenuItem();
@@ -306,7 +168,7 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
         @Override
         protected void updateItem​(final TreeTable.Node item, final boolean empty) {
             super.updateItem(item, empty);
-            if (!empty) {
+            if (!empty && copyAs != null) {
                 boolean disabled = true;
                 final TreeTable.Node node = getItem();
                 if (node != null) {
@@ -365,7 +227,7 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
                             text = value.toString();
                         }
                     } catch (Exception e) {
-                        final Resources localized = Resources.forLocale(((MetadataTree) getTreeTableView()).textLocale);
+                        final Resources localized = Resources.forLocale(((StandardMetadataTree) getTreeTableView()).textLocale);
                         ExceptionReporter.show(localized.getString(Resources.Keys.ErrorExportingData),
                                                localized.getString(Resources.Keys.CanNotCreateXML), e);
                         return;
@@ -376,101 +238,4 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> {
             }
         }
     }
-
-    /**
-     * A simple node encapsulating a {@link TreeTable.Node} in a view.
-     * The list of children is fetched when first needed.
-     */
-    private static final class Item extends TreeItem<TreeTable.Node> {
-        /**
-         * Whether this node is a leaf.
-         */
-        private final boolean isLeaf;
-
-        /**
-         * Creates a new node.
-         */
-        Item(final TreeTable.Node node) {
-            super(node);
-            isLeaf = node.isLeaf() || node.getChildren().isEmpty();
-        }
-
-        /**
-         * Returns whether the node can not have children.
-         */
-        @Override
-        public boolean isLeaf() {
-            return isLeaf;
-        }
-
-        /**
-         * Returns the items for all sub-nodes contained in this node.
-         */
-        @Override
-        public ObservableList<TreeItem<TreeTable.Node>> getChildren() {
-            final ObservableList<TreeItem<TreeTable.Node>> children = super.getChildren();
-            if (children.isEmpty()) {
-                final Collection<TreeTable.Node> data = getValue().getChildren();
-                final List<Item> wrappers = new ArrayList<>(data.size());
-                for (final TreeTable.Node child : data) {
-                    wrappers.add(new Item(child));
-                }
-                children.setAll(wrappers);      // Fire a single event instead of multiple `add`.
-            }
-            return children;
-        }
-    }
-
-    /**
-     * Returns the value for the specified column.
-     * The generic type in this method signature reduces the risk that we confuse columns.
-     *
-     * @param  <T>      the type of values in the column.
-     * @param  cell     a wrapper around the {@link TreeTable.Node} from which to get the value.
-     * @param  column   column of the desired value.
-     * @return value in the specified column. May be {@code null}.
-     */
-    private static <T> T getValue(final CellDataFeatures<TreeTable.Node, ? extends T> cell, final TableColumn<T> column) {
-        final TreeTable.Node node = cell.getValue().getValue();
-        return node.getValue(column);
-    }
-
-    /**
-     * Returns the name of the metadata property wrapped by the given argument.
-     * This method is invoked by JavaFX when a new cell needs to be rendered.
-     */
-    private static ObservableValue<String> getPropertyName(final CellDataFeatures<TreeTable.Node, String> cell) {
-        final CharSequence value = getValue(cell, TableColumn.NAME);
-        final String text;
-        if (value instanceof InternationalString) {
-            final MetadataTree view = (MetadataTree) cell.getTreeTableView();
-            text = ((InternationalString) value).toString(view.textLocale);
-        } else {
-            text = (value != null) ? value.toString() : null;
-        }
-        return new ReadOnlyStringWrapper(text);
-    }
-
-    /**
-     * Returns the value of the metadata property wrapped by the given argument.
-     * This method is invoked by JavaFX when a new cell needs to be rendered.
-     *
-     * @todo Format other kinds of objects (numbers, dates, timezones, etc.).
-     *       See {@link org.apache.sis.util.collection.TreeTableFormat},
-     *       if possible by putting some code in common.
-     */
-    private static ObservableValue<Object> getPropertyValue(final CellDataFeatures<TreeTable.Node, Object> cell) {
-        final MetadataTree view = (MetadataTree) cell.getTreeTableView();
-        Object value = getValue(cell, TableColumn.VALUE);
-        if (value instanceof IdentifiedObject) {
-            value = IdentifiedObjects.getDisplayName((IdentifiedObject) value, view.textLocale);
-        }
-        if (value instanceof ControlledVocabulary) {
-            value = Types.getCodeTitle((ControlledVocabulary) value);
-        }
-        if (value instanceof InternationalString) {
-            value = ((InternationalString) value).toString(view.textLocale);
-        }
-        return new ReadOnlyObjectWrapper<>(value);
-    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
index 2afc5b3..4d0f30c 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
@@ -410,6 +410,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short FillValue = 63;
 
         /**
+         * Format
+         */
+        public static final short Format = 196;
+
+        /**
          * Geocentric
          */
         public static final short Geocentric = 64;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
index 21688a3..df7d99c 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
@@ -85,6 +85,7 @@ EntryCount_1            = {0} entr{0,choice,0#y|2#ies}
 Envelope                = Envelope
 Errors                  = Errors
 FillValue               = Fill value
+Format                  = Format
 Geocentric              = Geocentric
 GeocentricRadius        = Geocentric radius
 GeocentricConversion    = Geocentric conversion
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
index 62f4c5a..e5bef50 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -92,6 +92,7 @@ EndPoint                = Point d\u2019arriv\u00e9
 Envelope                = Enveloppe
 Errors                  = Erreurs
 FillValue               = Valeur de remplissage
+Format                  = Format
 Geocentric              = G\u00e9ocentrique
 GeocentricRadius        = Rayon g\u00e9ocentrique
 GeocentricConversion    = Conversion g\u00e9ocentrique
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
index 95e3046..d3dc0e0 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
@@ -41,6 +41,7 @@ import org.apache.sis.util.Utilities;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.logging.PerformanceLevel;
+import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.Modules;
@@ -54,7 +55,7 @@ import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
  * Synchronizations are caller's responsibility.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.3
  * @module
  */
@@ -179,6 +180,13 @@ public abstract class Decoder extends ReferencingFactoryContainer implements Clo
     }
 
     /**
+     * Adds netCDF attributes to the given node.
+     *
+     * @param  root  the node where to add netCDF attributes.
+     */
+    public abstract void addNativeMetadata(TreeTable.Node root);
+
+    /**
      * Returns a filename for formatting error message and for information purpose.
      * The filename should not contain path, but may contain file extension.
      *
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
index c7e5481..bac80a7 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
@@ -60,6 +60,8 @@ import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.measure.Units;
 import org.apache.sis.math.Vector;
@@ -1036,6 +1038,26 @@ nextVar:    for (final VariableInfo variable : variables) {
     }
 
     /**
+     * Adds netCDF attributes to the given node.
+     *
+     * @param  root  the node where to add netCDF attributes.
+     */
+    @Override
+    public void addNativeMetadata(final TreeTable.Node root) {
+        for (final Map.Entry<String,Object> entry : attributeMap.entrySet()) {
+            final TreeTable.Node node = root.newChild();
+            node.setValue(TableColumn.NAME, entry.getKey());
+            Object value = entry.getValue();
+            if (value != null) {
+                if (value instanceof Vector) {
+                    value = ((Vector) value).toArray();
+                }
+                node.setValue(TableColumn.VALUE, value);
+            }
+        }
+    }
+
+    /**
      * Returns a string representation to be inserted in {@link org.apache.sis.storage.netcdf.NetcdfStore#toString()}
      * result. This is for debugging purpose only any may change in any future SIS version.
      *
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
index d9c6306..d2766ec 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
@@ -40,6 +40,8 @@ import ucar.nc2.ft.FeatureDatasetPoint;
 import ucar.nc2.ft.FeatureDatasetFactoryManager;
 import ucar.nc2.ft.FeatureCollection;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.internal.netcdf.Convention;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.Variable;
@@ -500,6 +502,50 @@ public final class DecoderWrapper extends Decoder implements CancelTask {
     }
 
     /**
+     * Adds netCDF attributes to the given node.
+     *
+     * @param  root  the node where to add netCDF attributes.
+     */
+    @Override
+    public void addNativeMetadata(final TreeTable.Node root) {
+        addNativeMetadata(root, file.getRootGroup());
+    }
+
+    /**
+     * Adds all attributes of the given group, then create nodes for sub-groups (if any).
+     * This method invokes itself recursively.
+     */
+    private void addNativeMetadata(final TreeTable.Node branch, final Group group) {
+        for (final Attribute attribute : group.getAttributes()) {
+            final TreeTable.Node node = branch.newChild();
+            node.setValue(TableColumn.NAME, attribute.getShortName());
+            final int length = attribute.getLength();
+            final Object value;
+            switch (length) {
+                case 0: continue;
+                case 1: {
+                    value = attribute.getValue(0);
+                    break;
+                }
+                default: {
+                    final Object[] values = new Object[length];
+                    for (int i=0; i<length; i++) {
+                        values[i] = attribute.getValue(i);
+                    }
+                    value = values;
+                    break;
+                }
+            }
+            node.setValue(TableColumn.VALUE, value);
+        }
+        for (final Group sub : group.getGroups()) {
+            final TreeTable.Node node = branch.newChild();
+            node.setValue(TableColumn.NAME, sub.getShortName());
+            addNativeMetadata(node, sub);
+        }
+    }
+
+    /**
      * Invoked by the UCAR netCDF library for checking if the reading process has been canceled.
      * This method returns the {@link #canceled} flag.
      *
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
index f5b8db7..4844f61 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
@@ -42,6 +42,9 @@ import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.event.StoreEvent;
 import org.apache.sis.storage.event.StoreListener;
 import org.apache.sis.storage.event.WarningEvent;
+import org.apache.sis.util.collection.DefaultTreeTable;
+import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Version;
 import ucar.nc2.constants.ACDD;
@@ -53,7 +56,7 @@ import ucar.nc2.constants.CDM;
  * Instances of this data store are created by {@link NetcdfStoreProvider#open(StorageConnector)}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see NetcdfStoreProvider
  *
@@ -191,6 +194,26 @@ public class NetcdfStore extends DataStore implements Aggregate {
     }
 
     /**
+     * Returns netCDF attributes. The meaning of those attributes may vary depending on data provider.
+     * The {@linkplain #getMetadata() standard metadata} should be preferred since they allow abstraction of
+     * data format details, but those native metadata are sometime useful when an information is not provided
+     * by the standard metadata.
+     *
+     * @return resources information structured in an implementation-specific way.
+     * @throws DataStoreException if an error occurred while reading the metadata.
+     *
+     * @since 1.1
+     */
+    @Override
+    public Optional<TreeTable> getNativeMetadata() throws DataStoreException {
+        final DefaultTreeTable table = new DefaultTreeTable(TableColumn.NAME, TableColumn.VALUE);
+        final TreeTable.Node root = table.getRoot();
+        root.setValue(TableColumn.NAME, NetcdfStoreProvider.NAME);
+        decoder.addNativeMetadata(root);
+        return Optional.of(table);
+    }
+
+    /**
      * Returns the resources (features or coverages) in this netCDF store.
      *
      * @return children resources that are components of this netCDF store.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/package-info.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/package-info.java
index fc4605e..468b167 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/package-info.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/package-info.java
@@ -48,7 +48,7 @@
  * Care must be taken for avoiding confusion when using SIS and UCAR libraries together.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.3
  * @module
  */
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
index dcf983c..e4368b1 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
@@ -29,6 +29,8 @@ import org.opengis.metadata.identification.Identification;
 import org.opengis.parameter.ParameterValueGroup;
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.internal.storage.StoreUtilities;
 import org.apache.sis.internal.storage.Resources;
 import org.apache.sis.internal.util.Strings;
@@ -63,7 +65,7 @@ import org.apache.sis.storage.event.StoreListeners;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see DataStores#open(Object)
  *
@@ -351,7 +353,7 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable {
      * file format and more.
      *
      * @return information about resources in the data store. Should not be {@code null}.
-     * @throws DataStoreException if an error occurred while reading the data.
+     * @throws DataStoreException if an error occurred while reading the metadata.
      *
      * @see #getIdentifier()
      */
@@ -359,6 +361,31 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable {
     public abstract Metadata getMetadata() throws DataStoreException;
 
     /**
+     * Returns implementation-specific metadata. The structure of those metadata varies for each file format.
+     * The {@linkplain #getMetadata() standard metadata} should be preferred since they allow abstraction of
+     * format details, but those native metadata are sometime useful when an information is not provided by
+     * the standard metadata.
+     *
+     * <p>The tree table should contain at least the following columns:</p>
+     * <ul>
+     *   <li>{@link TableColumn#NAME}  — a name for the metadata property, e.g. "Title".</li>
+     *   <li>{@link TableColumn#VALUE} — the property value typically as a string, number or date.</li>
+     * </ul>
+     *
+     * The {@link TableColumn#NAME} of the root node should be a format name such as "NetCDF" or "GeoTIFF".
+     * That name should be short since it may be used in widget as a designation of implementation-specific
+     * details.
+     *
+     * @return resources information structured in an implementation-specific way.
+     * @throws DataStoreException if an error occurred while reading the metadata.
+     *
+     * @since 1.1
+     */
+    public Optional<TreeTable> getNativeMetadata() throws DataStoreException {
+        return Optional.empty();
+    }
+
+    /**
      * Searches for a resource identified by the given identifier. The given identifier should be the string
      * representation of the return value of {@link Resource#getIdentifier()} on the desired resource.
      * Implementation may also accept aliases for convenience. For example if the full name of a resource


Mime
View raw message