blob: 6d37a20083ea5788630ec495db9112561abdc5e1 [file] [log] [blame]
/*
* Copyright (c) 2002-2018, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
public class NonBlocking {
public static NonBlockingPumpReader nonBlockingPumpReader() {
return new NonBlockingPumpReader();
}
public static NonBlockingPumpReader nonBlockingPumpReader(int size) {
return new NonBlockingPumpReader(size);
}
public static NonBlockingPumpInputStream nonBlockingPumpInputStream() {
return new NonBlockingPumpInputStream();
}
public static NonBlockingPumpInputStream nonBlockingPumpInputStream(int size) {
return new NonBlockingPumpInputStream(size);
}
public static NonBlockingInputStream nonBlockingStream(NonBlockingReader reader, Charset encoding) {
return new NonBlockingReaderInputStream(reader, encoding);
}
public static NonBlockingInputStream nonBlocking(String name, InputStream inputStream) {
if (inputStream instanceof NonBlockingInputStream) {
return (NonBlockingInputStream) inputStream;
}
return new NonBlockingInputStreamImpl(name, inputStream);
}
public static NonBlockingReader nonBlocking(String name, Reader reader) {
if (reader instanceof NonBlockingReader) {
return (NonBlockingReader) reader;
}
return new NonBlockingReaderImpl(name, reader);
}
public static NonBlockingReader nonBlocking(String name, InputStream inputStream, Charset encoding) {
return new NonBlockingInputStreamReader(nonBlocking(name, inputStream), encoding);
}
private static class NonBlockingReaderInputStream extends NonBlockingInputStream {
private final NonBlockingReader reader;
private final CharsetEncoder encoder;
// To encode a character with multiple bytes (e.g. certain Unicode characters)
// we need enough space to encode them. Reading would fail if the read() method
// is used to read a single byte in these cases.
// Use this buffer to ensure we always have enough space to encode a character.
private final ByteBuffer bytes;
private final CharBuffer chars;
private NonBlockingReaderInputStream(NonBlockingReader reader, Charset charset) {
this.reader = reader;
this.encoder = charset.newEncoder()
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.onMalformedInput(CodingErrorAction.REPLACE);
this.bytes = ByteBuffer.allocate(4);
this.chars = CharBuffer.allocate(2);
// No input available after initialization
this.bytes.limit(0);
this.chars.limit(0);
}
@Override
public int available() {
return (int) (reader.available() * this.encoder.averageBytesPerChar())
+ bytes.remaining();
}
@Override
public void close() throws IOException {
reader.close();
}
@Override
public int read(long timeout, boolean isPeek) throws IOException {
boolean isInfinite = (timeout <= 0L);
while (!bytes.hasRemaining() && (isInfinite || timeout > 0L)) {
long start = 0;
if (!isInfinite) {
start = System.currentTimeMillis();
}
int c = reader.read(timeout);
if (c == EOF) {
return EOF;
}
if (c >= 0) {
if (!chars.hasRemaining()) {
chars.position(0);
chars.limit(0);
}
int l = chars.limit();
chars.array()[chars.arrayOffset() + l] = (char) c;
chars.limit(l + 1);
bytes.clear();
encoder.encode(chars, bytes, false);
bytes.flip();
}
if (!isInfinite) {
timeout -= System.currentTimeMillis() - start;
}
}
if (bytes.hasRemaining()) {
if (isPeek) {
return Byte.toUnsignedInt(bytes.get(bytes.position()));
} else {
return Byte.toUnsignedInt(bytes.get());
}
} else {
return READ_EXPIRED;
}
}
}
private static class NonBlockingInputStreamReader extends NonBlockingReader {
private final NonBlockingInputStream input;
private final CharsetDecoder decoder;
private final ByteBuffer bytes;
private final CharBuffer chars;
public NonBlockingInputStreamReader(NonBlockingInputStream inputStream, Charset encoding) {
this(inputStream,
(encoding != null ? encoding : Charset.defaultCharset()).newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE));
}
public NonBlockingInputStreamReader(NonBlockingInputStream input, CharsetDecoder decoder) {
this.input = input;
this.decoder = decoder;
this.bytes = ByteBuffer.allocate(4);
this.chars = CharBuffer.allocate(2);
this.bytes.limit(0);
this.chars.limit(0);
}
@Override
protected int read(long timeout, boolean isPeek) throws IOException {
boolean isInfinite = (timeout <= 0L);
while (!chars.hasRemaining() && (isInfinite || timeout > 0L)) {
long start = 0;
if (!isInfinite) {
start = System.currentTimeMillis();
}
int b = input.read(timeout);
if (b == EOF) {
return EOF;
}
if (b >= 0) {
if (!bytes.hasRemaining()) {
bytes.position(0);
bytes.limit(0);
}
int l = bytes.limit();
bytes.array()[bytes.arrayOffset() + l] = (byte) b;
bytes.limit(l + 1);
chars.clear();
decoder.decode(bytes, chars, false);
chars.flip();
}
if (!isInfinite) {
timeout -= System.currentTimeMillis() - start;
}
}
if (chars.hasRemaining()) {
if (isPeek) {
return chars.get(chars.position());
} else {
return chars.get();
}
} else {
return READ_EXPIRED;
}
}
@Override
public int readBuffered(char[] b) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (b.length == 0) {
return 0;
} else {
if (chars.hasRemaining()) {
int r = Math.min(b.length, chars.remaining());
chars.get(b);
return r;
} else {
byte[] buf = new byte[b.length];
int l = input.readBuffered(buf);
if (l < 0) {
return l;
} else {
ByteBuffer currentBytes;
if (bytes.hasRemaining()) {
int transfer = bytes.remaining();
byte[] newBuf = new byte[l + transfer];
bytes.get(newBuf, 0, transfer);
System.arraycopy(buf, 0, newBuf, transfer, l);
currentBytes = ByteBuffer.wrap(newBuf);
bytes.position(0);
bytes.limit(0);
} else {
currentBytes = ByteBuffer.wrap(buf, 0, l);
}
CharBuffer chars = CharBuffer.wrap(b);
decoder.decode(currentBytes, chars, false);
chars.flip();
if (currentBytes.hasRemaining()) {
int pos = bytes.position();
bytes.limit(bytes.limit() + currentBytes.remaining());
bytes.put(currentBytes);
bytes.position(pos);
}
return chars.remaining();
}
}
}
}
@Override
public void shutdown() {
input.shutdown();
}
@Override
public void close() throws IOException {
input.close();
}
}
}