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