• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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