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