blob: 7cb15a73615048b29381d77b7b578cb832b2b29a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 Andreas Loth and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Andreas Loth - initial API and implementation
*******************************************************************************/
package org.eclipse.debug.internal.core;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
/**
* Wraps CharsetDecoder to decode a byte stream statefully to characters.
*
* @since 3.7 org.eclipse.ui.console
*/
public class StreamDecoder {
// For more context see https://bugs.eclipse.org/bugs/show_bug.cgi?id=507664
static private final int BUFFER_SIZE = 4096;
private final CharsetDecoder decoder;
private final ByteBuffer inputBuffer;
private final CharBuffer outputBuffer;
private boolean finished;
public StreamDecoder(Charset charset) {
this.decoder = charset.newDecoder();
this.decoder.onMalformedInput(CodingErrorAction.REPLACE);
this.decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
this.inputBuffer = ByteBuffer.allocate(StreamDecoder.BUFFER_SIZE);
this.inputBuffer.flip();
this.outputBuffer = CharBuffer.allocate(StreamDecoder.BUFFER_SIZE);
this.finished = false;
}
private void consume(StringBuilder consumer) {
this.outputBuffer.flip();
consumer.append(this.outputBuffer);
this.outputBuffer.clear();
}
private void internalDecode(StringBuilder consumer, byte[] buffer, int offset, int length) {
assert (offset >= 0);
assert (length >= 0);
int position = offset;
int end = offset + length;
assert (end <= buffer.length);
boolean finishedReading = false;
do {
CoderResult result = this.decoder.decode(this.inputBuffer, this.outputBuffer, false);
if (result.isOverflow()) {
this.consume(consumer);
} else if (result.isUnderflow()) {
this.inputBuffer.compact();
int remaining = this.inputBuffer.remaining();
assert (remaining > 0);
int read = Math.min(remaining, end - position);
if (read > 0) {
this.inputBuffer.put(buffer, position, read);
position += read;
} else {
finishedReading = true;
}
this.inputBuffer.flip();
} else {
assert false;
}
} while (!finishedReading);
}
public void decode(StringBuilder consumer, byte[] buffer, int offset, int length) {
this.internalDecode(consumer, buffer, offset, length);
this.consume(consumer);
}
public void finish(StringBuilder consumer) {
if (this.finished) {
return;
}
this.finished = true;
CoderResult result;
result = this.decoder.decode(this.inputBuffer, this.outputBuffer, true);
assert (result.isOverflow() || result.isUnderflow());
do {
result = this.decoder.flush(this.outputBuffer);
if (result.isOverflow()) {
this.consume(consumer);
} else {
assert result.isUnderflow();
}
} while (!result.isUnderflow());
this.consume(consumer);
}
}