Console.java revision 12745:f068a4ffddd2
1/*
2 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package java.io;
27
28import java.util.*;
29import java.nio.charset.Charset;
30import sun.nio.cs.StreamDecoder;
31import sun.nio.cs.StreamEncoder;
32
33/**
34 * Methods to access the character-based console device, if any, associated
35 * with the current Java virtual machine.
36 *
37 * <p> Whether a virtual machine has a console is dependent upon the
38 * underlying platform and also upon the manner in which the virtual
39 * machine is invoked.  If the virtual machine is started from an
40 * interactive command line without redirecting the standard input and
41 * output streams then its console will exist and will typically be
42 * connected to the keyboard and display from which the virtual machine
43 * was launched.  If the virtual machine is started automatically, for
44 * example by a background job scheduler, then it will typically not
45 * have a console.
46 * <p>
47 * If this virtual machine has a console then it is represented by a
48 * unique instance of this class which can be obtained by invoking the
49 * {@link java.lang.System#console()} method.  If no console device is
50 * available then an invocation of that method will return {@code null}.
51 * <p>
52 * Read and write operations are synchronized to guarantee the atomic
53 * completion of critical operations; therefore invoking methods
54 * {@link #readLine()}, {@link #readPassword()}, {@link #format format()},
55 * {@link #printf printf()} as well as the read, format and write operations
56 * on the objects returned by {@link #reader()} and {@link #writer()} may
57 * block in multithreaded scenarios.
58 * <p>
59 * Invoking {@code close()} on the objects returned by the {@link #reader()}
60 * and the {@link #writer()} will not close the underlying stream of those
61 * objects.
62 * <p>
63 * The console-read methods return {@code null} when the end of the
64 * console input stream is reached, for example by typing control-D on
65 * Unix or control-Z on Windows.  Subsequent read operations will succeed
66 * if additional characters are later entered on the console's input
67 * device.
68 * <p>
69 * Unless otherwise specified, passing a {@code null} argument to any method
70 * in this class will cause a {@link NullPointerException} to be thrown.
71 * <p>
72 * <b>Security note:</b>
73 * If an application needs to read a password or other secure data, it should
74 * use {@link #readPassword()} or {@link #readPassword(String, Object...)} and
75 * manually zero the returned character array after processing to minimize the
76 * lifetime of sensitive data in memory.
77 *
78 * <blockquote><pre>{@code
79 * Console cons;
80 * char[] passwd;
81 * if ((cons = System.console()) != null &&
82 *     (passwd = cons.readPassword("[%s]", "Password:")) != null) {
83 *     ...
84 *     java.util.Arrays.fill(passwd, ' ');
85 * }
86 * }</pre></blockquote>
87 *
88 * @author  Xueming Shen
89 * @since   1.6
90 */
91
92public final class Console implements Flushable
93{
94   /**
95    * Retrieves the unique {@link java.io.PrintWriter PrintWriter} object
96    * associated with this console.
97    *
98    * @return  The printwriter associated with this console
99    */
100    public PrintWriter writer() {
101        return pw;
102    }
103
104   /**
105    * Retrieves the unique {@link java.io.Reader Reader} object associated
106    * with this console.
107    * <p>
108    * This method is intended to be used by sophisticated applications, for
109    * example, a {@link java.util.Scanner} object which utilizes the rich
110    * parsing/scanning functionality provided by the {@code Scanner}:
111    * <blockquote><pre>
112    * Console con = System.console();
113    * if (con != null) {
114    *     Scanner sc = new Scanner(con.reader());
115    *     ...
116    * }
117    * </pre></blockquote>
118    * <p>
119    * For simple applications requiring only line-oriented reading, use
120    * {@link #readLine}.
121    * <p>
122    * The bulk read operations {@link java.io.Reader#read(char[]) read(char[]) },
123    * {@link java.io.Reader#read(char[], int, int) read(char[], int, int) } and
124    * {@link java.io.Reader#read(java.nio.CharBuffer) read(java.nio.CharBuffer)}
125    * on the returned object will not read in characters beyond the line
126    * bound for each invocation, even if the destination buffer has space for
127    * more characters. The {@code Reader}'s {@code read} methods may block if a
128    * line bound has not been entered or reached on the console's input device.
129    * A line bound is considered to be any one of a line feed ({@code '\n'}),
130    * a carriage return ({@code '\r'}), a carriage return followed immediately
131    * by a linefeed, or an end of stream.
132    *
133    * @return  The reader associated with this console
134    */
135    public Reader reader() {
136        return reader;
137    }
138
139   /**
140    * Writes a formatted string to this console's output stream using
141    * the specified format string and arguments.
142    *
143    * @param  fmt
144    *         A format string as described in <a
145    *         href="../util/Formatter.html#syntax">Format string syntax</a>
146    *
147    * @param  args
148    *         Arguments referenced by the format specifiers in the format
149    *         string.  If there are more arguments than format specifiers, the
150    *         extra arguments are ignored.  The number of arguments is
151    *         variable and may be zero.  The maximum number of arguments is
152    *         limited by the maximum dimension of a Java array as defined by
153    *         <cite>The Java&trade; Virtual Machine Specification</cite>.
154    *         The behaviour on a
155    *         {@code null} argument depends on the <a
156    *         href="../util/Formatter.html#syntax">conversion</a>.
157    *
158    * @throws  IllegalFormatException
159    *          If a format string contains an illegal syntax, a format
160    *          specifier that is incompatible with the given arguments,
161    *          insufficient arguments given the format string, or other
162    *          illegal conditions.  For specification of all possible
163    *          formatting errors, see the <a
164    *          href="../util/Formatter.html#detail">Details</a> section
165    *          of the formatter class specification.
166    *
167    * @return  This console
168    */
169    public Console format(String fmt, Object ...args) {
170        formatter.format(fmt, args).flush();
171        return this;
172    }
173
174   /**
175    * A convenience method to write a formatted string to this console's
176    * output stream using the specified format string and arguments.
177    *
178    * <p> An invocation of this method of the form
179    * {@code con.printf(format, args)} behaves in exactly the same way
180    * as the invocation of
181    * <pre>con.format(format, args)</pre>.
182    *
183    * @param  format
184    *         A format string as described in <a
185    *         href="../util/Formatter.html#syntax">Format string syntax</a>.
186    *
187    * @param  args
188    *         Arguments referenced by the format specifiers in the format
189    *         string.  If there are more arguments than format specifiers, the
190    *         extra arguments are ignored.  The number of arguments is
191    *         variable and may be zero.  The maximum number of arguments is
192    *         limited by the maximum dimension of a Java array as defined by
193    *         <cite>The Java&trade; Virtual Machine Specification</cite>.
194    *         The behaviour on a
195    *         {@code null} argument depends on the <a
196    *         href="../util/Formatter.html#syntax">conversion</a>.
197    *
198    * @throws  IllegalFormatException
199    *          If a format string contains an illegal syntax, a format
200    *          specifier that is incompatible with the given arguments,
201    *          insufficient arguments given the format string, or other
202    *          illegal conditions.  For specification of all possible
203    *          formatting errors, see the <a
204    *          href="../util/Formatter.html#detail">Details</a> section of the
205    *          formatter class specification.
206    *
207    * @return  This console
208    */
209    public Console printf(String format, Object ... args) {
210        return format(format, args);
211    }
212
213   /**
214    * Provides a formatted prompt, then reads a single line of text from the
215    * console.
216    *
217    * @param  fmt
218    *         A format string as described in <a
219    *         href="../util/Formatter.html#syntax">Format string syntax</a>.
220    *
221    * @param  args
222    *         Arguments referenced by the format specifiers in the format
223    *         string.  If there are more arguments than format specifiers, the
224    *         extra arguments are ignored.  The maximum number of arguments is
225    *         limited by the maximum dimension of a Java array as defined by
226    *         <cite>The Java&trade; Virtual Machine Specification</cite>.
227    *
228    * @throws  IllegalFormatException
229    *          If a format string contains an illegal syntax, a format
230    *          specifier that is incompatible with the given arguments,
231    *          insufficient arguments given the format string, or other
232    *          illegal conditions.  For specification of all possible
233    *          formatting errors, see the <a
234    *          href="../util/Formatter.html#detail">Details</a> section
235    *          of the formatter class specification.
236    *
237    * @throws IOError
238    *         If an I/O error occurs.
239    *
240    * @return  A string containing the line read from the console, not
241    *          including any line-termination characters, or {@code null}
242    *          if an end of stream has been reached.
243    */
244    public String readLine(String fmt, Object ... args) {
245        String line = null;
246        synchronized (writeLock) {
247            synchronized(readLock) {
248                if (fmt.length() != 0)
249                    pw.format(fmt, args);
250                try {
251                    char[] ca = readline(false);
252                    if (ca != null)
253                        line = new String(ca);
254                } catch (IOException x) {
255                    throw new IOError(x);
256                }
257            }
258        }
259        return line;
260    }
261
262   /**
263    * Reads a single line of text from the console.
264    *
265    * @throws IOError
266    *         If an I/O error occurs.
267    *
268    * @return  A string containing the line read from the console, not
269    *          including any line-termination characters, or {@code null}
270    *          if an end of stream has been reached.
271    */
272    public String readLine() {
273        return readLine("");
274    }
275
276   /**
277    * Provides a formatted prompt, then reads a password or passphrase from
278    * the console with echoing disabled.
279    *
280    * @param  fmt
281    *         A format string as described in <a
282    *         href="../util/Formatter.html#syntax">Format string syntax</a>
283    *         for the prompt text.
284    *
285    * @param  args
286    *         Arguments referenced by the format specifiers in the format
287    *         string.  If there are more arguments than format specifiers, the
288    *         extra arguments are ignored.  The maximum number of arguments is
289    *         limited by the maximum dimension of a Java array as defined by
290    *         <cite>The Java&trade; Virtual Machine Specification</cite>.
291    *
292    * @throws  IllegalFormatException
293    *          If a format string contains an illegal syntax, a format
294    *          specifier that is incompatible with the given arguments,
295    *          insufficient arguments given the format string, or other
296    *          illegal conditions.  For specification of all possible
297    *          formatting errors, see the <a
298    *          href="../util/Formatter.html#detail">Details</a>
299    *          section of the formatter class specification.
300    *
301    * @throws IOError
302    *         If an I/O error occurs.
303    *
304    * @return  A character array containing the password or passphrase read
305    *          from the console, not including any line-termination characters,
306    *          or {@code null} if an end of stream has been reached.
307    */
308    public char[] readPassword(String fmt, Object ... args) {
309        char[] passwd = null;
310        synchronized (writeLock) {
311            synchronized(readLock) {
312                try {
313                    echoOff = echo(false);
314                } catch (IOException x) {
315                    throw new IOError(x);
316                }
317                IOError ioe = null;
318                try {
319                    if (fmt.length() != 0)
320                        pw.format(fmt, args);
321                    passwd = readline(true);
322                } catch (IOException x) {
323                    ioe = new IOError(x);
324                } finally {
325                    try {
326                        echoOff = echo(true);
327                    } catch (IOException x) {
328                        if (ioe == null)
329                            ioe = new IOError(x);
330                        else
331                            ioe.addSuppressed(x);
332                    }
333                    if (ioe != null)
334                        throw ioe;
335                }
336                pw.println();
337            }
338        }
339        return passwd;
340    }
341
342   /**
343    * Reads a password or passphrase from the console with echoing disabled
344    *
345    * @throws IOError
346    *         If an I/O error occurs.
347    *
348    * @return  A character array containing the password or passphrase read
349    *          from the console, not including any line-termination characters,
350    *          or {@code null} if an end of stream has been reached.
351    */
352    public char[] readPassword() {
353        return readPassword("");
354    }
355
356    /**
357     * Flushes the console and forces any buffered output to be written
358     * immediately .
359     */
360    public void flush() {
361        pw.flush();
362    }
363
364    private Object readLock;
365    private Object writeLock;
366    private Reader reader;
367    private Writer out;
368    private PrintWriter pw;
369    private Formatter formatter;
370    private Charset cs;
371    private char[] rcb;
372    private static native String encoding();
373    private static native boolean echo(boolean on) throws IOException;
374    private static boolean echoOff;
375
376    private char[] readline(boolean zeroOut) throws IOException {
377        int len = reader.read(rcb, 0, rcb.length);
378        if (len < 0)
379            return null;  //EOL
380        if (rcb[len-1] == '\r')
381            len--;        //remove CR at end;
382        else if (rcb[len-1] == '\n') {
383            len--;        //remove LF at end;
384            if (len > 0 && rcb[len-1] == '\r')
385                len--;    //remove the CR, if there is one
386        }
387        char[] b = new char[len];
388        if (len > 0) {
389            System.arraycopy(rcb, 0, b, 0, len);
390            if (zeroOut) {
391                Arrays.fill(rcb, 0, len, ' ');
392            }
393        }
394        return b;
395    }
396
397    private char[] grow() {
398        assert Thread.holdsLock(readLock);
399        char[] t = new char[rcb.length * 2];
400        System.arraycopy(rcb, 0, t, 0, rcb.length);
401        rcb = t;
402        return rcb;
403    }
404
405    class LineReader extends Reader {
406        private Reader in;
407        private char[] cb;
408        private int nChars, nextChar;
409        boolean leftoverLF;
410        LineReader(Reader in) {
411            this.in = in;
412            cb = new char[1024];
413            nextChar = nChars = 0;
414            leftoverLF = false;
415        }
416        public void close () {}
417        public boolean ready() throws IOException {
418            //in.ready synchronizes on readLock already
419            return in.ready();
420        }
421
422        public int read(char cbuf[], int offset, int length)
423            throws IOException
424        {
425            int off = offset;
426            int end = offset + length;
427            if (offset < 0 || offset > cbuf.length || length < 0 ||
428                end < 0 || end > cbuf.length) {
429                throw new IndexOutOfBoundsException();
430            }
431            synchronized(readLock) {
432                boolean eof = false;
433                char c = 0;
434                for (;;) {
435                    if (nextChar >= nChars) {   //fill
436                        int n = 0;
437                        do {
438                            n = in.read(cb, 0, cb.length);
439                        } while (n == 0);
440                        if (n > 0) {
441                            nChars = n;
442                            nextChar = 0;
443                            if (n < cb.length &&
444                                cb[n-1] != '\n' && cb[n-1] != '\r') {
445                                /*
446                                 * we're in canonical mode so each "fill" should
447                                 * come back with an eol. if there no lf or nl at
448                                 * the end of returned bytes we reached an eof.
449                                 */
450                                eof = true;
451                            }
452                        } else { /*EOF*/
453                            if (off - offset == 0)
454                                return -1;
455                            return off - offset;
456                        }
457                    }
458                    if (leftoverLF && cbuf == rcb && cb[nextChar] == '\n') {
459                        /*
460                         * if invoked by our readline, skip the leftover, otherwise
461                         * return the LF.
462                         */
463                        nextChar++;
464                    }
465                    leftoverLF = false;
466                    while (nextChar < nChars) {
467                        c = cbuf[off++] = cb[nextChar];
468                        cb[nextChar++] = 0;
469                        if (c == '\n') {
470                            return off - offset;
471                        } else if (c == '\r') {
472                            if (off == end) {
473                                /* no space left even the next is LF, so return
474                                 * whatever we have if the invoker is not our
475                                 * readLine()
476                                 */
477                                if (cbuf == rcb) {
478                                    cbuf = grow();
479                                    end = cbuf.length;
480                                } else {
481                                    leftoverLF = true;
482                                    return off - offset;
483                                }
484                            }
485                            if (nextChar == nChars && in.ready()) {
486                                /*
487                                 * we have a CR and we reached the end of
488                                 * the read in buffer, fill to make sure we
489                                 * don't miss a LF, if there is one, it's possible
490                                 * that it got cut off during last round reading
491                                 * simply because the read in buffer was full.
492                                 */
493                                nChars = in.read(cb, 0, cb.length);
494                                nextChar = 0;
495                            }
496                            if (nextChar < nChars && cb[nextChar] == '\n') {
497                                cbuf[off++] = '\n';
498                                nextChar++;
499                            }
500                            return off - offset;
501                        } else if (off == end) {
502                           if (cbuf == rcb) {
503                                cbuf = grow();
504                                end = cbuf.length;
505                           } else {
506                               return off - offset;
507                           }
508                        }
509                    }
510                    if (eof)
511                        return off - offset;
512                }
513            }
514        }
515    }
516
517    // Set up JavaIOAccess in SharedSecrets
518    static {
519        try {
520            // Add a shutdown hook to restore console's echo state should
521            // it be necessary.
522            sun.misc.SharedSecrets.getJavaLangAccess()
523                .registerShutdownHook(0 /* shutdown hook invocation order */,
524                    false /* only register if shutdown is not in progress */,
525                    new Runnable() {
526                        public void run() {
527                            try {
528                                if (echoOff) {
529                                    echo(true);
530                                }
531                            } catch (IOException x) { }
532                        }
533                    });
534        } catch (IllegalStateException e) {
535            // shutdown is already in progress and console is first used
536            // by a shutdown hook
537        }
538
539        sun.misc.SharedSecrets.setJavaIOAccess(new sun.misc.JavaIOAccess() {
540            public Console console() {
541                if (istty()) {
542                    if (cons == null)
543                        cons = new Console();
544                    return cons;
545                }
546                return null;
547            }
548
549            public Charset charset() {
550                // This method is called in sun.security.util.Password,
551                // cons already exists when this method is called
552                return cons.cs;
553            }
554        });
555    }
556    private static Console cons;
557    private static native boolean istty();
558    private Console() {
559        readLock = new Object();
560        writeLock = new Object();
561        String csname = encoding();
562        if (csname != null) {
563            try {
564                cs = Charset.forName(csname);
565            } catch (Exception x) {}
566        }
567        if (cs == null)
568            cs = Charset.defaultCharset();
569        out = StreamEncoder.forOutputStreamWriter(
570                  new FileOutputStream(FileDescriptor.out),
571                  writeLock,
572                  cs);
573        pw = new PrintWriter(out, true) { public void close() {} };
574        formatter = new Formatter(out);
575        reader = new LineReader(StreamDecoder.forInputStreamReader(
576                     new FileInputStream(FileDescriptor.in),
577                     readLock,
578                     cs));
579        rcb = new char[1024];
580    }
581}
582