Commits

Jeff Allen committed c71cbf9

A substantial re-organisation of the interactive console.
Re-works (JLine, Readline or Plain) consoles, separating the console from the
notion of an interpreter, and fixing (almost) a series of niggles with
non-ascii encoding.

Comments (0)

Files changed (14)

            '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
 
     # 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")
 
 def insert_text(string):
     _reader.putString(string)
-    
+
 def read_init_file(filename=None):
     warn("read_init_file: %s" % (filename,), NotImplementedWarning, "module", 2)
 
     _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)
 
         return start
 
     _reader.addCompletor(complete_handler)
-    
+
 
 def get_completer():
     return _completer_function
                 <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" />
 #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
 

src/org/python/core/Console.java

+// 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;
+
+}

src/org/python/core/PlainConsole.java

+// 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;
+        }
+    }
+
+}

src/org/python/core/Py.java

 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 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;
         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;

src/org/python/core/PySystemState.java

 package org.python.core;
 
 import java.io.BufferedReader;
-import java.io.Console;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 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;
 import org.python.expose.ExposedGet;
 import org.python.expose.ExposedType;
 import org.python.modules.Setup;
-import org.python.modules.zipimport.zipimporter;
 import org.python.util.Generic;
 
 /**
         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();
 
         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);
             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;
     }
 
         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;
         }
     }
 
+    @Override
     public PyObject __call__(PyObject arg1, PyObject arg2, PyObject arg3) {
         switch (index) {
             case 30:
 
     private PyAttributeDeleted() {}
 
+    @Override
     public String toString() {
         return "";
     }
 
+    @Override
     public Object __tojava__(Class c) {
         if (c == PyObject.class) {
             return this;

src/org/python/core/__builtin__.java

  */
 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 {
         }
     }
 
+    /**
+     * 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) {
         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) {

src/org/python/util/ConsoleStream.java

+// 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;
+    }
+
+}

src/org/python/util/JLineConsole.java

-/* 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 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.
  */
-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. */
     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 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 {
         } 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");
     }
 
     /**

src/org/python/util/ReadlineConsole.java

-// 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;
+
 }

src/org/python/util/jython.java

 package org.python.util;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 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;
      * 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.
-     *
+     * 
      * @param filename The path to the filename to run.
      */
     public static void runJar(String filename) {
             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();
             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) {
     }
 
     /**
-     * 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() {

tests/java/javatests/Issue1972.java

 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;
  * 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>
     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
 
     /**
      * Check that on this system we know how to launch and read the error output from a subprocess.
-     * 
+     *
      * @throws IOException
      */
     @Test
 
     /**
      * Check that on this system we know how to launch and read standard output from a subprocess.
-     * 
+     *
      * @throws IOException
      */
     @Test
 
     /**
      * Check that on this system we know how to launch, write to and read from a subprocess.
-     * 
+     *
      * @throws IOException
      */
     @Test
      * <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
 
     /**
      * Test reading back from Jython subprocess with program on command-line.
-     * 
+     *
      * @throws Exception
      */
     @Test
 
     /**
      * 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();
         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
 
         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();
 
         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
 
         // 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();" //
      * <td>echo the characters as hexadecimal</td>
      * </tr>
      * </table>
-     * 
+     *
      * @param args
      * @throws IOException
      */
     /**
      * 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
      * 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
      */
     /**
      * Write this string into the <code>stdin</code> of the subprocess. The platform default
      * encoding will be used.
-     * 
+     *
      * @param s to write
      * @throws IOException
      */
      * {@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
         // 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) {
 
     /**
      * 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) {
 
     /**
      * Brevity for announcing tests on the console when that is used to dump values.
-     * 
+     *
      * @param verbose if <1 suppress output
      * @param name of test
      */
 
     /**
      * 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
      */
     /**
      * 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
 
     /**
      * 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
      */
     /**
      * 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
     /**
      * 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
      */
 
     /**
      * 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
      */
 
     /**
      * Convert bytes (interpreted as ASCII) to String where the non-ascii characters are escaped.
-     * 
+     *
      * @param b
      * @return
      */
 
         /**
          * Wrap a stream in the reader and immediately begin reading it.
-         * 
+         *
          * @param in
          */
         LineQueue(InputStream in) {
         /**
          * 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 {
         /**
          * 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() {
 
         /**
          * Return a hex dump the contents of the object as a list of strings
-         * 
+         *
          * @return dump as strings
          */
         public List<String> asHexDump() {
 
         /**
          * 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>)

tests/java/org/python/util/jythonTest.java

 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;