Skip to content

DataBufferInputStream violates InputStream contract #29642

@access-control-rtfm

Description

@access-control-rtfm

Affects: 6.0.2


The methods asInputStream(...) of the interface DataBuffer have a default implementations. Every such method returns an instance of the class DataBufferInputStream.

Violation of backward compatibility

The constructor of the DataBufferInputStream marks the current write position of the underlying data buffer as the end position of the input stream, which the created instance represents after. The further written data into the underlying data buffer are not reflected by the input stream instance. An InputStream instance, returned by the DefaultDataBuffer of previous versions (e.g. 5.*.*) had this reflections.

Violation of design by contract of InputStream

The class DataBufferInputStream supports marking. But the method mark takes the argument, which is used as a certain read position instead of read limit, as the method reset is called. This violates the specification of the marking concept of InputStream.

Suggestion how it should be.

final class DataBufferInputStream extends InputStream {

	private final DataBuffer dataBuffer;

	private final boolean releaseOnClose;

	private boolean closed;

	private int markedPosition;

        private int readLimit;


	public DataBufferInputStream(DataBuffer dataBuffer, boolean releaseOnClose) {
		Assert.notNull(dataBuffer, "DataBuffer must not be null");
		this.dataBuffer = dataBuffer;
		this.releaseOnClose = releaseOnClose;
                this.markedPosition = -1;
	}

	@Override
	public int read() throws IOException {
		checkClosed();
		if (available() == 0) {
			return -1;
		}
		return this.dataBuffer.read() & 0xFF;
	}

	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		checkClosed();
		int available = available();
		if (available == 0) {
			return -1;
		}
		len = Math.min(available, len);
		this.dataBuffer.read(b, off, len);
		return len;
	}

	@Override
	public boolean markSupported() {
		return true;
	}

	@Override
	public void mark(int readLimit) {
                if(readLimit < 1) {
                        throw new IllegalArgumentException("readLimit must be greater than 0");
                }
		this.markedPosition = this.dataBuffer.readPosition();
                this.readLimit = readLimit
	}

	@Override
	public int available() {
		return this.dataBuffer.readableByteCount();
	}

	@Override
	public void reset() {
		String errorMessage = null;
                int bytesReadSinceLastMark;
                if(this.markedPosition == -1) {
                         errorMessage = "The input stream was never marked";
                 } else if((bytesReadSinceLastMark = this.dataBuffer.readPosition() - this.markedPosition) > this.readLimit) {
                         errorMessage = "The read limit %d was exceeded since last mark at %d by %d bytes".formatted(this.readLimit, this.markedPosition, bytesReadSinceLastMark);
                 } else if(bytesReadSinceLastMark < 0) {
                         errorMessage = "The read position was set before marked position";
                 }
                 if(errorMessage != null) {
                           throw new IOException(errorMessage);
                 }
                 this.dataBuffer.readPosition(this.markedPosition);
	}

	@Override
	public void close() {
		if (this.closed) {
			return;
		}
		if (this.releaseOnClose) {
			DataBufferUtils.release(this.dataBuffer);
		}
		this.closed = true;
	}

	private void checkClosed() throws IOException {
		if (this.closed) {
			throw new IOException("DataBufferInputStream is closed");
		}
	}

}

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions