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}