[Jython-checkins] jython (merge default -> default): Merge console work to trunk

jeff.allen jython-checkins at python.org
Sat Sep 7 19:12:49 CEST 2013


http://hg.python.org/jython/rev/dbf21ca9391a
changeset:   7117:dbf21ca9391a
parent:      7113:be402c429680
parent:      7116:c71cbf98acf1
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Sat Sep 07 16:27:44 2013 +0100
summary:
  Merge console work to trunk

files:
  Lib/readline.py                             |   31 +-
  build.xml                                   |    1 +
  registry                                    |   15 +-
  src/org/python/core/Console.java            |   60 +
  src/org/python/core/PlainConsole.java       |  106 ++
  src/org/python/core/Py.java                 |   77 +-
  src/org/python/core/PySystemState.java      |  524 +++++----
  src/org/python/core/__builtin__.java        |   39 +-
  src/org/python/util/ConsoleStream.java      |  242 ++++
  src/org/python/util/InteractiveConsole.java |   70 +-
  src/org/python/util/JLineConsole.java       |  279 +++-
  src/org/python/util/ReadlineConsole.java    |  222 +++-
  src/org/python/util/jython.java             |  238 ++--
  tests/java/javatests/Issue1972.java         |  155 +-
  tests/java/org/python/util/jythonTest.java  |  108 +-
  15 files changed, 1499 insertions(+), 668 deletions(-)


diff --git a/Lib/readline.py b/Lib/readline.py
--- a/Lib/readline.py
+++ b/Lib/readline.py
@@ -14,10 +14,11 @@
            'set_history_length', 'set_pre_input_hook', 'set_startup_hook',
            'write_history_file']
 
-try:    
-    _reader = sys._jy_interpreter.reader
+try:
+    _console = sys._jy_console
+    _reader = _console.reader
 except AttributeError:
-    raise ImportError("Cannot access JLineConsole")
+    raise ImportError("Cannot access JLineConsole reader")
 
 _history_list = None
 
@@ -38,7 +39,7 @@
     # modify the history (ipython uses the function
     # remove_history_item to mutate the history relatively frequently)
     global _history_list
-    
+
     history = _reader.history
     try:
         history_list_field = history.class.getDeclaredField("history")
@@ -68,7 +69,7 @@
 
 def insert_text(string):
     _reader.putString(string)
-    
+
 def read_init_file(filename=None):
     warn("read_init_file: %s" % (filename,), NotImplementedWarning, "module", 2)
 
@@ -94,7 +95,7 @@
     _reader.history.clear()
 
 def add_history(line):
-    _reader.addToHistory(line)
+    _reader.history.addToHistory(line)
 
 def get_history_length():
     return _reader.history.maxSize
@@ -106,7 +107,11 @@
     return len(_reader.history.historyList)
 
 def get_history_item(index):
-    return _reader.history.historyList[index]
+    # JLine indexes from 0 while readline indexes from 1 (at least in test_readline)
+    if index>0:
+        return _reader.history.historyList[index-1]
+    else:
+        return None
 
 def remove_history_item(pos):
     if _history_list:
@@ -114,12 +119,18 @@
     else:
         warn("Cannot remove history item at position: %s" % (pos,), SecurityWarning, stacklevel=2)
 
+def replace_history_item(pos, line):
+    if _history_list:
+        _history_list.set(pos, line)
+    else:
+        warn("Cannot replace history item at position: %s" % (pos,), SecurityWarning, stacklevel=2)
+
 def redisplay():
     _reader.redrawLine()
 
 def set_startup_hook(function=None):
-    sys._jy_interpreter.startupHook = function
-    
+    _console.startupHook = function
+
 def set_pre_input_hook(function=None):
     warn("set_pre_input_hook %s" % (function,), NotImplementedWarning, stacklevel=2)
 
@@ -151,7 +162,7 @@
         return start
 
     _reader.addCompletor(complete_handler)
-    
+
 
 def get_completer():
     return _completer_function
diff --git a/build.xml b/build.xml
--- a/build.xml
+++ b/build.xml
@@ -936,6 +936,7 @@
                 <fileset dir="${test.source.dir}" includes="**/*Test*.java">
                     <exclude name="javatests/**/*" />
                     <exclude name="**/InterpTestCase.java" />
+                    <exclude name="**/jythonTest*" /> <!-- Must run interactively -->
                     <exclude name="org/python/antlr/**" />
                     <exclude name=".classpath" />
                     <exclude name=".project" />
diff --git a/registry b/registry
--- a/registry
+++ b/registry
@@ -31,15 +31,16 @@
 #python.verbose = message
 
 # Jython ships with a JLine console (http://jline.sourceforge.net/)
-# out of the box. Setting this to the name of a different console class,
-# new console features can be enabled. Readline support is such an
-# example:
+# out of the box.
+python.console=org.python.util.JLineConsole
+# To activate explicitly the featureless Jython console, choose:
+#python.console=org.python.core.PlainConsole
+# By setting this to the name of a different console class,
+# new console features can be enabled. For example:
 #python.console=org.python.util.ReadlineConsole
-#python.console.readlinelib=JavaReadline
-# To activate the legacy Jython console:
-#python.console=org.python.util.InteractiveConsole
+#python.console.readlinelib=GnuReadline
 
-# Setting this to a valid codec name will cause the console to use a
+# Setting this to a valid (Java) codec name will cause the console to use a
 # different encoding when reading commands from the console.
 #python.console.encoding = cp850
 
diff --git a/src/org/python/core/Console.java b/src/org/python/core/Console.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/core/Console.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2013 Jython Developers
+package org.python.core;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * A class named in configuration as the value of <code>python.console</code> must implement this
+ * interface, and provide a constructor with a single <code>String</code> argument, to be acceptable
+ * during initialization of the interpreter. The argument to the constructor names the encoding in
+ * use on the console. Such a class may provide line editing and history recall to an interactive
+ * console. A default implementation (that does not provide any such facilities) is available as
+ * {@link PlainConsole}.
+ */
+public interface Console {
+
+    /**
+     * Complete initialization and (optionally) install a stream object with line-editing as the
+     * replacement for <code>System.in</code>.
+     *
+     * @throws IOException in case of failure related to i/o
+     */
+    public void install() throws IOException;
+
+    /**
+     * Uninstall the Console (if possible). A Console that installs a replacement for
+     * <code>System.in</code> should put back the original value.
+     *
+     * @throws UnsupportedOperationException if the Console cannot be uninstalled
+     */
+    public void uninstall() throws UnsupportedOperationException;
+
+    /**
+     * Write a prompt and read a line from standard input. The returned line does not include the
+     * trailing newline. When the user enters the EOF key sequence, an EOFException should be
+     * raised. The built-in function <code>raw_input</code> calls this method on the installed
+     * console.
+     *
+     * @param prompt to output before reading a line
+     * @return the line read in (encoded as bytes)
+     * @throws IOException in case of failure related to i/o
+     * @throws EOFException when the user enters the EOF key sequence
+     */
+    public ByteBuffer raw_input(CharSequence prompt) throws IOException, EOFException;
+
+    /**
+     * Write a prompt and read a line from standard input. The returned line does not include the
+     * trailing newline. When the user enters the EOF key sequence, an EOFException should be
+     * raised. The Py3k built-in function <code>input</code> calls this method on the installed
+     * console.
+     *
+     * @param prompt to output before reading a line
+     * @return the line read in
+     * @throws IOException in case of failure related to i/o
+     * @throws EOFException when the user enters the EOF key sequence
+     */
+    public CharSequence input(CharSequence prompt) throws IOException, EOFException;
+
+}
diff --git a/src/org/python/core/PlainConsole.java b/src/org/python/core/PlainConsole.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/core/PlainConsole.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2013 Jython Developers
+package org.python.core;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+
+/**
+ * A base class for classes that can install a console wrapper for a specific console-handling
+ * library. The Jython command-line application, when it detects that the console is an interactive
+ * session, chooses and installs a class named in registry item <code>python.console</code>, and
+ * implementing interface {@link Console}. <code>PlainConsole</code> may be selected by the user
+ * through that registry, and is the default console when the selected one fails to load. It will
+ * also be installed by the Jython command-line application when in non-interactive mode.
+ * <p>
+ * Unlike some consoles, <code>PlainConsole</code> does not install a replacement for
+ * <code>System.in</code> or use a native library. It prompts on <code>System.out</code> and reads
+ * from <code>System.in</code> (wrapped with the console encoding).
+ */
+public class PlainConsole implements Console {
+
+    /** Encoding to use for line input. */
+    public final String encoding;
+
+    /** Encoding to use for line input as a <code>Charset</code>. */
+    public final Charset encodingCharset;
+
+    /** BufferedReader used by {@link #input(CharSequence)} */
+    private BufferedReader reader;
+
+    /**
+     * Construct an instance of the console class specifying the character encoding. This encoding
+     * must be one supported by the JVM. The PlainConsole does not replace <code>System.in</code>,
+     * and does not add any line-editing capability to what is standard for your OS console.
+     *
+     * @param encoding name of a supported encoding or <code>null</code> for
+     *            <code>Charset.defaultCharset()</code>
+     */
+    public PlainConsole(String encoding) throws IllegalCharsetNameException,
+            UnsupportedCharsetException {
+        if (encoding == null) {
+            encoding = Charset.defaultCharset().name();
+        }
+        this.encoding = encoding;
+        encodingCharset = Charset.forName(encoding);
+    }
+
+    @Override
+    public void install() {
+        // Create a Reader with the right character encoding
+        reader = new BufferedReader(new InputStreamReader(System.in, encodingCharset));
+    }
+
+    /**
+     * A <code>PlainConsole</code> may be uninstalled. This method assumes any sub-class may not be
+     * uninstalled. Sub-classes that permit themselves to be uninstalled <b>must</b> override (and
+     * not call) this method.
+     *
+     * @throws UnsupportedOperationException unless this class is exactly <code>PlainConsole</code>
+     */
+    @Override
+    public void uninstall() throws UnsupportedOperationException {
+        Class<? extends Console> myClass = this.getClass();
+        if (myClass != PlainConsole.class) {
+            throw new UnsupportedOperationException(myClass.getSimpleName()
+                        + " console may not be uninstalled.");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The base implementation calls {@link #input(CharSequence)} and applies the console encoding
+     * to obtain the bytes. This may be a surprise. Line-editing consoles necessarily operate in
+     * terms of characters rather than bytes, and therefore support a direct implementation of
+     * <code>input</code>.
+     */
+    @Override
+    public ByteBuffer raw_input(CharSequence prompt) throws IOException, EOFException {
+        CharSequence line = input(prompt);
+        return encodingCharset.encode(CharBuffer.wrap(line));
+    }
+
+    // The base implementation simply uses <code>System.out</code> and <code>System.in</code>.
+    @Override
+    public CharSequence input(CharSequence prompt) throws IOException, EOFException {
+
+        // Issue the prompt with no newline
+        System.out.print(prompt);
+
+        // Get the line from the console via java.io
+        String line = reader.readLine();
+        if (line == null) {
+            throw new EOFException();
+        } else {
+            return line;
+        }
+    }
+
+}
diff --git a/src/org/python/core/Py.java b/src/org/python/core/Py.java
--- a/src/org/python/core/Py.java
+++ b/src/org/python/core/Py.java
@@ -3,6 +3,7 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -17,14 +18,17 @@
 import java.sql.Date;
 import java.sql.Time;
 import java.sql.Timestamp;
+import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.List;
 import java.util.Set;
 
-import org.python.antlr.base.mod;
 import jnr.constants.Constant;
 import jnr.constants.platform.Errno;
-import java.util.ArrayList;
-import java.util.List;
+import jnr.posix.POSIX;
+import jnr.posix.POSIXFactory;
+
+import org.python.antlr.base.mod;
 import org.python.core.adapter.ClassicPyObjectAdapter;
 import org.python.core.adapter.ExtensiblePyObjectAdapter;
 import org.python.modules.posix.PosixModule;
@@ -1386,6 +1390,73 @@
         getThreadState().frame = f;
     }
 
+    /**
+     * The handler for interactive consoles, set by {@link #installConsole(Console)} and accessed by
+     * {@link #getConsole()}.
+     */
+    private static Console console;
+
+    /**
+     * Get the Jython Console (used for <code>input()</code>, <code>raw_input()</code>, etc.) as
+     * constructed and set by {@link PySystemState} initialization.
+     *
+     * @return the Jython Console
+     */
+    public static Console getConsole() {
+        if (console == null) {
+            // We really shouldn't ask for a console before PySystemState initialization but ...
+            try {
+                // ... something foolproof that we can supersede.
+                installConsole(new PlainConsole("ascii"));
+            } catch (Exception e) {
+                // This really, really shouldn't happen
+                throw Py.RuntimeError("Could not create fall-back PlainConsole: " + e);
+            }
+        }
+        return console;
+    }
+
+    /**
+     * Install the provided Console, first uninstalling any current one. The Jython Console is used
+     * for <code>raw_input()</code> etc., and may provide line-editing and history recall at the
+     * prompt. A Console may replace <code>System.in</code> with its line-editing input method.
+     *
+     * @param console The new Console object
+     * @throws UnsupportedOperationException if some prior Console refuses to uninstall
+     * @throws IOException if {@link Console#install()} raises it
+     */
+    public static void installConsole(Console console) throws UnsupportedOperationException,
+            IOException {
+        if (Py.console != null) {
+            // Some Console class already installed: may be able to uninstall
+            Py.console.uninstall();
+            Py.console = null;
+        }
+
+        // Install the specified Console
+        console.install();
+        Py.console = console;
+
+        // Cause sys (if it exists) to export the console handler that was installed
+        if (Py.defaultSystemState != null) {
+            Py.defaultSystemState.__setattr__("_jy_console", Py.java2py(console));
+        }
+    }
+
+    /**
+     * Check (using the {@link POSIX} library) whether we are in an interactive environment. Amongst
+     * other things, this affects the type of console that may be legitimately installed during
+     * system initialisation.
+     *
+     * @return
+     */
+    public static boolean isInteractive() {
+        // Decide if System.in is interactive
+        POSIX posix = POSIXFactory.getPOSIX();
+        FileDescriptor in = FileDescriptor.in;
+        return posix.isatty(in);
+    }
+
     /* A collection of functions for implementing the print statement */
     public static StdoutWrapper stderr;
     static StdoutWrapper stdout;
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
@@ -2,7 +2,6 @@
 package org.python.core;
 
 import java.io.BufferedReader;
-import java.io.Console;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -11,6 +10,8 @@
 import java.lang.ref.Reference;
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.net.URLDecoder;
@@ -21,26 +22,26 @@
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.Set;
 import java.util.StringTokenizer;
-import java.util.Map.Entry;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentMap;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 
 import jnr.posix.util.Platform;
+
 import org.python.Version;
 import org.python.core.adapter.ClassicPyObjectAdapter;
 import org.python.core.adapter.ExtensiblePyObjectAdapter;
 import org.python.core.packagecache.PackageManager;
 import org.python.core.packagecache.SysPackageManager;
-import org.python.modules.Setup;
-import org.python.modules.zipimport.zipimporter;
-import org.python.util.Generic;
 import org.python.expose.ExposedGet;
 import org.python.expose.ExposedType;
+import org.python.modules.Setup;
+import org.python.util.Generic;
 
 /**
  * The "sys" module.
@@ -48,6 +49,7 @@
 // xxx Many have lamented, this should really be a module!
 // but it will require some refactoring to see this wish come true.
 public class PySystemState extends PyObject implements ClassDictInit {
+
     public static final String PYTHON_CACHEDIR = "python.cachedir";
     public static final String PYTHON_CACHEDIR_SKIP = "python.cachedir.skip";
     public static final String PYTHON_CONSOLE_ENCODING = "python.console.encoding";
@@ -63,22 +65,19 @@
 
     public static final PyString version = new PyString(Version.getVersion());
 
-    public static final PyTuple subversion = new PyTuple(new PyString("Jython"),
-                                                         Py.newString(""),
-                                                         Py.newString(""));
+    public static final PyTuple subversion = new PyTuple(new PyString("Jython"), Py.newString(""),
+            Py.newString(""));
 
-    public static final int hexversion = ((Version.PY_MAJOR_VERSION << 24) |
-                                    (Version.PY_MINOR_VERSION << 16) |
-                                    (Version.PY_MICRO_VERSION <<  8) |
-                                    (Version.PY_RELEASE_LEVEL <<  4) |
-                                    (Version.PY_RELEASE_SERIAL << 0));
+    public static final int hexversion = ((Version.PY_MAJOR_VERSION << 24)
+            | (Version.PY_MINOR_VERSION << 16) | (Version.PY_MICRO_VERSION << 8)
+            | (Version.PY_RELEASE_LEVEL << 4) | (Version.PY_RELEASE_SERIAL << 0));
 
     public static PyTuple version_info;
 
     public final static int maxunicode = 1114111;
 
-    //XXX: we should someday make this Long.MAX_VALUE, but see test_index.py
-    //     for tests that would need to pass but today would not.
+    // XXX: we should someday make this Long.MAX_VALUE, but see test_index.py
+    // for tests that would need to pass but today would not.
     public final static int maxsize = Integer.MAX_VALUE;
 
     public final static PyString float_repr_style = Py.newString("short");
@@ -86,31 +85,26 @@
     public static boolean py3kwarning = false;
 
     public final static Class flags = Options.class;
-    
+
     public static PyTuple _mercurial;
     /**
      * The copyright notice for this release.
      */
 
-    public static final PyObject copyright = Py.newString(
-        "Copyright (c) 2000-2013 Jython Developers.\n" +
-        "All rights reserved.\n\n" +
+    public static final PyObject copyright = Py
+            .newString("Copyright (c) 2000-2013 Jython Developers.\n" + "All rights reserved.\n\n" +
 
-        "Copyright (c) 2000 BeOpen.com.\n" +
-        "All Rights Reserved.\n\n"+
+            "Copyright (c) 2000 BeOpen.com.\n" + "All Rights Reserved.\n\n" +
 
-        "Copyright (c) 2000 The Apache Software Foundation.\n" +
-        "All rights reserved.\n\n" +
+            "Copyright (c) 2000 The Apache Software Foundation.\n" + "All rights reserved.\n\n" +
 
-        "Copyright (c) 1995-2000 Corporation for National Research "+
-        "Initiatives.\n" +
-        "All Rights Reserved.\n\n" +
+            "Copyright (c) 1995-2000 Corporation for National Research " + "Initiatives.\n"
+                    + "All Rights Reserved.\n\n" +
 
-        "Copyright (c) 1991-1995 Stichting Mathematisch Centrum, " +
-        "Amsterdam.\n" +
-        "All Rights Reserved.");
+                    "Copyright (c) 1991-1995 Stichting Mathematisch Centrum, " + "Amsterdam.\n"
+                    + "All Rights Reserved.");
 
-    private static Map<String,String> builtinNames;
+    private static Map<String, String> builtinNames;
     public static PyTuple builtin_module_names = null;
 
     public static PackageManager packageManager;
@@ -182,8 +176,8 @@
     private final PySystemStateCloser closer;
     private static final ReferenceQueue<PySystemState> systemStateQueue =
             new ReferenceQueue<PySystemState>();
-    private static final ConcurrentMap<WeakReference<PySystemState>,
-                                       PySystemStateCloser> sysClosers = Generic.concurrentMap();
+    private static final ConcurrentMap<WeakReference<PySystemState>, PySystemStateCloser> sysClosers =
+            Generic.concurrentMap();
 
     // float_info
     public static PyObject float_info;
@@ -205,7 +199,7 @@
         meta_path = new PyList();
         path_hooks = new PyList();
         path_hooks.append(new JavaImporter());
-        path_hooks.append(zipimporter.TYPE);
+        path_hooks.append(org.python.modules.zipimport.zipimporter.TYPE);
         path_hooks.append(ClasspathPyImporter.TYPE);
         path_importer_cache = new PyDictionary();
 
@@ -246,24 +240,16 @@
     }
 
     private static void checkReadOnly(String name) {
-        if (name == "__dict__" ||
-            name == "__class__" ||
-            name == "registry" ||
-            name == "exec_prefix" ||
-            name == "packageManager") {
+        if (name == "__dict__" || name == "__class__" || name == "registry"
+                || name == "exec_prefix" || name == "packageManager") {
             throw Py.TypeError("readonly attribute");
         }
     }
 
     private static void checkMustExist(String name) {
-        if (name == "__dict__" ||
-            name == "__class__" ||
-            name == "registry" ||
-            name == "exec_prefix" ||
-            name == "platform" ||
-            name == "packageManager" ||
-            name == "builtins" ||
-            name == "warnoptions") {
+        if (name == "__dict__" || name == "__class__" || name == "registry"
+                || name == "exec_prefix" || name == "platform" || name == "packageManager"
+                || name == "builtins" || name == "warnoptions") {
             throw Py.TypeError("readonly attribute");
         }
     }
@@ -275,7 +261,7 @@
         }
 
         for (PyFile stdStream : new PyFile[] {(PyFile)this.stdin, (PyFile)this.stdout,
-                                              (PyFile)this.stderr}) {
+                (PyFile)this.stderr}) {
             if (stdStream.isatty()) {
                 stdStream.encoding = encoding;
             }
@@ -285,6 +271,7 @@
     // might be nice to have something general here, but for now these
     // seem to be the only values that need to be explicitly shadowed
     private Shadow shadowing;
+
     public synchronized void shadow() {
         if (shadowing == null) {
             shadowing = new Shadow();
@@ -292,6 +279,7 @@
     }
 
     private static class DefaultBuiltinsHolder {
+
         static final PyObject builtins = fillin();
 
         static PyObject fillin() {
@@ -355,6 +343,7 @@
     }
 
     // xxx fix this accessors
+    @Override
     public PyObject __findattr_ex__(String name) {
         if (name == "exc_value") {
             PyException exc = Py.getThreadState().exception;
@@ -398,6 +387,7 @@
         }
     }
 
+    @Override
     public void __setattr__(String name, PyObject value) {
         checkReadOnly(name);
         if (name == "builtins") {
@@ -418,6 +408,7 @@
         }
     }
 
+    @Override
     public void __delattr__(String name) {
         checkMustExist(name);
         PyObject ret = getType().lookup(name); // xxx fix fix fix
@@ -434,10 +425,12 @@
     }
 
     // xxx
+    @Override
     public void __rawdir__(PyDictionary accum) {
         accum.update(__dict__);
     }
 
+    @Override
     public String toString() {
         return "<module '" + __name__ + "' (built-in)>";
     }
@@ -447,7 +440,7 @@
     }
 
     public void setrecursionlimit(int recursionlimit) {
-        if(recursionlimit <= 0) {
+        if (recursionlimit <= 0) {
             throw Py.ValueError("Recursion limit must be positive");
         }
         this.recursionlimit = recursionlimit;
@@ -486,8 +479,7 @@
     /**
      * Change the current working directory to the specified path.
      *
-     * path is assumed to be absolute and canonical (via
-     * os.path.realpath).
+     * path is assumed to be absolute and canonical (via os.path.realpath).
      *
      * @param path a path String
      */
@@ -505,8 +497,7 @@
     }
 
     /**
-     * Resolve a path. Returns the full path taking the current
-     * working directory into account.
+     * Resolve a path. Returns the full path taking the current working directory into account.
      *
      * @param path a path String
      * @return a resolved path String
@@ -516,12 +507,10 @@
     }
 
     /**
-     * Resolve a path. Returns the full path taking the current
-     * working directory into account.
+     * Resolve a path. Returns the full path taking the current working directory into account.
      *
-     * Like getPath but called statically. The current PySystemState
-     * is only consulted for the current working directory when it's
-     * necessary (when the path is relative).
+     * Like getPath but called statically. The current PySystemState is only consulted for the
+     * current working directory when it's necessary (when the path is relative).
      *
      * @param path a path String
      * @return a resolved path String
@@ -539,8 +528,8 @@
         File file = new File(path);
         // Python considers r'\Jython25' and '/Jython25' abspaths on Windows, unlike
         // java.io.File
-        if (!file.isAbsolute() && (!Platform.IS_WINDOWS
-                                   || !(path.startsWith("\\") || path.startsWith("/")))) {
+        if (!file.isAbsolute()
+                && (!Platform.IS_WINDOWS || !(path.startsWith("\\") || path.startsWith("/")))) {
             if (sys == null) {
                 sys = Py.getSystemState();
             }
@@ -557,8 +546,7 @@
                 exitfunc.__call__();
             } catch (PyException exc) {
                 if (!exc.match(Py.SystemExit)) {
-                    Py.println(stderr,
-                               Py.newString("Error in sys.exitfunc:"));
+                    Py.println(stderr, Py.newString("Error in sys.exitfunc:"));
                 }
                 Py.printException(exc);
             }
@@ -574,18 +562,19 @@
         this.classLoader = classLoader;
     }
 
-    private static String findRoot(Properties preProperties,
-                                     Properties postProperties,
-                                     String jarFileName)
-    {
+    private static String findRoot(Properties preProperties, Properties postProperties,
+            String jarFileName) {
         String root = null;
         try {
-            if (postProperties != null)
+            if (postProperties != null) {
                 root = postProperties.getProperty("python.home");
-            if (root == null)
+            }
+            if (root == null) {
                 root = preProperties.getProperty("python.home");
-            if (root == null)
+            }
+            if (root == null) {
                 root = preProperties.getProperty("install.root");
+            }
 
             determinePlatform(preProperties);
         } catch (Exception exc) {
@@ -640,8 +629,7 @@
     }
 
     private static void initRegistry(Properties preProperties, Properties postProperties,
-                                       boolean standalone, String jarFileName)
-    {
+            boolean standalone, String jarFileName) {
         if (registry != null) {
             Py.writeError("systemState", "trying to reinitialize registry");
             return;
@@ -662,6 +650,7 @@
                 addRegistryFile(homeFile);
                 addRegistryFile(new File(prefix, "registry"));
             } catch (Exception exc) {
+                // Continue
             }
         }
         if (prefix != null) {
@@ -676,6 +665,7 @@
                 registry.setProperty("python.path", jythonpath);
             }
         } catch (SecurityException e) {
+            // Continue
         }
         registry.putAll(postProperties);
         if (standalone) {
@@ -693,7 +683,7 @@
         // Set up options from registry
         Options.setFromRegistry();
     }
-    
+
     /**
      * @return the encoding of the underlying platform; can be <code>null</code>
      */
@@ -724,7 +714,7 @@
         }
         return encoding;
     }
-    
+
     private static void addRegistryFile(File file) {
         if (file.exists()) {
             if (!file.isDirectory()) {
@@ -769,24 +759,18 @@
         initialize(preProperties, postProperties, new String[] {""});
     }
 
-    public static synchronized void initialize(Properties preProperties,
-                                               Properties postProperties,
-                                               String[] argv) {
+    public static synchronized void initialize(Properties preProperties, Properties postProperties,
+            String[] argv) {
         initialize(preProperties, postProperties, argv, null);
     }
 
-    public static synchronized void initialize(Properties preProperties,
-                                               Properties postProperties,
-                                               String[] argv,
-                                               ClassLoader classLoader) {
+    public static synchronized void initialize(Properties preProperties, Properties postProperties,
+            String[] argv, ClassLoader classLoader) {
         initialize(preProperties, postProperties, argv, classLoader, new ClassicPyObjectAdapter());
     }
 
-    public static synchronized void initialize(Properties preProperties,
-                                               Properties postProperties,
-                                               String[] argv,
-                                               ClassLoader classLoader,
-                                               ExtensiblePyObjectAdapter adapter) {
+    public static synchronized void initialize(Properties preProperties, Properties postProperties,
+            String[] argv, ClassLoader classLoader, ExtensiblePyObjectAdapter adapter) {
         if (initialized) {
             return;
         }
@@ -807,51 +791,45 @@
             }
             ClassLoader sysStateLoader = PySystemState.class.getClassLoader();
             if (sysStateLoader != null) {
-                if (initialize(preProperties,
-                               postProperties,
-                               argv,
-                               classLoader,
-                               adapter,
-                               sysStateLoader)) {
+                if (initialize(preProperties, postProperties, argv, classLoader, adapter,
+                        sysStateLoader)) {
                     return;
                 }
             } else {
                 Py.writeDebug("initializer", "PySystemState.class class loader null, skipping");
             }
         } catch (UnsupportedCharsetException e) {
-            Py.writeWarning("initializer", "Unable to load the UTF-8 charset to read an initializer definition");
+            Py.writeWarning("initializer",
+                    "Unable to load the UTF-8 charset to read an initializer definition");
             e.printStackTrace(System.err);
         } catch (SecurityException e) {
             // Must be running in a security environment that doesn't allow access to the class
             // loader
         } catch (Exception e) {
             Py.writeWarning("initializer",
-                            "Unexpected exception thrown while trying to use initializer service");
+                    "Unexpected exception thrown while trying to use initializer service");
             e.printStackTrace(System.err);
         }
         doInitialize(preProperties, postProperties, argv, classLoader, adapter);
     }
 
     private static final String INITIALIZER_SERVICE =
-        "META-INF/services/org.python.core.JythonInitializer";
+            "META-INF/services/org.python.core.JythonInitializer";
 
     /**
-     * Attempts to read a SystemStateInitializer service from the given classloader, instantiate it,
-     * and initialize with it.
+     * Attempts to read a SystemStateInitializer service from the given class loader, instantiate
+     * it, and initialize with it.
      *
-     * @throws UnsupportedCharsetException
-     *             if unable to load UTF-8 to read a service definition
+     * @throws UnsupportedCharsetException if unable to load UTF-8 to read a service definition
      * @return true if a service is found and successfully initializes.
      */
-    private static boolean initialize(Properties pre,
-                                      Properties post,
-                                      String[] argv,
-                                      ClassLoader sysClassLoader,
-                                      ExtensiblePyObjectAdapter adapter,
-                                      ClassLoader initializerClassLoader) {
+    private static boolean initialize(Properties pre, Properties post, String[] argv,
+            ClassLoader sysClassLoader, ExtensiblePyObjectAdapter adapter,
+            ClassLoader initializerClassLoader) {
         InputStream in = initializerClassLoader.getResourceAsStream(INITIALIZER_SERVICE);
         if (in == null) {
-            Py.writeDebug("initializer", "'" + INITIALIZER_SERVICE + "' not found on " + initializerClassLoader);
+            Py.writeDebug("initializer", "'" + INITIALIZER_SERVICE + "' not found on "
+                    + initializerClassLoader);
             return false;
         }
         BufferedReader r = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8")));
@@ -873,11 +851,8 @@
             return false;
         }
         try {
-            ((JythonInitializer)initializer.newInstance()).initialize(pre,
-                                                                      post,
-                                                                      argv,
-                                                                      sysClassLoader,
-                                                                      adapter);
+            ((JythonInitializer)initializer.newInstance()).initialize(pre, post, argv,
+                    sysClassLoader, adapter);
         } catch (Exception e) {
             Py.writeWarning("initializer", "Failed initializing with class '" + className
                     + "', continuing");
@@ -891,12 +866,9 @@
         return initialized;
     }
 
-
     public static synchronized PySystemState doInitialize(Properties preProperties,
-                                                 Properties postProperties,
-                                                 String[] argv,
-                                                 ClassLoader classLoader,
-                                                 ExtensiblePyObjectAdapter adapter) {
+            Properties postProperties, String[] argv, ClassLoader classLoader,
+            ExtensiblePyObjectAdapter adapter) {
         if (initialized) {
             return Py.defaultSystemState;
         }
@@ -907,17 +879,25 @@
         if (jarFileName != null) {
             standalone = isStandalone(jarFileName);
         }
+
         // initialize the Jython registry
         initRegistry(preProperties, postProperties, standalone, jarFileName);
+
         // other initializations
         initBuiltins(registry);
         initStaticFields();
+
         // Initialize the path (and add system defaults)
         defaultPath = initPath(registry, standalone, jarFileName);
         defaultArgv = initArgv(argv);
         defaultExecutable = initExecutable(registry);
+
         // Set up the known Java packages
         initPackages(registry);
+
+        // Condition the console
+        initConsole(registry);
+
         // Finish up standard Python initialization...
         Py.defaultSystemState = new PySystemState();
         Py.setSystemState(Py.defaultSystemState);
@@ -925,10 +905,16 @@
             Py.defaultSystemState.setClassLoader(classLoader);
         }
         Py.initClassExceptions(getDefaultBuiltins());
+
         // defaultSystemState can't init its own encoding, see its constructor
         Py.defaultSystemState.initEncoding();
+
         // Make sure that Exception classes have been loaded
         new PySyntaxError("", 1, 1, "", "");
+
+        // Cause sys to export the console handler that was installed
+        Py.defaultSystemState.__setattr__("_jy_console", Py.java2py(Py.getConsole()));
+
         return Py.defaultSystemState;
     }
 
@@ -961,32 +947,33 @@
         Py.stdout = new StdoutWrapper();
 
         String s;
-        if(Version.PY_RELEASE_LEVEL == 0x0A)
+        if (Version.PY_RELEASE_LEVEL == 0x0A) {
             s = "alpha";
-        else if(Version.PY_RELEASE_LEVEL == 0x0B)
+        } else if (Version.PY_RELEASE_LEVEL == 0x0B) {
             s = "beta";
-        else if(Version.PY_RELEASE_LEVEL == 0x0C)
+        } else if (Version.PY_RELEASE_LEVEL == 0x0C) {
             s = "candidate";
-        else if(Version.PY_RELEASE_LEVEL == 0x0F)
+        } else if (Version.PY_RELEASE_LEVEL == 0x0F) {
             s = "final";
-        else if(Version.PY_RELEASE_LEVEL == 0xAA)
+        } else if (Version.PY_RELEASE_LEVEL == 0xAA) {
             s = "snapshot";
-        else
-            throw new RuntimeException("Illegal value for PY_RELEASE_LEVEL: " +
-                                       Version.PY_RELEASE_LEVEL);
-        version_info = new PyTuple(Py.newInteger(Version.PY_MAJOR_VERSION),
-                                   Py.newInteger(Version.PY_MINOR_VERSION),
-                                   Py.newInteger(Version.PY_MICRO_VERSION),
-                                   Py.newString(s),
-                                   Py.newInteger(Version.PY_RELEASE_SERIAL));
-        _mercurial = new PyTuple(Py.newString("Jython"), Py.newString(Version.getHGIdentifier()),
-                                 Py.newString(Version.getHGVersion()));
+        } else {
+            throw new RuntimeException("Illegal value for PY_RELEASE_LEVEL: "
+                    + Version.PY_RELEASE_LEVEL);
+        }
+        version_info =
+                new PyTuple(Py.newInteger(Version.PY_MAJOR_VERSION),
+                        Py.newInteger(Version.PY_MINOR_VERSION),
+                        Py.newInteger(Version.PY_MICRO_VERSION), Py.newString(s),
+                        Py.newInteger(Version.PY_RELEASE_SERIAL));
+        _mercurial =
+                new PyTuple(Py.newString("Jython"), Py.newString(Version.getHGIdentifier()),
+                        Py.newString(Version.getHGVersion()));
 
         float_info = FloatInfo.getInfo();
         long_info = LongInfo.getInfo();
     }
 
-
     public static boolean isPackageCacheEnabled() {
         return cachedir != null;
     }
@@ -1025,8 +1012,8 @@
     }
 
     /**
-     * Determine the default sys.executable value from the
-     * registry. Returns Py.None is no executable can be found.
+     * Determine the default sys.executable value from the registry. Returns Py.None is no
+     * executable can be found.
      *
      * @param props a Properties registry
      * @return a PyObject path string or Py.None
@@ -1049,6 +1036,86 @@
         return new PyString(executableFile.getPath());
     }
 
+    /**
+     * Wrap standard input with a customised console handler specified in the property
+     * <code>python.console</code> in the supplied property set, which in practice is the
+     * fully-initialised Jython {@link #registry}. The value of <code>python.console</code> is the
+     * name of a class that implements {@link org.python.core.Console}. An instance is constructed
+     * with the value of <code>python.console.encoding</code>, and the console
+     * <code>System.in</code> returns characters in that encoding. After the call, the console
+     * object may be accessed via {@link Py#getConsole()}.
+     *
+     * @param props containing (or not) <code>python.console</code>
+     */
+    private static void initConsole(Properties props) {
+        // At this stage python.console.encoding is always defined (but null=default)
+        String encoding = props.getProperty(PYTHON_CONSOLE_ENCODING);
+        // The console type is chosen by this registry entry:
+        String consoleName = props.getProperty("python.console", "").trim();
+        // And must be of type ...
+        final Class<Console> consoleType = Console.class;
+
+        if (consoleName.length() > 0 && Py.isInteractive()) {
+            try {
+                // Load the class specified as the console
+                Class<?> consoleClass = Class.forName(consoleName);
+
+                // Ensure it can be cast to the interface type of all consoles
+                if (! consoleType.isAssignableFrom(consoleClass)) {
+                    throw new ClassCastException();
+                }
+
+                // Construct an instance
+                Constructor<?> consoleConstructor = consoleClass.getConstructor(String.class);
+                Object consoleObject = consoleConstructor.newInstance(encoding);
+                Console console = consoleType.cast(consoleObject);
+
+                // Replace System.in with stream this console manufactures
+                Py.installConsole(console);
+                return;
+
+            } catch (NoClassDefFoundError e) {
+                writeConsoleWarning(consoleName, "not found");
+            } catch (ClassCastException e) {
+                writeConsoleWarning(consoleName, "does not implement " + consoleType);
+            } catch (NoSuchMethodException e) {
+                writeConsoleWarning(consoleName, "has no constructor from String");
+            } catch (InvocationTargetException e) {
+                writeConsoleWarning(consoleName, e.getCause().toString());
+            } catch (Exception e) {
+                writeConsoleWarning(consoleName, e.toString());
+            }
+        }
+
+        // No special console required, or requested installation failed somehow
+        try {
+            // Default is a plain console
+            Py.installConsole(new PlainConsole(encoding));
+            return;
+        } catch (Exception e) {
+            /*
+             * May end up here if prior console won't uninstall: but then at least we have a
+             * console. Or it may be an unsupported encoding, in which case Py.getConsole() will try
+             * "ascii"
+             */
+            writeConsoleWarning(consoleName, e.toString());
+        }
+    }
+
+    /**
+     * Convenience method wrapping {@link Py#writeWarning(String, String)} to issue a warning
+     * message something like:
+     * "console: Failed to load 'org.python.util.ReadlineConsole': <b>msg</b>.". It's only a warning
+     * because the interpreter will fall back to a plain console, but it is useful to know exactly
+     * why it didn't work.
+     *
+     * @param consoleName console class name we're trying to initialise
+     * @param msg specific cause of the failure
+     */
+    private static void writeConsoleWarning(String consoleName, String msg) {
+        Py.writeWarning("console", "Failed to install '" + consoleName + "': " + msg + ".");
+    }
+
     private static void addBuiltin(String name) {
         String classname;
         String modname;
@@ -1057,19 +1124,20 @@
         if (colon != -1) {
             // name:fqclassname
             modname = name.substring(0, colon).trim();
-            classname = name.substring(colon+1, name.length()).trim();
-            if (classname.equals("null"))
+            classname = name.substring(colon + 1, name.length()).trim();
+            if (classname.equals("null")) {
                 // name:null, i.e. remove it
                 classname = null;
-        }
-        else {
+            }
+        } else {
             modname = name.trim();
             classname = "org.python.modules." + modname;
         }
-        if (classname != null)
+        if (classname != null) {
             builtinNames.put(modname, classname);
-        else
+        } else {
             builtinNames.remove(modname);
+        }
     }
 
     private static void initBuiltins(Properties props) {
@@ -1080,17 +1148,19 @@
         builtinNames.put("sys", "");
 
         // add builtins specified in the Setup.java file
-        for (String builtinModule : Setup.builtinModules)
+        for (String builtinModule : Setup.builtinModules) {
             addBuiltin(builtinModule);
+        }
 
         // add builtins specified in the registry file
         String builtinprop = props.getProperty("python.modules.builtin", "");
         StringTokenizer tok = new StringTokenizer(builtinprop, ",");
-        while (tok.hasMoreTokens())
+        while (tok.hasMoreTokens()) {
             addBuiltin(tok.nextToken());
+        }
 
         int n = builtinNames.size();
-        PyObject [] built_mod = new PyObject[n];
+        PyObject[] built_mod = new PyObject[n];
         int i = 0;
         for (String key : builtinNames.keySet()) {
             built_mod[i++] = Py.newString(key);
@@ -1133,11 +1203,13 @@
                 JarEntry jarEntry = jarFile.getJarEntry("Lib/os.py");
                 standalone = jarEntry != null;
             } catch (IOException ioe) {
+                // Continue
             } finally {
                 if (jarFile != null) {
                     try {
                         jarFile.close();
                     } catch (IOException e) {
+                        // Continue
                     }
                 }
             }
@@ -1146,7 +1218,8 @@
     }
 
     /**
-     * @return the full name of the jar file containing this class, <code>null</code> if not available.
+     * @return the full name of the jar file containing this class, <code>null</code> if not
+     *         available.
      */
     private static String getJarFileName() {
         Class<PySystemState> thisClass = PySystemState.class;
@@ -1204,10 +1277,10 @@
     }
 
     private static void addPaths(PyList path, String pypath) {
-        StringTokenizer tok = new StringTokenizer(pypath,
-                                                  java.io.File.pathSeparator);
-        while  (tok.hasMoreTokens())
+        StringTokenizer tok = new StringTokenizer(pypath, java.io.File.pathSeparator);
+        while (tok.hasMoreTokens()) {
             path.append(new PyString(tok.nextToken().trim()));
+        }
     }
 
     public static PyJavaPackage add_package(String n) {
@@ -1219,31 +1292,25 @@
     }
 
     /**
-     * Add a classpath directory to the list of places that are searched
-     * for java packages.
+     * Add a classpath directory to the list of places that are searched for java packages.
      * <p>
-     * <b>Note</b>. Classes found in directory and subdirectory are not
-     * made available to jython by this call. It only makes the java
-     * package found in the directory available. This call is mostly
-     * usefull if jython is embedded in an application that deals with
-     * its own classloaders. A servlet container is a very good example.
-     * Calling add_classdir("<context>/WEB-INF/classes") makes the java
-     * packages in WEB-INF classes available to jython import. However the
-     * actual classloading is completely handled by the servlet container's
-     * context classloader.
+     * <b>Note</b>. Classes found in directory and sub-directory are not made available to jython by
+     * this call. It only makes the java package found in the directory available. This call is
+     * mostly useful if jython is embedded in an application that deals with its own class loaders.
+     * A servlet container is a very good example. Calling add_classdir("<context>/WEB-INF/classes")
+     * makes the java packages in WEB-INF classes available to jython import. However the actual
+     * classloading is completely handled by the servlet container's context classloader.
      */
     public static void add_classdir(String directoryPath) {
         packageManager.addDirectory(new File(directoryPath));
     }
 
     /**
-     * Add a .jar & .zip directory to the list of places that are searched
-     * for java .jar and .zip files. The .jar and .zip files found will not
-     * be cached.
+     * Add a .jar & .zip directory to the list of places that are searched for java .jar and .zip
+     * files. The .jar and .zip files found will not be cached.
      * <p>
-     * <b>Note</b>. Classes in .jar and .zip files found in the directory
-     * are not made available to jython by this call. See the note for
-     * add_classdir(dir) for more details.
+     * <b>Note</b>. Classes in .jar and .zip files found in the directory are not made available to
+     * jython by this call. See the note for add_classdir(dir) for more details.
      *
      * @param directoryPath The name of a directory.
      *
@@ -1254,16 +1321,14 @@
     }
 
     /**
-     * Add a .jar & .zip directory to the list of places that are searched
-     * for java .jar and .zip files.
+     * Add a .jar & .zip directory to the list of places that are searched for java .jar and .zip
+     * files.
      * <p>
-     * <b>Note</b>. Classes in .jar and .zip files found in the directory
-     * are not made available to jython by this call. See the note for
-     * add_classdir(dir) for more details.
+     * <b>Note</b>. Classes in .jar and .zip files found in the directory are not made available to
+     * jython by this call. See the note for add_classdir(dir) for more details.
      *
      * @param directoryPath The name of a directory.
-     * @param cache         Controls if the packages in the zip and jar
-     *                      file should be cached.
+     * @param cache Controls if the packages in the zip and jar file should be cached.
      *
      * @see #add_classdir
      */
@@ -1278,8 +1343,9 @@
         /* Print value except if None */
         /* After printing, also assign to '_' */
         /* Before, set '_' to None to avoid recursion */
-        if (o == Py.None)
-             return;
+        if (o == Py.None) {
+            return;
+        }
 
         PyObject currentBuiltins = Py.getSystemState().getBuiltins();
         currentBuiltins.__setitem__("_", Py.None);
@@ -1295,8 +1361,8 @@
      * Exit a Python program with the given status.
      *
      * @param status the value to exit with
-     * @exception Py.SystemExit always throws this exception.
-     * When caught at top level the program will exit.
+     * @exception Py.SystemExit always throws this exception. When caught at top level the program
+     *                will exit.
      */
     public static void exit(PyObject status) {
         throw new PyException(Py.SystemExit, status);
@@ -1311,13 +1377,12 @@
 
     public static PyTuple exc_info() {
         PyException exc = Py.getThreadState().exception;
-        if(exc == null)
+        if (exc == null) {
             return new PyTuple(Py.None, Py.None, Py.None);
+        }
         PyObject tb = exc.traceback;
         PyObject value = exc.value;
-        return new PyTuple(exc.type,
-                value == null ? Py.None : value,
-                tb == null ? Py.None : tb);
+        return new PyTuple(exc.type, value == null ? Py.None : value, tb == null ? Py.None : tb);
     }
 
     public static void exc_clear() {
@@ -1335,8 +1400,9 @@
             f = f.f_back;
             --depth;
         }
-        if (f == null)
-             throw Py.ValueError("call stack is not deep enough");
+        if (f == null) {
+            throw Py.ValueError("call stack is not deep enough");
+        }
         return f;
     }
 
@@ -1441,7 +1507,7 @@
      * {@link PySystemStateCloser#cleanup()} to close resources (such as still-open files). The
      * closing sequence is from last-created resource to first-created, so that dependencies between
      * them are respected. (There are some amongst layers in the _io module.)
-     * 
+     *
      * @param resourceClosers to be called in turn
      */
     private static void runClosers(Set<Callable<Void>> resourceClosers) {
@@ -1467,52 +1533,68 @@
 }
 
 
-class PySystemStateFunctions extends PyBuiltinFunctionSet
-{
+class PySystemStateFunctions extends PyBuiltinFunctionSet {
+
     PySystemStateFunctions(String name, int index, int minargs, int maxargs) {
         super(name, index, minargs, maxargs);
     }
 
+    @Override
     public PyObject __call__(PyObject arg) {
         switch (index) {
-        case 10:
-            PySystemState.displayhook(arg);
-            return Py.None;
-        default:
-            throw info.unexpectedCall(1, false);
+            case 10:
+                PySystemState.displayhook(arg);
+                return Py.None;
+            default:
+                throw info.unexpectedCall(1, false);
         }
     }
+
+    @Override
     public PyObject __call__(PyObject arg1, PyObject arg2, PyObject arg3) {
         switch (index) {
-        case 30:
-            PySystemState.excepthook(arg1, arg2, arg3);
-            return Py.None;
-        default:
-            throw info.unexpectedCall(3, false);
+            case 30:
+                PySystemState.excepthook(arg1, arg2, arg3);
+                return Py.None;
+            default:
+                throw info.unexpectedCall(3, false);
         }
     }
 }
 
+
 /**
- * Value of a class or instance variable when the corresponding
- * attribute is deleted.  Used only in PySystemState for now.
+ * Value of a class or instance variable when the corresponding attribute is deleted. Used only in
+ * PySystemState for now.
  */
 class PyAttributeDeleted extends PyObject {
+
     final static PyAttributeDeleted INSTANCE = new PyAttributeDeleted();
+
     private PyAttributeDeleted() {}
-    public String toString() { return ""; }
+
+    @Override
+    public String toString() {
+        return "";
+    }
+
+    @Override
     public Object __tojava__(Class c) {
-        if (c == PyObject.class)
+        if (c == PyObject.class) {
             return this;
+        }
         // we can't quite "delete" non-PyObject attributes; settle for
         // null or nothing
-        if (c.isPrimitive())
+        if (c.isPrimitive()) {
             return Py.NoConversion;
+        }
         return null;
     }
 }
 
+
 class Shadow {
+
     PyObject builtins;
     PyList warnoptions;
     PyObject platform;
@@ -1527,13 +1609,14 @@
 
 @ExposedType(name = "sys.float_info", isBaseType = false)
 class FloatInfo extends PyTuple {
+
     @ExposedGet
-    public PyObject max, max_exp, max_10_exp, min, min_exp, min_10_exp, dig,
-                    mant_dig, epsilon, radix, rounds;
+    public PyObject max, max_exp, max_10_exp, min, min_exp, min_10_exp, dig, mant_dig, epsilon,
+            radix, rounds;
 
     public static final PyType TYPE = PyType.fromClass(FloatInfo.class);
-    
-    private FloatInfo(PyObject ...vals) {
+
+    private FloatInfo(PyObject... vals) {
         super(TYPE, vals);
 
         max = vals[0];
@@ -1552,42 +1635,41 @@
     static public FloatInfo getInfo() {
         // max_10_exp, dig and epsilon taken from ssj library Num class
         // min_10_exp, mant_dig, radix and rounds by ɲeuroburɳ (bit.ly/Iwo2LT)
-        return new FloatInfo(
-            Py.newFloat(Double.MAX_VALUE),       // DBL_MAX
-            Py.newLong(Double.MAX_EXPONENT),     // DBL_MAX_EXP
-            Py.newLong(308),                     // DBL_MIN_10_EXP
-            Py.newFloat(Double.MIN_VALUE),       // DBL_MIN
-            Py.newLong(Double.MIN_EXPONENT),     // DBL_MIN_EXP
-            Py.newLong(-307),                    // DBL_MIN_10_EXP
-            Py.newLong(10),                      // DBL_DIG
-            Py.newLong(53),                      // DBL_MANT_DIG
-            Py.newFloat(2.2204460492503131e-16), // DBL_EPSILON
-            Py.newLong(2),                       // FLT_RADIX
-            Py.newLong(1)                        // FLT_ROUNDS
+        return new FloatInfo( //
+                Py.newFloat(Double.MAX_VALUE),       // DBL_MAX
+                Py.newLong(Double.MAX_EXPONENT),     // DBL_MAX_EXP
+                Py.newLong(308),                     // DBL_MIN_10_EXP
+                Py.newFloat(Double.MIN_VALUE),       // DBL_MIN
+                Py.newLong(Double.MIN_EXPONENT),     // DBL_MIN_EXP
+                Py.newLong(-307),                    // DBL_MIN_10_EXP
+                Py.newLong(10),                      // DBL_DIG
+                Py.newLong(53),                      // DBL_MANT_DIG
+                Py.newFloat(2.2204460492503131e-16), // DBL_EPSILON
+                Py.newLong(2),                       // FLT_RADIX
+                Py.newLong(1)                        // FLT_ROUNDS
         );
     }
 }
 
+
 @ExposedType(name = "sys.long_info", isBaseType = false)
 class LongInfo extends PyTuple {
+
     @ExposedGet
     public PyObject bits_per_digit, sizeof_digit;
 
     public static final PyType TYPE = PyType.fromClass(LongInfo.class);
-    
-    private LongInfo(PyObject ...vals) {
+
+    private LongInfo(PyObject... vals) {
         super(TYPE, vals);
 
         bits_per_digit = vals[0];
         sizeof_digit = vals[1];
     }
 
-    //XXX: I've cheated and just used the values that CPython gives me for my
-    //     local Ubuntu system. I'm not sure that they are correct.
+    // XXX: I've cheated and just used the values that CPython gives me for my
+    // local Ubuntu system. I'm not sure that they are correct.
     static public LongInfo getInfo() {
-        return new LongInfo(
-            Py.newLong(30),
-            Py.newLong(4)
-        );
+        return new LongInfo(Py.newLong(30), Py.newLong(4));
     }
 }
diff --git a/src/org/python/core/__builtin__.java b/src/org/python/core/__builtin__.java
--- a/src/org/python/core/__builtin__.java
+++ b/src/org/python/core/__builtin__.java
@@ -4,18 +4,19 @@
  */
 package org.python.core;
 
+import java.io.EOFException;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.HashMap;
+import java.nio.ByteBuffer;
 import java.util.Iterator;
 import java.util.Map;
 
 import org.python.antlr.base.mod;
 import org.python.core.util.RelativeFile;
-
+import org.python.core.util.StringUtil;
 import org.python.modules._functools._functools;
 
 class BuiltinFunctions extends PyBuiltinFunctionSet {
@@ -1010,6 +1011,14 @@
         }
     }
 
+    /**
+     * Companion to <code>raw_input</code> built-in function used when the interactive interpreter
+     * is directed to a file.
+     *
+     * @param prompt to issue at console before read
+     * @param file a file-like object to read from
+     * @return line of text from the file (encoded as bytes values compatible with PyString)
+     */
     public static String raw_input(PyObject prompt, PyObject file) {
         PyObject stdout = Py.getSystemState().stdout;
         if (stdout instanceof PyAttributeDeleted) {
@@ -1027,16 +1036,32 @@
         return data;
     }
 
+    /**
+     * Implementation of <code>raw_input(prompt)</code> built-in function using the console
+     * directly.
+     *
+     * @param prompt to issue at console before read
+     * @return line of text from console (encoded as bytes values compatible with PyString)
+     */
     public static String raw_input(PyObject prompt) {
-        PyObject stdin = Py.getSystemState().stdin;
-        if (stdin instanceof PyAttributeDeleted) {
-            throw Py.RuntimeError("[raw_]input: lost sys.stdin");
+        try {
+            Console console = Py.getConsole();
+            ByteBuffer buf = console.raw_input(prompt.toString());
+            return StringUtil.fromBytes(buf);
+        } catch (EOFException eof) {
+            throw Py.EOFError("raw_input()");
+        } catch (IOException ioe) {
+            throw Py.IOError(ioe);
         }
-        return raw_input(prompt, stdin);
     }
 
+    /**
+     * Implementation of <code>raw_input()</code> built-in function using the console directly.
+     *
+     * @return line of text from console (encoded as bytes values compatible with PyString)
+     */
     public static String raw_input() {
-        return raw_input(new PyString(""));
+        return raw_input(Py.EmptyString);
     }
 
     public static PyObject reduce(PyObject f, PyObject l, PyObject z) {
diff --git a/src/org/python/util/ConsoleStream.java b/src/org/python/util/ConsoleStream.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/util/ConsoleStream.java
@@ -0,0 +1,242 @@
+// Copyright (c) 2013 Jython Developers
+package org.python.util;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+/**
+ * This class is intended to replace <code>System.in</code> for use with console libraries that
+ * provide a line-oriented input mechanism. The console libraries provide a method to get the next
+ * line from the console as a String. Particular sub-classes should wrap this character-oriented
+ * method in a definition of {@link #getLine()}.
+ * <p>
+ * The libraries JLine and Java Readline have both been used to give Jython line-recall, editing and
+ * a line history preserved between sessions. Both deal with the console encoding internally, and
+ * interact with the user in terms of a buffer of characters. Our need in Jython is to access a
+ * byte-stream encoding the characters, with line-endings, since it is the text layer of the Python
+ * io stack, whether we are using the <code>io</code> module or <code>file</code> built-in, that
+ * should deal with encoding.
+ */
+public abstract class ConsoleStream extends FilterInputStream {
+
+    /**
+     * Enumeration used to specify whether an end-of-line should be added or replaced at the end of
+     * each line read. LEAVE means process the line exactly as the library returns it; ADD means
+     * always add an end-of-line; and REPLACE means strip any final '\n', '\r', or '\r\n' and add an
+     * end-of-line. The end-of-line to add is specified as a String in the constructor.
+     */
+    public enum EOLPolicy {
+        LEAVE, ADD, REPLACE
+    };
+
+    /** The {@link EOLPolicy} specified in the constructor. */
+    protected final EOLPolicy eolPolicy;
+    /** The end-of-line String specified in the constructor. */
+    protected final String eol;
+    /** The end-of-line String specified in the constructor. */
+    protected final Charset encoding;
+    /** Bytes decoded from the last line read. */
+    private ByteBuffer buf;
+    /** Empty buffer */
+    protected static final ByteBuffer EMPTY_BUF = ByteBuffer.allocate(0);
+    /** Platform-defined end-of-line for convenience */
+    protected static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    /**
+     * Create a wrapper configured with end-of-line handling that matches the specific console
+     * library being wrapped, and a character encoding matching the expectations of the client.
+     * Since this is an abstract class, this constructor will be called as the first action of the
+     * library-specific concrete class. The end-of-line policy can be chosen from <code>LEAVE</code>
+     * (do not modify the line), <code>ADD</code> (always append <code>eol</code>, and
+     * <code>REPLACE</code> (remove a trailing '\n', '\r', or '\r\n' provided by the library, then
+     * add <code>eol</code>).
+     *
+     * @param encoding to use to encode the buffered characters
+     * @param eolPolicy choice of how to treat an end-of-line marker
+     * @param eol the end-of-line to use when <code>eolPolicy</code> is not <code>LEAVE</code>
+     */
+    ConsoleStream(Charset encoding, EOLPolicy eolPolicy, String eol) {
+
+        // Wrap original System.in so <code>StreamIO.isatty()</code> will find it reflectively
+        super(System.in);
+
+        // But our real input comes from (re-)encoding the console line
+        this.encoding = encoding;
+        this.eolPolicy = eolPolicy;
+        this.eol = eol != null ? eol : LINE_SEPARATOR;
+
+        // The logic is simpler if we always supply a buffer
+        buf = EMPTY_BUF;
+    }
+
+    /**
+     * Get one line of input from the console. Override this method with the actions specific to the
+     * library in use.
+     *
+     * @return Line entered by user
+     * @throws IOException in case of an error
+     * @throws EOFException if the library recognises an end-of-file condition
+     */
+    protected abstract CharSequence getLine() throws IOException, EOFException;
+
+    /**
+     * Get a line of text from the console and re-encode it using the console encoding to bytes that
+     * will be returned from this InputStream in subsequent read operations.
+     *
+     * @throws IOException
+     * @throws EOFException
+     */
+    private void fillBuffer() throws IOException, EOFException {
+
+        // In case we exit on an exception ...
+        buf = EMPTY_BUF;
+
+        // Bring in another line
+        CharSequence line = getLine();
+        CharBuffer cb = CharBuffer.allocate(line.length() + eol.length());
+        cb.append(line);
+
+        // Apply the EOL policy
+        switch (eolPolicy) {
+
+            case LEAVE:
+                // Do nothing
+                break;
+
+            case ADD:
+                // Always add eol
+                cb.append(eol);
+                break;
+
+            case REPLACE:
+                // Strip '\n', '\r', or '\r\n' and add eol
+                int n = cb.position() - 1;
+                if (n >= 0 && cb.charAt(n) == '\n') {
+                    n -= 1;
+                }
+                if (n >= 0 && cb.charAt(n) == '\r') {
+                    n -= 1;
+                }
+                cb.position(n + 1);
+                cb.append(eol);
+                break;
+        }
+
+        // Prepare to read
+        cb.flip();
+
+        // Make this line into a new buffer of encoded bytes
+        if (cb.hasRemaining()) {
+            buf = encoding.encode(cb); // includes a flip()
+        }
+    }
+
+    /**
+     * Reads the next byte of data from the buffered input line.
+     *
+     * The byte is returned as an int in the range 0 to 255. If no byte is available because the end
+     * of the stream has been recognised, the value -1 is returned. This method blocks until input
+     * data is available, the end of the stream is detected, or an exception is thrown. Normally, an
+     * empty line results in an encoded end-of-line being returned.
+     */
+    @Override
+    public int read() throws IOException {
+
+        try {
+            // Do we need to refill?
+            while (!buf.hasRemaining()) {
+                fillBuffer();
+            }
+            return buf.get() & 0xff;
+        } catch (EOFException e) {
+            // End of file condition recognised (e.g. ctrl-D, ctrl-Z)
+            return -1;
+        }
+    }
+
+    /**
+     * Reads up to len bytes of data from this input stream into an array of bytes. If len is not
+     * zero, the method blocks until some input is available; otherwise, no bytes are read and 0 is
+     * returned. This implementation calls {@link #fillBuffer()} at most once to get a line of
+     * characters from the console using {@link #getLine()}, and encodes them as bytes to be read
+     * back from the stream.
+     */
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException, EOFException {
+
+        if (off < 0 || len < 0 || len > b.length - off) {
+            throw new IndexOutOfBoundsException();
+
+        } else {
+            try {
+                if (len > 0) {
+                    // Do we need to refill? (Not if zero bytes demanded.)
+                    int n = buf.remaining();
+                    if (n <= 0) {
+                        fillBuffer();
+                        n = buf.remaining();
+                    }
+
+                    // Deliver all there is, or all that's wanted, whichever is less.
+                    len = n < len ? n : len;
+                    buf.get(b, off, len);
+                }
+                return len;
+
+            } catch (EOFException e) {
+                // Thrown from getLine
+                return -1;
+            }
+        }
+    }
+
+    /**
+     * Skip forward n bytes within the current encoded line. A call to <code>skip</code> will not
+     * result in reading a new line with {@link #getLine()}.
+     */
+    @Override
+    public long skip(long n) throws IOException {
+        long r = buf.remaining();
+        if (n > r) {
+            n = r;
+        }
+        buf.position(buf.position() + (int)n);
+        return n;
+    }
+
+    /** The number of bytes left unread in the current encoded line. */
+    @Override
+    public int available() throws IOException {
+        return buf.remaining();
+    }
+
+    /**
+     * If possible, restore the standard <code>System.in</code>. Override this if necessary to
+     * perform close down actions on the console library, then call <code>super.close()</code>.
+     */
+    @Override
+    public void close() throws IOException {
+        // Restore original System.in
+        System.setIn(in);
+    }
+
+    /** Mark is not supported. */
+    @Override
+    public synchronized void mark(int readlimit) {}
+
+    /** Mark is not supported. */
+    @Override
+    public synchronized void reset() throws IOException {}
+
+    /** Mark is not supported. */
+    @Override
+    public boolean markSupported() {
+        return false;
+    }
+
+}
diff --git a/src/org/python/util/InteractiveConsole.java b/src/org/python/util/InteractiveConsole.java
--- a/src/org/python/util/InteractiveConsole.java
+++ b/src/org/python/util/InteractiveConsole.java
@@ -28,20 +28,21 @@
     }
 
     /**
-     * @param replaceRawInput -
-     *            if true, we hook this Class's raw_input into the builtins
-     *            table so that clients like cmd.Cmd use it.
+     * @param replaceRawInput if true, we hook this Class's raw_input into the built-ins table so
+     *            that clients like cmd.Cmd use it.
      */
     public InteractiveConsole(PyObject locals, String filename, boolean replaceRawInput) {
         super(locals);
         this.filename = filename;
-        if(replaceRawInput) {
+        if (replaceRawInput) {
             PyObject newRawInput = new PyBuiltinFunctionSet("raw_input", 0, 0, 1) {
 
+                @Override
                 public PyObject __call__() {
                     return __call__(Py.EmptyString);
                 }
 
+                @Override
                 public PyObject __call__(PyObject prompt) {
                     return Py.newString(raw_input(prompt));
                 }
@@ -52,9 +53,9 @@
 
     /**
      * Closely emulate the interactive Python console.
-     * 
-     * The optional banner argument specifies the banner to print before the
-     * first interaction; by default it prints "Jython <version> on <platform>".
+     *
+     * The optional banner argument specifies the banner to print before the first interaction; by
+     * default it prints "Jython <version> on <platform>".
      */
     public void interact() {
         interact(getDefaultBanner(), null);
@@ -65,7 +66,7 @@
     }
 
     public void interact(String banner, PyObject file) {
-        if(banner != null) {
+        if (banner != null) {
             write(banner);
             write("\n");
         }
@@ -73,17 +74,19 @@
         exec("2");
         // System.err.println("interp2");
         boolean more = false;
-        while(true) {
+        while (true) {
             PyObject prompt = more ? systemState.ps2 : systemState.ps1;
             String line;
             try {
-        	if (file == null)
-        	    line = raw_input(prompt);
-        	else
-        	    line = raw_input(prompt, file);
-            } catch(PyException exc) {
-                if(!exc.match(Py.EOFError))
+                if (file == null) {
+                    line = raw_input(prompt);
+                } else {
+                    line = raw_input(prompt, file);
+                }
+            } catch (PyException exc) {
+                if (!exc.match(Py.EOFError)) {
                     throw exc;
+                }
                 write("\n");
                 break;
             }
@@ -93,43 +96,40 @@
 
     /**
      * Push a line to the interpreter.
-     * 
-     * The line should not have a trailing newline; it may have internal
-     * newlines. The line is appended to a buffer and the interpreter's
-     * runsource() method is called with the concatenated contents of the buffer
-     * as source. If this indicates that the command was executed or invalid,
-     * the buffer is reset; otherwise, the command is incomplete, and the buffer
-     * is left as it was after the line was appended. The return value is 1 if
-     * more input is required, 0 if the line was dealt with in some way (this is
-     * the same as runsource()).
+     *
+     * The line should not have a trailing newline; it may have internal newlines. The line is
+     * appended to a buffer and the interpreter's runsource() method is called with the concatenated
+     * contents of the buffer as source. If this indicates that the command was executed or invalid,
+     * the buffer is reset; otherwise, the command is incomplete, and the buffer is left as it was
+     * after the line was appended. The return value is 1 if more input is required, 0 if the line
+     * was dealt with in some way (this is the same as runsource()).
      */
     public boolean push(String line) {
-        if(buffer.length() > 0)
+        if (buffer.length() > 0) {
             buffer.append("\n");
+        }
         buffer.append(line);
         boolean more = runsource(buffer.toString(), filename);
-        if(!more)
+        if (!more) {
             resetbuffer();
+        }
         return more;
     }
 
     /**
-     * Write a prompt and read a line from standard input.
-     * 
-     * The returned line does not include the trailing newline. When the user
-     * enters the EOF key sequence, EOFError is raised.
-     * 
-     * The base implementation uses the built-in function raw_input(); a
-     * subclass may replace this with a different implementation.
+     * Write a prompt and read a line from standard input. The returned line does not include the
+     * trailing newline. When the user enters the EOF key sequence, EOFError is raised. The base
+     * implementation uses the built-in function raw_input(); a subclass may replace this with a
+     * different implementation.
      */
     public String raw_input(PyObject prompt) {
         return __builtin__.raw_input(prompt);
     }
-    
+
     /**
      * Write a prompt and read a line from a file.
      */
     public String raw_input(PyObject prompt, PyObject file) {
-	return __builtin__.raw_input(prompt, file);
+        return __builtin__.raw_input(prompt, file);
     }
 }
diff --git a/src/org/python/util/JLineConsole.java b/src/org/python/util/JLineConsole.java
--- a/src/org/python/util/JLineConsole.java
+++ b/src/org/python/util/JLineConsole.java
@@ -1,40 +1,41 @@
-/* Copyright (c) Jython Developers */
+// Copyright (c) 2013 Jython Developers
 package org.python.util;
 
+import java.io.EOFException;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
+import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
 import java.io.Writer;
 import java.util.Arrays;
 import java.util.List;
 
-import jnr.constants.platform.Errno;
-
 import jline.ConsoleReader;
 import jline.Terminal;
 import jline.WindowsTerminal;
+import jnr.constants.platform.Errno;
 
-import org.python.core.Py;
+import org.python.core.PlainConsole;
 import org.python.core.PyObject;
 
 /**
- * This class uses <a href="http://jline.sourceforge.net/">JLine</a> to provide
- * readline like functionality to its console without requiring native readline
- * support.
+ * This class uses <a href="http://jline.sourceforge.net/">JLine</a> to provide readline like
+ * functionality to its console without requiring native readline support.
  */
-public class JLineConsole extends InteractiveConsole {
+public class JLineConsole extends PlainConsole {
 
     /** Main interface to JLine. */
-    protected ConsoleReader reader;
+    public ConsoleReader reader;
 
-    /** Set by readline.set_startup_hook */
+    /** Callable object set by <code>readline.set_startup_hook</code>. */
     protected PyObject startup_hook;
 
+    /** <b>Not</b> currently set by <code>readline.set_pre_input_hook</code>. Why not? */
     protected PyObject pre_input_hook;
 
     /** Whether reader is a WindowsTerminal. */
@@ -44,62 +45,203 @@
     protected static final String CTRL_Z = "\u001a";
 
     /**
-     * Errno strerrors possibly caused by a SIGSTP (ctrl-z). They may propagate up to
-     * IOException messages.
+     * Errno strerrors possibly caused by a SIGSTP (ctrl-z). They may propagate up to IOException
+     * messages.
      */
-    private static final List<String> SUSPENDED_STRERRORS =
-            Arrays.asList(Errno.EINTR.description(), Errno.EIO.description());
+    private static final List<String> SUSPENDED_STRERRORS = Arrays.asList(
+            Errno.EINTR.description(), Errno.EIO.description());
 
-    public JLineConsole() {
-        this(null);
+    /**
+     * Construct an instance of the console class specifying the character encoding. This encoding
+     * must be one supported by the JVM.
+     * <p>
+     * Most of the initialisation is deferred to the {@link #install()} method so that any prior
+     * console can uninstall itself before we change system console settings and
+     * <code>System.in</code>.
+     * 
+     * @param encoding name of a supported encoding or <code>null</code> for
+     *            <code>Charset.defaultCharset()</code>
+     */
+    public JLineConsole(String encoding) {
+        /*
+         * Super-class needs the encoding in order to re-encode the characters that
+         * jline.ConsoleReader.readLine() has decoded.
+         */
+        super(encoding);
+        /*
+         * Communicate the specified encoding to JLine. jline.ConsoleReader.readLine() edits a line
+         * of characters, decoded from stdin.
+         */
+        System.setProperty("jline.WindowsTerminal.input.encoding", this.encoding);
+        System.setProperty("input.encoding", this.encoding);
+        // ... not "jline.UnixTerminal.input.encoding" as you might think, not even in JLine2
     }
 
-    public JLineConsole(PyObject locals) {
-        this(locals, CONSOLE_FILENAME);
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This implementation overrides that by setting <code>System.in</code> to a
+     * <code>FilterInputStream</code> object that wraps JLine.
+     */
+    @Override
+    public void install() {
+        Terminal.setupTerminal();
+
+        String userHomeSpec = System.getProperty("user.home", ".");
+
+        // Configure a ConsoleReader (the object that does most of the line editing).
         try {
-            File historyFile = new File(System.getProperty("user.home"), ".jline-jython.history");
+            /*
+             * Wrap System.out in the specified encoding. jline.ConsoleReader.readLine() echoes the
+             * line through this Writer.
+             */
+            Writer out = new PrintWriter(new OutputStreamWriter(System.out, encoding));
+
+            // Get the key bindings (built in ones treat TAB Pythonically).
+            InputStream bindings = getBindings(userHomeSpec, getClass().getClassLoader());
+
+            // Create the reader as unbuffered as possible
+            InputStream in = new FileInputStream(FileDescriptor.in);
+            reader = new ConsoleReader(in, out, bindings);
+
+            // We find the bell too noisy
+            reader.setBellEnabled(false);
+
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        // Access and load (if possible) the line history.
+        try {
+            File historyFile = new File(userHomeSpec, ".jline-jython.history");
             reader.getHistory().setHistoryFile(historyFile);
         } catch (IOException e) {
             // oh well, no history from file
         }
+
+        // Check for OS type
+        windows = reader.getTerminal() instanceof WindowsTerminal;
+
+        // Replace System.in
+        FilterInputStream wrapper = new Stream();
+        System.setIn(wrapper);
     }
 
-    public JLineConsole(PyObject locals, String filename) {
-        super(locals, filename, true);
+    // Inherited raw_input() is adequate: calls input()
 
-        // Disable JLine's unicode handling so it yields raw bytes
-        System.setProperty("jline.UnixTerminal.input.encoding", "ISO-8859-1");
-        System.setProperty("jline.WindowsTerminal.input.encoding", "ISO-8859-1");
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This console implements <code>input</code> using JLine to handle the prompt and data entry,
+     * so that the cursor may be correctly handled in relation to the prompt string.
+     */
+    @Override
+    public CharSequence input(CharSequence prompt) throws IOException, EOFException {
+        // Get the line from the console via the library
+        String line = readerReadLine(prompt.toString());
+        if (line == null) {
+            throw new EOFException();
+        } else {
+            return line;
+        }
+    }
 
-        Terminal.setupTerminal();
-        try {
-            InputStream input = new FileInputStream(FileDescriptor.in);
-            // Raw bytes in, so raw bytes out
-            Writer output = new OutputStreamWriter(new FileOutputStream(FileDescriptor.out),
-                                                   "ISO-8859-1");
-            reader = new ConsoleReader(input, output, getBindings());
-            reader.setBellEnabled(false);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
+    /**
+     * Class to wrap the line-oriented interface to JLine with an InputStream that can replace
+     * System.in.
+     */
+    private class Stream extends ConsoleStream {
+
+        /** Create a System.in replacement with JLine that adds system-specific line endings */
+        Stream() {
+            super(encodingCharset, EOLPolicy.ADD, LINE_SEPARATOR);
         }
 
-        windows = reader.getTerminal() instanceof WindowsTerminal;
+        @Override
+        protected CharSequence getLine() throws IOException, EOFException {
+
+            // Get a line and hope to be done. Suppress any remembered prompt.
+            String line = readerReadLine("");
+
+            if (!isEOF(line)) {
+                return line;
+            } else {
+                // null or ctrl-z on Windows indicates EOF
+                throw new EOFException();
+            }
+        }
+    }
+
+    /**
+     * Wrapper on reader.readLine(prompt) that deals with retries (on Unix) when the user enters
+     * cvtrl-Z to background Jython, the brings it back to the foreground. The inherited
+     * implementation says this is necessary and effective on BSD Unix.
+     * 
+     * @param prompt to display
+     * @return line of text read in
+     * @throws IOException if an error occurs (other than an end of suspension)
+     * @throws EOFException if an EOF is detected
+     */
+    private String readerReadLine(String prompt) throws IOException, EOFException {
+
+        // We must be prepared to try repeatedly since the read may be interrupted.
+
+        while (true) {
+
+            try {
+                // If there's a hook, call it
+                if (startup_hook != null) {
+                    startup_hook.__call__();
+                }
+                // Get a line and hope to be done.
+                String line = reader.readLine(prompt);
+                return line;
+
+            } catch (IOException ioe) {
+                // Something went wrong, or we were interrupted (seems only BSD throws this)
+                if (!fromSuspend(ioe)) {
+                    // The interruption is not the result of (the end of) a ctrl-Z suspension
+                    throw ioe;
+
+                } else {
+                    // The interruption seems to be (return from) a ctrl-Z suspension:
+                    try {
+                        // Must reset JLine and continue (not repeating the prompt)
+                        reader.getTerminal().initializeTerminal();
+                        prompt = "";
+                    } catch (Exception e) {
+                        // Do our best to say what went wrong
+                        throw new IOException("Failed to re-initialize JLine: " + e.getMessage());
+                    }
+                }
+            }
+        }
+
     }
 
     /**
      * Return the JLine bindings file.
-     *
-     * This handles loading the user's custom keybindings (normally JLine does) so it can
-     * fallback to Jython's (which disable tab completition) when the user's are not
-     * available.
-     *
+     * 
+     * This handles loading the user's custom key bindings (normally JLine does) so it can fall back
+     * to Jython's (which disable tab completion) when the user's are not available.
+     * 
      * @return an InputStream of the JLine bindings file.
      */
-    protected InputStream getBindings() {
-        String userBindings = new File(System.getProperty("user.home"),
-                                       ".jlinebindings.properties").getAbsolutePath();
-        File bindingsFile = new File(System.getProperty("jline.keybindings", userBindings));
+    protected static InputStream getBindings(String userHomeSpec, ClassLoader loader) {
 
+        // The key bindings file may be specified explicitly
+        String bindingsFileSpec = System.getProperty("jline.keybindings");
+        File bindingsFile;
+
+        if (bindingsFileSpec != null) {
+            // Bindings file explicitly specified
+            bindingsFile = new File(bindingsFileSpec);
+        } else {
+            // Otherwise try ~/.jlinebindings.properties
+            bindingsFile = new File(userHomeSpec, ".jlinebindings.properties");
+        }
+
+        // See if that file really exists (and can be read)
         try {
             if (bindingsFile.isFile()) {
                 try {
@@ -112,52 +254,14 @@
         } catch (SecurityException se) {
             // continue
         }
-        return getClass().getResourceAsStream("jline-keybindings.properties");
-    }
 
-    @Override
-    public String raw_input(PyObject prompt) {
-        String line = null;
-        String promptString = prompt.toString();
-
-        while (true) {
-            try {
-                if (startup_hook != null) {
-                    try {
-                        startup_hook.__call__();
-                    } catch (Exception ex) {
-                        System.err.println(ex);
-                    }
-                }
-                line = reader.readLine(promptString);
-                break;
-            } catch (IOException ioe) {
-                if (!fromSuspend(ioe)) {
-                    throw Py.IOError(ioe);
-                }
-
-                // Hopefully an IOException caused by ctrl-z (seems only BSD throws this).
-                // Must reset jline to continue
-                try {
-                    reader.getTerminal().initializeTerminal();
-                } catch (Exception e) {
-                    throw Py.IOError(e.getMessage());
-                }
-                // Don't redisplay the prompt
-                promptString = "";
-            }
-        }
-
-        if (isEOF(line)) {
-            throw Py.EOFError("");
-        }
-
-        return line;
+        // User/specific key bindings could not be read: use the ones from the class path or jar.
+        return loader.getResourceAsStream("org/python/util/jline-keybindings.properties");
     }
 
     /**
-     * Determine if the IOException was likely caused by a SIGSTP (ctrl-z). Seems only
-     * applicable to BSD platforms.
+     * Determine if the IOException was likely caused by a SIGSTP (ctrl-z). Seems only applicable to
+     * BSD platforms.
      */
     private boolean fromSuspend(IOException ioe) {
         return !windows && SUSPENDED_STRERRORS.contains(ioe.getMessage());
@@ -177,7 +281,6 @@
         return reader;
     }
 
-
     /**
      * @return the startup hook (called prior to each readline)
      */
diff --git a/src/org/python/util/ReadlineConsole.java b/src/org/python/util/ReadlineConsole.java
--- a/src/org/python/util/ReadlineConsole.java
+++ b/src/org/python/util/ReadlineConsole.java
@@ -1,72 +1,208 @@
-// Copyright (c) Corporation for National Research Initiatives
+// Copyright (c) 2013 Jython Developers
 package org.python.util;
 
 import java.io.EOFException;
+import java.io.FilterInputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
 
 import org.gnu.readline.Readline;
 import org.gnu.readline.ReadlineLibrary;
-
-import org.python.core.Py;
-import org.python.core.PyException;
-import org.python.core.PyObject;
-import org.python.core.PySystemState;
+import org.python.core.PlainConsole;
 
 /**
- * Uses: <a href="http://java-readline.sourceforge.net/">Java Readline</a> <p/>
- * 
- * Based on CPython-1.5.2's code module
- * 
+ * Uses: <a href="http://java-readline.sourceforge.net/">Java Readline</a> to provide readline like
+ * functionality to its console through native readline support (either GNU Readline or Editline).
  */
-public class ReadlineConsole extends InteractiveConsole {
+public class ReadlineConsole extends PlainConsole {
 
-    public String filename;
+    /**
+     * Construct an instance of the console class specifying the character encoding. This encoding
+     * must be one supported by the JVM. The particular backing library loaded will be as specified
+     * by registry item <code>python.console.readlinelib</code>, or "Editline" by default.
+     * <p>
+     * Most of the initialisation is deferred to the {@link #install()} method so that any prior
+     * console can uninstall itself before we change system console settings and
+     * <code>System.in</code>.
+     *
+     * @param encoding name of a supported encoding or <code>null</code> for
+     *            <code>Charset.defaultCharset()</code>
+     */
+    public ReadlineConsole(String encoding) {
+        super(encoding);
+        /*
+         * Load the chosen native library. If it's not there, raise UnsatisfiedLinkError. We cannot
+         * fall back to Readline's Java mode since it reads from System.in, which would be pointless
+         * ... and fatal once we have replaced System.in with a wrapper on Readline.
+         */
+        String backingLib = System.getProperty("python.console.readlinelib", "Editline");
+        Readline.load(ReadlineLibrary.byName(backingLib));
 
-    public ReadlineConsole() {
-        this(null, CONSOLE_FILENAME);
+        /*
+         * The following is necessary to compensate for (a possible thinking error in) Readline's
+         * handling of the bytes returned from the library, and of the prompt.
+         */
+        String name = encodingCharset.name();
+        if (name.equals("ISO-8859-1") || name.equals("US-ASCII")) {
+            // Indicate that Readline's Latin fixation will work for this encoding
+            latin1 = null;
+        } else {
+            // We'll need the bytes-to-pointcode mapping
+            latin1 = Charset.forName("ISO-8859-1");
+        }
     }
 
-    public ReadlineConsole(PyObject locals) {
-        this(locals, CONSOLE_FILENAME);
-    }
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This implementation overrides that by setting <code>System.in</code> to a
+     * <code>FilterInputStream</code> object that wraps the configured console library.
+     */
+    @Override
+    public void install() {
 
-    public ReadlineConsole(PyObject locals, String filename) {
-        super(locals, filename, true);
-        String backingLib = PySystemState.registry.getProperty("python.console.readlinelib",
-                                                               "Editline");
-        try {
-            Readline.load(ReadlineLibrary.byName(backingLib));
-        } catch(RuntimeException e) {
-            // Silently ignore errors during load of the native library.
-            // Will use a pure java fallback.
-        }
+        // Complete the initialisation
         Readline.initReadline("jython");
 
         try {
             // Force rebind of tab to insert a tab instead of complete
             Readline.parseAndBind("tab: tab-insert");
+        } catch (UnsupportedOperationException uoe) {
+            // parseAndBind not supported by this readline
         }
-        catch (UnsupportedOperationException uoe) {
-            // parseAndBind not supported by this readline
+
+        // Replace System.in
+        FilterInputStream wrapper = new Stream();
+        System.setIn(wrapper);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This console implements <code>input</code> using the configured library to handle the prompt
+     * and data entry, so that the cursor may be correctly handled in relation to the prompt string.
+     */
+    @Override
+    public ByteBuffer raw_input(CharSequence prompt) throws IOException {
+        // If Readline.readline really returned the line as typed, we could simply use:
+        // return line==null ? "" : line;
+        // Compensate for Readline.readline prompt handling
+        prompt = preEncode(prompt);
+        // Get the line from the console via the library
+        String line = Readline.readline(prompt.toString());
+        return postDecodeToBuffer(line);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This console implements <code>input</code> using the configured library to handle the prompt
+     * and data entry, so that the cursor may be correctly handled in relation to the prompt string.
+     */
+    @Override
+    public CharSequence input(CharSequence prompt) throws IOException, EOFException {
+        // Compensate for Readline.readline prompt handling
+        prompt = preEncode(prompt);
+        // Get the line from the console via the library
+        String line = Readline.readline(prompt.toString());
+        // If Readline.readline really returned the line as typed, next would have been:
+        // return line==null ? "" : line;
+        return postDecode(line);
+    }
+
+    /**
+     * Class to wrap the line-oriented interface to Readline with an InputStream that can replace
+     * System.in.
+     */
+    protected class Stream extends ConsoleStream {
+
+        /** Create a System.in replacement with Readline that adds Unix-like line endings */
+        Stream() {
+            super(encodingCharset, EOLPolicy.ADD, LINE_SEPARATOR);
+        }
+
+        @Override
+        protected CharSequence getLine() throws IOException, EOFException {
+            // The Py3k input method does exactly what we want
+            return input("");
         }
     }
 
     /**
-     * Write a prompt and read a line.
-     * 
-     * The returned line does not include the trailing newline. When the user
-     * enters the EOF key sequence, EOFError is raised.
-     * 
-     * This subclass implements the functionality using JavaReadline.
+     * Encode a prompt to bytes in the console encoding and represent these bytes as the point codes
+     * of a Java String. The actual GNU readline function expects a prompt string that is C char
+     * array in the console encoding, but the wrapper <code>Readline.readline</code> acts as if this
+     * encoding is always Latin-1. This transformation compensates by encoding correctly then
+     * representing those bytes as point codes.
+     *
+     * @param prompt to display via <code>Readline.readline</code>
+     * @return encoded form of prompt
      */
-    public String raw_input(PyObject prompt) {
-        try {
-            String line = Readline.readline(prompt == null ? "" : prompt.toString());
-            return (line == null ? "" : line);
-        } catch(EOFException eofe) {
-            throw new PyException(Py.EOFError);
-        } catch(IOException ioe) {
-            throw new PyException(Py.IOError);
+    private CharSequence preEncode(CharSequence prompt) {
+        if (prompt == null || prompt.length() == 0) {
+            return "";
+        } else if (latin1 == null) {
+            // Encoding is such that readline does the right thing
+            return prompt;
+        } else {
+            // Compensate for readline prompt handling
+            CharBuffer cb = CharBuffer.wrap(prompt);
+            ByteBuffer bb = encodingCharset.encode(cb);
+            return latin1.decode(bb).toString();
         }
     }
+
+    /**
+     * Decode the bytes argument (a return from code>Readline.readline</code>) to the String
+     * actually entered at the console. The actual GNU readline function returns a C char array in
+     * the console encoding, but the wrapper <code>Readline.readline</code> acts as if this encoding
+     * is always Latin-1, and on this basis it gives us a Java String whose point codes are the
+     * encoded bytes. This method gets the bytes back, then decodes them correctly to a String.
+     *
+     * @param bytes encoded line (or <code>null</code> for an empty line)
+     * @return bytes recovered from the argument
+     */
+    private CharSequence postDecode(String line) {
+        if (line == null) {
+            // Library returns null for an empty line
+            return "";
+        } else if (latin1 == null) {
+            // Readline's assumed Latin-1 encoding will have produced the correct result
+            return line;
+        } else {
+            // We have to transcode the line
+            CharBuffer cb = CharBuffer.wrap(line);
+            ByteBuffer bb = latin1.encode(cb);
+            return encodingCharset.decode(bb).toString();
+        }
+    }
+
+    /**
+     * Decode the line (a return from code>Readline.readline</code>) to bytes in the console
+     * encoding. The actual GNU readline function returns a C char array in the console encoding,
+     * but the wrapper <code>Readline.readline</code> acts as if this encoding is always Latin-1,
+     * and on this basis it gives us a Java String whose point codes are the encoded bytes. This
+     * method gets the bytes back.
+     *
+     * @param bytes encoded line (or <code>null</code> for an empty line)
+     * @return bytes recovered from the argument
+     */
+    private ByteBuffer postDecodeToBuffer(String line) {
+        if (line == null) {
+            // Library returns null for an empty line
+            return ConsoleStream.EMPTY_BUF;
+        } else if (latin1 == null) {
+            // Readline's assumed Latin-1 encoding will have produced the correct result
+            return encodingCharset.encode(line);
+        } else {
+            // We have to transcode the line
+            CharBuffer cb = CharBuffer.wrap(line);
+            return latin1.encode(cb);
+        }
+    }
+
+    private final Charset latin1;
+
 }
diff --git a/src/org/python/util/jython.java b/src/org/python/util/jython.java
--- a/src/org/python/util/jython.java
+++ b/src/org/python/util/jython.java
@@ -2,6 +2,7 @@
 package org.python.util;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -14,6 +15,9 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
+import jnr.posix.POSIX;
+import jnr.posix.POSIXFactory;
+
 import org.python.Version;
 import org.python.core.CodeFlag;
 import org.python.core.CompileMode;
@@ -33,67 +37,68 @@
 import org.python.modules.thread.thread;
 
 public class jython {
+
     private static final String COPYRIGHT =
-        "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.";
+            "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.";
 
     static final String usageHeader =
-        "usage: jython [option] ... [-c cmd | -m mod | file | -] [arg] ...\n";
+            "usage: jython [option] ... [-c cmd | -m mod | file | -] [arg] ...\n";
 
-    private static final String usage = usageHeader +
-        "Options and arguments:\n" + //(and corresponding environment variables):\n" +
-        "-B       : don't write .py[co] files on import\n" + // "; also PYTHONDONTWRITEBYTECODE=x\n" +
-        "-c cmd   : program passed in as string (terminates option list)\n" +
-        //"-d       : debug output from parser (also PYTHONDEBUG=x)\n" +
-        "-Dprop=v : Set the property `prop' to value `v'\n"+
-        //"-E       : ignore environment variables (such as PYTHONPATH)\n" +
-        "-C codec : Use a different codec when reading from the console.\n"+
-        "-h       : print this help message and exit (also --help)\n" +
-        "-i       : inspect interactively after running script\n" + //, (also PYTHONINSPECT=x)\n" +
-        "           and force prompts, even if stdin does not appear to be a terminal\n" +
-        "-jar jar : program read from __run__.py in jar file\n"+
-        "-m mod   : run library module as a script (terminates option list)\n" +
-        //"-O       : optimize generated bytecode (a tad; also PYTHONOPTIMIZE=x)\n" +
-        //"-OO      : remove doc-strings in addition to the -O optimizations\n" +
-        "-Q arg   : division options: -Qold (default), -Qwarn, -Qwarnall, -Qnew\n" +
-        "-s       : don't add user site directory to sys.path;\n" + // also PYTHONNOUSERSITE\n" +
-        "-S       : don't imply 'import site' on initialization\n" +
-        //"-t       : issue warnings about inconsistent tab usage (-tt: issue errors)\n" +
-        "-u       : unbuffered binary stdout and stderr\n" + // (also PYTHONUNBUFFERED=x)\n" +
-        //"           see man page for details on internal buffering relating to '-u'\n" +
-        "-v       : verbose (trace import statements)\n" + // (also PYTHONVERBOSE=x)\n" +
-        "           can be supplied multiple times to increase verbosity\n" +
-        "-V       : print the Python version number and exit (also --version)\n" +
-        "-W arg   : warning control (arg is action:message:category:module:lineno)\n" +
-        //"-x       : skip first line of source, allowing use of non-Unix forms of #!cmd\n" +
-        "-3       : warn about Python 3.x incompatibilities that 2to3 cannot trivially fix\n" +
-        "file     : program read from script file\n" +
-        "-        : program read from stdin (default; interactive mode if a tty)\n" +
-        "arg ...  : arguments passed to program in sys.argv[1:]\n" +
-        "\n" +
-        "Other environment variables:\n" +
-        "JYTHONPATH: '" + File.pathSeparator +
-        "'-separated list of directories prefixed to the default module\n" +
-        "            search path.  The result is sys.path.";
+    private static final String usage = usageHeader
+            + "Options and arguments:\n"
+            // + "(and corresponding environment variables):\n"
+            + "-B       : don't write .py[co] files on import\n"
+            // + "also PYTHONDONTWRITEBYTECODE=x\n" +
+            + "-c cmd   : program passed in as string (terminates option list)\n"
+            // + "-d       : debug output from parser (also PYTHONDEBUG=x)\n"
+            + "-Dprop=v : Set the property `prop' to value `v'\n"
+            // + "-E       : ignore environment variables (such as PYTHONPATH)\n"
+            + "-C codec : Use a different codec when reading from the console.\n"
+            + "-h       : print this help message and exit (also --help)\n"
+            + "-i       : inspect interactively after running script\n"
+            // + ", (also PYTHONINSPECT=x)\n"
+            + "           and force prompts, even if stdin does not appear to be a terminal\n"
+            + "-jar jar : program read from __run__.py in jar file\n"
+            + "-m mod   : run library module as a script (terminates option list)\n"
+            // + "-O       : optimize generated bytecode (a tad; also PYTHONOPTIMIZE=x)\n"
+            // + "-OO      : remove doc-strings in addition to the -O optimizations\n"
+            + "-Q arg   : division options: -Qold (default), -Qwarn, -Qwarnall, -Qnew\n"
+            + "-s       : don't add user site directory to sys.path;\n"
+            // + "also PYTHONNOUSERSITE\n"
+            + "-S       : don't imply 'import site' on initialization\n"
+            // + "-t       : issue warnings about inconsistent tab usage (-tt: issue errors)\n"
+            + "-u       : unbuffered binary stdout and stderr\n"
+            // + "(also PYTHONUNBUFFERED=x)\n"
+            // + "           see man page for details on internal buffering relating to '-u'\n"
+            + "-v       : verbose (trace import statements)\n"
+            // + "(also PYTHONVERBOSE=x)\n"
+            + "           can be supplied multiple times to increase verbosity\n"
+            + "-V       : print the Python version number and exit (also --version)\n"
+            + "-W arg   : warning control (arg is action:message:category:module:lineno)\n"
+            // + "-x       : skip first line of source, allowing use of non-Unix forms of #!cmd\n"
+            + "-3       : warn about Python 3.x incompatibilities that 2to3 cannot trivially fix\n"
+            + "file     : program read from script file\n"
+            + "-        : program read from stdin (default; interactive mode if a tty)\n"
+            + "arg ...  : arguments passed to program in sys.argv[1:]\n" + "\n"
+            + "Other environment variables:\n" + "JYTHONPATH: '" + File.pathSeparator
+            + "'-separated list of directories prefixed to the default module\n"
+            + "            search path.  The result is sys.path.";
 
     public static boolean shouldRestart;
 
     /**
-     * Runs a JAR file, by executing the code found in the file __run__.py, 
-     * which should be in the root of the  JAR archive. 
+     * Runs a JAR file, by executing the code found in the file __run__.py, which should be in the
+     * root of the JAR archive. Note that the __name__ is set to the base name of the JAR file and
+     * not to "__main__" (for historic reasons). This method do NOT handle exceptions. the caller
+     * SHOULD handle any (Py)Exceptions thrown by the code.
      * 
-     * Note that the __name__ is set to the base name of the JAR file and not 
-     * to "__main__" (for historic reasons). 
-     * 
-     * This method do NOT handle exceptions. the caller SHOULD handle any 
-     * (Py)Exceptions thrown by the code.
-     * 
-     * @param filename The path to the filename to run. 
+     * @param filename The path to the filename to run.
      */
     public static void runJar(String filename) {
         // TBD: this is kind of gross because a local called `zipfile' just magically
-        // shows up in the module's globals.  Either `zipfile' should be called
-        // `__zipfile__' or (preferrably, IMO), __run__.py should be imported and a main()
-        // function extracted.  This function should be called passing zipfile in as an
+        // shows up in the module's globals. Either `zipfile' should be called
+        // `__zipfile__' or (preferably, IMO), __run__.py should be imported and a main()
+        // function extracted. This function should be called passing zipfile in as an
         // argument.
         //
         // Probably have to keep this code around for backwards compatibility (?)
@@ -146,7 +151,6 @@
                 return opts;
             }
 
-
             for (String opt : envVar.split(",")) {
                 opt = opt.trim();
                 if (opt.length() == 0) {
@@ -155,13 +159,14 @@
                 opts.add(opt);
             }
         } catch (SecurityException e) {
+            // Continue
         }
 
         return opts;
     }
 
-    private static List<String> validWarnActions = Arrays.asList(
-        "error", "ignore", "always", "default", "module", "once");
+    private static List<String> validWarnActions = Arrays.asList("error", "ignore", "always",
+            "default", "module", "once");
 
     private static void addWarnings(List<String> from, PyList to) {
         outerLoop : for (String opt : from) {
@@ -170,14 +175,14 @@
                 if (validWarnAction.startsWith(action)) {
                     if (opt.contains(":")) {
                         to.append(Py.newString(validWarnAction + opt.substring(opt.indexOf(":"))));
-                    }
-                    else {
+                    } else {
                         to.append(Py.newString(validWarnAction));
                     }
                     continue outerLoop;
                 }
             }
-            System.err.println(String.format("Invalid -W option ignored: invalid action: '%s'", action));
+            System.err.println(String.format("Invalid -W option ignored: invalid action: '%s'",
+                    action));
         }
     }
 
@@ -200,8 +205,17 @@
             System.exit(exitcode);
         }
 
+        // Get system properties (or empty set if we're prevented from accessing them)
+        Properties preProperties = PySystemState.getBaseProperties();
+
+        // Decide if System.in is interactive
+        if (!opts.fixInteractive || opts.interactive) {
+            // The options suggest System.in is interactive: but only if isatty() agrees
+            opts.interactive = Py.isInteractive();
+        }
+
         // Setup the basic python system state from these options
-        PySystemState.initialize(PySystemState.getBaseProperties(), opts.properties, opts.argv);
+        PySystemState.initialize(preProperties, opts.properties, opts.argv);
         PySystemState systemState = Py.getSystemState();
 
         PyList warnoptions = new PyList();
@@ -215,18 +229,12 @@
             imp.load("warnings");
         }
 
-
-        // Decide if stdin is interactive
-        if (!opts.fixInteractive || opts.interactive) {
-            opts.interactive = ((PyFile)Py.defaultSystemState.stdin).isatty();
-            if (!opts.interactive) {
-                systemState.ps1 = systemState.ps2 = Py.EmptyString;
-            }
+        // Now create an interpreter
+        if (!opts.interactive) {
+            // Not (really) interactive, so do not use console prompts
+            systemState.ps1 = systemState.ps2 = Py.EmptyString;
         }
-
-        // Now create an interpreter
-        InteractiveConsole interp = newInterpreter(opts.interactive);
-        systemState.__setattr__("_jy_interpreter", Py.java2py(interp));
+        InteractiveConsole interp = new InteractiveConsole();
 
         // Print banner and copyright information (or not)
         if (opts.interactive && opts.notice && !opts.runModule) {
@@ -265,21 +273,21 @@
         if (opts.filename != null) {
             String path;
             try {
-                 path = new File(opts.filename).getCanonicalFile().getParent();
+                path = new File(opts.filename).getCanonicalFile().getParent();
             } catch (IOException ioe) {
-                 path = new File(opts.filename).getAbsoluteFile().getParent();
+                path = new File(opts.filename).getAbsoluteFile().getParent();
             }
             if (path == null) {
                 path = "";
             }
             Py.getSystemState().path.insert(0, new PyString(path));
             if (opts.jar) {
-            	try {
-            		runJar(opts.filename);
-            	} catch (Throwable t) {
+                try {
+                    runJar(opts.filename);
+                } catch (Throwable t) {
                     Py.printException(t);
-                    System.exit(-1);            		
-            	}
+                    System.exit(-1);
+                }
             } else if (opts.filename.equals("-")) {
                 try {
                     interp.globals.__setitem__(new PyString("__file__"), new PyString("<stdin>"));
@@ -289,29 +297,29 @@
                 }
             } else {
                 try {
-                   interp.globals.__setitem__(new PyString("__file__"),
-                                              new PyString(opts.filename));
+                    interp.globals.__setitem__(new PyString("__file__"),
+                            new PyString(opts.filename));
 
-                   FileInputStream file;
-                   try {
-                       file = new FileInputStream(new RelativeFile(opts.filename));
-                   } catch (FileNotFoundException e) {
-                       throw Py.IOError(e);
-                   }
-                   try {
-                       if (PosixModule.getPOSIX().isatty(file.getFD())) {
-                           opts.interactive = true;
-                           interp.interact(null, new PyFile(file));
-                           return;
-                       } else {
-                           interp.execfile(file, opts.filename);
-                       }
-                   } finally {
-                       file.close();
-                   }
+                    FileInputStream file;
+                    try {
+                        file = new FileInputStream(new RelativeFile(opts.filename));
+                    } catch (FileNotFoundException e) {
+                        throw Py.IOError(e);
+                    }
+                    try {
+                        if (PosixModule.getPOSIX().isatty(file.getFD())) {
+                            opts.interactive = true;
+                            interp.interact(null, new PyFile(file));
+                            return;
+                        } else {
+                            interp.execfile(file, opts.filename);
+                        }
+                    } finally {
+                        file.close();
+                    }
                 } catch (Throwable t) {
                     if (t instanceof PyException
-                        && ((PyException)t).match(_systemrestart.SystemRestart)) {
+                            && ((PyException)t).match(_systemrestart.SystemRestart)) {
                         // Shutdown this instance...
                         shouldRestart = true;
                         shutdownInterpreter();
@@ -327,10 +335,9 @@
                     }
                 }
             }
-        }
-        else {
+        } else {
             // if there was no file name on the command line, then "" is the first element
-            // on sys.path.  This is here because if there /was/ a filename on the c.l.,
+            // on sys.path. This is here because if there /was/ a filename on the c.l.,
             // and say the -i option was given, sys.path[0] will have gotten filled in
             // with the dir of the argument filename.
             Py.getSystemState().path.insert(0, Py.EmptyString);
@@ -367,8 +374,8 @@
             if (opts.encoding != null) {
                 if (!Charset.isSupported(opts.encoding)) {
                     System.err.println(opts.encoding
-                                       + " is not a supported encoding on this JVM, so it can't "
-                                       + "be used in python.console.encoding.");
+                            + " is not a supported encoding on this JVM, so it can't "
+                            + "be used in python.console.encoding.");
                     System.exit(1);
                 }
                 interp.cflags.encoding = opts.encoding;
@@ -383,31 +390,6 @@
     }
 
     /**
-     * Returns a new python interpreter using the InteractiveConsole subclass from the
-     * <tt>python.console</tt> registry key.
-     * <p>
-
-     * When stdin is interactive the default is {@link JLineConsole}. Otherwise the
-     * featureless {@link InteractiveConsole} is always used as alternative consoles cause
-     * unexpected behavior with the std file streams.
-     */
-    private static InteractiveConsole newInterpreter(boolean interactiveStdin) {
-        if (!interactiveStdin) {
-            return new InteractiveConsole();
-        }
-
-        String interpClass = PySystemState.registry.getProperty("python.console", "");
-        if (interpClass.length() > 0) {
-            try {
-                return (InteractiveConsole)Class.forName(interpClass).newInstance();
-            } catch (Throwable t) {
-                // fall through
-            }
-        }
-        return new JLineConsole();
-    }
-
-    /**
      * Run any finalizations on the current interpreter in preparation for a SytemRestart.
      */
     public static void shutdownInterpreter() {
@@ -424,7 +406,9 @@
     }
 }
 
+
 class CommandLineOptions {
+
     public String filename;
     public boolean jar, interactive, notice;
     public boolean runCommand, runModule;
@@ -455,7 +439,7 @@
             // continue
         }
     }
-    
+
     private boolean argumentExpected(String arg) {
         System.err.println("Argument expected for the " + arg + " option");
         return false;
@@ -492,7 +476,7 @@
             } else if (arg.equals("-vv")) {
                 Options.verbose += 2;
             } else if (arg.equals("-vvv")) {
-                Options.verbose +=3 ;
+                Options.verbose += 3;
             } else if (arg.equals("-s")) {
                 Options.no_user_site = true;
             } else if (arg.equals("-S")) {
@@ -525,7 +509,7 @@
                 encoding = args[++index];
                 setProperty("python.console.encoding", encoding);
             } else if (arg.equals("-E")) {
-                // XXX: accept -E (ignore environment variables) to be compatiable with
+                // XXX: accept -E (ignore environment variables) to be compatible with
                 // CPython. do nothing for now (we could ignore the registry)
                 Options.ignore_environment = true;
             } else if (arg.startsWith("-D")) {
diff --git a/tests/java/javatests/Issue1972.java b/tests/java/javatests/Issue1972.java
--- a/tests/java/javatests/Issue1972.java
+++ b/tests/java/javatests/Issue1972.java
@@ -14,6 +14,7 @@
 import java.util.List;
 import java.util.Properties;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.regex.Pattern;
 
 import org.junit.After;
 import org.junit.Test;
@@ -26,9 +27,7 @@
  * debugging of the subprocess.
  * <p>
  * This test passes in Jython 2.5.2 and 2.5.4rc1. The test {@link #jythonReadline()} fails with
- * Jython 2.5.3. The test will fail the first time it is run on a clean build, or after switching
- * Jython versions (JAR files). This is because it monitors stderr from the subprocess and does not
- * expect the messages the cache manager produces on a first run.
+ * Jython 2.5.3.
  * <p>
  * The bulk of this program is designed to be run as JUnit tests, but it also has a
  * {@link #main(String[])} method that echoes <code>System.in</code> onto <code>System.out</code>
@@ -46,7 +45,13 @@
     static int DEBUG_PORT = 0; // 8000 or 0
 
     /** Control the amount of output to the console: 0, 1 or 2. */
-    static int VERBOSE = 2;
+    static int VERBOSE = 0;
+
+    /** Lines in stdout (as regular expressions) to ignore when checking subprocess output. */
+    static String[] STDOUT_IGNORE = {"^Listening for transport dt_socket"};
+
+    /** Lines in stderr (as regular expressions) to ignore when checking subprocess output. */
+    static String[] STDERR_IGNORE = {"^Jython 2", "^\\*sys-package-mgr"};
 
     /**
      * Extra JVM options used when debugging is enabled. <code>DEBUG_PORT</code> will be substituted
@@ -81,7 +86,7 @@
 
     /**
      * Check that on this system we know how to launch and read the error output from a subprocess.
-     * 
+     *
      * @throws IOException
      */
     @Test
@@ -103,7 +108,7 @@
 
     /**
      * Check that on this system we know how to launch and read standard output from a subprocess.
-     * 
+     *
      * @throws IOException
      */
     @Test
@@ -122,7 +127,7 @@
 
     /**
      * Check that on this system we know how to launch, write to and read from a subprocess.
-     * 
+     *
      * @throws IOException
      */
     @Test
@@ -152,7 +157,7 @@
      * <code>System.in</code> in the subprocess, which of course writes hex to
      * <code>System.out</code> but that data is not received back in the parent process until
      * <code>System.out.println()</code> is called in the subprocess.
-     * 
+     *
      * @throws IOException
      */
     @Test
@@ -176,7 +181,7 @@
 
     /**
      * Test reading back from Jython subprocess with program on command-line.
-     * 
+     *
      * @throws Exception
      */
     @Test
@@ -195,43 +200,42 @@
 
     /**
      * Discover what is handling the "console" when the program is on the command line only.
-     * 
+     *
      * @throws Exception
      */
     @Test
-    public void jythonNonInteractiveConsole() throws Exception {
+    public void jythonNonInteractive() throws Exception {
         announceTest(VERBOSE, "jythonNonInteractiveConsole");
 
         // Run Jython enquiry about console as -c program
         setProcJava("org.python.util.jython", "-c",
-                "import sys; print type(sys._jy_interpreter).__name__; print sys.stdin.isatty()");
+                "import sys; print type(sys._jy_console).__name__; print sys.stdin.isatty()");
         proc.waitFor();
 
         outputAsStrings(VERBOSE, inFromProc, errFromProc);
 
         checkErrFromProc();
-        checkInFromProc("InteractiveConsole", "False");
+        checkInFromProc("PlainConsole", "False");
     }
 
     /**
      * Discover what is handling the "console" when the program is entered interactively at the
      * Jython prompt.
-     * 
+     *
      * @throws Exception
      */
     @Test
-    public void jythonInteractiveConsole() throws Exception {
+    public void jythonInteractive() throws Exception {
         announceTest(VERBOSE, "jythonInteractiveConsole");
 
         // Run Jython with simple actions at the command prompt
         setProcJava(    //
-                "-Dpython.console=org.python.util.InteractiveConsole", //
                 "-Dpython.home=" + pythonHome, //
                 "org.python.util.jython");
 
         writeToProc("12+3\n");
         writeToProc("import sys\n");
-        writeToProc("print type(sys._jy_interpreter).__name__\n");
+        writeToProc("print type(sys._jy_console).__name__\n");
         writeToProc("print sys.stdin.isatty()\n");
         toProc.close();
         proc.waitFor();
@@ -239,13 +243,13 @@
         outputAsStrings(VERBOSE, inFromProc, errFromProc);
 
         checkErrFromProc("");   // stderr produces one empty line. Why?
-        checkInFromProc("15", "InteractiveConsole", "False");
+        checkInFromProc("15", "PlainConsole", "False");
     }
 
     /**
      * Discover what is handling the "console" when the program is entered interactively at the
      * Jython prompt, and we try to force use of JLine (which fails).
-     * 
+     *
      * @throws Exception
      */
     @Test
@@ -260,7 +264,7 @@
 
         writeToProc("12+3\n");
         writeToProc("import sys\n");
-        writeToProc("print type(sys._jy_interpreter).__name__\n");
+        writeToProc("print type(sys._jy_console).__name__\n");
         writeToProc("print sys.stdin.isatty()\n");
         toProc.close();
         proc.waitFor();
@@ -269,13 +273,13 @@
 
         checkErrFromProc("");   // stderr produces one empty line. Why?
 
-        // Although we asked for it, a subprocess doesn't get JLine, and isatty() is false
-        checkInFromProc("15", "InteractiveConsole", "False");
+        // We can specify JLineConsole, but isatty() is not fooled.
+        checkInFromProc("15", "PlainConsole", "False");
     }
 
     /**
      * Test writing to and reading back from Jython subprocess with echo program on command-line.
-     * 
+     *
      * @throws Exception
      */
     @Test
@@ -284,6 +288,9 @@
 
         // Run Jython simple readline programme
         setProcJava( //
+                "-Dpython.console=org.python.util.JLineConsole", //
+                // "-Dpython.console.interactive=True", //
+                "-Dpython.home=" + pythonHome, //
                 "org.python.util.jython", //
                 "-c", //
                 "import sys; sys.stdout.write(sys.stdin.readline()); sys.stdout.flush();" //
@@ -338,7 +345,7 @@
      * <td>echo the characters as hexadecimal</td>
      * </tr>
      * </table>
-     * 
+     *
      * @param args
      * @throws IOException
      */
@@ -373,7 +380,7 @@
     /**
      * Invoke the java command with the given arguments. The class path will be the same as this
      * programme's class path (as in the property <code>java.class.path</code>).
-     * 
+     *
      * @param args further arguments to the program run
      * @return the running process
      * @throws IOException
@@ -413,7 +420,7 @@
      * programme's class path (as in the property <code>java.class.path</code>). After the call,
      * {@link #proc} references the running process and {@link #inFromProc} and {@link #errFromProc}
      * are handling the <code>stdout</code> and <code>stderr</code> of the subprocess.
-     * 
+     *
      * @param args further arguments to the program run
      * @throws IOException
      */
@@ -427,7 +434,7 @@
     /**
      * Write this string into the <code>stdin</code> of the subprocess. The platform default
      * encoding will be used.
-     * 
+     *
      * @param s to write
      * @throws IOException
      */
@@ -441,14 +448,15 @@
      * {@link #escape(byte[])} transormation has been applied, are expected to be equal to the
      * strings supplied, optionally after {@link #escapedSeparator} has been added to the expected
      * strings.
-     * 
+     *
      * @param message identifies the queue in error message
      * @param addSeparator if true, system-defined line separator expected
      * @param queue to be compared
+     * @param toIgnore patterns defining lines to ignore while processing
      * @param expected lines of text (given without line separators)
      */
     private void checkFromProc(String message, boolean addSeparator, LineQueue queue,
-            String... expected) {
+            List<Pattern> toIgnore, String... expected) {
 
         if (addSeparator) {
             // Each expected string must be treated as if the lineSeparator were appended
@@ -470,29 +478,73 @@
         // Count through the results, stopping when either results or expected strings run out
         int count = 0;
         for (String r : results) {
-            if (count < expected.length) {
+            if (count >= expected.length) {
+                break;
+            } else if (!matchesAnyOf(r, toIgnore)) {
                 assertEquals(message, expected[count++], r);
-            } else {
-                break;
             }
         }
         assertEquals(message, expected.length, results.size());
     }
 
+    /** Compiled regular expressions for the lines to ignore (on stdout). */
+    private static List<Pattern> stdoutIgnore;
+
+    /** Compiled regular expressions for the lines to ignore (on stderr). */
+    private static List<Pattern> stderrIgnore;
+
+    /** If not already done, compile the regular expressions we need. */
+    private static void compileToIgnore() {
+        if (stdoutIgnore == null || stderrIgnore == null) {
+            // Compile the lines to ignore to Pattern objects
+            stdoutIgnore = compileAll(STDOUT_IGNORE);
+            stderrIgnore = compileAll(STDERR_IGNORE);
+        }
+    }
+
+    /** If not already done, compile one set of regular expressions to patterns. */
+    private static List<Pattern> compileAll(String[] regex) {
+        List<Pattern> result = new LinkedList<Pattern>();
+        if (regex != null) {
+            for (String s : regex) {
+                Pattern p = Pattern.compile(s);
+                result.add(p);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Compute whether a string matches any of a set of strings.
+     *
+     * @param s the string in question
+     * @param patterns to check against
+     * @return
+     */
+    private static boolean matchesAnyOf(String s, List<Pattern> patterns) {
+        for (Pattern p : patterns) {
+            if (p.matcher(s).matches()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Check lines of {@link #inFromProc} against expected text.
-     * 
+     *
      * @param addSeparator if true, system-defined line separator expected
      * @param expected lines of text (given without line separators)
      */
     private void checkInFromProc(boolean addSeparator, String... expected) {
-        checkFromProc("subprocess stdout", addSeparator, inFromProc, expected);
+        compileToIgnore();        // Make sure we have the matcher patterns
+        checkFromProc("subprocess stdout", addSeparator, inFromProc, stdoutIgnore, expected);
     }
 
     /**
      * Check lines of {@link #inFromProc} against expected text. Lines from the subprocess are
      * expected to be equal to those supplied after {@link #escapedSeparator} has been added.
-     * 
+     *
      * @param expected lines of text (given without line separators)
      */
     private void checkInFromProc(String... expected) {
@@ -501,18 +553,19 @@
 
     /**
      * Check lines of {@link #errFromProc} against expected text.
-     * 
+     *
      * @param addSeparator if true, system-defined line separator expected
      * @param expected lines of text (given without line separators)
      */
     private void checkErrFromProc(boolean addSeparator, String... expected) {
-        checkFromProc("subprocess stderr", addSeparator, errFromProc, expected);
+        compileToIgnore();        // Make sure we have the matcher patterns
+        checkFromProc("subprocess stderr", addSeparator, errFromProc, stderrIgnore, expected);
     }
 
     /**
      * Check lines of {@link #errFromProc} against expected text. Lines from the subprocess are
      * expected to be equal to those supplied after {@link #escapedSeparator} has been added.
-     * 
+     *
      * @param expected lines of text (given without line separators)
      */
     private void checkErrFromProc(String... expected) {
@@ -521,7 +574,7 @@
 
     /**
      * Brevity for announcing tests on the console when that is used to dump values.
-     * 
+     *
      * @param verbose if <1 suppress output
      * @param name of test
      */
@@ -533,7 +586,7 @@
 
     /**
      * Output is System.out the formatted strings representing lines from a subprocess stdout.
-     * 
+     *
      * @param verbose if <2 suppress output
      * @param inFromProc lines received from the stdout of a subprocess
      */
@@ -546,7 +599,7 @@
     /**
      * Output is System.out the formatted strings representing lines from a subprocess stdout, and
      * if there are any, from stderr.
-     * 
+     *
      * @param verbose if <2 suppress output
      * @param inFromProc lines received from the stdout of a subprocess
      * @param errFromProc lines received from the stderr of a subprocess
@@ -559,7 +612,7 @@
 
     /**
      * Output is System.out a hex dump of lines from a subprocess stdout.
-     * 
+     *
      * @param verbose if <2 suppress output
      * @param inFromProc lines received from the stdout of a subprocess
      */
@@ -572,7 +625,7 @@
     /**
      * Output is System.out a hex dump of lines from a subprocess stdout, and if there are any, from
      * stderr.
-     * 
+     *
      * @param verbose if <2 suppress output
      * @param inFromProc lines received from the stdout of a subprocess
      * @param errFromProc lines received from the stderr of a subprocess
@@ -586,7 +639,7 @@
     /**
      * Output is System.out the formatted strings representing lines from a subprocess stdout, and
      * if there are any, from stderr.
-     * 
+     *
      * @param stdout to output labelled "Output stream:"
      * @param stderr to output labelled "Error stream:" unless an empty list or null
      */
@@ -612,7 +665,7 @@
 
     /**
      * Helper to format one line of string output hex-escaping non-ASCII characters.
-     * 
+     *
      * @param sb to overwrite with the line of dump output
      * @param bb from which to take the bytes
      */
@@ -646,7 +699,7 @@
 
     /**
      * Convert bytes (interpreted as ASCII) to String where the non-ascii characters are escaped.
-     * 
+     *
      * @param b
      * @return
      */
@@ -676,7 +729,7 @@
 
         /**
          * Wrap a stream in the reader and immediately begin reading it.
-         * 
+         *
          * @param in
          */
         LineQueue(InputStream in) {
@@ -701,7 +754,7 @@
         /**
          * Scan every byte read from the input and squirrel them away in buffers, one per line,
          * where lines are delimited by \r, \n or \r\n..
-         * 
+         *
          * @throws IOException
          */
         private void runScribe() throws IOException {
@@ -754,7 +807,7 @@
         /**
          * Return the contents of the queue as a list of escaped strings, interpreting the bytes as
          * ASCII.
-         * 
+         *
          * @return contents as strings
          */
         public List<String> asStrings() {
@@ -774,7 +827,7 @@
 
         /**
          * Return a hex dump the contents of the object as a list of strings
-         * 
+         *
          * @return dump as strings
          */
         public List<String> asHexDump() {
@@ -799,7 +852,7 @@
 
         /**
          * Helper to format one line of hex dump output up to a maximum number of bytes.
-         * 
+         *
          * @param sb to overwrite with the line of dump output
          * @param bb from which to take the bytes
          * @param n number of bytes to take (up to <code>len</code>)
diff --git a/tests/java/org/python/util/jythonTest.java b/tests/java/org/python/util/jythonTest.java
--- a/tests/java/org/python/util/jythonTest.java
+++ b/tests/java/org/python/util/jythonTest.java
@@ -1,88 +1,44 @@
 package org.python.util;
 
-import java.lang.reflect.Method;
-import java.util.Properties;
+import static org.junit.Assert.*;
 
-import org.python.core.PySystemState;
-
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.python.core.Console;
+import org.python.core.PlainConsole;
+import org.python.core.Py;
 
 /**
- * Tests for creating the right interactive console.
+ * Tests of creating and getting the right interactive console.
+ * <p>
+ * System initialisation is a one-time thing normally, and the embedding of a console handler
+ * similarly, so it is difficult to test more than one console choice in a single executable. For
+ * this reason, there are two programs like this one: one that follows the native preference for a
+ * {@link PlainConsole} and this one that induces selection of a {@link JLineConsole}. Other
+ * features of the JLine console (such as access history) could be tested here. But the test
+ * Lib/test/test_readline.py does this fairly well, although it has to be run manually.
+ * <p>
+ * Automated testing of the console seems impossible since, in a scripted context (e.g. as a
+ * subprocess or under Ant) Jython is no longer interactive. To run it at the prompt, suggested
+ * idiom is (all one line):
+ *
+ * <pre>
+ * java -cp build/exposed;build/classes;extlibs/* -Dpython.home=dist
+ *              org.junit.runner.JUnitCore org.python.util.jythonTest
+ * </pre>
  */
-public class jythonTest extends TestCase {
+public class jythonTest {
 
-    private static final String PYTHON_CONSOLE = "python.console";
-
-    private Properties _originalRegistry;
-
-    @Override
-    protected void setUp() throws Exception {
-        _originalRegistry = PySystemState.registry;
-        Properties registry;
-        if (_originalRegistry != null) {
-            registry = new Properties(_originalRegistry);
-        } else {
-            registry = new Properties();
-        }
-        PySystemState.registry = registry;
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        PySystemState.registry = _originalRegistry;
-    }
+    private static String[] commands = {"-c", "import sys; print type(sys._jy_console)"};
 
     /**
-     * test the default behavior
-     * 
-     * @throws Exception
+     * Test that the default behaviour is to provide a JLineConsole. If CALL_RUN is true, it fails
+     * under Ant (or Eclipse) as the console is not then recognised to be interactive.
      */
-    public void testNewInterpreter() throws Exception {
-        assertEquals(JLineConsole.class, invokeNewInterpreter(true).getClass());
-    }
-
-    /**
-     * test registry override
-     * 
-     * @throws Exception
-     */
-    public void testNewInterpreter_registry() throws Exception {
-        PySystemState.registry.setProperty(PYTHON_CONSOLE, "org.python.util.InteractiveConsole");
-        assertEquals(InteractiveConsole.class, invokeNewInterpreter(true).getClass());
-    }
-
-    /**
-     * test fallback in case of an invalid registry value
-     * 
-     * @throws Exception
-     */
-    public void testNewInterpreter_unknown() throws Exception {
-        PySystemState.registry.setProperty(PYTHON_CONSOLE, "foo.bar.NoConsole");
-        assertEquals(JLineConsole.class, invokeNewInterpreter(true).getClass());
-    }
-
-    /**
-     * test non-interactive fallback to legacy console
-     * 
-     * @throws Exception
-     */
-    public void testNewInterpreter_NonInteractive() throws Exception {
-        assertEquals(InteractiveConsole.class, invokeNewInterpreter(false).getClass());
-    }
-
-    /**
-     * Invoke the private static method 'newInterpreter(boolean)' on jython.class
-     * 
-     * @throws Exception
-     */
-    private InteractiveConsole invokeNewInterpreter(boolean interactiveStdin) throws Exception {
-        Method method = jython.class.getDeclaredMethod("newInterpreter", Boolean.TYPE);
-        assertNotNull(method);
-        method.setAccessible(true);
-        Object result = method.invoke(null, interactiveStdin);
-        assertNotNull(result);
-        assertTrue(result instanceof InteractiveConsole);
-        return (InteractiveConsole)result;
+    @Test
+    public void testDefaultConsole() {
+        // This path only if you changed it to run manually
+        jython.run(commands);
+        Console console = Py.getConsole();
+        assertEquals(JLineConsole.class, console.getClass());
     }
 }

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


More information about the Jython-checkins mailing list