001/*
002 * Copyright (C) 2012 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.io;
016
017import static com.google.common.base.Preconditions.checkNotNull;
018
019import com.google.common.annotations.Beta;
020import com.google.common.annotations.GwtIncompatible;
021import com.google.errorprone.annotations.CanIgnoreReturnValue;
022import java.io.BufferedWriter;
023import java.io.IOException;
024import java.io.Reader;
025import java.io.Writer;
026import java.nio.charset.Charset;
027import java.util.Iterator;
028import java.util.stream.Stream;
029
030/**
031 * A destination to which characters can be written, such as a text file. Unlike a {@link Writer}, a
032 * {@code CharSink} is not an open, stateful stream that can be written to and closed. Instead, it
033 * is an immutable <i>supplier</i> of {@code Writer} instances.
034 *
035 * <p>{@code CharSink} provides two kinds of methods:
036 * <ul>
037 * <li><b>Methods that return a writer:</b> These methods should return a <i>new</i>, independent
038 *     instance each time they are called. The caller is responsible for ensuring that the returned
039 *     writer is closed.
040 * <li><b>Convenience methods:</b> These are implementations of common operations that are typically
041 *     implemented by opening a writer using one of the methods in the first category, doing
042 *     something and finally closing the writer that was opened.
043 * </ul>
044 *
045 * <p>Any {@link ByteSink} may be viewed as a {@code CharSink} with a specific {@linkplain Charset
046 * character encoding} using {@link ByteSink#asCharSink(Charset)}. Characters written to the
047 * resulting {@code CharSink} will written to the {@code ByteSink} as encoded bytes.
048 *
049 * @since 14.0
050 * @author Colin Decker
051 */
052@GwtIncompatible
053public abstract class CharSink {
054
055  /**
056   * Constructor for use by subclasses.
057   */
058  protected CharSink() {}
059
060  /**
061   * Opens a new {@link Writer} for writing to this sink. This method returns a new, independent
062   * writer each time it is called.
063   *
064   * <p>The caller is responsible for ensuring that the returned writer is closed.
065   *
066   * @throws IOException if an I/O error occurs while opening the writer
067   */
068  public abstract Writer openStream() throws IOException;
069
070  /**
071   * Opens a new buffered {@link Writer} for writing to this sink. The returned stream is not
072   * required to be a {@link BufferedWriter} in order to allow implementations to simply delegate to
073   * {@link #openStream()} when the stream returned by that method does not benefit from additional
074   * buffering. This method returns a new, independent writer each time it is called.
075   *
076   * <p>The caller is responsible for ensuring that the returned writer is closed.
077   *
078   * @throws IOException if an I/O error occurs while opening the writer
079   * @since 15.0 (in 14.0 with return type {@link BufferedWriter})
080   */
081  public Writer openBufferedStream() throws IOException {
082    Writer writer = openStream();
083    return (writer instanceof BufferedWriter)
084        ? (BufferedWriter) writer
085        : new BufferedWriter(writer);
086  }
087
088  /**
089   * Writes the given character sequence to this sink.
090   *
091   * @throws IOException if an I/O error while writing to this sink
092   */
093  public void write(CharSequence charSequence) throws IOException {
094    checkNotNull(charSequence);
095
096    Closer closer = Closer.create();
097    try {
098      Writer out = closer.register(openStream());
099      out.append(charSequence);
100      out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330
101    } catch (Throwable e) {
102      throw closer.rethrow(e);
103    } finally {
104      closer.close();
105    }
106  }
107
108  /**
109   * Writes the given lines of text to this sink with each line (including the last) terminated with
110   * the operating system's default line separator. This method is equivalent to
111   * {@code writeLines(lines, System.getProperty("line.separator"))}.
112   *
113   * @throws IOException if an I/O error occurs while writing to this sink
114   */
115  public void writeLines(Iterable<? extends CharSequence> lines) throws IOException {
116    writeLines(lines, System.getProperty("line.separator"));
117  }
118
119  /**
120   * Writes the given lines of text to this sink with each line (including the last) terminated with
121   * the given line separator.
122   *
123   * @throws IOException if an I/O error occurs while writing to this sink
124   */
125  public void writeLines(Iterable<? extends CharSequence> lines, String lineSeparator)
126      throws IOException {
127    writeLines(lines.iterator(), lineSeparator);
128  }
129
130  /**
131   * Writes the given lines of text to this sink with each line (including the last) terminated with
132   * the operating system's default line separator. This method is equivalent to {@code
133   * writeLines(lines, System.getProperty("line.separator"))}.
134   *
135   * @throws IOException if an I/O error occurs while writing to this sink
136   * @since 22.0
137   */
138  @Beta
139  public void writeLines(Stream<? extends CharSequence> lines) throws IOException {
140    writeLines(lines, System.getProperty("line.separator"));
141  }
142
143  /**
144   * Writes the given lines of text to this sink with each line (including the last) terminated with
145   * the given line separator.
146   *
147   * @throws IOException if an I/O error occurs while writing to this sink
148   * @since 22.0
149   */
150  @Beta
151  public void writeLines(Stream<? extends CharSequence> lines, String lineSeparator)
152      throws IOException {
153    writeLines(lines.iterator(), lineSeparator);
154  }
155
156  private void writeLines(Iterator<? extends CharSequence> lines, String lineSeparator)
157      throws IOException {
158    checkNotNull(lineSeparator);
159
160    try (Writer out = openBufferedStream()) {
161      while (lines.hasNext()) {
162        out.append(lines.next()).append(lineSeparator);
163      }
164    }
165  }
166
167  /**
168   * Writes all the text from the given {@link Readable} (such as a {@link Reader}) to this sink.
169   * Does not close {@code readable} if it is {@code Closeable}.
170   *
171   * @return the number of characters written
172   * @throws IOException if an I/O error occurs while reading from {@code readable} or writing to
173   *     this sink
174   */
175  @CanIgnoreReturnValue
176  public long writeFrom(Readable readable) throws IOException {
177    checkNotNull(readable);
178
179    Closer closer = Closer.create();
180    try {
181      Writer out = closer.register(openStream());
182      long written = CharStreams.copy(readable, out);
183      out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330
184      return written;
185    } catch (Throwable e) {
186      throw closer.rethrow(e);
187    } finally {
188      closer.close();
189    }
190  }
191}