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