diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java index 9d92ed694..226bceaa2 100644 --- a/src/main/java/picocli/CommandLine.java +++ b/src/main/java/picocli/CommandLine.java @@ -1206,7 +1206,7 @@ public CommandLine setColorScheme(Help.ColorScheme colorScheme) { * help with a {@code --help} or similar option, the usage help message is printed to the standard output stream so that it can be easily searched and paged.

* @since 4.0 */ public PrintWriter getOut() { - if (out == null) { setOut(new PrintWriter(System.out, true)); } + if (out == null) { setOut(newPrintWriter(System.out, getStdoutEncoding())); } return out; } @@ -1233,7 +1233,7 @@ public CommandLine setOut(PrintWriter out) { * should use this writer to print error messages (which may include a usage help message) when an unexpected error occurs.

* @since 4.0 */ public PrintWriter getErr() { - if (err == null) { setErr(new PrintWriter(System.err, true)); } + if (err == null) { setErr(newPrintWriter(System.err, getStderrEncoding())); } return err; } @@ -1807,7 +1807,7 @@ protected R throwOrExit(ExecutionException ex) { * @since 2.0 */ @Deprecated public static class DefaultExceptionHandler extends AbstractHandler> implements IExceptionHandler, IExceptionHandler2 { public List handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args) { - internalHandleParseException(ex, new PrintWriter(out, true), Help.defaultColorScheme(ansi)); return Collections.emptyList(); } + internalHandleParseException(ex, newPrintWriter(out, getStdoutEncoding()), Help.defaultColorScheme(ansi)); return Collections.emptyList(); } /** Prints the message of the specified exception, followed by the usage message for the command or subcommand * whose input was invalid, to the stream returned by {@link #err()}. @@ -1817,7 +1817,7 @@ public List handleException(ParameterException ex, PrintStream out, Help * @return the empty list * @since 3.0 */ public R handleParseException(ParameterException ex, String[] args) { - internalHandleParseException(ex, new PrintWriter(err(), true), colorScheme()); return returnResultOrExit(null); } + internalHandleParseException(ex, newPrintWriter(err(), getStderrEncoding()), colorScheme()); return returnResultOrExit(null); } static void internalHandleParseException(ParameterException ex, PrintWriter writer, Help.ColorScheme colorScheme) { writer.println(colorScheme.errorText(ex.getMessage())); @@ -1878,7 +1878,7 @@ public static boolean printHelpIfRequested(ParseResult parseResult) { * @since 3.6 */ @Deprecated public static boolean printHelpIfRequested(List parsedCommands, PrintStream out, PrintStream err, Help.ColorScheme colorScheme) { // for backwards compatibility - for (CommandLine cmd : parsedCommands) { cmd.setOut(new PrintWriter(out, true)).setErr(new PrintWriter(err, true)).setColorScheme(colorScheme); } + for (CommandLine cmd : parsedCommands) { cmd.setOut(newPrintWriter(out, getStdoutEncoding())).setErr(newPrintWriter(err, getStderrEncoding())).setColorScheme(colorScheme); } return executeHelpRequest(parsedCommands) != null; } @@ -2134,8 +2134,8 @@ private T enrichForBackwardsCompatibility(T obj) { // and the application called #useOut, #useErr or #useAnsi on it if (obj instanceof AbstractHandler) { AbstractHandler handler = (AbstractHandler) obj; - if (handler.out() != System.out) { setOut(new PrintWriter(handler.out(), true)); } - if (handler.err() != System.err) { setErr(new PrintWriter(handler.err(), true)); } + if (handler.out() != System.out) { setOut(newPrintWriter(handler.out(), getStdoutEncoding())); } + if (handler.err() != System.err) { setErr(newPrintWriter(handler.err(), getStderrEncoding())); } if (handler.ansi() != Help.Ansi.AUTO) { setColorScheme(handler.colorScheme()); } } return obj; @@ -14476,6 +14476,17 @@ static void close(Closeable closeable) { new Tracer().warn("Could not close " + closeable + ": " + ex.toString()); } } + static Charset getStdoutEncoding() { + String encoding = System.getProperty("sun.stdout.encoding"); + return encoding != null ? Charset.forName(encoding) : Charset.defaultCharset(); + } + static Charset getStderrEncoding() { + String encoding = System.getProperty("sun.stderr.encoding"); + return encoding != null ? Charset.forName(encoding) : Charset.defaultCharset(); + } + static PrintWriter newPrintWriter(OutputStream stream, Charset charset) { + return new PrintWriter(new BufferedWriter(new OutputStreamWriter(stream, charset)), true); + } static class PositionalParametersSorter implements Comparator { private static final Range OPTION_INDEX = new Range(0, 0, false, true, "0"); public int compare(ArgSpec p1, ArgSpec p2) { @@ -18015,7 +18026,7 @@ static List stripErrorMessage(List unmatched) { public boolean isUnknownOption() { return isUnknownOption(unmatched, getCommandLine()); } /** Returns {@code true} and prints suggested solutions to the specified stream if such solutions exist, otherwise returns {@code false}. * @since 3.3.0 */ - public boolean printSuggestions(PrintStream out) { return printSuggestions(new PrintWriter(out, true)); } + public boolean printSuggestions(PrintStream out) { return printSuggestions(newPrintWriter(out, getStdoutEncoding())); } /** Returns {@code true} and prints suggested solutions to the specified stream if such solutions exist, otherwise returns {@code false}. * @since 4.0 */ public boolean printSuggestions(PrintWriter writer) { diff --git a/src/test/java/picocli/ExecuteTest.java b/src/test/java/picocli/ExecuteTest.java index 4eb180179..26df3106b 100644 --- a/src/test/java/picocli/ExecuteTest.java +++ b/src/test/java/picocli/ExecuteTest.java @@ -51,19 +51,8 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.*; -import static picocli.CommandLine.Command; -import static picocli.CommandLine.ExecutionException; -import static picocli.CommandLine.Help; -import static picocli.CommandLine.IExecutionStrategy; +import static picocli.CommandLine.*; import static picocli.CommandLine.Model.UsageMessageSpec.keyValuesMap; -import static picocli.CommandLine.Option; -import static picocli.CommandLine.ParameterException; -import static picocli.CommandLine.Parameters; -import static picocli.CommandLine.ParseResult; -import static picocli.CommandLine.RunAll; -import static picocli.CommandLine.RunFirst; -import static picocli.CommandLine.RunLast; -import static picocli.CommandLine.Spec; public class ExecuteTest { @Rule @@ -415,7 +404,7 @@ public void testExecuteWithInvalidInput_Ansi_ON() { @Test public void testExecuteWithInvalidInput_Ansi_ON_CustomErr() { new CommandLine(new MyCallable()) - .setErr(new PrintWriter(System.out, true)) + .setErr(CommandLine.newPrintWriter(System.out, getStdoutEncoding())) .setColorScheme(Help.defaultColorScheme(Help.Ansi.ON)).execute("invalid input"); assertEquals("", systemErrRule.getLog()); assertEquals(INVALID_INPUT_ANSI + MYCALLABLE_USAGE_ANSI, systemOutRule.getLog()); diff --git a/src/test/java/picocli/HelpSubCommandTest.java b/src/test/java/picocli/HelpSubCommandTest.java index 1e1f439d8..2468cee53 100644 --- a/src/test/java/picocli/HelpSubCommandTest.java +++ b/src/test/java/picocli/HelpSubCommandTest.java @@ -168,8 +168,8 @@ public void run() { } int exitCode = new CommandLine(new App()) .setExecutionStrategy(new RunLast()) - .setOut(new PrintWriter(System.out, true)) - .setErr(new PrintWriter(System.err, true)) + .setOut(CommandLine.newPrintWriter(System.out, getStdoutEncoding())) + .setErr(CommandLine.newPrintWriter(System.err, getStderrEncoding())) .setColorScheme(Help.defaultColorScheme(Help.Ansi.OFF)) .execute("customHelp"); @@ -206,8 +206,8 @@ public void run() { } int exitCode = new CommandLine(new App()) .setExecutionStrategy(new RunLast()) - .setOut(new PrintWriter(System.out, true)) - .setErr(new PrintWriter(System.err, true)) + .setOut(CommandLine.newPrintWriter(System.out, getStdoutEncoding())) + .setErr(CommandLine.newPrintWriter(System.err, getStderrEncoding())) .setColorScheme(Help.defaultColorScheme(Help.Ansi.OFF)) .execute("newCustomHelp"); diff --git a/src/test/java/picocli/Issue1320.java b/src/test/java/picocli/Issue1320.java new file mode 100644 index 000000000..be807f6b4 --- /dev/null +++ b/src/test/java/picocli/Issue1320.java @@ -0,0 +1,80 @@ +package picocli; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; +import org.junit.contrib.java.lang.system.SystemErrRule; +import org.junit.contrib.java.lang.system.SystemOutRule; +import org.junit.rules.TestRule; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Parameters; +import picocli.CommandLine.Spec; + +import java.nio.charset.Charset; + +import static org.junit.Assert.assertEquals; + +public class Issue1320 { + + @Rule + public final TestRule restoreSystemProperties = new RestoreSystemProperties(); + + @Rule + public final SystemOutRule systemOutRule = new SystemOutRule().enableLog(); + + @Rule + public final SystemErrRule systemErrRule = new SystemErrRule().enableLog(); + + @Command(name = "test") + static class TestCommand implements Runnable { + + @Parameters + String text; + + @Spec + CommandSpec spec; + + @Override + public void run() { + spec.commandLine().getOut().print(text); + spec.commandLine().getOut().flush(); + spec.commandLine().getErr().print(text); + spec.commandLine().getErr().flush(); + } + } + + @Test + public void testIssue1320() { + String unmappable = "[abcµ]"; + + resetLogs(); + System.clearProperty(SUN_STDOUT_ENCODING); + System.clearProperty(SUN_STDERR_ENCODING); + fixLogPrintStream(Charset.defaultCharset().name()); + assertEquals(CommandLine.ExitCode.OK, new CommandLine(new TestCommand()).execute(unmappable)); + assertEquals(unmappable, systemOutRule.getLog()); + assertEquals(unmappable, systemErrRule.getLog()); + + resetLogs(); + System.setProperty(SUN_STDOUT_ENCODING, CP_437); + System.setProperty(SUN_STDERR_ENCODING, CP_437); + fixLogPrintStream(CP_437); + assertEquals(CommandLine.ExitCode.OK, new CommandLine(new TestCommand()).execute(unmappable)); + assertEquals(unmappable, systemOutRule.getLog()); + assertEquals(unmappable, systemErrRule.getLog()); + } + + private void resetLogs() { + systemOutRule.clearLog(); + systemErrRule.clearLog(); + } + + private void fixLogPrintStream(String encoding) { + System.setProperty("file.encoding", encoding); + } + + private static final String CP_437 = "cp437"; + private static final String SUN_STDOUT_ENCODING = "sun.stdout.encoding"; + private static final String SUN_STDERR_ENCODING = "sun.stderr.encoding"; +}