001/*
002 * Copyright (C) 2012 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.io;
018
019import static com.google.common.base.Preconditions.checkNotNull;
020
021import com.google.common.annotations.Beta;
022import com.google.common.base.Splitter;
023import com.google.common.collect.AbstractIterator;
024import com.google.common.collect.ImmutableList;
025import com.google.common.collect.Lists;
026
027import java.io.BufferedReader;
028import java.io.IOException;
029import java.io.Reader;
030import java.io.Writer;
031import java.nio.charset.Charset;
032import java.util.Iterator;
033import java.util.List;
034import java.util.regex.Pattern;
035
036import javax.annotation.Nullable;
037
038/**
039 * A readable source of characters, such as a text file. Unlike a {@link Reader}, a
040 * {@code CharSource} is not an open, stateful stream of characters that can be read and closed.
041 * Instead, it is an immutable <i>supplier</i> of {@code Reader} instances.
042 *
043 * <p>{@code CharSource} provides two kinds of methods:
044 * <ul>
045 *   <li><b>Methods that return a reader:</b> These methods should return a <i>new</i>, independent
046 *   instance each time they are called. The caller is responsible for ensuring that the returned
047 *   reader is closed.
048 *   <li><b>Convenience methods:</b> These are implementations of common operations that are
049 *   typically implemented by opening a reader using one of the methods in the first category,
050 *   doing something and finally closing the reader that was opened.
051 * </ul>
052 *
053 * <p>Several methods in this class, such as {@link #readLines()}, break the contents of the
054 * source into lines. Like {@link BufferedReader}, these methods break lines on any of {@code \n},
055 * {@code \r} or {@code \r\n}, do not include the line separator in each line and do not consider
056 * there to be an empty line at the end if the contents are terminated with a line separator.
057 *
058 * <p>Any {@link ByteSource} containing text encoded with a specific {@linkplain Charset character
059 * encoding} may be viewed as a {@code CharSource} using {@link ByteSource#asCharSource(Charset)}.
060 *
061 * @since 14.0
062 * @author Colin Decker
063 */
064public abstract class CharSource implements InputSupplier<Reader> {
065
066  /**
067   * Constructor for use by subclasses.
068   */
069  protected CharSource() {}
070
071  /**
072   * Opens a new {@link Reader} for reading from this source. This method should return a new,
073   * independent reader each time it is called.
074   *
075   * <p>The caller is responsible for ensuring that the returned reader is closed.
076   *
077   * @throws IOException if an I/O error occurs in the process of opening the reader
078   */
079  public abstract Reader openStream() throws IOException;
080
081  /**
082   * This method is a temporary method provided for easing migration from suppliers to sources and
083   * sinks.
084   *
085   * @since 15.0
086   * @deprecated This method is only provided for temporary compatibility with the
087   *     {@link InputSupplier} interface and should not be called directly. Use {@link #openStream}
088   *     instead. This method is scheduled for removal in Guava 18.0.
089   */
090  @Override
091  @Deprecated
092  public final Reader getInput() throws IOException {
093    return openStream();
094  }
095
096  /**
097   * Opens a new {@link BufferedReader} for reading from this source. This method should return a
098   * new, independent reader each time it is called.
099   *
100   * <p>The caller is responsible for ensuring that the returned reader is closed.
101   *
102   * @throws IOException if an I/O error occurs in the process of opening the reader
103   */
104  public BufferedReader openBufferedStream() throws IOException {
105    Reader reader = openStream();
106    return (reader instanceof BufferedReader)
107        ? (BufferedReader) reader
108        : new BufferedReader(reader);
109  }
110
111  /**
112   * Appends the contents of this source to the given {@link Appendable} (such as a {@link Writer}).
113   * Does not close {@code appendable} if it is {@code Closeable}.
114   *
115   * @throws IOException if an I/O error occurs in the process of reading from this source or
116   *     writing to {@code appendable}
117   */
118  public long copyTo(Appendable appendable) throws IOException {
119    checkNotNull(appendable);
120
121    Closer closer = Closer.create();
122    try {
123      Reader reader = closer.register(openStream());
124      return CharStreams.copy(reader, appendable);
125    } catch (Throwable e) {
126      throw closer.rethrow(e);
127    } finally {
128      closer.close();
129    }
130  }
131
132  /**
133   * Copies the contents of this source to the given sink.
134   *
135   * @throws IOException if an I/O error occurs in the process of reading from this source or
136   *     writing to {@code sink}
137   */
138  public long copyTo(CharSink sink) throws IOException {
139    checkNotNull(sink);
140
141    Closer closer = Closer.create();
142    try {
143      Reader reader = closer.register(openStream());
144      Writer writer = closer.register(sink.openStream());
145      return CharStreams.copy(reader, writer);
146    } catch (Throwable e) {
147      throw closer.rethrow(e);
148    } finally {
149      closer.close();
150    }
151  }
152
153  /**
154   * Reads the contents of this source as a string.
155   *
156   * @throws IOException if an I/O error occurs in the process of reading from this source
157   */
158  public String read() throws IOException {
159    Closer closer = Closer.create();
160    try {
161      Reader reader = closer.register(openStream());
162      return CharStreams.toString(reader);
163    } catch (Throwable e) {
164      throw closer.rethrow(e);
165    } finally {
166      closer.close();
167    }
168  }
169
170  /**
171   * Reads the first link of this source as a string. Returns {@code null} if this source is empty.
172   *
173   * <p>Like {@link BufferedReader}, this method breaks lines on any of {@code \n}, {@code \r} or
174   * {@code \r\n}, does not include the line separator in the returned line and does not consider
175   * there to be an extra empty line at the end if the content is terminated with a line separator.
176   *
177   * @throws IOException if an I/O error occurs in the process of reading from this source
178   */
179  public @Nullable String readFirstLine() throws IOException {
180    Closer closer = Closer.create();
181    try {
182      BufferedReader reader = closer.register(openBufferedStream());
183      return reader.readLine();
184    } catch (Throwable e) {
185      throw closer.rethrow(e);
186    } finally {
187      closer.close();
188    }
189  }
190
191  /**
192   * Reads all the lines of this source as a list of strings. The returned list will be empty if
193   * this source is empty.
194   *
195   * <p>Like {@link BufferedReader}, this method breaks lines on any of {@code \n}, {@code \r} or
196   * {@code \r\n}, does not include the line separator in the returned lines and does not consider
197   * there to be an extra empty line at the end if the content is terminated with a line separator.
198   *
199   * @throws IOException if an I/O error occurs in the process of reading from this source
200   */
201  public ImmutableList<String> readLines() throws IOException {
202    Closer closer = Closer.create();
203    try {
204      BufferedReader reader = closer.register(openBufferedStream());
205      List<String> result = Lists.newArrayList();
206      String line;
207      while ((line = reader.readLine()) != null) {
208        result.add(line);
209      }
210      return ImmutableList.copyOf(result);
211    } catch (Throwable e) {
212      throw closer.rethrow(e);
213    } finally {
214      closer.close();
215    }
216  }
217
218  /**
219   * Reads lines of text from this source, processing each line as it is read using the given
220   * {@link LineProcessor processor}. Stops when all lines have been processed or the processor
221   * returns {@code false} and returns the result produced by the processor.
222   *
223   * <p>Like {@link BufferedReader}, this method breaks lines on any of {@code \n}, {@code \r} or
224   * {@code \r\n}, does not include the line separator in the lines passed to the {@code processor}
225   * and does not consider there to be an extra empty line at the end if the content is terminated
226   * with a line separator.
227   *
228   * @throws IOException if an I/O error occurs in the process of reading from this source or if
229   *     {@code processor} throws an {@code IOException}
230   * @since 16.0
231   */
232  @Beta
233  public <T> T readLines(LineProcessor<T> processor) throws IOException {
234    checkNotNull(processor);
235
236    Closer closer = Closer.create();
237    try {
238      Reader reader = closer.register(openStream());
239      return CharStreams.readLines(reader, processor);
240    } catch (Throwable e) {
241      throw closer.rethrow(e);
242    } finally {
243      closer.close();
244    }
245  }
246
247  /**
248   * Returns whether the source has zero chars. The default implementation is to open a stream and
249   * check for EOF.
250   *
251   * @throws IOException if an I/O error occurs
252   * @since 15.0
253   */
254  public boolean isEmpty() throws IOException {
255    Closer closer = Closer.create();
256    try {
257      Reader reader = closer.register(openStream());
258      return reader.read() == -1;
259    } catch (Throwable e) {
260      throw closer.rethrow(e);
261    } finally {
262      closer.close();
263    }
264  }
265
266  /**
267   * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from
268   * the source will contain the concatenated data from the streams of the underlying sources.
269   *
270   * <p>Only one underlying stream will be open at a time. Closing the  concatenated stream will
271   * close the open underlying stream.
272   *
273   * @param sources the sources to concatenate
274   * @return a {@code CharSource} containing the concatenated data
275   * @since 15.0
276   */
277  public static CharSource concat(Iterable<? extends CharSource> sources) {
278    return new ConcatenatedCharSource(sources);
279  }
280
281  /**
282   * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from
283   * the source will contain the concatenated data from the streams of the underlying sources.
284   *
285   * <p>Only one underlying stream will be open at a time. Closing the concatenated stream will
286   * close the open underlying stream.
287   *
288   * <p>Note: The input {@code Iterator} will be copied to an {@code ImmutableList} when this
289   * method is called. This will fail if the iterator is infinite and may cause problems if the
290   * iterator eagerly fetches data for each source when iterated (rather than producing sources
291   * that only load data through their streams). Prefer using the {@link #concat(Iterable)}
292   * overload if possible.
293   *
294   * @param sources the sources to concatenate
295   * @return a {@code CharSource} containing the concatenated data
296   * @throws NullPointerException if any of {@code sources} is {@code null}
297   * @since 15.0
298   */
299  public static CharSource concat(Iterator<? extends CharSource> sources) {
300    return concat(ImmutableList.copyOf(sources));
301  }
302
303  /**
304   * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from
305   * the source will contain the concatenated data from the streams of the underlying sources.
306   *
307   * <p>Only one underlying stream will be open at a time. Closing the concatenated stream will
308   * close the open underlying stream.
309   *
310   * @param sources the sources to concatenate
311   * @return a {@code CharSource} containing the concatenated data
312   * @throws NullPointerException if any of {@code sources} is {@code null}
313   * @since 15.0
314   */
315  public static CharSource concat(CharSource... sources) {
316    return concat(ImmutableList.copyOf(sources));
317  }
318
319  /**
320   * Returns a view of the given character sequence as a {@link CharSource}. The behavior of the
321   * returned {@code CharSource} and any {@code Reader} instances created by it is unspecified if
322   * the {@code charSequence} is mutated while it is being read, so don't do that.
323   *
324   * @since 15.0 (since 14.0 as {@code CharStreams.asCharSource(String)})
325   */
326  public static CharSource wrap(CharSequence charSequence) {
327    return new CharSequenceCharSource(charSequence);
328  }
329
330  /**
331   * Returns an immutable {@link CharSource} that contains no characters.
332   *
333   * @since 15.0
334   */
335  public static CharSource empty() {
336    return EmptyCharSource.INSTANCE;
337  }
338
339  private static class CharSequenceCharSource extends CharSource {
340
341    private static final Splitter LINE_SPLITTER
342        = Splitter.on(Pattern.compile("\r\n|\n|\r"));
343
344    private final CharSequence seq;
345
346    protected CharSequenceCharSource(CharSequence seq) {
347      this.seq = checkNotNull(seq);
348    }
349
350    @Override
351    public Reader openStream() {
352      return new CharSequenceReader(seq);
353    }
354
355    @Override
356    public String read() {
357      return seq.toString();
358    }
359
360    @Override
361    public boolean isEmpty() {
362      return seq.length() == 0;
363    }
364
365    /**
366     * Returns an iterable over the lines in the string. If the string ends in
367     * a newline, a final empty string is not included to match the behavior of
368     * BufferedReader/LineReader.readLine().
369     */
370    private Iterable<String> lines() {
371      return new Iterable<String>() {
372        @Override
373        public Iterator<String> iterator() {
374          return new AbstractIterator<String>() {
375            Iterator<String> lines = LINE_SPLITTER.split(seq).iterator();
376
377            @Override
378            protected String computeNext() {
379              if (lines.hasNext()) {
380                String next = lines.next();
381                // skip last line if it's empty
382                if (lines.hasNext() || !next.isEmpty()) {
383                  return next;
384                }
385              }
386              return endOfData();
387            }
388          };
389        }
390      };
391    }
392
393    @Override
394    public String readFirstLine() {
395      Iterator<String> lines = lines().iterator();
396      return lines.hasNext() ? lines.next() : null;
397    }
398
399    @Override
400    public ImmutableList<String> readLines() {
401      return ImmutableList.copyOf(lines());
402    }
403
404    @Override
405    public <T> T readLines(LineProcessor<T> processor) throws IOException {
406      for (String line : lines()) {
407        if (!processor.processLine(line)) {
408          break;
409        }
410      }
411      return processor.getResult();
412    }
413
414    @Override
415    public String toString() {
416      CharSequence shortened = (seq.length() <= 15) ? seq : seq.subSequence(0, 12) + "...";
417      return "CharSource.wrap(" + shortened + ")";
418    }
419  }
420
421  private static final class EmptyCharSource extends CharSequenceCharSource {
422
423    private static final EmptyCharSource INSTANCE = new EmptyCharSource();
424
425    private EmptyCharSource() {
426      super("");
427    }
428
429    @Override
430    public String toString() {
431      return "CharSource.empty()";
432    }
433  }
434
435  private static final class ConcatenatedCharSource extends CharSource {
436
437    private final Iterable<? extends CharSource> sources;
438
439    ConcatenatedCharSource(Iterable<? extends CharSource> sources) {
440      this.sources = checkNotNull(sources);
441    }
442
443    @Override
444    public Reader openStream() throws IOException {
445      return new MultiReader(sources.iterator());
446    }
447
448    @Override
449    public boolean isEmpty() throws IOException {
450      for (CharSource source : sources) {
451        if (!source.isEmpty()) {
452          return false;
453        }
454      }
455      return true;
456    }
457
458    @Override
459    public String toString() {
460      return "CharSource.concat(" + sources + ")";
461    }
462  }
463}