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 */