Home | History | Annotate | Download | only in javapoet
      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 
     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. */
     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. */
     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. */
     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. */
    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. */
    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