[Jython-checkins] jython: Determine console encoding without private method access (fixes #2659)

jeff.allen jython-checkins at python.org
Sat May 5 08:34:14 EDT 2018


https://hg.python.org/jython/rev/9185f0a117f0
changeset:   8160:9185f0a117f0
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Sat May 05 12:03:43 2018 +0100
summary:
  Determine console encoding without private method access (fixes #2659)

This change removes the reflective access to private method Console.encoding()
to which Java 9 objects, simplifing the determination of encoding a little.
We do not fall back to "file.encoding" as a guess, but on a hard-coded "utf-8".
Use of chcp was contributed by Oti Humbel. Use of "locale charmap" awaits
test on Unix-like platforms (check the Java property python.console.encoding).

Sub-process management is factored out of getSystemVersionString for shared use.

files:
  NEWS                                   |    1 +
  src/org/python/core/PySystemState.java |  139 +++++++-----
  2 files changed, 79 insertions(+), 61 deletions(-)


diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@
 
 Developement tip
   Bugs fixed
+    - [ 2659 ] Determine console encoding without access violation (Java 9)
     - [ 2662 ] IllegalAccessException accessing public abstract method via PyReflectedFunction
     - [ 2501 ] JAVA_STACK doesn't work (fixed for Windows launcher only)
     - [ 1866 ] Parser does not have mismatch token error messages caught by BaseRecognizer
diff --git a/src/org/python/core/PySystemState.java b/src/org/python/core/PySystemState.java
--- a/src/org/python/core/PySystemState.java
+++ b/src/org/python/core/PySystemState.java
@@ -13,7 +13,6 @@
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.nio.charset.Charset;
 import java.nio.charset.UnsupportedCharsetException;
 import java.security.AccessControlException;
@@ -32,6 +31,8 @@
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.python.Version;
 import org.python.core.adapter.ClassicPyObjectAdapter;
@@ -923,10 +924,7 @@
          * python.io.encoding is dubious.
          */
         if (!registry.containsKey(PYTHON_CONSOLE_ENCODING)) {
-            String encoding = getPlatformEncoding();
-            if (encoding != null) {
-                registry.put(PYTHON_CONSOLE_ENCODING, encoding);
-            }
+                registry.put(PYTHON_CONSOLE_ENCODING, getConsoleEncoding());
         }
 
         // Set up options from registry
@@ -934,37 +932,43 @@
     }
 
     /**
-     * Return the encoding of the underlying platform, if we can work it out by any means at all.
+     * Try to determine the console encoding from the platform, if necessary using a sub-process to
+     * enquire. If everything fails, assume UTF-8.
      *
-     * @return the encoding of the underlying platform
+     * @return the console encoding (and never {@code null})
      */
-    private static String getPlatformEncoding() {
-        // first try to grab the Console encoding
-        String encoding = getConsoleEncoding();
-        if (encoding == null) {
-            try {
-                // Not quite the console encoding (differs on Windows)
-                encoding = System.getProperty("file.encoding");
-            } catch (SecurityException se) {
-                // ignore, can't do anything about it
+    private static String getConsoleEncoding() {
+
+        // From Java 8 onwards, the answer may already be to hand in the registry:
+        String encoding = System.getProperty("sun.stdout.encoding");
+
+        if (encoding != null) {
+            return encoding;
+
+        } else if (System.getProperty("os.name").startsWith("Windows")) {
+            // Go via the Windows code page built-in command "chcp".
+            String output = getCommandResult("cmd", "/c", "chcp");
+            /*
+             * The output will be like "Active code page: 850" or maybe "Aktive Codepage: 1252." or
+             * "활성 코드 페이지: 949". Assume the first number with 2 or more digits is the code page.
+             */
+            final Pattern DIGITS_PATTERN = Pattern.compile("[1-9]\\d+");
+            Matcher matcher = DIGITS_PATTERN.matcher(output);
+            if (matcher.find()) {
+                return "cp".concat(output.substring(matcher.start(), matcher.end()));
+            }
+
+        } else {
+            // Try a Unix-like "locale charmap".
+            String output = getCommandResult("locale", "charmap");
+            // The result of "locale charmap" is just the charmap name ~ Charset or codec name.
+            if (output.length() > 0) {
+                return output;
             }
         }
-        return encoding;
-    }
 
-    /**
-     * @return the console encoding; can be <code>null</code>
-     */
-    private static String getConsoleEncoding() {
-        String encoding = null;
-        try {
-            Method encodingMethod = java.io.Console.class.getDeclaredMethod("encoding");
-            encodingMethod.setAccessible(true); // private static method
-            encoding = (String) encodingMethod.invoke(Console.class);
-        } catch (Exception e) {
-            // ignore any exception
-        }
-        return encoding;
+        // If we land here it is because we found no answer, and we will assume UTF-8.
+        return "utf-8";
     }
 
     /**
@@ -1717,46 +1721,59 @@
     }
 
     /**
-     * Attempt to find the OS version. The mechanism on Windows is to extract it from
-     * the result of <code>cmd.exe /C ver</code>, and otherwise (assumed Unix-like OS)
-     * to use <code>uname -v</code>.
+     * Attempt to find the OS version. The mechanism on Windows is to extract it from the result of
+     * <code>cmd.exe /C ver</code>, and otherwise (assumed Unix-like OS) to use
+     * <code>uname -v</code>.
      */
     public static String getSystemVersionString() {
+        if (System.getProperty("os.name").startsWith("Windows")) {
+            String ver = getCommandResult("cmd.exe", "/c", "ver");
+            int start = ver.toLowerCase().indexOf("version ");
+            if (start != -1) {
+                start += 8;
+                int end = ver.length();
+                if (ver.endsWith("]")) {
+                    --end;
+                }
+                ver = ver.substring(start, end);
+            }
+            return ver;
+        } else {
+            return getCommandResult("uname", "-v");
+        }
+    }
+
+    /**
+     * Run a command as a sub-process and return as the result the first line of output that consist
+     * of more than white space. It returns "" on any kind of error.
+     *
+     * @param command as strings (as for <code>ProcessBuilder</code>)
+     * @return the first line with content, or ""
+     */
+    private static String getCommandResult(String... command) {
+        String result = "", line = null;
+        ProcessBuilder pb = new ProcessBuilder(command);
         try {
-            String uname_sysver;
-            boolean win = System.getProperty("os.name").startsWith("Windows");
-            Process p = Runtime.getRuntime().exec(win ? "cmd.exe /C ver" : "uname -v");
+            Process p = pb.start();
             java.io.BufferedReader br =
                     new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()));
-            uname_sysver = br.readLine();
-            while (uname_sysver != null && uname_sysver.length() == 0) {
-                uname_sysver = br.readLine();
-            }
-            // to end the process sanely in case we deal with some
-            // implementation that emits additional new-lines:
-            while (br.readLine() != null) {
-                ;
+            // We read to the end-of-stream in case the sub-process cannot end cleanly without.
+            while ((line = br.readLine()) != null) {
+                if (line.length() > 0 && result.length() == 0) {
+                    // This is the first line with content (maybe).
+                    result = line.trim();
+                }
             }
             br.close();
+            // Now we wait for the sub-process to terminate nicely.
             if (p.waitFor() != 0) {
-                // No fallback for sysver available
-                uname_sysver = "";
+                // Bad exit status: don't take the result.
+                result = "";
             }
-            if (win && uname_sysver.length() > 0) {
-                int start = uname_sysver.toLowerCase().indexOf("version ");
-                if (start != -1) {
-                    start += 8;
-                    int end = uname_sysver.length();
-                    if (uname_sysver.endsWith("]")) {
-                        --end;
-                    }
-                    uname_sysver = uname_sysver.substring(start, end);
-                }
-            }
-            return uname_sysver;
-        } catch (Exception e) {
-            return "";
+        } catch (IOException | InterruptedException | SecurityException e) {
+            result = "";
         }
+        return result;
     }
 
     /* Traverseproc implementation */

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list