java.io.Consoleクラスはコマンドラインでインタラクティブな操作を行う際のインタフェースを提供する。ソースコードの中央よりやや下にインスタンス変数が宣言されている。コンストラクタはソースコードの末尾に置かれており,privateで宣言されているため,外部からインスタンスを作成することはできなくなっている。インスタンスはSystem.console()メソッドで取得するようにドキュメントに記載されている。
writer()メソッドはコンソール画面に対して出力するためのPrintWriterを返す。reader()メソッドはコンソール画面から読み込むためのReaderを返す。printf(String format, Object … args)メソッドは,C言語のprintf()関数風の編集を行って出力する。format(String fmt, Object …args)メソッドはjava.text.Formatクラスを用いて書式化して出力する。
readLine(String fmt, Object … args)メソッドでは,改行コードが現れるまでコンソールから文字列を読み込む。一旦char[]配列に1文字ずつ読み込んだ後,String型へ変換している。与えられた引数はコンソール画面に出力される。内部でprivateなreadline()メソッドを使用している。 readline() メソッドでは,バッファからchar[]配列にデータを読み込む。末尾にある’\r’あるいは’\n’を削除して新たなchar[]配列に内容をコピーして返している。
readPassword(String fmt, Object … args)メソッドはパスワードを読み込むためのメソッドである。入力した文字列を画面に返さないように設定している。
下の方にLineReaderというReaderクラスを継承した packge privateな クラスが定義されている。このクラスは最後尾にあるstatic initializerの中でインスタンスが作成されている。
* Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.io;
import java.util.*;
import java.nio.charset.Charset;
import jdk.internal.access.JavaIOAccess;
import jdk.internal.access.SharedSecrets;
import sun.nio.cs.StreamDecoder;
import sun.nio.cs.StreamEncoder;
public final class Console implements Flushable
{
public PrintWriter writer() {
return pw;
}
public Reader reader() {
return reader;
}
public Console format(String fmt, Object …args) {
formatter.format(fmt, args).flush();
return this;
}
public Console printf(String format, Object … args) {
return format(format, args);
}
public String readLine(String fmt, Object … args) {
String line = null;
synchronized (writeLock) {
synchronized(readLock) {
if (!fmt.isEmpty())
pw.format(fmt, args);
try {
char[] ca = readline(false);
if (ca != null)
line = new String(ca);
} catch (IOException x) {
throw new IOError(x);
}
}
}
return line;
}
public String readLine() {
return readLine("");
}
public char[] readPassword(String fmt, Object … args) {
char[] passwd = null;
synchronized (writeLock) {
synchronized(readLock) {
installShutdownHook();
try {
restoreEcho = echo(false);
} catch (IOException x) {
throw new IOError(x);
}
IOError ioe = null;
try {
if (!fmt.isEmpty())
pw.format(fmt, args);
passwd = readline(true);
} catch (IOException x) {
ioe = new IOError(x);
} finally {
try {
if (restoreEcho)
restoreEcho = echo(true);
} catch (IOException x) {
if (ioe == null)
ioe = new IOError(x);
else
ioe.addSuppressed(x);
}
if (ioe != null)
throw ioe;
}
pw.println();
}
}
return passwd;
}
private void installShutdownHook() {
if (shutdownHookInstalled)
return;
try {
// Add a shutdown hook to restore console's echo state should
// it be necessary.
SharedSecrets.getJavaLangAccess()
.registerShutdownHook(0 /* shutdown hook invocation order */,
false /* only register if shutdown is not in progress */,
new Runnable() {
public void run() {
try {
if (restoreEcho) {
echo(true);
}
} catch (IOException x) { }
}
});
} catch (IllegalStateException e) {
// shutdown is already in progress and readPassword is first used
// by a shutdown hook
}
shutdownHookInstalled = true;
}
public char[] readPassword() {
return readPassword("");
}
public void flush() {
pw.flush();
}
private Object readLock;
private Object writeLock;
private Reader reader;
private Writer out;
private PrintWriter pw;
private Formatter formatter;
private Charset cs;
private char[] rcb;
private boolean restoreEcho;
private boolean shutdownHookInstalled;
private static native String encoding();
/*
* Sets the console echo status to {@code on} and returns the previous
* console on/off status.
* @param on the echo status to set to. {@code true} for echo on and
* {@code false} for echo off
* @return true if the previous console echo status is on
*/
private static native boolean echo(boolean on) throws IOException;
private char[] readline(boolean zeroOut) throws IOException {
int len = reader.read(rcb, 0, rcb.length);
if (len < 0)
return null; //EOL
if (rcb[len-1] == '\r')
len–; //remove CR at end;
else if (rcb[len-1] == '\n') {
len–; //remove LF at end;
if (len > 0 && rcb[len-1] == '\r')
len–; //remove the CR, if there is one
}
char[] b = new char[len];
if (len > 0) {
System.arraycopy(rcb, 0, b, 0, len);
if (zeroOut) {
Arrays.fill(rcb, 0, len, ' ');
}
}
return b;
}
private char[] grow() {
assert Thread.holdsLock(readLock);
char[] t = new char[rcb.length * 2];
System.arraycopy(rcb, 0, t, 0, rcb.length);
rcb = t;
return rcb;
}
class LineReader extends Reader {
private Reader in;
private char[] cb;
private int nChars, nextChar;
boolean leftoverLF;
LineReader(Reader in) {
this.in = in;
cb = new char[1024];
nextChar = nChars = 0;
leftoverLF = false;
}
public void close () {}
public boolean ready() throws IOException {
//in.ready synchronizes on readLock already
return in.ready();
}
public int read(char cbuf[], int offset, int length)
throws IOException
{
int off = offset;
int end = offset + length;
if (offset < 0 || offset > cbuf.length || length < 0 ||
end < 0 || end > cbuf.length) {
throw new IndexOutOfBoundsException();
}
synchronized(readLock) {
boolean eof = false;
char c = 0;
for (;;) {
if (nextChar >= nChars) { //fill
int n = 0;
do {
n = in.read(cb, 0, cb.length);
} while (n == 0);
if (n > 0) {
nChars = n;
nextChar = 0;
if (n < cb.length &&
cb[n-1] != '\n' && cb[n-1] != '\r') {
/*
* we're in canonical mode so each "fill" should
* come back with an eol. if there no lf or nl at
* the end of returned bytes we reached an eof.
*/
eof = true;
}
} else { /*EOF*/
if (off – offset == 0)
return -1;
return off – offset;
}
}
if (leftoverLF && cbuf == rcb && cb[nextChar] == '\n') {
/*
* if invoked by our readline, skip the leftover, otherwise
* return the LF.
*/
nextChar++;
}
leftoverLF = false;
while (nextChar < nChars) {
c = cbuf[off++] = cb[nextChar];
cb[nextChar++] = 0;
if (c == '\n') {
return off – offset;
} else if (c == '\r') {
if (off == end) {
/* no space left even the next is LF, so return
* whatever we have if the invoker is not our
* readLine()
*/
if (cbuf == rcb) {
cbuf = grow();
end = cbuf.length;
} else {
leftoverLF = true;
return off – offset;
}
}
if (nextChar == nChars && in.ready()) {
/*
* we have a CR and we reached the end of
* the read in buffer, fill to make sure we
* don't miss a LF, if there is one, it's possible
* that it got cut off during last round reading
* simply because the read in buffer was full.
*/
nChars = in.read(cb, 0, cb.length);
nextChar = 0;
}
if (nextChar < nChars && cb[nextChar] == '\n') {
cbuf[off++] = '\n';
nextChar++;
}
return off – offset;
} else if (off == end) {
if (cbuf == rcb) {
cbuf = grow();
end = cbuf.length;
} else {
return off – offset;
}
}
}
if (eof)
return off – offset;
}
}
}
}
// Set up JavaIOAccess in SharedSecrets
static {
SharedSecrets.setJavaIOAccess(new JavaIOAccess() {
public Console console() {
if (istty()) {
if (cons == null)
cons = new Console();
return cons;
}
return null;
}
public Charset charset() {
// This method is called in sun.security.util.Password,
// cons already exists when this method is called
return cons.cs;
}
});
}
private static Console cons;
private static native boolean istty();
private Console() {
readLock = new Object();
writeLock = new Object();
String csname = encoding();
if (csname != null) {
try {
cs = Charset.forName(csname);
} catch (Exception x) {}
}
if (cs == null)
cs = Charset.defaultCharset();
out = StreamEncoder.forOutputStreamWriter(
new FileOutputStream(FileDescriptor.out),
writeLock,
cs);
pw = new PrintWriter(out, true) { public void close() {} };
formatter = new Formatter(out);
reader = new LineReader(StreamDecoder.forInputStreamReader(
new FileInputStream(FileDescriptor.in),
readLock,
cs));
rcb = new char[1024];
}
}