IndentedPrintWriter.java
/*
* Copyright (c) 2016, Stein Eldar Johnsen
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 net.morimekta.strings.io;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Locale;
import java.util.Stack;
/**
* Print writer that prints lines with indent. In order to have full control
* over the lines line-separators will be pre-pended the println calls, not
* appended. This is a break with the standard convention, but then the calls
* to println, appendln etc will always be consistent with the next
* line-length.
* <p>
* Example usage:
* <pre>{@code
* IndentedPrintWriter pw = new IndentedPrintWriter(out);
* pw.append("Start:");
* pw.begin();
* pw.println(144);
* pw.println(12.77d);
* pw.end();
* pw.appendln("end.");
* }</pre>
* <p>
* Will print the following:
* <pre>{@code
* Start:
* 144
* 12.77
* end.
* }</pre>
* <p>
* The print writer also has support for writing multiline strings into it's
* indented structure. E.g. (assuming java 14 string blocks):
* <pre>{@code
* IndentedPrintWriter pw = new IndentedPrintWriter(out, " ", "\n");
* pw.append("container:);
* pw.begin()
* pw.appendln("""
* foo:
* bar: bar
* list:
* - entry
* - entry2
* """);
* wp.end();
* }</pre>
* <p>
* This should print the following:
* <pre>{@code
* container:
* foo:
* bar: bar
* list:
* - entry
* - entry2
* }</pre>
* <p>
* Note that since the writer assumes to be at the end of last line, and
* will start a new line just <i>before</i> writing a new "line", the first
* line's content must be written with a call to {@link #append(CharSequence)}
* not to {@link #appendln(CharSequence)}.
*/
public class IndentedPrintWriter extends PrintWriter {
/**
* The default newline string.
*/
public final static String NEWLINE = "\n";
/**
* The default indent string.
*/
public final static String INDENT = " ";
private final Stack<String> indents;
private final String indent;
private final String newline;
private String current;
/**
* Make indented print writer with default indent and newline.
*
* @param out Stream to write to.
*/
public IndentedPrintWriter(OutputStream out) {
this(out, INDENT, NEWLINE);
}
/**
* Make indented print writer.
*
* @param out Stream to write to.
* @param indent Default indent.
* @param newline Newline character used.
*/
public IndentedPrintWriter(OutputStream out, String indent, String newline) {
this(new Utf8StreamWriter(out), indent, newline);
}
/**
* Make indented print writer with default indent and newline.
*
* @param out Writer to output to.
*/
public IndentedPrintWriter(Writer out) {
this(out, INDENT, NEWLINE);
}
/**
* Make indented print writer.
*
* @param out Writer to output to.
* @param indent Default indent.
* @param newline Newline character used.
*/
public IndentedPrintWriter(Writer out, String indent, String newline) {
super(out);
this.indent = indent;
this.newline = newline;
this.indents = new Stack<>();
this.current = "";
}
/**
* Begin a new indent block.
*
* @return The writer.
*/
public IndentedPrintWriter begin() {
return begin(indent);
}
/**
* Begin a new indent block.
*
* @param indent Indent to use instead of default.
* @return The writer.
*/
public IndentedPrintWriter begin(String indent) {
indents.push(current);
current = current + indent;
return this;
}
/**
* End an indent block.
*
* @return The writer.
*/
public IndentedPrintWriter end() {
if (indents.isEmpty()) {
throw new IllegalStateException("No indent to end");
}
current = indents.pop();
return this;
}
/**
* Create a new-line char, no indent after it.
*
* @return The writer.
*/
public IndentedPrintWriter newline() {
return append(newline);
}
/**
* Append a new line and prepare indent on new.
*
* @return The writer.
*/
public IndentedPrintWriter appendln() {
newline();
super.append(current);
return this;
}
/**
* Append a new line, indent and write char.
*
* @param c Char to write.
* @return The writer.
*/
public IndentedPrintWriter appendln(char c) {
appendln();
super.append(c);
return this;
}
/**
* Append a new line, indent and write string.
*
* @param str String to write. If the string is multiline, it will indent all the
* lines with the current indent. Empty lines will not be indented.
* @return The writer.
*/
public IndentedPrintWriter appendln(CharSequence str) {
String[] lines = str.toString().split("[\\n]");
for (String line : lines) {
if (line.isBlank()) {
newline();
} else {
appendln();
print(line);
}
}
return this;
}
/**
* Append a new line, indent and write a formatted string.
*
* @param format String format. If the resulting string is multiline, it will
* indent all the lines with the current indent. Empty lines will not be indented.
* @param args Args used to format line.
* @return The writer.
*/
public IndentedPrintWriter formatln(String format, Object... args) {
return appendln(String.format(format, args));
}
// --- Override PrintWriter methods to return IndentedPrintWriter.
@Override
public IndentedPrintWriter printf(String format, Object... args) {
super.format(format, args);
return this;
}
@Override
public IndentedPrintWriter printf(Locale l, String format, Object... args) {
super.format(l, format, args);
return this;
}
@Override
public IndentedPrintWriter format(String format, Object... args) {
super.format(format, args);
return this;
}
@Override
public IndentedPrintWriter format(Locale l, String format, Object... args) {
super.format(l, format, args);
return this;
}
@Override
public IndentedPrintWriter append(CharSequence str) {
super.append(str);
return this;
}
@Override
public IndentedPrintWriter append(CharSequence str, int start, int end) {
super.append(str, start, end);
return this;
}
@Override
public IndentedPrintWriter append(char c) {
super.append(c);
return this;
}
// --- Override PrintWriter methods to work like IndentedPrintWriter.
@Override
public void println() {
newline();
}
@Override
public void println(boolean x) {
appendln().print(x);
}
@Override
public void println(char x) {
appendln().print(x);
}
@Override
public void println(int x) {
appendln().print(x);
}
@Override
public void println(long x) {
appendln().print(x);
}
@Override
public void println(float x) {
appendln().print(x);
}
@Override
public void println(double x) {
appendln().print(x);
}
@Override
public void println(char[] x) {
appendln(new String(x));
}
@Override
public void println(String x) {
appendln(x);
}
@Override
public void println(Object x) {
appendln(String.valueOf(x));
}
}