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 d517cda884a5d80fb43b96ac163fa2e4f9bdf28a
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Oct 27 15:16:08 2021 +0200

    Load native metadata in a background thread separated from standard 
metadata.
    This allows loading those metadata only if the "native metadata" tab is 
selected.
---
 .../apache/sis/gui/dataset/ResourceExplorer.java   | 156 +++++++++++++++++----
 .../apache/sis/gui/metadata/MetadataSummary.java   |  28 +---
 .../org/apache/sis/gui/metadata/MetadataTree.java  |   7 +-
 .../org/apache/sis/gui/metadata/package-info.java  |   2 +-
 4 files changed, 132 insertions(+), 61 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 aed5ecf..d0936a4 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
@@ -20,6 +20,7 @@ import java.util.Objects;
 import java.util.Collection;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
+import javafx.beans.binding.BooleanBinding;
 import javafx.beans.property.ReadOnlyProperty;
 import javafx.beans.property.ReadOnlyObjectWrapper;
 import javafx.collections.ListChangeListener;
@@ -39,16 +40,19 @@ import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.DataSet;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreProvider;
 import org.apache.sis.storage.DataStoreException;
 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.ImageRequest;
 import org.apache.sis.gui.coverage.CoverageExplorer;
-import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.LogHandler;
 
 
@@ -76,10 +80,28 @@ public class ResourceExplorer extends WindowManager {
 
     /**
      * The widget showing metadata about a selected resource.
+     * Its content will be updated only when the tab is visible.
      */
     private final MetadataSummary metadata;
 
     /**
+     * The widget showing native metadata about a selected resource.
+     * Its content will be updated only when the tab is visible.
+     */
+    private final MetadataTree nativeMetadata;
+
+    /**
+     * The tab containing {@link #nativeMetadata}.
+     * The table title will change depending on the selected resource.
+     */
+    private final Tab nativeMetadataTab;
+
+    /**
+     * Default label for {@link #nativeMetadataTab} when no resource is 
selected.
+     */
+    private final String defaultNativeTabLabel;
+
+    /**
      * The gridded data as an image or as a table, created when first needed.
      */
     private CoverageExplorer coverage;
@@ -126,6 +148,11 @@ public class ResourceExplorer extends WindowManager {
     private boolean isDataTabSet;
 
     /**
+     * Whether one of the standard metadata tab (either "summary" or 
"metadata") is selected.
+     */
+    private final BooleanBinding metadataShown;
+
+    /**
      * Last divider position as a fraction between 0 and 1, or {@code NaN} if 
undefined.
      * This is used for keeping the position constant when adding and removing 
controls.
      */
@@ -142,42 +169,48 @@ public class ResourceExplorer extends WindowManager {
         
resources.getSelectionModel().getSelectedItems().addListener(this::onResourceSelected);
         resources.setPrefWidth(400);
         selectedResource = new ReadOnlyObjectWrapper<>(this, 
"selectedResource");
+        final Vocabulary vocabulary = 
Vocabulary.getResources(resources.locale);
+        /*
+         * "Summary" tab showing a summary of resource metadata.
+         */
         metadata = new MetadataSummary();
+        final Tab summaryTab = new 
Tab(vocabulary.getString(Vocabulary.Keys.Summary),  metadata.getView());
         /*
-         * Build the tabs.
+         * "Visual" tab showing the raster data as an image.
+         *
+         * TODO: add contextual menu for creating a window showing directly 
the visual.
          */
-        final Vocabulary vocabulary = 
Vocabulary.getResources(resources.locale);
         viewTab = new Tab(vocabulary.getString(Vocabulary.Keys.Visual));
-        // TODO: add contextual menu for window showing directly the visual.
-
+        /*
+         * "Data" tab showing raster data as a table.
+         */
         tableTab = new Tab(vocabulary.getString(Vocabulary.Keys.Data));
         tableTab.setContextMenu(new 
ContextMenu(SelectedData.setTabularView(createNewWindowMenu())));
+        /*
+         * "Metadata" tab showing ISO 19115 metadata as a tree.
+         */
+        final Tab metadataTab = new 
Tab(vocabulary.getString(Vocabulary.Keys.Metadata), new 
StandardMetadataTree(metadata));
+        /*
+         * "Native metadata" tab showing metadata in their "raw" form 
(specific to the format).
+         */
+        nativeMetadata = new MetadataTree(metadata);
+        defaultNativeTabLabel = vocabulary.getString(Vocabulary.Keys.Format);
+        nativeMetadataTab = new Tab(defaultNativeTabLabel, nativeMetadata);
+        nativeMetadataTab.setDisable(true);
+        /*
+         * "Logging" tab showing log records specific to the selected resource
+         * (as opposed to the application menu showing all loggings regardless 
their source).
+         */
         final LogViewer logging = new LogViewer(vocabulary);
         logging.source.bind(selectedResource);
-
-        final String nativeTabText = 
vocabulary.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 Tab loggingTab = new 
Tab(vocabulary.getString(Vocabulary.Keys.Logs), logging.getView());
         loggingTab.disableProperty().bind(logging.isEmptyProperty());
-
-        final TabPane tabs = new TabPane(
-            new Tab(vocabulary.getString(Vocabulary.Keys.Summary),  
metadata.getView()), viewTab, tableTab,
-            new Tab(vocabulary.getString(Vocabulary.Keys.Metadata), new 
StandardMetadataTree(metadata)),
-            nativeTab, loggingTab);
-
-        tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
-        tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
         /*
          * Build the main pane which put everything together.
          */
+        final TabPane tabs = new TabPane(summaryTab, viewTab, tableTab, 
metadataTab, nativeMetadataTab, loggingTab);
+        tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
+        tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
         controls = new SplitPane(resources);
         controls.setOrientation(Orientation.VERTICAL);
         content = new SplitPane(controls, tabs);
@@ -187,9 +220,22 @@ public class ResourceExplorer extends WindowManager {
         SplitPane.setResizableWithParent(tabs, Boolean.TRUE);
         /*
          * Register listeners last, for making sure we don't have undesired 
event.
+         * Those listeners trig loading of various objects (data, standard 
metadata,
+         * native metadata) when the corresponding tab become visible.
          */
         viewTab .selectedProperty().addListener((p,o,n) -> dataTabShown(n, 
true));
         tableTab.selectedProperty().addListener((p,o,n) -> dataTabShown(n, 
false));
+        metadataShown = 
summaryTab.selectedProperty().or(metadataTab.selectedProperty());
+        metadataShown.addListener((p,o,n) -> {
+            if (Boolean.FALSE.equals(o) && Boolean.TRUE.equals(n)) {
+                metadata.setMetadata(getSelectedResource());
+            }
+        });
+        nativeMetadataTab.selectedProperty().addListener((p,o,n) -> {
+            if (Boolean.FALSE.equals(o) && Boolean.TRUE.equals(n)) {
+                loadNativeMetadata();
+            }
+        });
     }
 
     /**
@@ -279,20 +325,72 @@ public class ResourceExplorer extends WindowManager {
                 if (resource != null) break;
             }
         }
+        /*
+         * Fetch metadata immediately if one of the two ISO 19115 metadata 
tabs is selected.
+         * Otherwise metadata will be fetched when one of those tabs will 
become selected
+         * (listener registered in the constructor). A similar policy is 
applied for data.
+         */
         selectedResource.set(resource);
-        metadata.setMetadata(resource);
-        isDataTabSet = isDataTabSelected();
+        metadata.setMetadata(metadataShown.get() ? resource : null);
+        isDataTabSet = viewTab.isSelected() || tableTab.isSelected();
         updateDataTab(isDataTabSet ? resource : null, true);
         if (!isDataTabSet) {
             setNewWindowDisabled(!(resource instanceof GridCoverageResource || 
resource instanceof FeatureSet));
         }
+        /*
+         * Update the label is disabled state of the native metadata tab. We 
do not have a reliable way
+         * to know if metadata are present without trying to fetch them, so 
current implementation only
+         * checks if the data store implementation override the 
`getNativeMetadata()` method.
+         */
+        String  label    = null;
+        boolean disabled = true;
+        if (resource instanceof DataStore) {
+            final DataStore store = (DataStore) resource;
+            final DataStoreProvider provider = store.getProvider();
+            if (provider != null) {
+                label = provider.getShortName();
+            }
+            try {
+                disabled = 
resource.getClass().getMethod("getNativeMetadata").getDeclaringClass() == 
DataStore.class;
+            } catch (NoSuchMethodException e) {
+                // Should never happen.
+            }
+        }
+        nativeMetadataTab.setText(Objects.toString(label, 
defaultNativeTabLabel));
+        nativeMetadataTab.setDisable(disabled);
+        nativeMetadata.setPlaceholder(null);
+        nativeMetadata.setContent(null);
+        if (nativeMetadataTab.isSelected()) {
+            loadNativeMetadata();
+        }
     }
 
     /**
-     * Returns whether the currently selected tab is {@link #viewTab} or 
{@link #tableTab}.
+     * Loads native metadata in a background thread and shows them in the 
"native metadata" tab.
      */
-    private boolean isDataTabSelected() {
-        return viewTab.isSelected() || tableTab.isSelected();
+    private final void loadNativeMetadata() {
+        final Resource resource = getSelectedResource();
+        if (resource instanceof DataStore) {
+            final DataStore store = (DataStore) resource;
+            BackgroundThreads.execute(new Task<TreeTable>() {
+                /** Invoked in a background thread for fetching metadata. */
+                @Override protected TreeTable call() throws DataStoreException 
{
+                    return store.getNativeMetadata().orElse(null);
+                }
+
+                /** Shows the result in JavaFX thread. */
+                @Override protected void succeeded() {
+                    if (resource == getSelectedResource()) {
+                        nativeMetadata.setContent(getValue());
+                    }
+                }
+
+                /** Invoked in JavaFX thread if metadata loading failed. */
+                @Override protected void failed() {
+                    nativeMetadata.setPlaceholder(new 
ExceptionReporter(getException()).getView());
+                }
+            });
+        }
     }
 
     /**
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 8bc8a0d..dee04f1 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
@@ -20,7 +20,6 @@ import java.util.Locale;
 import java.text.DateFormat;
 import java.text.NumberFormat;
 import java.util.Collection;
-import java.util.ArrayList;
 import java.util.StringJoiner;
 import javafx.application.Platform;
 import javafx.beans.DefaultProperty;
@@ -42,13 +41,11 @@ import org.apache.sis.internal.gui.BackgroundThreads;
 import org.apache.sis.internal.gui.ExceptionReporter;
 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.storage.Aggregate;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.gui.Widget;
 
@@ -58,7 +55,7 @@ import org.apache.sis.gui.Widget;
  *
  * @author  Smaniotto Enzo (GSoC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -133,13 +130,6 @@ public class MetadataSummary extends Widget {
     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() {
@@ -151,7 +141,6 @@ public class MetadataSummary extends Widget {
         };
         content = new ScrollPane(new VBox());
         content.setFitToWidth(true);
-        nativeMetadataViews = new ArrayList<>();
         metadataProperty = new SimpleObjectProperty<>(this, "metadata");
         metadataProperty.addListener(MetadataSummary::applyChange);
     }
@@ -230,9 +219,6 @@ public class MetadataSummary extends Widget {
         /** The resource from which to load metadata. */
         private final Resource resource;
 
-        /** The native metadata, or {@code null} if none or not requested. */
-        TreeTable nativeMetadata;
-
         /** Creates a new metadata getter. */
         Getter(final Resource resource) {
             this.resource = resource;
@@ -240,9 +226,6 @@ public class MetadataSummary extends Widget {
 
         /** 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();
         }
 
@@ -328,15 +311,6 @@ public class MetadataSummary extends Widget {
         s.getter = null;                // In case this method is invoked 
before `Getter` completed.
         s.error  = null;
         if (metadata != oldValue) {
-            /*
-             * The native metadata are handled in a special way by 
`setMetadata(Resource)`.
-             * Since we have no public API for setting native metadata in 
`MetadataSummary`,
-             * we set the value to null if this method is not invoked from 
`Getter.succeeded()`.
-             */
-            final TreeTable nativeMetadata = (getter != null && 
getter.isDone()) ? getter.nativeMetadata : null;
-            for (final MetadataTree view : s.nativeMetadataViews) {
-                view.setContent(nativeMetadata);
-            }
             final ObservableList<Node> children = s.getChildren();
             if (!children.isEmpty() && !(children.get(0) instanceof Section)) {
                 children.clear();       // If we were previously showing an 
error, clear all.
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 a1a725b..3d322fa 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
@@ -68,7 +68,7 @@ import org.apache.sis.util.logging.Logging;
  *
  * @author  Siddhesh Rane (GSoC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -185,9 +185,6 @@ check:      if (data != null) {
         contentProperty.addListener(MetadataTree::applyChange);
         if (!standard) {
             setShowRoot(false);
-            if (controller != null) {
-                controller.nativeMetadataViews.add(this);
-            }
         }
     }
 
@@ -231,6 +228,8 @@ check:      if (data != null) {
 
     /**
      * Invoked when {@link #contentProperty} value changed.
+     * This method invokes {@link TreeTable#getRoot()} and
+     * wraps the value as the root node of this control.
      *
      * @param  property  the property which has been modified.
      * @param  oldValue  the old tree table.
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/package-info.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/package-info.java
index 413a219..3d94a52 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/package-info.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/package-info.java
@@ -23,7 +23,7 @@
  * @author  Smaniotto Enzo (GSoC)
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */

Reply via email to