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 e7f10e63a8d08618cff00896e817350571eb6633
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sat Dec 2 17:37:33 2023 +0100

    Prepare a more robust handling of console charset in preparation for JShell.
---
 .../main/org/apache/sis/console/Command.java       | 37 ++++++++--------
 .../main/org/apache/sis/console/CommandRunner.java | 51 ++++++++++++++++++----
 .../apache/sis/console/FormattedOutputCommand.java |  6 +--
 .../main/org/apache/sis/console/Option.java        |  2 +-
 .../src/org.apache.sis.util/main/module-info.java  |  3 +-
 .../main/org/apache/sis/pending/jdk/JDK17.java     | 43 ++++++++++++++++++
 6 files changed, 110 insertions(+), 32 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Command.java 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Command.java
index d2033feca6..a1905b42d8 100644
--- 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Command.java
+++ 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Command.java
@@ -18,8 +18,6 @@ package org.apache.sis.console;
 
 import java.util.Locale;
 import java.util.logging.LogManager;
-import java.io.Console;
-import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -241,6 +239,18 @@ public final class Command {
         return false;
     }
 
+    /**
+     * Returns the writer where this command sends its output.
+     *
+     * @param  error  {@code true} for the error stream, or {@code false} for 
the standard output stream.
+     * @return the stream where this command sends it output.
+     *
+     * @since 1.5
+     */
+    public PrintWriter writer(final boolean error) {
+        return error ? command.err : command.out;
+    }
+
     /**
      * Runs the command. If an exception occurs, then the exception message is 
sent to the error output stream
      * before to be thrown. Callers can map the exception to a {@linkplain 
System#exit(int) system exit code}
@@ -295,27 +305,16 @@ public final class Command {
      *
      * @param  args  the command line arguments, used only for detecting if 
the {@code --debug} option was present.
      */
+    @SuppressWarnings("UseOfSystemOutOrSystemErr")
     private static void error(final String[] args, final Exception e) {
         final boolean debug = ArraysExt.containsIgnoreCase(args, Option.PREFIX 
+ "debug");
-        final Console console = System.console();
-        if (console != null) {
-            final PrintWriter err = console.writer();
-            if (debug) {
-                e.printStackTrace(err);
-            } else {
-                err.println(e.getLocalizedMessage());
-            }
-            err.flush();
+        final PrintWriter err = CommandRunner.writer(System.console(), 
System.err);
+        if (debug) {
+            e.printStackTrace(err);
         } else {
-            @SuppressWarnings("UseOfSystemOutOrSystemErr")
-            final PrintStream err = System.err;
-            if (debug) {
-                e.printStackTrace(err);
-            } else {
-                err.println(e.getLocalizedMessage());
-            }
-            err.flush();
+            err.println(e.getLocalizedMessage());
         }
+        err.flush();
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java
 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java
index 5a5aa9f9b5..581834dfa6 100644
--- 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java
+++ 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java
@@ -24,14 +24,17 @@ import java.util.EnumMap;
 import java.util.TimeZone;
 import java.io.Console;
 import java.io.IOException;
+import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.io.OutputStreamWriter;
 import java.nio.charset.Charset;
 import org.apache.sis.util.Locales;
 import org.apache.sis.util.Exceptions;
+import org.apache.sis.util.Workaround;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.internal.X364;
+import org.apache.sis.pending.jdk.JDK17;
 
 
 /**
@@ -50,6 +53,25 @@ abstract class CommandRunner {
      */
     static final String TEST = "TEST";
 
+    /**
+     * Whether the use of the console writer should be avoided.
+     * In our tests with Java 21, sending non ASCII characters to the console 
writer in a Linux system
+     * from a JShell session resulted in wrong characters being printed, 
sometime followed by JShell errors.
+     * This flag is set only if the commands are run from JShell.
+     *
+     * @see #writer(Console, PrintStream)
+     */
+    @Workaround(library="jshell", version="21")
+    private static boolean avoidConsoleWriter;
+
+    /**
+     * Notifies the command runners that the use of console writer should be 
avoided.
+     * This method should be invoked in JShell environment only.
+     */
+    static void avoidConsoleWriter() {
+        avoidConsoleWriter = true;
+    }
+
     /**
      * The instance, used by {@link ResourcesDownloader} only.
      * We use this static field as a workaround for the fact that {@code 
ResourcesDownloader} is not
@@ -241,19 +263,32 @@ abstract class CommandRunner {
             err = out;
         } else {
             outputBuffer = null;
-            err = (console != null) ? console.writer() : new 
PrintWriter(System.err, true);
-            if (!explicitEncoding && console != null) {
-                out = console.writer();
+            err = writer(console, System.err);
+            if (explicitEncoding) {
+                out = new PrintWriter(new OutputStreamWriter(System.out, 
encoding), true);
             } else {
-                if (explicitEncoding) {
-                    out = new PrintWriter(new OutputStreamWriter(System.out, 
encoding), true);
-                } else {
-                    out = new PrintWriter(System.out, true);
-                }
+                out = writer(console, System.out);
             }
         }
     }
 
+    /**
+     * Returns the console print writer, or the given alternative if the 
console cannot be used.
+     *
+     * @param  console   the value of {@link System#console()}, potentially 
null.
+     * @param  fallback  the fallback to use if the console cannot be used.
+     * @return the writer.
+     */
+    static PrintWriter writer(final Console console, final PrintStream 
fallback) {
+        if (console == null) {
+            return new PrintWriter(fallback, true);
+        } else if (avoidConsoleWriter) {
+            return new PrintWriter(new OutputStreamWriter(fallback, 
JDK17.charset(console)), true);
+        } else {
+            return console.writer();
+        }
+    }
+
     /**
      * Returns the value of the specified option as a character string.
      *
diff --git 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/FormattedOutputCommand.java
 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/FormattedOutputCommand.java
index 70c1ceb5a9..f268dbbff2 100644
--- 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/FormattedOutputCommand.java
+++ 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/FormattedOutputCommand.java
@@ -289,9 +289,9 @@ abstract class FormattedOutputCommand extends CommandRunner 
{
 
     /**
      * Returns {@code true} if {@link #out} is sending its output to the 
console.
-     * If not, then we are probably writing to a file or the user specified 
his own encoding.
-     * In such case, we will send the XML output to an {@code OutputStream} 
instead of to a
-     * {@code Writer} and let the marshaller apply the encoding itself.
+     * If not, then we are probably either writing to a file, or the user 
specified his own encoding.
+     * In such case, we will send the XML output to an {@code OutputStream} 
instead of {@code Writer},
+     * and let the marshaller applies the encoding itself.
      */
     private boolean isConsole() {
         if (outputBuffer != null) return true;                      // Special 
case for JUnit tests only.
diff --git 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java
index 90dd4ca0e2..23e6f2f1f3 100644
--- 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java
+++ 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java
@@ -140,7 +140,7 @@ enum Option {
     /**
      * Return the string representation as used on the command line.
      */
-    String label() {
+    synchronized String label() {
         if (label == null) {
             label = name().toLowerCase(Locale.US);
         }
diff --git a/endorsed/src/org.apache.sis.util/main/module-info.java 
b/endorsed/src/org.apache.sis.util/main/module-info.java
index 4078e07f42..33c1e9dfec 100644
--- a/endorsed/src/org.apache.sis.util/main/module-info.java
+++ b/endorsed/src/org.apache.sis.util/main/module-info.java
@@ -169,5 +169,6 @@ module org.apache.sis.util {
             org.apache.sis.storage,
             org.apache.sis.storage.sql,
             org.apache.sis.storage.netcdf,
-            org.apache.sis.storage.geotiff;
+            org.apache.sis.storage.geotiff,
+            org.apache.sis.console;
 }
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK17.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK17.java
new file mode 100644
index 0000000000..c1ccbe0a06
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK17.java
@@ -0,0 +1,43 @@
+/*
+ * 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.pending.jdk;
+
+import java.io.Console;
+import java.nio.charset.Charset;
+
+
+/**
+ * Place holder for some functionalities defined in a JDK more recent than 
Java 11.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public final class JDK17 {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private JDK17() {
+    }
+
+    /**
+     * {@return a placeholder for the character set of the console}.
+     *
+     * @param  console  the console for which to get the character set.
+     */
+    public static Charset charset(final Console console) {
+        return Charset.defaultCharset();
+    }
+}

Reply via email to