org.postgresql.util.ReaderInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of postgresql Show documentation
Show all versions of postgresql Show documentation
PostgreSQL JDBC Driver JDBC4
/*
* Copyright (c) 2016, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
/**
* ReaderInputStream accepts a UTF-16 char stream (Reader) as input and
* converts it to a UTF-8 byte stream (InputStream) as output.
*
* This is the inverse of java.io.InputStreamReader which converts a
* binary stream to a character stream.
*/
public class ReaderInputStream extends InputStream {
private static final int DEFAULT_CHAR_BUFFER_SIZE = 8 * 1024;
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Reader reader;
private final CharsetEncoder encoder;
private final ByteBuffer bbuf;
private final CharBuffer cbuf;
/**
* true when all of the characters have been read from the reader into inbuf.
*/
private boolean endOfInput;
private final byte[] oneByte = new byte[1];
public ReaderInputStream(Reader reader) {
this(reader, DEFAULT_CHAR_BUFFER_SIZE);
}
/**
* Allow ReaderInputStreamTest to use small buffers to force UTF-16
* surrogate pairs to cross buffer boundaries in interesting ways.
* Because this constructor is package-private, the unit test must be in
* the same package.
*/
ReaderInputStream(Reader reader, int charBufferSize) {
if (reader == null) {
throw new IllegalArgumentException("reader cannot be null");
}
// The standard UTF-8 encoder will only encode a UTF-16 surrogate pair
// when both surrogates are available in the CharBuffer.
if (charBufferSize < 2) {
throw new IllegalArgumentException("charBufferSize must be at least 2 chars");
}
this.reader = reader;
this.encoder = UTF_8.newEncoder();
// encoder.maxBytesPerChar() always returns 3.0 for UTF-8
this.bbuf = ByteBuffer.allocate(3 * charBufferSize);
this.bbuf.flip(); // prepare for subsequent write
this.cbuf = CharBuffer.allocate(charBufferSize);
this.cbuf.flip(); // prepare for subsequent write
}
private void advance() throws IOException {
assert !endOfInput;
assert !bbuf.hasRemaining()
: "advance() should be called when output byte buffer is empty. bbuf: " + bbuf + ", as string: " + bbuf.asCharBuffer().toString();
assert cbuf.remaining() < 2;
// given that bbuf.capacity = 3 x cbuf.capacity, the only time that we should have a
// remaining char is if the last char read was the 1st half of a surrogate pair
if (cbuf.remaining() == 0) {
cbuf.clear();
} else {
cbuf.compact();
}
int n = reader.read(cbuf); // read #1
cbuf.flip();
CoderResult result;
endOfInput = n == -1;
bbuf.clear();
result = encoder.encode(cbuf, bbuf, endOfInput);
checkEncodeResult(result);
if (endOfInput) {
result = encoder.flush(bbuf);
checkEncodeResult(result);
}
bbuf.flip();
}
private void checkEncodeResult(CoderResult result) throws CharacterCodingException {
if (result.isError()) {
result.throwException();
}
}
@Override
public int read() throws IOException {
int res = 0;
while (res != -1) {
res = read(oneByte);
if (res > 0) {
return oneByte[0];
}
}
return -1;
}
// The implementation of InputStream.read(byte[], int, int) silently ignores
// an IOException thrown by overrides of the read() method.
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
if (endOfInput && !bbuf.hasRemaining()) {
return -1;
}
int totalRead = 0;
while (len > 0 && !endOfInput) {
if (bbuf.hasRemaining()) {
int remaining = Math.min(len, bbuf.remaining());
bbuf.get(b, off, remaining);
totalRead += remaining;
off += remaining;
len -= remaining;
if (len == 0) {
return totalRead;
}
}
advance();
}
if (endOfInput && !bbuf.hasRemaining() && totalRead == 0) {
return -1;
}
return totalRead;
}
@Override
public void close() throws IOException {
endOfInput = true;
reader.close();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy