All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.deplant.javapoet.LineWrapper Maven / Gradle / Ivy

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);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy