tech.deplant.javapoet.LineWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javapoet-core Show documentation
Show all versions of javapoet-core Show documentation
Use beautiful Java code to generate beautiful Java code.
The newest version!
/*
* Copyright (C) 2016 Square, Inc.
*
* 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 tech.deplant.javapoet;
import java.io.IOException;
/**
* Implements soft line wrapping on an appendable. To use, append characters using {@link #append}
* or soft-wrapping spaces using {@link #wrappingSpace}.
*/
final class LineWrapper {
private final RecordingAppendable out;
private final String indent;
private final int columnLimit;
/**
* Characters written since the last wrapping space that haven't yet been flushed.
*/
private final StringBuilder buffer = new StringBuilder();
private boolean closed;
/**
* The number of characters since the most recent newline. Includes both out and the buffer.
*/
private int column = 0;
/**
* -1 if we have no buffering; otherwise the number of {@code indent}s to write after wrapping.
*/
private int indentLevel = -1;
/**
* Null if we have no buffering; otherwise the type to pass to the next call to {@link #flush}.
*/
private FlushType nextFlush;
LineWrapper(Appendable out, String indent, int columnLimit) {
Util.checkNotNull(out, "out == null");
this.out = new RecordingAppendable(out);
this.indent = indent;
this.columnLimit = columnLimit;
}
/**
* @return the last emitted char or {@link Character#MIN_VALUE} if nothing emitted yet.
*/
char lastChar() {
return this.out.lastChar;
}
/**
* Emit {@code s}. This may be buffered to permit line wraps to be inserted.
*/
void append(String s) throws IOException {
if (this.closed) {
throw new IllegalStateException("closed");
}
if (this.nextFlush != null) {
int nextNewline = s.indexOf('\n');
// If s doesn't cause the current line to cross the limit, buffer it and return. We'll decide
// whether or not we have to wrap it later.
if (nextNewline == -1 && this.column + s.length() <= this.columnLimit) {
this.buffer.append(s);
this.column += s.length();
return;
}
// Wrap if appending s would overflow the current line.
boolean wrap = nextNewline == -1 || this.column + nextNewline > this.columnLimit;
flush(wrap ? FlushType.WRAP : this.nextFlush);
}
this.out.append(s);
int lastNewline = s.lastIndexOf('\n');
this.column = lastNewline != -1
? s.length() - lastNewline - 1
: this.column + s.length();
}
/**
* Emit either a space or a newline character.
*/
void wrappingSpace(int indentLevel) throws IOException {
if (this.closed) {
throw new IllegalStateException("closed");
}
if (this.nextFlush != null) {
flush(this.nextFlush);
}
this.column++; // Increment the column even though the space is deferred to next call to flush().
this.nextFlush = FlushType.SPACE;
this.indentLevel = indentLevel;
}
/**
* Emit a newline character if the line will exceed it's limit, otherwise do nothing.
*/
void zeroWidthSpace(int indentLevel) throws IOException {
if (this.closed) {
throw new IllegalStateException("closed");
}
if (this.column == 0) {
return;
}
if (this.nextFlush != null) {
flush(this.nextFlush);
}
this.nextFlush = FlushType.EMPTY;
this.indentLevel = indentLevel;
}
/**
* Flush any outstanding text and forbid future writes to this line wrapper.
*/
void close() throws IOException {
if (this.nextFlush != null) {
flush(this.nextFlush);
}
this.closed = true;
}
/**
* Write the space followed by any buffered text that follows it.
*/
private void flush(FlushType flushType) throws IOException {
switch (flushType) {
case WRAP:
this.out.append('\n');
for (int i = 0; i < this.indentLevel; i++) {
this.out.append(this.indent);
}
this.column = this.indentLevel * this.indent.length();
this.column += this.buffer.length();
break;
case SPACE:
this.out.append(' ');
break;
case EMPTY:
break;
default:
throw new IllegalArgumentException("Unknown FlushType: " + flushType);
}
this.out.append(this.buffer);
this.buffer.delete(0, this.buffer.length());
this.indentLevel = -1;
this.nextFlush = null;
}
private enum FlushType {
WRAP,
SPACE,
EMPTY
}
/**
* A delegating {@link Appendable} that records info about the chars passing through it.
*/
static final class RecordingAppendable implements Appendable {
private final Appendable delegate;
char lastChar = Character.MIN_VALUE;
RecordingAppendable(Appendable delegate) {
this.delegate = delegate;
}
@Override
public Appendable append(CharSequence csq) throws IOException {
int length = csq.length();
if (length != 0) {
this.lastChar = csq.charAt(length - 1);
}
return this.delegate.append(csq);
}
@Override
public Appendable append(CharSequence csq, int start, int end) throws IOException {
CharSequence sub = csq.subSequence(start, end);
return append(sub);
}
@Override
public Appendable append(char c) throws IOException {
this.lastChar = c;
return this.delegate.append(c);
}
}
}