com.diffplug.common.base.StringPrinter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of durian Show documentation
Show all versions of durian Show documentation
Durian - Guava's spikier (unofficial) cousin
/**
* Copyright 2015 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.common.base;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
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;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
/**
* A simple class for receiving a stream of Strings in a way that resembles a PrintStream or PrintWriter.
*/
public class StringPrinter {
private final Consumer consumer;
/** StringPrinter will pass all the strings it receives to the given consumer. */
public StringPrinter(Consumer consumer) {
this.consumer = consumer;
}
/** Prints the string and a newline (always '\n'). */
public void println(String line) {
consumer.accept(line);
consumer.accept("\n");
}
/** Prints the string. */
public void print(String content) {
consumer.accept(content);
}
/** Easy way to create a String using a StringPrinter. */
public static String buildString(Consumer printer) {
StringBuilder builder = new StringBuilder();
printer.accept(new StringPrinter(builder::append));
return builder.toString();
}
/** Easy way to create a String from a bunch of lines. */
public static String buildStringFromLines(String... lines) {
int numChars = lines.length;
for (String line : lines) {
numChars += line.length();
}
StringBuilder builder = new StringBuilder(numChars);
for (String line : lines) {
builder.append(line);
builder.append('\n');
}
return builder.toString();
}
/**
* Creates an OutputStream which will print its content to the given StringPrinter, encoding bytes according to the given Charset.
* Doesn't matter if you close the stream or not, because StringPrinter doesn't have a close().
*
* Strings are sent to the consumer as soon as their consituent bytes are written to this OutputStream.
*
* The implementation is lifted from Apache commons-io. Many thanks to them!
*/
public OutputStream toOutputStream(Charset charset) {
CharsetDecoder decoder = charset.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.replaceWith("?");
ByteBuffer decoderIn = ByteBuffer.allocate(DECODER_BUFFER);
CharBuffer decoderOut = CharBuffer.allocate(DECODER_BUFFER);
return new OutputStream() {
@Override
public void write(final int b) throws IOException {
write(new byte[]{(byte) b});
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
while (len > 0) {
final int c = Math.min(len, decoderIn.remaining());
decoderIn.put(b, off, c);
processInput(false);
len -= c;
off += c;
}
flushOutput();
}
private void processInput(final boolean endOfInput) throws IOException {
// Prepare decoderIn for reading
decoderIn.flip();
CoderResult coderResult;
while (true) {
coderResult = decoder.decode(decoderIn, decoderOut, endOfInput);
if (coderResult.isOverflow()) {
flushOutput();
} else if (coderResult.isUnderflow()) {
break;
} else {
// The decoder is configured to replace malformed input and unmappable characters,
// so we should not get here.
throw new IOException("Unexpected coder result");
}
}
// Discard the bytes that have been read
decoderIn.compact();
}
private void flushOutput() throws IOException {
if (decoderOut.position() > 0) {
consumer.accept(new String(decoderOut.array(), 0, decoderOut.position()));
decoderOut.rewind();
}
}
};
}
/** Buffer size for decoding characters. */
private static final int DECODER_BUFFER = 128;
/** Creates a UTF-8 PrintStream which passes its content to this StringPrinter. */
public PrintStream toPrintStream() {
return toPrintStream(StandardCharsets.UTF_8);
}
/** Creates a PrintStream of the given charset, which passes its content to this StringPrinter. */
public PrintStream toPrintStream(Charset charset) {
return Errors.rethrow().get(() -> {
return new PrintStream(toOutputStream(charset), true, charset.name());
});
}
/** Creates a Writer which passes its content to this StringPrinter. */
public Writer toWriter() {
return new Writer() {
@Override
public Writer append(char c) {
consumer.accept(new String(new char[]{c}));
return this;
}
@Override
public Writer append(CharSequence csq) {
StringBuilder sb = new StringBuilder(csq.length());
sb.append(csq);
consumer.accept(sb.toString());
return this;
}
@Override
public Writer append(CharSequence csq, int start, int end) {
StringBuilder sb = new StringBuilder(end - start);
sb.append(csq.subSequence(start, end));
consumer.accept(sb.toString());
return this;
}
@Override
public void close() throws IOException {}
@Override
public void flush() throws IOException {}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
consumer.accept(new String(cbuf, off, len));
}
@Override
public void write(String str) {
consumer.accept(str);
}
@Override
public void write(String str, int off, int len) {
consumer.accept(str.substring(off, off + len));
}
};
}
/** Creates a PrintWriter which passes its content to this StringPrinter. */
public PrintWriter toPrintWriter() {
boolean autoflush = true;
return new PrintWriter(toWriter(), autoflush);
}
/**
* Given a consumer of lines, creates a stateful consumer of strings
* which will combine its input until it finds a newline, and
* split its input when it contains multiple newlines. Examples
* make this more clear:
*
* e.g. "some", "\n", "simple ", "lines", "\n" -> "some", "simple lines"
* "some\nsimple lines\n" -> "some", "simple lines"
* "no newline\nno output" -> "no newline"
*
* @param perLine a Consumer which will receive strings which were terminated by newlines (but aren't anymore).
* @return a Consumer which accepts any strings, and will feed them to perLine.
*/
public static Consumer stringsToLines(Consumer perLine) {
Box.NonNull leftover = Box.NonNull.of("");
return rawString -> {
rawString = leftover.get() + rawString.replace("\r", "");
int lastIdx = 0;
int idx = 0;
while ((idx = rawString.indexOf('\n', lastIdx)) > -1) {
perLine.accept(rawString.substring(lastIdx, idx));
lastIdx = idx + 1;
}
leftover.set(rawString.substring(lastIdx));
};
}
}