1/* 2 * Copyright (C) 2012 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.common.io; 18 19import static com.google.common.base.Preconditions.checkNotNull; 20 21import com.google.common.annotations.Beta; 22import com.google.common.base.Ascii; 23import com.google.common.base.Splitter; 24import com.google.common.collect.AbstractIterator; 25import com.google.common.collect.ImmutableList; 26import com.google.common.collect.Lists; 27 28import java.io.BufferedReader; 29import java.io.IOException; 30import java.io.Reader; 31import java.io.Writer; 32import java.nio.charset.Charset; 33import java.util.Iterator; 34import java.util.List; 35import java.util.regex.Pattern; 36 37import javax.annotation.Nullable; 38 39/** 40 * A readable source of characters, such as a text file. Unlike a {@link Reader}, a 41 * {@code CharSource} is not an open, stateful stream of characters that can be read and closed. 42 * Instead, it is an immutable <i>supplier</i> of {@code Reader} instances. 43 * 44 * <p>{@code CharSource} provides two kinds of methods: 45 * <ul> 46 * <li><b>Methods that return a reader:</b> These methods should return a <i>new</i>, independent 47 * instance each time they are called. The caller is responsible for ensuring that the returned 48 * reader is closed. 49 * <li><b>Convenience methods:</b> These are implementations of common operations that are 50 * typically implemented by opening a reader using one of the methods in the first category, 51 * doing something and finally closing the reader that was opened. 52 * </ul> 53 * 54 * <p>Several methods in this class, such as {@link #readLines()}, break the contents of the 55 * source into lines. Like {@link BufferedReader}, these methods break lines on any of {@code \n}, 56 * {@code \r} or {@code \r\n}, do not include the line separator in each line and do not consider 57 * there to be an empty line at the end if the contents are terminated with a line separator. 58 * 59 * <p>Any {@link ByteSource} containing text encoded with a specific {@linkplain Charset character 60 * encoding} may be viewed as a {@code CharSource} using {@link ByteSource#asCharSource(Charset)}. 61 * 62 * @since 14.0 63 * @author Colin Decker 64 */ 65public abstract class CharSource { 66 67 /** 68 * Constructor for use by subclasses. 69 */ 70 protected CharSource() {} 71 72 /** 73 * Opens a new {@link Reader} for reading from this source. This method should return a new, 74 * independent reader each time it is called. 75 * 76 * <p>The caller is responsible for ensuring that the returned reader is closed. 77 * 78 * @throws IOException if an I/O error occurs in the process of opening the reader 79 */ 80 public abstract Reader openStream() throws IOException; 81 82 /** 83 * Opens a new {@link BufferedReader} for reading from this source. This method should return a 84 * new, independent reader each time it is called. 85 * 86 * <p>The caller is responsible for ensuring that the returned reader is closed. 87 * 88 * @throws IOException if an I/O error occurs in the process of opening the reader 89 */ 90 public BufferedReader openBufferedStream() throws IOException { 91 Reader reader = openStream(); 92 return (reader instanceof BufferedReader) 93 ? (BufferedReader) reader 94 : new BufferedReader(reader); 95 } 96 97 /** 98 * Appends the contents of this source to the given {@link Appendable} (such as a {@link Writer}). 99 * Does not close {@code appendable} if it is {@code Closeable}. 100 * 101 * @throws IOException if an I/O error occurs in the process of reading from this source or 102 * writing to {@code appendable} 103 */ 104 public long copyTo(Appendable appendable) throws IOException { 105 checkNotNull(appendable); 106 107 Closer closer = Closer.create(); 108 try { 109 Reader reader = closer.register(openStream()); 110 return CharStreams.copy(reader, appendable); 111 } catch (Throwable e) { 112 throw closer.rethrow(e); 113 } finally { 114 closer.close(); 115 } 116 } 117 118 /** 119 * Copies the contents of this source to the given sink. 120 * 121 * @throws IOException if an I/O error occurs in the process of reading from this source or 122 * writing to {@code sink} 123 */ 124 public long copyTo(CharSink sink) throws IOException { 125 checkNotNull(sink); 126 127 Closer closer = Closer.create(); 128 try { 129 Reader reader = closer.register(openStream()); 130 Writer writer = closer.register(sink.openStream()); 131 return CharStreams.copy(reader, writer); 132 } catch (Throwable e) { 133 throw closer.rethrow(e); 134 } finally { 135 closer.close(); 136 } 137 } 138 139 /** 140 * Reads the contents of this source as a string. 141 * 142 * @throws IOException if an I/O error occurs in the process of reading from this source 143 */ 144 public String read() throws IOException { 145 Closer closer = Closer.create(); 146 try { 147 Reader reader = closer.register(openStream()); 148 return CharStreams.toString(reader); 149 } catch (Throwable e) { 150 throw closer.rethrow(e); 151 } finally { 152 closer.close(); 153 } 154 } 155 156 /** 157 * Reads the first link of this source as a string. Returns {@code null} if this source is empty. 158 * 159 * <p>Like {@link BufferedReader}, this method breaks lines on any of {@code \n}, {@code \r} or 160 * {@code \r\n}, does not include the line separator in the returned line and does not consider 161 * there to be an extra empty line at the end if the content is terminated with a line separator. 162 * 163 * @throws IOException if an I/O error occurs in the process of reading from this source 164 */ 165 public @Nullable String readFirstLine() throws IOException { 166 Closer closer = Closer.create(); 167 try { 168 BufferedReader reader = closer.register(openBufferedStream()); 169 return reader.readLine(); 170 } catch (Throwable e) { 171 throw closer.rethrow(e); 172 } finally { 173 closer.close(); 174 } 175 } 176 177 /** 178 * Reads all the lines of this source as a list of strings. The returned list will be empty if 179 * this source is empty. 180 * 181 * <p>Like {@link BufferedReader}, this method breaks lines on any of {@code \n}, {@code \r} or 182 * {@code \r\n}, does not include the line separator in the returned lines and does not consider 183 * there to be an extra empty line at the end if the content is terminated with a line separator. 184 * 185 * @throws IOException if an I/O error occurs in the process of reading from this source 186 */ 187 public ImmutableList<String> readLines() throws IOException { 188 Closer closer = Closer.create(); 189 try { 190 BufferedReader reader = closer.register(openBufferedStream()); 191 List<String> result = Lists.newArrayList(); 192 String line; 193 while ((line = reader.readLine()) != null) { 194 result.add(line); 195 } 196 return ImmutableList.copyOf(result); 197 } catch (Throwable e) { 198 throw closer.rethrow(e); 199 } finally { 200 closer.close(); 201 } 202 } 203 204 /** 205 * Reads lines of text from this source, processing each line as it is read using the given 206 * {@link LineProcessor processor}. Stops when all lines have been processed or the processor 207 * returns {@code false} and returns the result produced by the processor. 208 * 209 * <p>Like {@link BufferedReader}, this method breaks lines on any of {@code \n}, {@code \r} or 210 * {@code \r\n}, does not include the line separator in the lines passed to the {@code processor} 211 * and does not consider there to be an extra empty line at the end if the content is terminated 212 * with a line separator. 213 * 214 * @throws IOException if an I/O error occurs in the process of reading from this source or if 215 * {@code processor} throws an {@code IOException} 216 * @since 16.0 217 */ 218 @Beta 219 public <T> T readLines(LineProcessor<T> processor) throws IOException { 220 checkNotNull(processor); 221 222 Closer closer = Closer.create(); 223 try { 224 Reader reader = closer.register(openStream()); 225 return CharStreams.readLines(reader, processor); 226 } catch (Throwable e) { 227 throw closer.rethrow(e); 228 } finally { 229 closer.close(); 230 } 231 } 232 233 /** 234 * Returns whether the source has zero chars. The default implementation is to open a stream and 235 * check for EOF. 236 * 237 * @throws IOException if an I/O error occurs 238 * @since 15.0 239 */ 240 public boolean isEmpty() throws IOException { 241 Closer closer = Closer.create(); 242 try { 243 Reader reader = closer.register(openStream()); 244 return reader.read() == -1; 245 } catch (Throwable e) { 246 throw closer.rethrow(e); 247 } finally { 248 closer.close(); 249 } 250 } 251 252 /** 253 * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from 254 * the source will contain the concatenated data from the streams of the underlying sources. 255 * 256 * <p>Only one underlying stream will be open at a time. Closing the concatenated stream will 257 * close the open underlying stream. 258 * 259 * @param sources the sources to concatenate 260 * @return a {@code CharSource} containing the concatenated data 261 * @since 15.0 262 */ 263 public static CharSource concat(Iterable<? extends CharSource> sources) { 264 return new ConcatenatedCharSource(sources); 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 * <p>Note: The input {@code Iterator} will be copied to an {@code ImmutableList} when this 275 * method is called. This will fail if the iterator is infinite and may cause problems if the 276 * iterator eagerly fetches data for each source when iterated (rather than producing sources 277 * that only load data through their streams). Prefer using the {@link #concat(Iterable)} 278 * overload if possible. 279 * 280 * @param sources the sources to concatenate 281 * @return a {@code CharSource} containing the concatenated data 282 * @throws NullPointerException if any of {@code sources} is {@code null} 283 * @since 15.0 284 */ 285 public static CharSource concat(Iterator<? extends CharSource> sources) { 286 return concat(ImmutableList.copyOf(sources)); 287 } 288 289 /** 290 * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from 291 * the source will contain the concatenated data from the streams of the underlying sources. 292 * 293 * <p>Only one underlying stream will be open at a time. Closing the concatenated stream will 294 * close the open underlying stream. 295 * 296 * @param sources the sources to concatenate 297 * @return a {@code CharSource} containing the concatenated data 298 * @throws NullPointerException if any of {@code sources} is {@code null} 299 * @since 15.0 300 */ 301 public static CharSource concat(CharSource... sources) { 302 return concat(ImmutableList.copyOf(sources)); 303 } 304 305 /** 306 * Returns a view of the given character sequence as a {@link CharSource}. The behavior of the 307 * returned {@code CharSource} and any {@code Reader} instances created by it is unspecified if 308 * the {@code charSequence} is mutated while it is being read, so don't do that. 309 * 310 * @since 15.0 (since 14.0 as {@code CharStreams.asCharSource(String)}) 311 */ 312 public static CharSource wrap(CharSequence charSequence) { 313 return new CharSequenceCharSource(charSequence); 314 } 315 316 /** 317 * Returns an immutable {@link CharSource} that contains no characters. 318 * 319 * @since 15.0 320 */ 321 public static CharSource empty() { 322 return EmptyCharSource.INSTANCE; 323 } 324 325 private static class CharSequenceCharSource extends CharSource { 326 327 private static final Splitter LINE_SPLITTER 328 = Splitter.on(Pattern.compile("\r\n|\n|\r")); 329 330 private final CharSequence seq; 331 332 protected CharSequenceCharSource(CharSequence seq) { 333 this.seq = checkNotNull(seq); 334 } 335 336 @Override 337 public Reader openStream() { 338 return new CharSequenceReader(seq); 339 } 340 341 @Override 342 public String read() { 343 return seq.toString(); 344 } 345 346 @Override 347 public boolean isEmpty() { 348 return seq.length() == 0; 349 } 350 351 /** 352 * Returns an iterable over the lines in the string. If the string ends in 353 * a newline, a final empty string is not included to match the behavior of 354 * BufferedReader/LineReader.readLine(). 355 */ 356 private Iterable<String> lines() { 357 return new Iterable<String>() { 358 @Override 359 public Iterator<String> iterator() { 360 return new AbstractIterator<String>() { 361 Iterator<String> lines = LINE_SPLITTER.split(seq).iterator(); 362 363 @Override 364 protected String computeNext() { 365 if (lines.hasNext()) { 366 String next = lines.next(); 367 // skip last line if it's empty 368 if (lines.hasNext() || !next.isEmpty()) { 369 return next; 370 } 371 } 372 return endOfData(); 373 } 374 }; 375 } 376 }; 377 } 378 379 @Override 380 public String readFirstLine() { 381 Iterator<String> lines = lines().iterator(); 382 return lines.hasNext() ? lines.next() : null; 383 } 384 385 @Override 386 public ImmutableList<String> readLines() { 387 return ImmutableList.copyOf(lines()); 388 } 389 390 @Override 391 public <T> T readLines(LineProcessor<T> processor) throws IOException { 392 for (String line : lines()) { 393 if (!processor.processLine(line)) { 394 break; 395 } 396 } 397 return processor.getResult(); 398 } 399 400 @Override 401 public String toString() { 402 return "CharSource.wrap(" + Ascii.truncate(seq, 30, "...") + ")"; 403 } 404 } 405 406 private static final class EmptyCharSource extends CharSequenceCharSource { 407 408 private static final EmptyCharSource INSTANCE = new EmptyCharSource(); 409 410 private EmptyCharSource() { 411 super(""); 412 } 413 414 @Override 415 public String toString() { 416 return "CharSource.empty()"; 417 } 418 } 419 420 private static final class ConcatenatedCharSource extends CharSource { 421 422 private final Iterable<? extends CharSource> sources; 423 424 ConcatenatedCharSource(Iterable<? extends CharSource> sources) { 425 this.sources = checkNotNull(sources); 426 } 427 428 @Override 429 public Reader openStream() throws IOException { 430 return new MultiReader(sources.iterator()); 431 } 432 433 @Override 434 public boolean isEmpty() throws IOException { 435 for (CharSource source : sources) { 436 if (!source.isEmpty()) { 437 return false; 438 } 439 } 440 return true; 441 } 442 443 @Override 444 public String toString() { 445 return "CharSource.concat(" + sources + ")"; 446 } 447 } 448} 449