You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2020/08/13 23:59:29 UTC

[sis] branch geoapi-4.0 updated (2bc3e93 -> 42b1726)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git.


    from 2bc3e93  Add EPSG database if explicitly requested by profile.
     new 40e6693  Add a splash screen. Fix destination directory of log file.
     new be67730  Ask user to specify JavaFX installation directory on the first execution.
     new 42b1726  Initial Windows batch script for launching the application.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 application/sis-javafx/src/main/artifact/bin/sisfx |  19 ++-
 .../sis-javafx/src/main/artifact/bin/sisfx.bat     |  36 +++++
 .../src/main/artifact/conf/logging.properties      |   5 +
 .../sis-javafx/src/main/artifact/conf/setenv.bat   |   2 +
 .../sis-javafx/src/main/artifact/conf/setenv.sh    |   2 +
 .../sis-javafx/src/main/artifact/lib/logo.jpg      | Bin 0 -> 44981 bytes
 .../org/apache/sis/internal/setup/FXFinder.java    | 180 +++++++++++++++++++++
 .../sis/internal/setup/LoggingConfiguration.java   |  85 ++++++++++
 .../apache/sis/internal/setup}/package-info.java   |   6 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |   6 +-
 10 files changed, 332 insertions(+), 9 deletions(-)
 create mode 100644 application/sis-javafx/src/main/artifact/bin/sisfx.bat
 create mode 100644 application/sis-javafx/src/main/artifact/conf/setenv.bat
 create mode 100644 application/sis-javafx/src/main/artifact/conf/setenv.sh
 create mode 100644 application/sis-javafx/src/main/artifact/lib/logo.jpg
 create mode 100644 application/sis-javafx/src/main/java/org/apache/sis/internal/setup/FXFinder.java
 create mode 100644 application/sis-javafx/src/main/java/org/apache/sis/internal/setup/LoggingConfiguration.java
 copy {core/sis-feature/src/main/java/org/apache/sis/internal/coverage => application/sis-javafx/src/main/java/org/apache/sis/internal/setup}/package-info.java (88%)


[sis] 03/03: Initial Windows batch script for launching the application.

Posted by de...@apache.org.
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 42b1726538d57d1f6ccc19b54d10f6886f964567
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Aug 14 01:57:46 2020 +0200

    Initial Windows batch script for launching the application.
---
 application/sis-javafx/src/main/artifact/bin/sisfx |  4 +--
 .../sis-javafx/src/main/artifact/bin/sisfx.bat     | 36 ++++++++++++++++++++++
 .../sis-javafx/src/main/artifact/conf/setenv.bat   |  2 ++
 .../org/apache/sis/internal/setup/FXFinder.java    |  8 +++--
 .../sis/internal/setup/LoggingConfiguration.java   | 11 ++++---
 .../referencing/factory/sql/EPSGDataAccess.java    |  6 ++--
 6 files changed, 57 insertions(+), 10 deletions(-)

diff --git a/application/sis-javafx/src/main/artifact/bin/sisfx b/application/sis-javafx/src/main/artifact/bin/sisfx
index a13852b..4f13a6b 100755
--- a/application/sis-javafx/src/main/artifact/bin/sisfx
+++ b/application/sis-javafx/src/main/artifact/bin/sisfx
@@ -20,7 +20,7 @@ set -o errexit
 
 BASE_DIR="`readlink --canonicalize-existing $0`"
 BASE_DIR="`dirname $BASE_DIR`/.."
-source $BASE_DIR/conf/setenv.sh
+source "$BASE_DIR/conf/setenv.sh"
 
 if [ -z "$PATH_TO_FX" ]
 then
@@ -29,7 +29,7 @@ then
     then
         exit
     fi
-    source $BASE_DIR/conf/setenv.sh
+    source "$BASE_DIR/conf/setenv.sh"
 fi
 
 SIS_DATA="${SIS_DATA:-$BASE_DIR/data}"
diff --git a/application/sis-javafx/src/main/artifact/bin/sisfx.bat b/application/sis-javafx/src/main/artifact/bin/sisfx.bat
new file mode 100644
index 0000000..92a5f0f
--- /dev/null
+++ b/application/sis-javafx/src/main/artifact/bin/sisfx.bat
@@ -0,0 +1,36 @@
+@echo off
+
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to you under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with the
+REM License.  You may obtain a copy of the License at
+REM
+REM    http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+
+SET BASE_DIR=%~dp0\..
+CALL "%BASE_DIR%\conf\setenv.bat"
+SET SIS_DATA=%BASE_DIR%\data
+
+IF "%PATH_TO_FX%"=="" (
+    java --class-path "%BASE_DIR%\lib\*" org.apache.sis.internal.setup.FXFinder "%BASE_DIR%\conf\setenv.bat"
+    if %ERRORLEVEL% GEQ 1 EXIT /B 1
+    CALL "%BASE_DIR%\conf\setenv.bat"
+)
+
+java -splash:"%BASE_DIR%\lib\logo.jpg"^
+ --add-modules javafx.graphics,javafx.controls^
+ --module-path "%PATH_TO_FX%"^
+ --class-path "%BASE_DIR%\lib\*"^
+ -Djava.util.logging.config.class=org.apache.sis.internal.setup.LoggingConfiguration^
+ -Djava.util.logging.config.file="%BASE_DIR%\conf\logging.properties"^
+ -Dderby.stream.error.file="%BASE_DIR%\log\derby.log"^
+ org.apache.sis.gui.DataViewer %SIS_OPTS%
diff --git a/application/sis-javafx/src/main/artifact/conf/setenv.bat b/application/sis-javafx/src/main/artifact/conf/setenv.bat
new file mode 100644
index 0000000..9825bd1
--- /dev/null
+++ b/application/sis-javafx/src/main/artifact/conf/setenv.bat
@@ -0,0 +1,2 @@
+REM Environment variables required for SIS execution.
+REM Contents may be added at Apache SIS setup time.
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/FXFinder.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/FXFinder.java
index 708af4c..ba91b5b 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/FXFinder.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/FXFinder.java
@@ -157,11 +157,15 @@ public final class FXFinder {
      * @param  dir     directory selected by user.
      */
     private static void setDirectory(final Path setenv, final File dir) throws IOException {
+        String command = "PATH_TO_FX";
+        if (setenv.getFileName().toString().endsWith(".bat")) {
+            command = "SET " + command;                             // Microsoft Windows syntax.
+        }
         final ArrayList<String> content = new ArrayList<>();
         int insertAt = -1;
         for (String line : Files.readAllLines(setenv)) {
             line = line.trim();
-            if (line.startsWith("PATH_TO_FX")) {
+            if (line.startsWith(command)) {
                 insertAt = content.size();
             } else {
                 content.add(line);
@@ -170,7 +174,7 @@ public final class FXFinder {
         if (insertAt < 0) {
             insertAt = content.size();
         }
-        content.add(insertAt, "PATH_TO_FX=" + dir);
+        content.add(insertAt, command + '=' + dir);
         Files.write(setenv, content, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
     }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/LoggingConfiguration.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/LoggingConfiguration.java
index 3601b33..0af0f66 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/LoggingConfiguration.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/LoggingConfiguration.java
@@ -19,6 +19,7 @@ package org.apache.sis.internal.setup;
 import java.util.logging.LogManager;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -67,11 +68,13 @@ public final class LoggingConfiguration {
                     if (line.startsWith(PROPERTY)) {
                         final int i = buffer.indexOf(PATTERN, base + PROPERTY.length());
                         if (i >= 0) {
-                            Path replacement = path;
-                            for (int j=Math.min(replacement.getNameCount(), 2); --j >= 0;) {
-                                replacement = replacement.getParent();
+                            Path parent = path;
+                            for (int j=Math.min(parent.getNameCount(), 2); --j >= 0;) {
+                                parent = parent.getParent();
                             }
-                            buffer.replace(i, i + PATTERN.length(), replacement.toString());
+                            String replacement = parent.toString();
+                            replacement = replacement.replace(File.separatorChar, '/');
+                            buffer.replace(i, i + PATTERN.length(), replacement);
                         }
                     }
                 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index e2f26ba..87931ed 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -449,7 +449,7 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho
             }
             /*
              * Add some hard-coded links to EPSG resources, and finally add the JDBC driver name and version number.
-             * The list last OnlineResource looks like:
+             * The last OnlineResource looks like:
              *
              *    Linkage:      jdbc:derby:/my/path/to/SIS_DATA/Databases/SpatialMetadata
              *    Function:     Connection
@@ -480,7 +480,9 @@ addURIs:    for (int i=0; ; i++) {
                 try {
                     r.setLinkage(new URI(url));
                 } catch (URISyntaxException exception) {
-                    unexpectedException("getAuthority", exception);
+                    // May happen if there is spaces in the URI.
+                    Logging.recoverableException(Logging.getLogger(Loggers.CRS_FACTORY),
+                                                 EPSGDataAccess.class, "getAuthority", exception);
                 }
                 r.setFunction(function);
                 r.setDescription(description);


[sis] 01/03: Add a splash screen. Fix destination directory of log file.

Posted by de...@apache.org.
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 40e66933c357571fb14358f26aad032a1991b335
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Aug 13 16:52:23 2020 +0200

    Add a splash screen. Fix destination directory of log file.
---
 application/sis-javafx/src/main/artifact/bin/sisfx |   5 +-
 .../src/main/artifact/conf/logging.properties      |   5 ++
 .../sis-javafx/src/main/artifact/lib/logo.jpg      | Bin 0 -> 44981 bytes
 .../sis/internal/setup/LoggingConfiguration.java   |  82 +++++++++++++++++++++
 .../apache/sis/internal/setup/package-info.java    |  29 ++++++++
 5 files changed, 119 insertions(+), 2 deletions(-)

diff --git a/application/sis-javafx/src/main/artifact/bin/sisfx b/application/sis-javafx/src/main/artifact/bin/sisfx
index 89b0086..b2d9448 100755
--- a/application/sis-javafx/src/main/artifact/bin/sisfx
+++ b/application/sis-javafx/src/main/artifact/bin/sisfx
@@ -24,10 +24,11 @@ SIS_DATA="${SIS_DATA:-$BASE_DIR/data}"
 export SIS_DATA
 
 # Execute SIS with any optional JAR that the user may put in the `lib` directory.
-java --add-modules javafx.graphics,javafx.controls \
+java -splash:"$BASE_DIR/lib/logo.jpg" \
+     --add-modules javafx.graphics,javafx.controls \
      --module-path $PATH_TO_FX \
      --class-path "$BASE_DIR/lib/*" \
+     -Djava.util.logging.config.class="org.apache.sis.internal.setup.LoggingConfiguration" \
      -Djava.util.logging.config.file="$BASE_DIR/conf/logging.properties" \
-     -Djava.util.logging.FileHandler.pattern="$BASE_DIR/log/system.log" \
      -Dderby.stream.error.file="$BASE_DIR/log/derby.log" \
      org.apache.sis.gui.DataViewer $SIS_OPTS "$@"
diff --git a/application/sis-javafx/src/main/artifact/conf/logging.properties b/application/sis-javafx/src/main/artifact/conf/logging.properties
index 0ccf4a9..159cab5 100644
--- a/application/sis-javafx/src/main/artifact/conf/logging.properties
+++ b/application/sis-javafx/src/main/artifact/conf/logging.properties
@@ -44,6 +44,10 @@ java.util.logging.ConsoleHandler.level = WARNING
 #   - java.util.logging.SimpleFormatter
 #   - org.apache.sis.util.logging.MonolineFormatter
 #
+# FileHandler pattern accepts %t, %h, %g, %u (see Javadoc for details).
+# The %p pattern is SIS-specific and stands for the parent directory of
+# this configuration file.
+#
 # By default, MonolineFormatter displays only the log level
 # and the message. Additional options can be specified here:
 #
@@ -56,6 +60,7 @@ java.util.logging.ConsoleHandler.level = WARNING
 #         Valid argument values are "none", "logger:short", "logger:long",
 #         "class:short", "class:long" and "class.method".
 
+java.util.logging.FileHandler.pattern      = %p/log/system.log
 java.util.logging.FileHandler.formatter    = org.apache.sis.util.logging.MonolineFormatter
 java.util.logging.ConsoleHandler.formatter = org.apache.sis.util.logging.MonolineFormatter
 org.apache.sis.util.logging.MonolineFormatter.source = logger:long
diff --git a/application/sis-javafx/src/main/artifact/lib/logo.jpg b/application/sis-javafx/src/main/artifact/lib/logo.jpg
new file mode 100644
index 0000000..8bab417
Binary files /dev/null and b/application/sis-javafx/src/main/artifact/lib/logo.jpg differ
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/LoggingConfiguration.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/LoggingConfiguration.java
new file mode 100644
index 0000000..3601b33
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/LoggingConfiguration.java
@@ -0,0 +1,82 @@
+/*
+ * 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.setup;
+
+import java.util.logging.LogManager;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+
+/**
+ * Loads {@code conf/logging.properties} file and filter the {@code %b} pattern
+ * before to delegate to Java logging system. The filtering replaces {@code "%p"}
+ * by the parent directory of configuration file.
+ *
+ * <p>This class should not use any SIS classes because it may be invoked early
+ * while the application is still initializing.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final class LoggingConfiguration {
+    /**
+     * The property to filter.
+     */
+    private static final String PROPERTY = "java.util.logging.FileHandler.pattern";
+
+    /**
+     * The pattern to replace.
+     */
+    private static final String PATTERN = "%p";
+
+    /**
+     * Invoked by Java if the {@code java.util.logging.config.class} property is set
+     * to this class name. This constructor filters {@code conf/logging.properties},
+     * then delegate to Java logging system.
+     *
+     * @throws IOException if an error occurred while reading the configuration file.
+     */
+    public LoggingConfiguration() throws IOException {
+        final String file = System.getProperty("java.util.logging.config.file");
+        if (file != null) {
+            final Path path = Paths.get(file).normalize();
+            final StringBuilder buffer = new StringBuilder(600);
+            for (String line : Files.readAllLines(path)) {
+                if (!(line = line.trim()).isEmpty() && line.charAt(0) != '#') {
+                    final int base = buffer.length();
+                    buffer.append(line).append('\n');
+                    if (line.startsWith(PROPERTY)) {
+                        final int i = buffer.indexOf(PATTERN, base + PROPERTY.length());
+                        if (i >= 0) {
+                            Path replacement = path;
+                            for (int j=Math.min(replacement.getNameCount(), 2); --j >= 0;) {
+                                replacement = replacement.getParent();
+                            }
+                            buffer.replace(i, i + PATTERN.length(), replacement.toString());
+                        }
+                    }
+                }
+            }
+            LogManager.getLogManager().readConfiguration​(new ByteArrayInputStream(buffer.toString().getBytes()));
+        }
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/package-info.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/package-info.java
new file mode 100644
index 0000000..0b9d164
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/**
+ * Helper classes for SIS application setup.
+ *
+ * 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  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+package org.apache.sis.internal.setup;


[sis] 02/03: Ask user to specify JavaFX installation directory on the first execution.

Posted by de...@apache.org.
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 be6773070529e6bdcbf5d6863b91626d5483fa31
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Aug 14 00:11:14 2020 +0200

    Ask user to specify JavaFX installation directory on the first execution.
---
 application/sis-javafx/src/main/artifact/bin/sisfx |  14 +-
 .../sis-javafx/src/main/artifact/conf/setenv.sh    |   2 +
 .../org/apache/sis/internal/setup/FXFinder.java    | 176 +++++++++++++++++++++
 3 files changed, 191 insertions(+), 1 deletion(-)

diff --git a/application/sis-javafx/src/main/artifact/bin/sisfx b/application/sis-javafx/src/main/artifact/bin/sisfx
index b2d9448..a13852b 100755
--- a/application/sis-javafx/src/main/artifact/bin/sisfx
+++ b/application/sis-javafx/src/main/artifact/bin/sisfx
@@ -20,13 +20,25 @@ set -o errexit
 
 BASE_DIR="`readlink --canonicalize-existing $0`"
 BASE_DIR="`dirname $BASE_DIR`/.."
+source $BASE_DIR/conf/setenv.sh
+
+if [ -z "$PATH_TO_FX" ]
+then
+    java --class-path "$BASE_DIR/lib/*" org.apache.sis.internal.setup.FXFinder $BASE_DIR/conf/setenv.sh
+    if [ $? -ne 0 ]
+    then
+        exit
+    fi
+    source $BASE_DIR/conf/setenv.sh
+fi
+
 SIS_DATA="${SIS_DATA:-$BASE_DIR/data}"
 export SIS_DATA
 
 # Execute SIS with any optional JAR that the user may put in the `lib` directory.
 java -splash:"$BASE_DIR/lib/logo.jpg" \
      --add-modules javafx.graphics,javafx.controls \
-     --module-path $PATH_TO_FX \
+     --module-path "$PATH_TO_FX" \
      --class-path "$BASE_DIR/lib/*" \
      -Djava.util.logging.config.class="org.apache.sis.internal.setup.LoggingConfiguration" \
      -Djava.util.logging.config.file="$BASE_DIR/conf/logging.properties" \
diff --git a/application/sis-javafx/src/main/artifact/conf/setenv.sh b/application/sis-javafx/src/main/artifact/conf/setenv.sh
new file mode 100644
index 0000000..f4f3c84
--- /dev/null
+++ b/application/sis-javafx/src/main/artifact/conf/setenv.sh
@@ -0,0 +1,2 @@
+# Environment variables required for SIS execution.
+# Contents may be added at Apache SIS setup time.
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/FXFinder.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/FXFinder.java
new file mode 100644
index 0000000..708af4c
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/setup/FXFinder.java
@@ -0,0 +1,176 @@
+/*
+ * 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.setup;
+
+import java.awt.Desktop;
+import java.awt.Font;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+
+
+/**
+ * Ask user to specify the path to JavaFX installation directory.
+ * This is used one JavaFX can not be found on the classpath.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final class FXFinder {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private FXFinder() {
+    }
+
+    /**
+     * Popups a modal dialog box asking user to choose a directory.
+     *
+     * @param  args  command line arguments. Should have a length of 1,
+     *               with {@code args[0]} containing the path of the file to edit.
+     */
+    public static void main(String[] args) {
+        boolean success = false;
+        try {
+            success = askDirectory(Paths.get(args[0]).normalize());
+        } catch (Exception e) {
+            JOptionPane.showMessageDialog(null, e.toString(), "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        System.exit(success ? 0 : 1);
+    }
+
+    /**
+     * Popups a modal dialog box asking user to choose a directory.
+     *
+     * @param  setenv  path of the {@code setenv.sh} file to edit.
+     * @return {@code true} if we can continue with application launch,
+     *         or {@code false} on error or cancellation.
+     */
+    private static boolean askDirectory(final Path setenv) throws Exception {
+        try {
+            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+        } catch (ReflectiveOperationException | UnsupportedLookAndFeelException e) {
+            // Ignore.
+        }
+        /*
+         * Checks now that we can edit `setenv.sh` content in order to not show the next
+         * dialog box if we czn not read that file (e.g. because the file was not found).
+         */
+        if (!Files.isReadable(setenv) || !Files.isWritable(setenv)) {
+            JOptionPane.showMessageDialog(null, "Can not edit " + setenv,
+                    "Configuration error", JOptionPane.WARNING_MESSAGE);
+            return false;
+        }
+        /*
+         * Ask the user what he wants to do.
+         */
+        final JLabel description = new JLabel(
+                "<html><body><p style=\"width:400px; text-align:justify;\">" +
+                "This application requires <b>JavaFX</b> version 13 or later. " +
+                "Click on “Download” for opening the free download page. " +
+                "If JavaFX is already installed on this computer, " +
+                "click on “Set directory” for specifying the installation directory." +
+                "</p></body></html>");
+
+        description.setFont(description.getFont().deriveFont(Font.PLAIN));
+        final Object[] options = {"Download", "Set directory", "Cancel"};
+        final int choice = JOptionPane.showOptionDialog(null, description,
+                "JavaFX installation directory",
+                JOptionPane.YES_NO_CANCEL_OPTION,
+                JOptionPane.QUESTION_MESSAGE,
+                null,
+                options,
+                options[2]);
+
+        if (choice == 0) {
+            if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
+                Desktop.getDesktop().browse(URI.create("https://openjfx.io/"));
+            } else {
+                JOptionPane.showMessageDialog(null, "See https://openjfx.io/",
+                        "JavaFX download", JOptionPane.INFORMATION_MESSAGE);
+            }
+        } else if (choice == 1) {
+            final JFileChooser fd = new JFileChooser();
+            fd.setDialogTitle("JavaFX installation directory");
+            fd.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+            while (fd.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
+                final File dir = findSubDirectory(fd.getSelectedFile());
+                if (dir == null) {
+                    JOptionPane.showMessageDialog(null, "Not a JavaFX directory.",
+                            "JavaFX installation directory", JOptionPane.WARNING_MESSAGE);
+                } else {
+                    setDirectory(setenv, dir);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Tries to locate the {@code lib} sub-folder in a JavaFX installation directory.
+     *
+     * @param  dir  the directory from where to start the search.
+     * @return      the {@code lib} directory, or {@code null} if not found.
+     */
+    private static File findSubDirectory(final File dir) {
+        if (new File(dir, "javafx.controls.jar").exists()) {
+            return dir;
+        }
+        final File lib = new File(dir, "lib");
+        if (new File(lib, "javafx.controls.jar").exists()) {
+            return lib;
+        }
+        return null;
+    }
+
+    /**
+     * Sets the JavaFX directory.
+     *
+     * @param  setenv  path to the {@code setenv.sh} file to edit.
+     * @param  dir     directory selected by user.
+     */
+    private static void setDirectory(final Path setenv, final File dir) throws IOException {
+        final ArrayList<String> content = new ArrayList<>();
+        int insertAt = -1;
+        for (String line : Files.readAllLines(setenv)) {
+            line = line.trim();
+            if (line.startsWith("PATH_TO_FX")) {
+                insertAt = content.size();
+            } else {
+                content.add(line);
+            }
+        }
+        if (insertAt < 0) {
+            insertAt = content.size();
+        }
+        content.add(insertAt, "PATH_TO_FX=" + dir);
+        Files.write(setenv, content, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
+    }
+}