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.checkArgument; 020import static com.google.common.base.Preconditions.checkNotNull; 021 022import com.google.common.hash.Funnels; 023import com.google.common.hash.HashCode; 024import com.google.common.hash.HashFunction; 025import com.google.common.hash.Hasher; 026 027import java.io.BufferedInputStream; 028import java.io.IOException; 029import java.io.InputStream; 030import java.io.InputStreamReader; 031import java.io.OutputStream; 032import java.io.Reader; 033import java.nio.charset.Charset; 034import java.util.Arrays; 035 036/** 037 * A readable source of bytes, such as a file. Unlike an {@link InputStream}, a 038 * {@code ByteSource} is not an open, stateful stream for input that can be read and closed. 039 * Instead, it is an immutable <i>supplier</i> of {@code InputStream} instances. 040 * 041 * <p>{@code ByteSource} provides two kinds of methods: 042 * <ul> 043 * <li><b>Methods that return a stream:</b> These methods should return a <i>new</i>, independent 044 * instance each time they are called. The caller is responsible for ensuring that the returned 045 * stream is closed. 046 * <li><b>Convenience methods:</b> These are implementations of common operations that are 047 * typically implemented by opening a stream using one of the methods in the first category, doing 048 * something and finally closing the stream that was opened. 049 * </ul> 050 * 051 * @since 14.0 052 * @author Colin Decker 053 */ 054public abstract class ByteSource { 055 056 private static final int BUF_SIZE = 0x1000; // 4K 057 058 /** 059 * Returns a {@link CharSource} view of this byte source that decodes bytes read from this source 060 * as characters using the given {@link Charset}. 061 */ 062 public CharSource asCharSource(Charset charset) { 063 return new AsCharSource(charset); 064 } 065 066 /** 067 * Opens a new {@link InputStream} for reading from this source. This method should return a new, 068 * independent stream each time it is called. 069 * 070 * <p>The caller is responsible for ensuring that the returned stream is closed. 071 * 072 * @throws IOException if an I/O error occurs in the process of opening the stream 073 */ 074 public abstract InputStream openStream() throws IOException; 075 076 /** 077 * Opens a new {@link BufferedInputStream} for reading from this source. This method should return 078 * a new, independent stream each time it is called. 079 * 080 * <p>The caller is responsible for ensuring that the returned stream is closed. 081 * 082 * @throws IOException if an I/O error occurs in the process of opening the stream 083 */ 084 public BufferedInputStream openBufferedStream() throws IOException { 085 InputStream in = openStream(); 086 return (in instanceof BufferedInputStream) 087 ? (BufferedInputStream) in 088 : new BufferedInputStream(in); 089 } 090 091 /** 092 * Returns a view of a slice of this byte source that is at most {@code length} bytes long 093 * starting at the given {@code offset}. 094 * 095 * @throws IllegalArgumentException if {@code offset} or {@code length} is negative 096 */ 097 public ByteSource slice(long offset, long length) { 098 return new SlicedByteSource(offset, length); 099 } 100 101 /** 102 * Returns the size of this source in bytes. For most implementations, this is a heavyweight 103 * operation that will open a stream, read (or {@link InputStream#skip(long) skip}, if possible) 104 * to the end of the stream and return the total number of bytes that were read. 105 * 106 * <p>For some sources, such as a file, this method may use a more efficient implementation. Note 107 * that in such cases, it is <i>possible</i> that this method will return a different number of 108 * bytes than would be returned by reading all of the bytes (for example, some special files may 109 * return a size of 0 despite actually having content when read). 110 * 111 * <p>In either case, if this is a mutable source such as a file, the size it returns may not be 112 * the same number of bytes a subsequent read would return. 113 * 114 * @throws IOException if an I/O error occurs in the process of reading the size of this source 115 */ 116 public long size() throws IOException { 117 Closer closer = Closer.create(); 118 try { 119 InputStream in = closer.register(openStream()); 120 return countBySkipping(in); 121 } catch (IOException e) { 122 // skip may not be supported... at any rate, try reading 123 } finally { 124 closer.close(); 125 } 126 127 closer = Closer.create(); 128 try { 129 InputStream in = closer.register(openStream()); 130 return countByReading(in); 131 } catch (Throwable e) { 132 throw closer.rethrow(e); 133 } finally { 134 closer.close(); 135 } 136 } 137 138 /** 139 * Counts the bytes in the given input stream using skip if possible. Returns SKIP_FAILED if the 140 * first call to skip threw, in which case skip may just not be supported. 141 */ 142 private long countBySkipping(InputStream in) throws IOException { 143 long count = 0; 144 while (true) { 145 // don't try to skip more than available() 146 // things may work really wrong with FileInputStream otherwise 147 long skipped = in.skip(Math.min(in.available(), Integer.MAX_VALUE)); 148 if (skipped <= 0) { 149 if (in.read() == -1) { 150 return count; 151 } 152 count++; 153 } else { 154 count += skipped; 155 } 156 } 157 } 158 159 private static final byte[] countBuffer = new byte[BUF_SIZE]; 160 161 private long countByReading(InputStream in) throws IOException { 162 long count = 0; 163 long read; 164 while ((read = in.read(countBuffer)) != -1) { 165 count += read; 166 } 167 return count; 168 } 169 170 /** 171 * Copies the contents of this byte source to the given {@code OutputStream}. Does not close 172 * {@code output}. 173 * 174 * @throws IOException if an I/O error occurs in the process of reading from this source or 175 * writing to {@code output} 176 */ 177 public long copyTo(OutputStream output) throws IOException { 178 checkNotNull(output); 179 180 Closer closer = Closer.create(); 181 try { 182 InputStream in = closer.register(openStream()); 183 return ByteStreams.copy(in, output); 184 } catch (Throwable e) { 185 throw closer.rethrow(e); 186 } finally { 187 closer.close(); 188 } 189 } 190 191 /** 192 * Copies the contents of this byte source to the given {@code ByteSink}. 193 * 194 * @throws IOException if an I/O error occurs in the process of reading from this source or 195 * writing to {@code sink} 196 */ 197 public long copyTo(ByteSink sink) throws IOException { 198 checkNotNull(sink); 199 200 Closer closer = Closer.create(); 201 try { 202 InputStream in = closer.register(openStream()); 203 OutputStream out = closer.register(sink.openStream()); 204 return ByteStreams.copy(in, out); 205 } catch (Throwable e) { 206 throw closer.rethrow(e); 207 } finally { 208 closer.close(); 209 } 210 } 211 212 /** 213 * Reads the full contents of this byte source as a byte array. 214 * 215 * @throws IOException if an I/O error occurs in the process of reading from this source 216 */ 217 public byte[] read() throws IOException { 218 Closer closer = Closer.create(); 219 try { 220 InputStream in = closer.register(openStream()); 221 return ByteStreams.toByteArray(in); 222 } catch (Throwable e) { 223 throw closer.rethrow(e); 224 } finally { 225 closer.close(); 226 } 227 } 228 229 /** 230 * Hashes the contents of this byte source using the given hash function. 231 * 232 * @throws IOException if an I/O error occurs in the process of reading from this source 233 */ 234 public HashCode hash(HashFunction hashFunction) throws IOException { 235 Hasher hasher = hashFunction.newHasher(); 236 copyTo(Funnels.asOutputStream(hasher)); 237 return hasher.hash(); 238 } 239 240 /** 241 * Checks that the contents of this byte source are equal to the contents of the given byte 242 * source. 243 * 244 * @throws IOException if an I/O error occurs in the process of reading from this source or 245 * {@code other} 246 */ 247 public boolean contentEquals(ByteSource other) throws IOException { 248 checkNotNull(other); 249 250 byte[] buf1 = new byte[BUF_SIZE]; 251 byte[] buf2 = new byte[BUF_SIZE]; 252 253 Closer closer = Closer.create(); 254 try { 255 InputStream in1 = closer.register(openStream()); 256 InputStream in2 = closer.register(other.openStream()); 257 while (true) { 258 int read1 = ByteStreams.read(in1, buf1, 0, BUF_SIZE); 259 int read2 = ByteStreams.read(in2, buf2, 0, BUF_SIZE); 260 if (read1 != read2 || !Arrays.equals(buf1, buf2)) { 261 return false; 262 } else if (read1 != BUF_SIZE) { 263 return true; 264 } 265 } 266 } catch (Throwable e) { 267 throw closer.rethrow(e); 268 } finally { 269 closer.close(); 270 } 271 } 272 273 /** 274 * A char source that reads bytes from this source and decodes them as characters using a 275 * charset. 276 */ 277 private final class AsCharSource extends CharSource { 278 279 private final Charset charset; 280 281 private AsCharSource(Charset charset) { 282 this.charset = checkNotNull(charset); 283 } 284 285 @Override 286 public Reader openStream() throws IOException { 287 return new InputStreamReader(ByteSource.this.openStream(), charset); 288 } 289 290 @Override 291 public String toString() { 292 return ByteSource.this.toString() + ".asCharSource(" + charset + ")"; 293 } 294 } 295 296 /** 297 * A view of a subsection of the containing byte source. 298 */ 299 private final class SlicedByteSource extends ByteSource { 300 301 private final long offset; 302 private final long length; 303 304 private SlicedByteSource(long offset, long length) { 305 checkArgument(offset >= 0, "offset (%s) may not be negative", offset); 306 checkArgument(length >= 0, "length (%s) may not be negative", length); 307 this.offset = offset; 308 this.length = length; 309 } 310 311 @Override 312 public InputStream openStream() throws IOException { 313 InputStream in = ByteSource.this.openStream(); 314 if (offset > 0) { 315 try { 316 ByteStreams.skipFully(in, offset); 317 } catch (Throwable e) { 318 Closer closer = Closer.create(); 319 closer.register(in); 320 try { 321 throw closer.rethrow(e); 322 } finally { 323 closer.close(); 324 } 325 } 326 } 327 return ByteStreams.limit(in, length); 328 } 329 330 @Override 331 public ByteSource slice(long offset, long length) { 332 checkArgument(offset >= 0, "offset (%s) may not be negative", offset); 333 checkArgument(length >= 0, "length (%s) may not be negative", length); 334 long maxLength = this.length - offset; 335 return ByteSource.this.slice(this.offset + offset, Math.min(length, maxLength)); 336 } 337 338 @Override 339 public String toString() { 340 return ByteSource.this.toString() + ".slice(" + offset + ", " + length + ")"; 341 } 342 } 343}