Coverage Summary for Class: CharSource (com.google.common.io)

Class Method, % Line, %
CharSource 9.5% (2/21) 2% (2/102)
CharSource$AsByteSource 0% (0/4) 0% (0/8)
CharSource$CharSequenceCharSource 14.3% (2/14) 19% (4/21)
CharSource$CharSequenceCharSource$1 0% (0/2) 0% (0/7)
CharSource$ConcatenatedCharSource 0% (0/6) 0% (0/23)
CharSource$EmptyCharSource 75% (3/4) 80% (4/5)
CharSource$StringCharSource 25% (1/4) 14.3% (2/14)
Total 14.5% (8/55) 6.7% (12/180)


1 /* 2  * Copyright (C) 2012 The Guava Authors 3  * 4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5  * in compliance with the License. You may obtain a copy of the License at 6  * 7  * http://www.apache.org/licenses/LICENSE-2.0 8  * 9  * Unless required by applicable law or agreed to in writing, software distributed under the License 10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11  * or implied. See the License for the specific language governing permissions and limitations under 12  * the License. 13  */ 14  15 package com.google.common.io; 16  17 import static com.google.common.base.Preconditions.checkNotNull; 18  19 import com.google.common.annotations.Beta; 20 import com.google.common.annotations.GwtIncompatible; 21 import com.google.common.base.Ascii; 22 import com.google.common.base.Optional; 23 import com.google.common.base.Splitter; 24 import com.google.common.collect.AbstractIterator; 25 import com.google.common.collect.ImmutableList; 26 import com.google.common.collect.Lists; 27 import com.google.common.collect.Streams; 28 import com.google.errorprone.annotations.CanIgnoreReturnValue; 29 import com.google.errorprone.annotations.MustBeClosed; 30 import java.io.BufferedReader; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.Reader; 34 import java.io.StringReader; 35 import java.io.UncheckedIOException; 36 import java.io.Writer; 37 import java.nio.charset.Charset; 38 import java.util.Iterator; 39 import java.util.List; 40 import java.util.function.Consumer; 41 import java.util.stream.Stream; 42 import javax.annotation.CheckForNull; 43 import org.checkerframework.checker.nullness.qual.Nullable; 44  45 /** 46  * A readable source of characters, such as a text file. Unlike a {@link Reader}, a {@code 47  * CharSource} is not an open, stateful stream of characters that can be read and closed. Instead, 48  * it is an immutable <i>supplier</i> of {@code Reader} instances. 49  * 50  * <p>{@code CharSource} provides two kinds of methods: 51  * 52  * <ul> 53  * <li><b>Methods that return a reader:</b> These methods should return a <i>new</i>, independent 54  * instance each time they are called. The caller is responsible for ensuring that the 55  * returned reader is closed. 56  * <li><b>Convenience methods:</b> These are implementations of common operations that are 57  * typically implemented by opening a reader using one of the methods in the first category, 58  * doing something and finally closing the reader that was opened. 59  * </ul> 60  * 61  * <p>Several methods in this class, such as {@link #readLines()}, break the contents of the source 62  * into lines. Like {@link BufferedReader}, these methods break lines on any of {@code \n}, {@code 63  * \r} or {@code \r\n}, do not include the line separator in each line and do not consider there to 64  * be an empty line at the end if the contents are terminated with a line separator. 65  * 66  * <p>Any {@link ByteSource} containing text encoded with a specific {@linkplain Charset character 67  * encoding} may be viewed as a {@code CharSource} using {@link ByteSource#asCharSource(Charset)}. 68  * 69  * <p><b>Note:</b> In general, {@code CharSource} is intended to be used for "file-like" sources 70  * that provide readers that are: 71  * 72  * <ul> 73  * <li><b>Finite:</b> Many operations, such as {@link #length()} and {@link #read()}, will either 74  * block indefinitely or fail if the source creates an infinite reader. 75  * <li><b>Non-destructive:</b> A <i>destructive</i> reader will consume or otherwise alter the 76  * source as they are read from it. A source that provides such readers will not be reusable, 77  * and operations that read from the stream (including {@link #length()}, in some 78  * implementations) will prevent further operations from completing as expected. 79  * </ul> 80  * 81  * @since 14.0 82  * @author Colin Decker 83  */ 84 @GwtIncompatible 85 @ElementTypesAreNonnullByDefault 86 public abstract class CharSource { 87  88  /** Constructor for use by subclasses. */ 89  protected CharSource() {} 90  91  /** 92  * Returns a {@link ByteSource} view of this char source that encodes chars read from this source 93  * as bytes using the given {@link Charset}. 94  * 95  * <p>If {@link ByteSource#asCharSource} is called on the returned source with the same charset, 96  * the default implementation of this method will ensure that the original {@code CharSource} is 97  * returned, rather than round-trip encoding. Subclasses that override this method should behave 98  * the same way. 99  * 100  * @since 20.0 101  */ 102  @Beta 103  public ByteSource asByteSource(Charset charset) { 104  return new AsByteSource(charset); 105  } 106  107  /** 108  * Opens a new {@link Reader} for reading from this source. This method returns a new, independent 109  * reader each time it is called. 110  * 111  * <p>The caller is responsible for ensuring that the returned reader is closed. 112  * 113  * @throws IOException if an I/O error occurs while opening the reader 114  */ 115  public abstract Reader openStream() throws IOException; 116  117  /** 118  * Opens a new {@link BufferedReader} for reading from this source. This method returns a new, 119  * independent reader each time it is called. 120  * 121  * <p>The caller is responsible for ensuring that the returned reader is closed. 122  * 123  * @throws IOException if an I/O error occurs while of opening the reader 124  */ 125  public BufferedReader openBufferedStream() throws IOException { 126  Reader reader = openStream(); 127  return (reader instanceof BufferedReader) 128  ? (BufferedReader) reader 129  : new BufferedReader(reader); 130  } 131  132  /** 133  * Opens a new {@link Stream} for reading text one line at a time from this source. This method 134  * returns a new, independent stream each time it is called. 135  * 136  * <p>The returned stream is lazy and only reads from the source in the terminal operation. If an 137  * I/O error occurs while the stream is reading from the source or when the stream is closed, an 138  * {@link UncheckedIOException} is thrown. 139  * 140  * <p>Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of 141  * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code 142  * \n}. If the source's content does not end in a line termination sequence, it is treated as if 143  * it does. 144  * 145  * <p>The caller is responsible for ensuring that the returned stream is closed. For example: 146  * 147  * <pre>{@code 148  * try (Stream<String> lines = source.lines()) { 149  * lines.map(...) 150  * .filter(...) 151  * .forEach(...); 152  * } 153  * }</pre> 154  * 155  * @throws IOException if an I/O error occurs while opening the stream 156  * @since 22.0 157  */ 158  @Beta 159  @MustBeClosed 160  public Stream<String> lines() throws IOException { 161  BufferedReader reader = openBufferedStream(); 162  return reader 163  .lines() 164  .onClose( 165  () -> { 166  try { 167  reader.close(); 168  } catch (IOException e) { 169  throw new UncheckedIOException(e); 170  } 171  }); 172  } 173  174  /** 175  * Returns the size of this source in chars, if the size can be easily determined without actually 176  * opening the data stream. 177  * 178  * <p>The default implementation returns {@link Optional#absent}. Some sources, such as a {@code 179  * CharSequence}, may return a non-absent value. Note that in such cases, it is <i>possible</i> 180  * that this method will return a different number of chars than would be returned by reading all 181  * of the chars. 182  * 183  * <p>Additionally, for mutable sources such as {@code StringBuilder}s, a subsequent read may 184  * return a different number of chars if the contents are changed. 185  * 186  * @since 19.0 187  */ 188  @Beta 189  public Optional<Long> lengthIfKnown() { 190  return Optional.absent(); 191  } 192  193  /** 194  * Returns the length of this source in chars, even if doing so requires opening and traversing an 195  * entire stream. To avoid a potentially expensive operation, see {@link #lengthIfKnown}. 196  * 197  * <p>The default implementation calls {@link #lengthIfKnown} and returns the value if present. If 198  * absent, it will fall back to a heavyweight operation that will open a stream, {@link 199  * Reader#skip(long) skip} to the end of the stream, and return the total number of chars that 200  * were skipped. 201  * 202  * <p>Note that for sources that implement {@link #lengthIfKnown} to provide a more efficient 203  * implementation, it is <i>possible</i> that this method will return a different number of chars 204  * than would be returned by reading all of the chars. 205  * 206  * <p>In either case, for mutable sources such as files, a subsequent read may return a different 207  * number of chars if the contents are changed. 208  * 209  * @throws IOException if an I/O error occurs while reading the length of this source 210  * @since 19.0 211  */ 212  @Beta 213  public long length() throws IOException { 214  Optional<Long> lengthIfKnown = lengthIfKnown(); 215  if (lengthIfKnown.isPresent()) { 216  return lengthIfKnown.get(); 217  } 218  219  Closer closer = Closer.create(); 220  try { 221  Reader reader = closer.register(openStream()); 222  return countBySkipping(reader); 223  } catch (Throwable e) { 224  throw closer.rethrow(e); 225  } finally { 226  closer.close(); 227  } 228  } 229  230  private long countBySkipping(Reader reader) throws IOException { 231  long count = 0; 232  long read; 233  while ((read = reader.skip(Long.MAX_VALUE)) != 0) { 234  count += read; 235  } 236  return count; 237  } 238  239  /** 240  * Appends the contents of this source to the given {@link Appendable} (such as a {@link Writer}). 241  * Does not close {@code appendable} if it is {@code Closeable}. 242  * 243  * @return the number of characters copied 244  * @throws IOException if an I/O error occurs while reading from this source or writing to {@code 245  * appendable} 246  */ 247  @CanIgnoreReturnValue 248  public long copyTo(Appendable appendable) throws IOException { 249  checkNotNull(appendable); 250  251  Closer closer = Closer.create(); 252  try { 253  Reader reader = closer.register(openStream()); 254  return CharStreams.copy(reader, appendable); 255  } catch (Throwable e) { 256  throw closer.rethrow(e); 257  } finally { 258  closer.close(); 259  } 260  } 261  262  /** 263  * Copies the contents of this source to the given sink. 264  * 265  * @return the number of characters copied 266  * @throws IOException if an I/O error occurs while reading from this source or writing to {@code 267  * sink} 268  */ 269  @CanIgnoreReturnValue 270  public long copyTo(CharSink sink) throws IOException { 271  checkNotNull(sink); 272  273  Closer closer = Closer.create(); 274  try { 275  Reader reader = closer.register(openStream()); 276  Writer writer = closer.register(sink.openStream()); 277  return CharStreams.copy(reader, writer); 278  } catch (Throwable e) { 279  throw closer.rethrow(e); 280  } finally { 281  closer.close(); 282  } 283  } 284  285  /** 286  * Reads the contents of this source as a string. 287  * 288  * @throws IOException if an I/O error occurs while reading from this source 289  */ 290  public String read() throws IOException { 291  Closer closer = Closer.create(); 292  try { 293  Reader reader = closer.register(openStream()); 294  return CharStreams.toString(reader); 295  } catch (Throwable e) { 296  throw closer.rethrow(e); 297  } finally { 298  closer.close(); 299  } 300  } 301  302  /** 303  * Reads the first line of this source as a string. Returns {@code null} if this source is empty. 304  * 305  * <p>Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of 306  * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code 307  * \n}. If the source's content does not end in a line termination sequence, it is treated as if 308  * it does. 309  * 310  * @throws IOException if an I/O error occurs while reading from this source 311  */ 312  @CheckForNull 313  public String readFirstLine() throws IOException { 314  Closer closer = Closer.create(); 315  try { 316  BufferedReader reader = closer.register(openBufferedStream()); 317  return reader.readLine(); 318  } catch (Throwable e) { 319  throw closer.rethrow(e); 320  } finally { 321  closer.close(); 322  } 323  } 324  325  /** 326  * Reads all the lines of this source as a list of strings. The returned list will be empty if 327  * this source is empty. 328  * 329  * <p>Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of 330  * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code 331  * \n}. If the source's content does not end in a line termination sequence, it is treated as if 332  * it does. 333  * 334  * @throws IOException if an I/O error occurs while reading from this source 335  */ 336  public ImmutableList<String> readLines() throws IOException { 337  Closer closer = Closer.create(); 338  try { 339  BufferedReader reader = closer.register(openBufferedStream()); 340  List<String> result = Lists.newArrayList(); 341  String line; 342  while ((line = reader.readLine()) != null) { 343  result.add(line); 344  } 345  return ImmutableList.copyOf(result); 346  } catch (Throwable e) { 347  throw closer.rethrow(e); 348  } finally { 349  closer.close(); 350  } 351  } 352  353  /** 354  * Reads lines of text from this source, processing each line as it is read using the given {@link 355  * LineProcessor processor}. Stops when all lines have been processed or the processor returns 356  * {@code false} and returns the result produced by the processor. 357  * 358  * <p>Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of 359  * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code 360  * \n}. If the source's content does not end in a line termination sequence, it is treated as if 361  * it does. 362  * 363  * @throws IOException if an I/O error occurs while reading from this source or if {@code 364  * processor} throws an {@code IOException} 365  * @since 16.0 366  */ 367  @Beta 368  @CanIgnoreReturnValue // some processors won't return a useful result 369  @ParametricNullness 370  public <T extends @Nullable Object> T readLines(LineProcessor<T> processor) throws IOException { 371  checkNotNull(processor); 372  373  Closer closer = Closer.create(); 374  try { 375  Reader reader = closer.register(openStream()); 376  return CharStreams.readLines(reader, processor); 377  } catch (Throwable e) { 378  throw closer.rethrow(e); 379  } finally { 380  closer.close(); 381  } 382  } 383  384  /** 385  * Reads all lines of text from this source, running the given {@code action} for each line as it 386  * is read. 387  * 388  * <p>Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of 389  * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code 390  * \n}. If the source's content does not end in a line termination sequence, it is treated as if 391  * it does. 392  * 393  * @throws IOException if an I/O error occurs while reading from this source or if {@code action} 394  * throws an {@code UncheckedIOException} 395  * @since 22.0 396  */ 397  @Beta 398  public void forEachLine(Consumer<? super String> action) throws IOException { 399  try (Stream<String> lines = lines()) { 400  // The lines should be ordered regardless in most cases, but use forEachOrdered to be sure 401  lines.forEachOrdered(action); 402  } catch (UncheckedIOException e) { 403  throw e.getCause(); 404  } 405  } 406  407  /** 408  * Returns whether the source has zero chars. The default implementation first checks {@link 409  * #lengthIfKnown}, returning true if it's known to be zero and false if it's known to be 410  * non-zero. If the length is not known, it falls back to opening a stream and checking for EOF. 411  * 412  * <p>Note that, in cases where {@code lengthIfKnown} returns zero, it is <i>possible</i> that 413  * chars are actually available for reading. This means that a source may return {@code true} from 414  * {@code isEmpty()} despite having readable content. 415  * 416  * @throws IOException if an I/O error occurs 417  * @since 15.0 418  */ 419  public boolean isEmpty() throws IOException { 420  Optional<Long> lengthIfKnown = lengthIfKnown(); 421  if (lengthIfKnown.isPresent()) { 422  return lengthIfKnown.get() == 0L; 423  } 424  Closer closer = Closer.create(); 425  try { 426  Reader reader = closer.register(openStream()); 427  return reader.read() == -1; 428  } catch (Throwable e) { 429  throw closer.rethrow(e); 430  } finally { 431  closer.close(); 432  } 433  } 434  435  /** 436  * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from 437  * the source will contain the concatenated data from the streams of the underlying sources. 438  * 439  * <p>Only one underlying stream will be open at a time. Closing the concatenated stream will 440  * close the open underlying stream. 441  * 442  * @param sources the sources to concatenate 443  * @return a {@code CharSource} containing the concatenated data 444  * @since 15.0 445  */ 446  public static CharSource concat(Iterable<? extends CharSource> sources) { 447  return new ConcatenatedCharSource(sources); 448  } 449  450  /** 451  * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from 452  * the source will contain the concatenated data from the streams of the underlying sources. 453  * 454  * <p>Only one underlying stream will be open at a time. Closing the concatenated stream will 455  * close the open underlying stream. 456  * 457  * <p>Note: The input {@code Iterator} will be copied to an {@code ImmutableList} when this method 458  * is called. This will fail if the iterator is infinite and may cause problems if the iterator 459  * eagerly fetches data for each source when iterated (rather than producing sources that only 460  * load data through their streams). Prefer using the {@link #concat(Iterable)} overload if 461  * possible. 462  * 463  * @param sources the sources to concatenate 464  * @return a {@code CharSource} containing the concatenated data 465  * @throws NullPointerException if any of {@code sources} is {@code null} 466  * @since 15.0 467  */ 468  public static CharSource concat(Iterator<? extends CharSource> sources) { 469  return concat(ImmutableList.copyOf(sources)); 470  } 471  472  /** 473  * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from 474  * the source will contain the concatenated data from the streams of the underlying sources. 475  * 476  * <p>Only one underlying stream will be open at a time. Closing the concatenated stream will 477  * close the open underlying stream. 478  * 479  * @param sources the sources to concatenate 480  * @return a {@code CharSource} containing the concatenated data 481  * @throws NullPointerException if any of {@code sources} is {@code null} 482  * @since 15.0 483  */ 484  public static CharSource concat(CharSource... sources) { 485  return concat(ImmutableList.copyOf(sources)); 486  } 487  488  /** 489  * Returns a view of the given character sequence as a {@link CharSource}. The behavior of the 490  * returned {@code CharSource} and any {@code Reader} instances created by it is unspecified if 491  * the {@code charSequence} is mutated while it is being read, so don't do that. 492  * 493  * @since 15.0 (since 14.0 as {@code CharStreams.asCharSource(String)}) 494  */ 495  public static CharSource wrap(CharSequence charSequence) { 496  return charSequence instanceof String 497  ? new StringCharSource((String) charSequence) 498  : new CharSequenceCharSource(charSequence); 499  } 500  501  /** 502  * Returns an immutable {@link CharSource} that contains no characters. 503  * 504  * @since 15.0 505  */ 506  public static CharSource empty() { 507  return EmptyCharSource.INSTANCE; 508  } 509  510  /** A byte source that reads chars from this source and encodes them as bytes using a charset. */ 511  private final class AsByteSource extends ByteSource { 512  513  final Charset charset; 514  515  AsByteSource(Charset charset) { 516  this.charset = checkNotNull(charset); 517  } 518  519  @Override 520  public CharSource asCharSource(Charset charset) { 521  if (charset.equals(this.charset)) { 522  return CharSource.this; 523  } 524  return super.asCharSource(charset); 525  } 526  527  @Override 528  public InputStream openStream() throws IOException { 529  return new ReaderInputStream(CharSource.this.openStream(), charset, 8192); 530  } 531  532  @Override 533  public String toString() { 534  return CharSource.this.toString() + ".asByteSource(" + charset + ")"; 535  } 536  } 537  538  private static class CharSequenceCharSource extends CharSource { 539  540  private static final Splitter LINE_SPLITTER = Splitter.onPattern("\r\n|\n|\r"); 541  542  protected final CharSequence seq; 543  544  protected CharSequenceCharSource(CharSequence seq) { 545  this.seq = checkNotNull(seq); 546  } 547  548  @Override 549  public Reader openStream() { 550  return new CharSequenceReader(seq); 551  } 552  553  @Override 554  public String read() { 555  return seq.toString(); 556  } 557  558  @Override 559  public boolean isEmpty() { 560  return seq.length() == 0; 561  } 562  563  @Override 564  public long length() { 565  return seq.length(); 566  } 567  568  @Override 569  public Optional<Long> lengthIfKnown() { 570  return Optional.of((long) seq.length()); 571  } 572  573  /** 574  * Returns an iterator over the lines in the string. If the string ends in a newline, a final 575  * empty string is not included, to match the behavior of BufferedReader/LineReader.readLine(). 576  */ 577  private Iterator<String> linesIterator() { 578  return new AbstractIterator<String>() { 579  Iterator<String> lines = LINE_SPLITTER.split(seq).iterator(); 580  581  @Override 582  protected String computeNext() { 583  if (lines.hasNext()) { 584  String next = lines.next(); 585  // skip last line if it's empty 586  if (lines.hasNext() || !next.isEmpty()) { 587  return next; 588  } 589  } 590  return endOfData(); 591  } 592  }; 593  } 594  595  @Override 596  public Stream<String> lines() { 597  return Streams.stream(linesIterator()); 598  } 599  600  @Override 601  @CheckForNull 602  public String readFirstLine() { 603  Iterator<String> lines = linesIterator(); 604  return lines.hasNext() ? lines.next() : null; 605  } 606  607  @Override 608  public ImmutableList<String> readLines() { 609  return ImmutableList.copyOf(linesIterator()); 610  } 611  612  @Override 613  @ParametricNullness 614  public <T extends @Nullable Object> T readLines(LineProcessor<T> processor) throws IOException { 615  Iterator<String> lines = linesIterator(); 616  while (lines.hasNext()) { 617  if (!processor.processLine(lines.next())) { 618  break; 619  } 620  } 621  return processor.getResult(); 622  } 623  624  @Override 625  public String toString() { 626  return "CharSource.wrap(" + Ascii.truncate(seq, 30, "...") + ")"; 627  } 628  } 629  630  /** 631  * Subclass specialized for string instances. 632  * 633  * <p>Since Strings are immutable and built into the jdk we can optimize some operations 634  * 635  * <ul> 636  * <li>use {@link StringReader} instead of {@link CharSequenceReader}. It is faster since it can 637  * use {@link String#getChars(int, int, char[], int)} instead of copying characters one by 638  * one with {@link CharSequence#charAt(int)}. 639  * <li>use {@link Appendable#append(CharSequence)} in {@link #copyTo(Appendable)} and {@link 640  * #copyTo(CharSink)}. We know this is correct since strings are immutable and so the length 641  * can't change, and it is faster because many writers and appendables are optimized for 642  * appending string instances. 643  * </ul> 644  */ 645  private static class StringCharSource extends CharSequenceCharSource { 646  protected StringCharSource(String seq) { 647  super(seq); 648  } 649  650  @Override 651  public Reader openStream() { 652  return new StringReader((String) seq); 653  } 654  655  @Override 656  public long copyTo(Appendable appendable) throws IOException { 657  appendable.append(seq); 658  return seq.length(); 659  } 660  661  @Override 662  public long copyTo(CharSink sink) throws IOException { 663  checkNotNull(sink); 664  Closer closer = Closer.create(); 665  try { 666  Writer writer = closer.register(sink.openStream()); 667  writer.write((String) seq); 668  return seq.length(); 669  } catch (Throwable e) { 670  throw closer.rethrow(e); 671  } finally { 672  closer.close(); 673  } 674  } 675  } 676  677  private static final class EmptyCharSource extends StringCharSource { 678  679  private static final EmptyCharSource INSTANCE = new EmptyCharSource(); 680  681  private EmptyCharSource() { 682  super(""); 683  } 684  685  @Override 686  public String toString() { 687  return "CharSource.empty()"; 688  } 689  } 690  691  private static final class ConcatenatedCharSource extends CharSource { 692  693  private final Iterable<? extends CharSource> sources; 694  695  ConcatenatedCharSource(Iterable<? extends CharSource> sources) { 696  this.sources = checkNotNull(sources); 697  } 698  699  @Override 700  public Reader openStream() throws IOException { 701  return new MultiReader(sources.iterator()); 702  } 703  704  @Override 705  public boolean isEmpty() throws IOException { 706  for (CharSource source : sources) { 707  if (!source.isEmpty()) { 708  return false; 709  } 710  } 711  return true; 712  } 713  714  @Override 715  public Optional<Long> lengthIfKnown() { 716  long result = 0L; 717  for (CharSource source : sources) { 718  Optional<Long> lengthIfKnown = source.lengthIfKnown(); 719  if (!lengthIfKnown.isPresent()) { 720  return Optional.absent(); 721  } 722  result += lengthIfKnown.get(); 723  } 724  return Optional.of(result); 725  } 726  727  @Override 728  public long length() throws IOException { 729  long result = 0L; 730  for (CharSource source : sources) { 731  result += source.length(); 732  } 733  return result; 734  } 735  736  @Override 737  public String toString() { 738  return "CharSource.concat(" + sources + ")"; 739  } 740  } 741 }