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 cd6d1919be6fb006f4effe5e087d650b7f79a84b
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sat Oct 30 00:41:01 2021 +0200

    Add a "System monitor" view showing which region of files are read.
    We use it for monitoring behavior of GeoTIFF reader.
---
 .../main/java/org/apache/sis/gui/DataViewer.java   |  32 +--
 .../java/org/apache/sis/gui/SystemMonitor.java     | 116 ++++++++
 .../main/java/org/apache/sis/gui/package-info.java |   2 +-
 .../sis/internal/gui/FixedHeaderColumnSize.java    |  89 +++++++
 .../apache/sis/internal/gui/ResourceLoader.java    |  44 +++-
 .../org/apache/sis/internal/gui/Resources.java     |  15 ++
 .../apache/sis/internal/gui/Resources.properties   |   3 +
 .../sis/internal/gui/Resources_fr.properties       |   3 +
 .../java/org/apache/sis/internal/gui/Styles.java   |   2 +-
 .../apache/sis/internal/gui/io/FileAccessItem.java | 292 +++++++++++++++++++++
 .../apache/sis/internal/gui/io/FileAccessView.java | 136 ++++++++++
 .../sis/{gui => internal/gui/io}/package-info.java |  15 +-
 12 files changed, 720 insertions(+), 29 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java
index bd0eee2..539081d 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java
@@ -24,6 +24,7 @@ import java.util.Set;
 import java.awt.SplashScreen;
 import javafx.application.Application;
 import javafx.application.Platform;
+import javafx.collections.ObservableList;
 import javafx.geometry.Rectangle2D;
 import javafx.scene.Scene;
 import javafx.scene.control.Menu;
@@ -36,7 +37,6 @@ import javafx.stage.FileChooser;
 import javafx.scene.image.Image;
 import javafx.stage.Screen;
 import javafx.stage.Stage;
-import org.apache.sis.gui.dataset.LogViewer;
 import org.apache.sis.gui.dataset.ResourceExplorer;
 import org.apache.sis.internal.gui.BackgroundThreads;
 import org.apache.sis.internal.gui.LogHandler;
@@ -59,7 +59,7 @@ import org.apache.sis.util.resources.Vocabulary;
  *
  * @author  Smaniotto Enzo (GSoC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -162,18 +162,20 @@ public class DataViewer extends Application {
                 close.setDisable(!(n instanceof DataStore));
             });
         }
+        final Menu windows = new 
Menu(localized.getString(Resources.Keys.Windows));
+        {
+            final ObservableList<MenuItem> items = windows.getItems();
+            content.setWindowsItems(items);
+            final MenuItem logging = new 
MenuItem(localized.getString(Resources.Keys.SystemMonitor));
+            logging.setOnAction((e) -> showSystemLogsWindow());
+            items.addAll(content.createNewWindowMenu(), logging);
+        }
         final Menu help = new Menu(localized.getString(Resources.Keys.Help));
         {   // For keeping variables locale.
-            final MenuItem logging = new 
MenuItem(vocabulary.getString(Vocabulary.Keys.Logs));
-            logging.setOnAction((e) -> showSystemLogsWindow());
             help.getItems().addAll(
-                    localized.menu(Resources.Keys.WebSite, (e) -> 
getHostServices().showDocument("https://sis.apache.org/";)),
-                    new SeparatorMenuItem(), logging,
+                    localized.menu(Resources.Keys.WebSite, (e) -> 
getHostServices().showDocument("https://sis.apache.org/javafx.html";)),
                     localized.menu(Resources.Keys.About, (e) -> 
AboutDialog.show()));
         }
-        final Menu windows = new 
Menu(localized.getString(Resources.Keys.Windows));
-        windows.getItems().add(content.createNewWindowMenu());
-        content.setWindowsItems(windows.getItems());
         menus.getMenus().addAll(file, windows, help);
         /*
          * Set the main content and show.
@@ -277,18 +279,10 @@ public class DataViewer extends Application {
      */
     private void showSystemLogsWindow() {
         if (systemLogsWindow == null) {
-            final LogViewer viewer = new LogViewer();
-            viewer.systemLogs.set(true);
-            final Stage w = new Stage();
-            w.setTitle(Vocabulary.format(Vocabulary.Keys.Logs) + " — Apache 
SIS");
-            w.getIcons().setAll(window.getIcons());
-            w.setScene(new Scene(viewer.getView()));
-            w.setWidth (800);
-            w.setHeight(600);
-            window.setOnHidden((e) -> w.hide());
-            systemLogsWindow = w;
+            systemLogsWindow = SystemMonitor.create(window, null);
         }
         systemLogsWindow.show();
+        systemLogsWindow.toFront();
     }
 
     /**
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/SystemMonitor.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/SystemMonitor.java
new file mode 100644
index 0000000..a29f57d
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/SystemMonitor.java
@@ -0,0 +1,116 @@
+/*
+ * 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.gui;
+
+import java.util.Locale;
+import java.util.function.UnaryOperator;
+import javafx.event.EventHandler;
+import javafx.event.EventType;
+import javafx.stage.WindowEvent;
+import javafx.stage.Stage;
+import javafx.scene.Scene;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
+import org.apache.sis.gui.dataset.LogViewer;
+import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.internal.gui.ResourceLoader;
+import org.apache.sis.internal.gui.io.FileAccessView;
+import org.apache.sis.internal.storage.io.ChannelFactory;
+import org.apache.sis.util.resources.Vocabulary;
+
+
+/**
+ * Shows the "System monitor" window.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+final class SystemMonitor implements EventHandler<WindowEvent> {
+    /**
+     * The provider of wrappers around channels used for reading data.
+     * Those wrappers are used for listening to file accesses.
+     *
+     * @see ResourceLoader#setFactoryWrapper(UnaryOperator)
+     */
+    private final UnaryOperator<ChannelFactory> listener;
+
+    /**
+     * Creates new event handler.
+     */
+    private SystemMonitor(final UnaryOperator<ChannelFactory> listener) {
+        this.listener = listener;
+    }
+
+    /**
+     * Invoked when the system monitor window is shown or hidden.
+     * This method starts or stops listening to read events on channels.
+     */
+    @Override
+    public void handle(final WindowEvent event) {
+        final EventType<WindowEvent> type = event.getEventType();
+        UnaryOperator<ChannelFactory> wrapper;
+        if (WindowEvent.WINDOW_SHOWN.equals(type)) {
+            wrapper = listener;
+        } else if (WindowEvent.WINDOW_HIDDEN.equals(type)) {
+            wrapper = null;
+        } else {
+            return;
+        }
+        ResourceLoader.setFactoryWrapper(wrapper);
+    }
+
+    /**
+     * Creates the system monitor window.
+     *
+     * @param  parent  the parent window.
+     * @param  locale  the locale, or {@code null} for default.
+     */
+    static Stage create(final Stage parent, final Locale locale) {
+        final Resources  resources  = Resources.forLocale(locale);
+        final Vocabulary vocabulary = Vocabulary.getResources(locale);
+        final FileAccessView files = new FileAccessView(resources, vocabulary);
+        final LogViewer logging = new LogViewer();
+        logging.systemLogs.set(true);
+        /*
+         * Creates the tab pane.
+         */
+        final Tab fileTab = new Tab(resources 
.getString(Resources.Keys.FileAccesses), files.getView());
+        final Tab logTab  = new 
Tab(vocabulary.getString(Vocabulary.Keys.Logs), logging.getView());
+        fileTab.setClosable(false);
+        logTab .setClosable(false);
+        final TabPane panes = new TabPane(fileTab, logTab);
+        /*
+         * Create the window.
+         */
+        final Stage w = new Stage();
+        w.setTitle(resources.getString(Resources.Keys.SystemMonitor) + " — 
Apache SIS");
+        w.getIcons().setAll(parent.getIcons());
+        w.setScene(new Scene(panes));
+        w.setWidth (800);
+        w.setHeight(600);
+        /*
+         * Install listeners.
+         */
+        final SystemMonitor handler = new SystemMonitor(files);
+        w.setOnShown (handler);
+        w.setOnHidden(handler);
+        parent.setOnHidden((e) -> w.hide());
+        return w;
+    }
+}
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/package-info.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/package-info.java
index 8901cca..30f10ab 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/package-info.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/package-info.java
@@ -21,7 +21,7 @@
  * @author  Smaniotto Enzo (GSoC)
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/FixedHeaderColumnSize.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/FixedHeaderColumnSize.java
new file mode 100644
index 0000000..7e8b988
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/FixedHeaderColumnSize.java
@@ -0,0 +1,89 @@
+/*
+ * 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.gui;
+
+import java.util.List;
+import javafx.util.Callback;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TableColumn;
+
+
+/**
+ * A column resize policy where the size of the first column stay unchanged 
during table resize event.
+ * But the column size can still be changed by moving the column separator.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+public final class FixedHeaderColumnSize<E> implements 
Callback<TableView.ResizeFeatures<E>, Boolean> {
+    /**
+     * The singleton instance.
+     */
+    @SuppressWarnings({"rawtypes","unchecked"})     // Partially unchecked for 
compatibility with JavaFX.
+    public static final Callback<TableView.ResizeFeatures, Boolean> INSTANCE = 
new FixedHeaderColumnSize();
+
+    /**
+     * Creates the singleton instance.
+     */
+    private FixedHeaderColumnSize() {
+    }
+
+    /**
+     * Returns an identifier for this policy.
+     */
+    @Override
+    public String toString() {
+        return "fixed-header-resize";
+    }
+
+    /**
+     * Invoked by {@link TableView#resizeColumn(TableColumn, double)} when a 
column is resized.
+     * This implementation temporarily freezes the size of the first column 
(in declaration order,
+     * not necessarily in visual order) before to resize the other columns.
+     *
+     * @param  prop  the table and the column on which resizing is applied, 
together with size delta.
+     * @return whether resizing is allowed.
+     */
+    @Override
+    public Boolean call(final TableView.ResizeFeatures<E> prop) {
+        if (prop.getColumn() == null) {
+            final List<TableColumn<E,?>> columns = 
prop.getTable().getColumns();
+            if (!columns.isEmpty()) {
+                final TableColumn<E,?> column = columns.get(0);
+                final boolean reducing = prop.getDelta() < 0;
+                final double width = column.getWidth();
+                final double save = reducing ? column.getMinWidth() : 
column.getMaxWidth();
+                final Boolean result;
+                try {
+                    column.setMinWidth(width);
+                    column.setMaxWidth(width);
+                    result = TableView.CONSTRAINED_RESIZE_POLICY.call(prop);
+                } finally {
+                    if (reducing) {
+                        column.setMinWidth(save);
+                    } else {
+                        column.setMaxWidth(save);
+                    }
+                }
+                return result;
+            }
+        }
+        return TableView.CONSTRAINED_RESIZE_POLICY.call(prop);
+    }
+}
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ResourceLoader.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ResourceLoader.java
index d18916c..1191b40 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ResourceLoader.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ResourceLoader.java
@@ -24,6 +24,7 @@ import java.nio.file.Paths;
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.util.function.Consumer;
+import java.util.function.UnaryOperator;
 import java.util.stream.Stream;
 import javafx.concurrent.Task;
 import javafx.event.EventHandler;
@@ -36,7 +37,10 @@ import org.apache.sis.storage.DataStores;
 import org.apache.sis.util.collection.Cache;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.internal.storage.io.IOUtilities;
+import org.apache.sis.internal.storage.io.ChannelFactory;
+import org.apache.sis.internal.storage.io.InternalOptionKey;
 import org.apache.sis.storage.DataStore;
+import org.apache.sis.gui.DataViewer;
 
 
 /**
@@ -60,7 +64,7 @@ import org.apache.sis.storage.DataStore;
  * @todo Set title. Add progress listener and cancellation capability.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  *
  * @see BackgroundThreads#execute(Runnable)
  *
@@ -76,6 +80,19 @@ public final class ResourceLoader extends Task<Resource> {
     private static final Cache<Object,DataStore> CACHE = new Cache<>();
 
     /**
+     * Provider of wrappers around channels used for reading data.
+     * Those wrappers can be used for listening to file accesses.
+     *
+     * <p><b>Note:</b> it should be a package-private field of {@link 
DataViewer}, but we put it here because
+     * we have no public access to {@code DataViewer} non-public members. 
Using a static field is okay if only
+     * one {@link DataViewer} application is running in the same JVM.</p>
+     *
+     * @see #setFactoryWrapper(UnaryOperator)
+     * @see DataViewer#getCurrentStage()
+     */
+    private static volatile UnaryOperator<ChannelFactory> factoryWrapper;
+
+    /**
      * The {@link Resource} input.
      * This is usually a {@link File} or {@link Path}.
      */
@@ -150,9 +167,22 @@ public final class ResourceLoader extends Task<Resource> {
 
     /**
      * Loads the resource after we verified that it is not in the cache.
+     * This method is invoked from a background thread.
      */
     private DataStore load() throws DataStoreException {
-        return DataStores.open(source);
+        Object input = source;
+        final UnaryOperator<ChannelFactory> wrapper = factoryWrapper;
+        if (wrapper != null) {
+            final StorageConnector connector;
+            if (input instanceof StorageConnector) {
+                connector = (StorageConnector) input;
+            } else {
+                connector = new StorageConnector(input);
+            }
+            connector.setOption(InternalOptionKey.CHANNEL_FACTORY_WRAPPER, 
wrapper);
+            input = connector;
+        }
+        return DataStores.open(input);
     }
 
     /**
@@ -171,6 +201,16 @@ public final class ResourceLoader extends Task<Resource> {
     }
 
     /**
+     * Set the provider of wrappers around channels used for reading data.
+     * Those wrappers can be used for listening to file accesses.
+     *
+     * @param  wrapper  the wrapper, or {@code null} if none.
+     */
+    public static void setFactoryWrapper(final UnaryOperator<ChannelFactory> 
wrapper) {
+        factoryWrapper = wrapper;
+    }
+
+    /**
      * Removes the given data store from cache and closes it. It is caller's 
responsibility
      * to ensure that the given data store is not used anymore before to 
invoke this method.
      * This method should be invoked from JavaFX thread for making sure there 
is no new usage
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
index a2ba112..e9e4b79 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
@@ -61,6 +61,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short About = 46;
 
         /**
+         * Accessed regions
+         */
+        public static final short AccessedRegions = 65;
+
+        /**
          * All files
          */
         public static final short AllFiles = 1;
@@ -222,6 +227,11 @@ public final class Resources extends IndexedResourceBundle 
{
         public static final short Exit = 20;
 
         /**
+         * File accesses
+         */
+        public static final short FileAccesses = 63;
+
+        /**
          * Full screen
          */
         public static final short FullScreen = 22;
@@ -338,6 +348,11 @@ public final class Resources extends IndexedResourceBundle 
{
         public static final short StandardErrorStream = 32;
 
         /**
+         * System monitor
+         */
+        public static final short SystemMonitor = 64;
+
+        /**
          * Tabular data
          */
         public static final short TabularData = 33;
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
index 561c4a1..5fdf198 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
@@ -21,6 +21,7 @@
 #
 
 About                  = About\u2026
+AccessedRegions        = Accessed regions
 AllFiles               = All files
 Along_1                = Along {0}
 AzimuthalEquidistant   = Azimuthal equidistant
@@ -53,6 +54,7 @@ ErrorCreatingCRS       = Error creating reference system
 ErrorDataAccess        = Error during data access
 ErrorAt                = An error occurred at the following location:
 Exit                   = Exit
+FileAccesses           = File accesses
 FullScreen             = Full screen
 GeodeticDataset_1      = {0} geodetic dataset
 GeospatialFiles        = Geospatial data files
@@ -76,6 +78,7 @@ SelectCrsByContextMenu = For changing the projection, use 
contextual menu on the
 SendTo                 = Send to
 SizeOrPosition         = Size or position
 StandardErrorStream    = Standard error stream
+SystemMonitor          = System monitor
 TabularData            = Tabular data
 TileIndexStart         = Tile index start
 UTM                    = Universal Transverse Mercator
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
index 1213f19..c2bf0d5 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
@@ -26,6 +26,7 @@
 #
 
 About                  = \u00c0 propos de\u2026
+AccessedRegions        = R\u00e9gions acc\u00e9d\u00e9es
 AllFiles               = Tous les fichiers
 Along_1                = Selon {0}
 AzimuthalEquidistant   = Azimutal \u00e9quidistant
@@ -58,6 +59,7 @@ ErrorCreatingCRS       = Erreur \u00e0 la cr\u00e9ation du 
syst\u00e8me de r\u00
 ErrorDataAccess        = Erreur lors de l\u2019acc\u00e8s \u00e0 la donn\u00e9e
 ErrorAt                = Une erreur est survenue \u00e0 l\u2019endroit 
suivant\u00a0:
 Exit                   = Quitter
+FileAccesses           = Acc\u00e8s aux fichiers
 FullScreen             = Plein \u00e9cran
 GeodeticDataset_1      = Base de donn\u00e9es g\u00e9od\u00e9siques {0}
 GeospatialFiles        = Fichiers de donn\u00e9es g\u00e9ospatiales
@@ -81,6 +83,7 @@ SelectCrsByContextMenu = Pour changer la projection, utilisez 
le menu contextuel
 SendTo                 = Envoyer vers
 SizeOrPosition         = Taille ou position
 StandardErrorStream    = Flux d\u2019erreur standard
+SystemMonitor          = Moniteur syst\u00e8me
 TabularData            = Tableau de valeurs
 TileIndexStart         = D\u00e9but des indices de tuiles
 UTM                    = Transverse universelle de Mercator
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
index 075303f..fb244d0 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
@@ -67,7 +67,7 @@ public final class Styles extends Static {
     /**
      * "Standard" height of table rows. Can be approximate.
      */
-    public static final double ROW_HEIGHT = 30;
+    public static final int ROW_HEIGHT = 30;
 
     /**
      * Usual color of text.
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/io/FileAccessItem.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/io/FileAccessItem.java
new file mode 100644
index 0000000..8586781
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/io/FileAccessItem.java
@@ -0,0 +1,292 @@
+/*
+ * 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.gui.io;
+
+import java.util.List;
+import java.util.ListIterator;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import javafx.application.Platform;
+import javafx.collections.ObservableList;
+import javafx.scene.Node;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.StrokeType;
+import org.apache.sis.measure.Range;
+import org.apache.sis.util.collection.RangeSet;
+
+
+/**
+ * A table row with bars showing which parts of a file have been loaded.
+ * This is a row in the table shown by {@link FileAccessView} table.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+final class FileAccessItem implements Runnable {
+    /**
+     * The height of bars in pixels.
+     */
+    private static final int HEIGHT = 16;
+
+    /**
+     * Number of pixels between cell top border and background border.
+     */
+    private static final int MARGIN_TOP = 1;
+
+    /**
+     * Number of pixels between cell right border and background border.
+     */
+    private static final int MARGIN_RIGHT = 6;
+
+    /**
+     * Color to use for filling the rectangles.
+     */
+    private static final Color FILL_COLOR = Color.LIGHTSEAGREEN;
+
+    /**
+     * Color to use for rectangles border.
+     */
+    private static final Color BORDER_COLOR = FILL_COLOR.darker();
+
+    /**
+     * The list of rows shown by the table.
+     * This is used for removing this item when the file is closed.
+     */
+    final List<FileAccessItem> owner;
+
+    /**
+     * The text to show in the "File" column.
+     */
+    final String filename;
+
+    /**
+     * Range of bytes on which a read or write operation has been performed.
+     */
+    private final RangeSet<Long> accessRanges;
+
+    /**
+     * Visual representation of {@link #accessRanges}.
+     * The first child is the background rectangle and must be always present.
+     * All other children are rectangles built from {@link #accessRanges}.
+     */
+    final Pane accessView;
+
+    /**
+     * Size of the file in bytes.
+     */
+    private long fileSize;
+
+    /**
+     * Width in pixels of the column were to draw the boxes.
+     */
+    private double columnWidth;
+
+    /**
+     * Creates a new row in the table of files.
+     *
+     * @param  owner     list where this rows will be added by the caller.
+     * @param  filename  text to show in the "File" column.
+     */
+    FileAccessItem(final List<FileAccessItem> owner, final String filename) {
+        this.owner    = owner;
+        this.filename = filename;
+        accessRanges = RangeSet.create(Long.class, true, false);
+        accessView = new Pane();
+        /*
+         * Background rectangle.
+         */
+        final Rectangle background = new Rectangle();
+        background.setY(MARGIN_TOP);
+        background.setHeight(HEIGHT);
+        background.setStroke(FILL_COLOR.brighter());
+        background.setFill(Color.TRANSPARENT);
+        background.setStrokeType(StrokeType.INSIDE);
+        accessView.getChildren().add(background);
+        accessView.widthProperty().addListener((p,o,n) -> {
+            columnWidth = n.doubleValue() - MARGIN_RIGHT;
+            adjustSizes(true);
+        });
+    }
+
+    /**
+     * Reports a read or write operation on a range of bytes.
+     * This method is invoked by the {@link Observer} wrapper.
+     *
+     * @param  position  offset of the first byte read or written.
+     * @param  count     number of bytes read or written.
+     * @param  write     {@code false} for a read operation, or {@code true} 
for a write operation.
+     */
+    private void addRange(final long position, final int count, final boolean 
write) {
+        Platform.runLater(() -> {
+            if (accessRanges.add(position, position + count)) {
+                adjustSizes(false);
+            }
+        });
+    }
+
+    /**
+     * Recomputes all rectangles from current {@link #columnWidth} and {@link 
#accessRanges}.
+     *
+     * @param  resized  {@code true} if this method is invoked because of a 
change of column width
+     *         with (presumably) no change in {@link #accessRanges}, or {@code 
false} if invoked
+     *         after a new range has been added with (presumably) no change in 
{@link #columnWidth}.
+     */
+    final void adjustSizes(final boolean resized) {
+        final double scale = columnWidth / fileSize;
+        if (!Double.isFinite(scale)) {
+            return;
+        }
+        final ObservableList<Node> children = accessView.getChildren();
+        final ListIterator<Node> bars = children.listIterator();
+        ((Rectangle) bars.next()).setWidth(columnWidth);                    // 
Background.
+        /*
+         * Adjust the position and width of all rectangles.
+         */
+        for (final Range<Long> range : accessRanges) {
+            final long min = range.getMinValue();
+            final long max = range.getMaxValue();
+            final double x = scale * min;
+            final double width = scale * (max - min);
+            if (bars.hasNext()) {
+                final Rectangle r = (Rectangle) bars.next();
+                if (resized || r.getX() + r.getWidth() >= x) {
+                    r.setX(x);
+                    r.setWidth(width);
+                    continue;
+                }
+                /*
+                 * Newly added range may have merged two or more ranges in a 
single one.
+                 * Discard all ranges that are fully on the left side of 
current range.
+                 * This is not really mandatory, but we do that in an effort 
to keep the
+                 * most "relevant" rectangles (before change) for the new set 
of ranges.
+                 */
+                bars.remove();
+            }
+            final Rectangle r = new Rectangle(x, MARGIN_TOP, width, HEIGHT);
+            r.setStrokeType(StrokeType.INSIDE);
+            r.setStroke(BORDER_COLOR);
+            r.setFill(FILL_COLOR);
+            bars.add(r);
+        }
+        // Remove all remaining children, if any.
+        children.remove(bars.nextIndex(), children.size());
+    }
+
+    /**
+     * Wrapper around a {@link SeekableByteChannel} which will observe the 
ranges of bytes read or written.
+     */
+    final class Observer implements SeekableByteChannel {
+        /**
+         * The channel doing the actual read or write operations.
+         */
+        private final SeekableByteChannel channel;
+
+        /**
+         * Creates a new wrapper around the given channel.
+         */
+        Observer(final SeekableByteChannel channel) throws IOException {
+            this.channel = channel;
+            fileSize = channel.size();
+        }
+
+        /**
+         * Forwards to the wrapped channel and report the range of bytes read.
+         */
+        @Override
+        public int read(final ByteBuffer dst) throws IOException {
+            final long position = position();
+            final int count = channel.read(dst);
+            addRange(position, count, false);
+            return count;
+        }
+
+        /**
+         * Forwards to the wrapped channel and report the range of bytes 
written.
+         */
+        @Override
+        public int write(final ByteBuffer src) throws IOException {
+            final long position = position();
+            final int count = channel.write(src);
+            addRange(position, count, true);
+            return count;
+        }
+
+        /**
+         * Forwards to the wrapper channel.
+         */
+        @Override
+        public long position() throws IOException {
+            return channel.position();
+        }
+
+        /**
+         * Forwards to the wrapper channel.
+         */
+        @Override
+        public SeekableByteChannel position(final long newPosition) throws 
IOException {
+            channel.position(newPosition);
+            return this;
+        }
+
+        /**
+         * Forwards to the wrapper channel.
+         */
+        @Override
+        public long size() throws IOException {
+            return channel.size();
+        }
+
+        /**
+         * Forwards to the wrapper channel.
+         */
+        @Override
+        public SeekableByteChannel truncate(final long size) throws 
IOException {
+            fileSize = channel.truncate(size).size();
+            return this;
+        }
+
+        /**
+         * Forwards to the wrapper channel.
+         */
+        @Override
+        public boolean isOpen() {
+            return channel.isOpen();
+        }
+
+        /**
+         * Forwards to the wrapper channel and remove the enclosing row from 
the table.
+         */
+        @Override
+        public void close() throws IOException {
+            Platform.runLater(FileAccessItem.this);
+            channel.close();
+        }
+    }
+
+    /**
+     * Invoked in JavaFX thread for removing this row from the table.
+     */
+    @Override
+    public void run() {
+        owner.remove(this);
+    }
+}
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/io/FileAccessView.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/io/FileAccessView.java
new file mode 100644
index 0000000..ea7db4a
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/io/FileAccessView.java
@@ -0,0 +1,136 @@
+/*
+ * 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.gui.io;
+
+import java.io.IOException;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.function.UnaryOperator;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Region;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TableColumn;
+import javafx.application.Platform;
+import org.apache.sis.gui.Widget;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.internal.gui.FixedHeaderColumnSize;
+import org.apache.sis.internal.gui.ImmutableObjectProperty;
+import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.internal.storage.io.ChannelFactory;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.event.StoreListeners;
+
+
+/**
+ * A table of filenames associated with bars showing which parts of the files 
have been read or written.
+ * New rows are added when files are opened and removed when files are closed.
+ *
+ * <p>The {@link org.apache.sis.gui.SystemMonitor} class will track only 
channels opened while the
+ * "System monitor" window was visible. This policy is for avoiding the cost 
of tracking operations
+ * in the vast majority of cases when user has no interest in those 
information.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+public final class FileAccessView extends Widget implements 
UnaryOperator<ChannelFactory> {
+    /**
+     * The table where opened files are listed.
+     */
+    private final TableView<FileAccessItem> table;
+
+    /**
+     * Creates a new widget.
+     *
+     * @param  resources   localized resources, provided because already known 
by caller.
+     * @param  vocabulary  localized resources, provided because already known 
by caller.
+     */
+    public FileAccessView(final Resources resources, final Vocabulary 
vocabulary) {
+        final TableColumn<FileAccessItem, String> filenameColumn;
+        final TableColumn<FileAccessItem,Pane> accessColumn;
+        filenameColumn = new 
TableColumn<>(vocabulary.getString(Vocabulary.Keys.File));
+        accessColumn   = new 
TableColumn<>(resources.getString(Resources.Keys.AccessedRegions));
+        accessColumn  .setSortable(false);
+        accessColumn  .setMinWidth (120);
+        filenameColumn.setMinWidth ( 80);
+        filenameColumn.setPrefWidth(200);
+        filenameColumn.setCellValueFactory((cell) -> new 
ImmutableObjectProperty<>(cell.getValue().filename));
+        accessColumn  .setCellValueFactory((cell) -> new 
ImmutableObjectProperty<>(cell.getValue().accessView));
+        table = new TableView<>();
+        table.setColumnResizePolicy(FixedHeaderColumnSize.INSTANCE);
+        table.getColumns().setAll(filenameColumn, accessColumn);
+    }
+
+    /**
+     * Returns the node to show in a window.
+     *
+     * @return the node to show.
+     */
+    @Override
+    public Region getView() {
+        return table;
+    }
+
+    /**
+     * Invoked when a new {@link ReadableByteChannel} or {@link 
WritableByteChannel} is about to be created.
+     * The caller will replace the given factory by the returned factory. It 
allows us to wrap the channel
+     * in an object will will collect information about blocks read.
+     *
+     * @param  factory  the factory for creating channels.
+     * @return the factory to use instead of the factory given in argument.
+     */
+    @Override
+    public ChannelFactory apply(final ChannelFactory factory) {
+        return new ChannelFactory() {
+            /**
+             * Creates a readable channel and listens (if possible) read 
operations.
+             * Current implementation listens only to {@link 
SeekableByteChannel}
+             * because otherwise we do not know the file size.
+             *
+             * @param  filename  data store name.
+             * @param  listeners set of registered {@code StoreListener}s for 
the data store, or {@code null} if none.
+             * @return the channel for the given input.
+             * @throws DataStoreException if the channel is read-once.
+             * @throws IOException if an error occurred while opening the 
channel.
+             */
+            @Override
+            public ReadableByteChannel readable(final String filename, final 
StoreListeners listeners)
+                    throws DataStoreException, IOException
+            {
+                final ReadableByteChannel channel = factory.readable(filename, 
listeners);
+                if (channel instanceof SeekableByteChannel) {
+                    final FileAccessItem item = new 
FileAccessItem(table.getItems(), filename);
+                    Platform.runLater(() -> item.owner.add(item));
+                    return item.new Observer((SeekableByteChannel) channel);
+                }
+                return channel;
+            }
+
+            /**
+             * Forwards to the factory (listeners not yet implemented).
+             */
+            @Override
+            public WritableByteChannel writable(final String filename, final 
StoreListeners listeners)
+                    throws DataStoreException, IOException
+            {
+                return factory.writable(filename, listeners);
+            }
+        };
+    }
+}
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/package-info.java 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/io/package-info.java
similarity index 71%
copy from 
application/sis-javafx/src/main/java/org/apache/sis/gui/package-info.java
copy to 
application/sis-javafx/src/main/java/org/apache/sis/internal/gui/io/package-info.java
index 8901cca..5b0fcfb 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/package-info.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/io/package-info.java
@@ -16,13 +16,16 @@
  */
 
 /**
- * JavaFX application for Apache SIS.
+ * Widgets related to I/O operations, together with classes for listening to 
I/O activity.
+ *
+ * <STRONG>Do not use!</STRONG>
+ *
+ * This package is for internal use by SIS only. Classes in this package
+ * may change in incompatible ways in any future version without notice.
  *
- * @author  Smaniotto Enzo (GSoC)
- * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
+ * @version 1.2
+ * @since   1.2
  * @module
  */
-package org.apache.sis.gui;
+package org.apache.sis.internal.gui.io;

Reply via email to