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;
018import static com.google.common.base.StandardSystemProperty.LINE_SEPARATOR;
019
020import com.google.common.annotations.GwtIncompatible;
021import com.google.common.annotations.J2ktIncompatible;
022import com.google.errorprone.annotations.CanIgnoreReturnValue;
023import java.io.BufferedWriter;
024import java.io.IOException;
025import java.io.Reader;
026import java.io.Writer;
027import java.nio.charset.Charset;
028import java.util.Iterator;
029import java.util.stream.Stream;
030
031/**
032 * A destination to which characters can be written, such as a text file. Unlike a {@link Writer}, a
033 * {@code CharSink} is not an open, stateful stream that can be written to and closed. Instead, it
034 * is an immutable <i>supplier</i> of {@code Writer} instances.
035 *
036 * <p>{@code CharSink} provides two kinds of methods:
037 *
038 * <ul>
039 *   <li><b>Methods that return a writer:</b> These methods should return a <i>new</i>, independent
040 *       instance each time they are called. The caller is responsible for ensuring that the
041 *       returned writer is closed.
042 *   <li><b>Convenience methods:</b> These are implementations of common operations that are
043 *       typically implemented by opening a writer using one of the methods in the first category,
044 *       doing something and finally closing the writer that was opened.
045 * </ul>
046 *
047 * <p>Any {@link ByteSink} may be viewed as a {@code CharSink} with a specific {@linkplain Charset
048 * character encoding} using {@link ByteSink#asCharSink(Charset)}. Characters written to the
049 * resulting {@code CharSink} will written to the {@code ByteSink} as encoded bytes.
050 *
051 * @since 14.0
052 * @author Colin Decker
053 */
054@J2ktIncompatible
055@GwtIncompatible
056public abstract class CharSink {
057
058  /** Constructor for use by subclasses. */
059  protected CharSink() {}
060
061  /**
062   * Opens a new {@link Writer} for writing to this sink. This method returns a new, independent
063   * writer each time it is called.
064   *
065   * <p>The caller is responsible for ensuring that the returned writer is closed.
066   *
067   * @throws IOException if an I/O error occurs while opening the writer
068   */
069  public abstract Writer openStream() throws IOException;
070
071  /**
072   * Opens a new buffered {@link Writer} for writing to this sink. The returned stream is not
073   * required to be a {@link BufferedWriter} in order to allow implementations to simply delegate to
074   * {@link #openStream()} when the stream returned by that method does not benefit from additional
075   * buffering. This method returns a new, independent writer each time it is called.
076   *
077   * <p>The caller is responsible for ensuring that the returned writer is closed.
078   *
079   * @throws IOException if an I/O error occurs while opening the writer
080   * @since 15.0 (in 14.0 with return type {@link BufferedWriter})
081   */
082  public Writer openBufferedStream() throws IOException {
083    Writer writer = openStream();
084    return (writer instanceof BufferedWriter)
085        ? (BufferedWriter) writer
086        : new BufferedWriter(writer);
087  }
088
089  /**
090   * Writes the given character sequence to this sink.
091   *
092   * @throws IOException if an I/O error while writing to this sink
093   */
094  public void write(CharSequence charSequence) throws IOException {
095    checkNotNull(charSequence);
096
097    try (Writer out = openStream()) {
098      out.append(charSequence);
099    }
100  }
101
102  /**
103   * Writes the given lines of text to this sink with each line (including the last) terminated with
104   * the operating system's default line separator. This method is equivalent to {@code
105   * writeLines(lines, System.getProperty("line.separator"))}.
106   *
107   * @throws IOException if an I/O error occurs while writing to this sink
108   */
109  public void writeLines(Iterable<? extends CharSequence> lines) throws IOException {
110    writeLines(lines, System.getProperty("line.separator"));
111  }
112
113  /**
114   * Writes the given lines of text to this sink with each line (including the last) terminated with
115   * the given line separator.
116   *
117   * @throws IOException if an I/O error occurs while writing to this sink
118   */
119  public void writeLines(Iterable<? extends CharSequence> lines, String lineSeparator)
120      throws IOException {
121    writeLines(lines.iterator(), lineSeparator);
122  }
123
124  /**
125   * Writes the given lines of text to this sink with each line (including the last) terminated with
126   * the operating system's default line separator. This method is equivalent to {@code
127   * writeLines(lines, System.getProperty("line.separator"))}.
128   *
129   * @throws IOException if an I/O error occurs while writing to this sink
130   * @since 22.0 (but only since 33.4.0 in the Android flavor)
131   */
132  public void writeLines(Stream<? extends CharSequence> lines) throws IOException {
133    writeLines(lines, LINE_SEPARATOR.value());
134  }
135
136  /**
137   * Writes the given lines of text to this sink with each line (including the last) terminated with
138   * the given line separator.
139   *
140   * @throws IOException if an I/O error occurs while writing to this sink
141   * @since 22.0 (but only since 33.4.0 in the Android flavor)
142   */
143  public void writeLines(Stream<? extends CharSequence> lines, String lineSeparator)
144      throws IOException {
145    writeLines(lines.iterator(), lineSeparator);
146  }
147
148  private void writeLines(Iterator<? extends CharSequence> lines, String lineSeparator)
149      throws IOException {
150    checkNotNull(lineSeparator);
151
152    try (Writer out = openBufferedStream()) {
153      while (lines.hasNext()) {
154        out.append(lines.next()).append(lineSeparator);
155      }
156    }
157  }
158
159  /**
160   * Writes all the text from the given {@link Readable} (such as a {@link Reader}) to this sink.
161   * Does not close {@code readable} if it is {@code Closeable}.
162   *
163   * @return the number of characters written
164   * @throws IOException if an I/O error occurs while reading from {@code readable} or writing to
165   *     this sink
166   */
167  @CanIgnoreReturnValue
168  public long writeFrom(Readable readable) throws IOException {
169    checkNotNull(readable);
170
171    try (Writer out = openStream()) {
172      return CharStreams.copy(readable, out);
173    }
174  }
175}