• 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 RecordingAppendable 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 = new RecordingAppendable(out);
51     this.indent = indent;
52     this.columnLimit = columnLimit;
53   }
54 
55   /** @return the last emitted char or {@link Character#MIN_VALUE} if nothing emitted yet. */
lastChar()56   char lastChar() {
57     return out.lastChar;
58   }
59 
60   /** Emit {@code s}. This may be buffered to permit line wraps to be inserted. */
append(String s)61   void append(String s) throws IOException {
62     if (closed) throw new IllegalStateException("closed");
63 
64     if (nextFlush != null) {
65       int nextNewline = s.indexOf('\n');
66 
67       // If s doesn't cause the current line to cross the limit, buffer it and return. We'll decide
68       // whether or not we have to wrap it later.
69       if (nextNewline == -1 && column + s.length() <= columnLimit) {
70         buffer.append(s);
71         column += s.length();
72         return;
73       }
74 
75       // Wrap if appending s would overflow the current line.
76       boolean wrap = nextNewline == -1 || column + nextNewline > columnLimit;
77       flush(wrap ? FlushType.WRAP : nextFlush);
78     }
79 
80     out.append(s);
81     int lastNewline = s.lastIndexOf('\n');
82     column = lastNewline != -1
83         ? s.length() - lastNewline - 1
84         : column + s.length();
85   }
86 
87   /** Emit either a space or a newline character. */
wrappingSpace(int indentLevel)88   void wrappingSpace(int indentLevel) throws IOException {
89     if (closed) throw new IllegalStateException("closed");
90 
91     if (this.nextFlush != null) flush(nextFlush);
92     column++; // Increment the column even though the space is deferred to next call to flush().
93     this.nextFlush = FlushType.SPACE;
94     this.indentLevel = indentLevel;
95   }
96 
97   /** Emit a newline character if the line will exceed it's limit, otherwise do nothing. */
zeroWidthSpace(int indentLevel)98   void zeroWidthSpace(int indentLevel) throws IOException {
99     if (closed) throw new IllegalStateException("closed");
100 
101     if (column == 0) return;
102     if (this.nextFlush != null) flush(nextFlush);
103     this.nextFlush = FlushType.EMPTY;
104     this.indentLevel = indentLevel;
105   }
106 
107   /** Flush any outstanding text and forbid future writes to this line wrapper. */
close()108   void close() throws IOException {
109     if (nextFlush != null) flush(nextFlush);
110     closed = true;
111   }
112 
113   /** Write the space followed by any buffered text that follows it. */
flush(FlushType flushType)114   private void flush(FlushType flushType) throws IOException {
115     switch (flushType) {
116       case WRAP:
117         out.append('\n');
118         for (int i = 0; i < indentLevel; i++) {
119           out.append(indent);
120         }
121         column = indentLevel * indent.length();
122         column += buffer.length();
123         break;
124       case SPACE:
125         out.append(' ');
126         break;
127       case EMPTY:
128         break;
129       default:
130         throw new IllegalArgumentException("Unknown FlushType: " + flushType);
131     }
132 
133     out.append(buffer);
134     buffer.delete(0, buffer.length());
135     indentLevel = -1;
136     nextFlush = null;
137   }
138 
139   private enum FlushType {
140     WRAP, SPACE, EMPTY;
141   }
142 
143   /** A delegating {@link Appendable} that records info about the chars passing through it. */
144   static final class RecordingAppendable implements Appendable {
145     private final Appendable delegate;
146 
147     char lastChar = Character.MIN_VALUE;
148 
RecordingAppendable(Appendable delegate)149     RecordingAppendable(Appendable delegate) {
150       this.delegate = delegate;
151     }
152 
append(CharSequence csq)153     @Override public Appendable append(CharSequence csq) throws IOException {
154       int length = csq.length();
155       if (length != 0) {
156         lastChar = csq.charAt(length - 1);
157       }
158       return delegate.append(csq);
159     }
160 
append(CharSequence csq, int start, int end)161     @Override public Appendable append(CharSequence csq, int start, int end) throws IOException {
162       CharSequence sub = csq.subSequence(start, end);
163       return append(sub);
164     }
165 
append(char c)166     @Override public Appendable append(char c) throws IOException {
167       lastChar = c;
168       return delegate.append(c);
169     }
170   }
171 }
172