1 /* 2 * Copyright (C) 2016 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.squareup.javapoet; 17 18 import java.io.IOException; 19 20 import static com.squareup.javapoet.Util.checkNotNull; 21 22 /** 23 * Implements soft line wrapping on an appendable. To use, append characters using {@link #append} 24 * or soft-wrapping spaces using {@link #wrappingSpace}. 25 */ 26 final class LineWrapper { 27 private final Appendable out; 28 private final String indent; 29 private final int columnLimit; 30 private boolean closed; 31 32 /** Characters written since the last wrapping space that haven't yet been flushed. */ 33 private final StringBuilder buffer = new StringBuilder(); 34 35 /** The number of characters since the most recent newline. Includes both out and the buffer. */ 36 private int column = 0; 37 38 /** 39 * -1 if we have no buffering; otherwise the number of {@code indent}s to write after wrapping. 40 */ 41 private int indentLevel = -1; 42 43 /** 44 * Null if we have no buffering; otherwise the type to pass to the next call to {@link #flush}. 45 */ 46 private FlushType nextFlush; 47 LineWrapper(Appendable out, String indent, int columnLimit)48 LineWrapper(Appendable out, String indent, int columnLimit) { 49 checkNotNull(out, "out == null"); 50 this.out = out; 51 this.indent = indent; 52 this.columnLimit = columnLimit; 53 } 54 55 /** Emit {@code s}. This may be buffered to permit line wraps to be inserted. */ append(String s)56 void append(String s) throws IOException { 57 if (closed) throw new IllegalStateException("closed"); 58 59 if (nextFlush != null) { 60 int nextNewline = s.indexOf('\n'); 61 62 // If s doesn't cause the current line to cross the limit, buffer it and return. We'll decide 63 // whether or not we have to wrap it later. 64 if (nextNewline == -1 && column + s.length() <= columnLimit) { 65 buffer.append(s); 66 column += s.length(); 67 return; 68 } 69 70 // Wrap if appending s would overflow the current line. 71 boolean wrap = nextNewline == -1 || column + nextNewline > columnLimit; 72 flush(wrap ? FlushType.WRAP : nextFlush); 73 } 74 75 out.append(s); 76 int lastNewline = s.lastIndexOf('\n'); 77 column = lastNewline != -1 78 ? s.length() - lastNewline - 1 79 : column + s.length(); 80 } 81 82 /** Emit either a space or a newline character. */ wrappingSpace(int indentLevel)83 void wrappingSpace(int indentLevel) throws IOException { 84 if (closed) throw new IllegalStateException("closed"); 85 86 if (this.nextFlush != null) flush(nextFlush); 87 column++; // Increment the column even though the space is deferred to next call to flush(). 88 this.nextFlush = FlushType.SPACE; 89 this.indentLevel = indentLevel; 90 } 91 92 /** Emit a newline character if the line will exceed it's limit, otherwise do nothing. */ zeroWidthSpace(int indentLevel)93 void zeroWidthSpace(int indentLevel) throws IOException { 94 if (closed) throw new IllegalStateException("closed"); 95 96 if (column == 0) return; 97 if (this.nextFlush != null) flush(nextFlush); 98 this.nextFlush = FlushType.EMPTY; 99 this.indentLevel = indentLevel; 100 } 101 102 /** Flush any outstanding text and forbid future writes to this line wrapper. */ close()103 void close() throws IOException { 104 if (nextFlush != null) flush(nextFlush); 105 closed = true; 106 } 107 108 /** Write the space followed by any buffered text that follows it. */ flush(FlushType flushType)109 private void flush(FlushType flushType) throws IOException { 110 switch (flushType) { 111 case WRAP: 112 out.append('\n'); 113 for (int i = 0; i < indentLevel; i++) { 114 out.append(indent); 115 } 116 column = indentLevel * indent.length(); 117 column += buffer.length(); 118 break; 119 case SPACE: 120 out.append(' '); 121 break; 122 case EMPTY: 123 break; 124 default: 125 throw new IllegalArgumentException("Unknown FlushType: " + flushType); 126 } 127 128 out.append(buffer); 129 buffer.delete(0, buffer.length()); 130 indentLevel = -1; 131 nextFlush = null; 132 } 133 134 private enum FlushType { 135 WRAP, SPACE, EMPTY; 136 } 137 } 138