1. /*
  2. * @(#)FileCacheImageInputStream.java 1.28 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.imageio.stream;
  8. import java.io.DataInput;
  9. import java.io.File;
  10. import java.io.InputStream;
  11. import java.io.IOException;
  12. import java.io.RandomAccessFile;
  13. /**
  14. * An implementation of <code>ImageInputStream</code> that gets its
  15. * input from a regular <code>InputStream</code>. A file is used to
  16. * cache previously read data.
  17. *
  18. * @version 0.5
  19. */
  20. public class FileCacheImageInputStream extends ImageInputStreamImpl {
  21. private InputStream stream;
  22. private File cacheFile;
  23. private RandomAccessFile cache;
  24. private static final int BUFFER_LENGTH = 1024;
  25. private byte[] buf = new byte[BUFFER_LENGTH];
  26. private long length = 0L;
  27. private boolean foundEOF = false;
  28. /**
  29. * Constructs a <code>FileCacheImageInputStream</code> that will read
  30. * from a given <code>InputStream</code>.
  31. *
  32. * <p> A temporary file is used as a cache. If
  33. * <code>cacheDir</code>is non-<code>null</code> and is a
  34. * directory, the file will be created there. If it is
  35. * <code>null</code>, the system-dependent default temporary-file
  36. * directory will be used (see the documentation for
  37. * <code>File.createTempFile</code> for details).
  38. *
  39. * @param stream an <code>InputStream</code> to read from.
  40. * @param cacheDir a <code>File</code> indicating where the
  41. * cache file should be created, or <code>null</code> to use the
  42. * system directory.
  43. *
  44. * @exception IllegalArgumentException if <code>stream</code> is
  45. * <code>null</code>.
  46. * @exception IllegalArgumentException if <code>cacheDir</code> is
  47. * non-<code>null</code> but is not a directory.
  48. * @exception IOException if a cache file cannot be created.
  49. */
  50. public FileCacheImageInputStream(InputStream stream, File cacheDir)
  51. throws IOException {
  52. if (stream == null) {
  53. throw new IllegalArgumentException("stream == null!");
  54. }
  55. if ((cacheDir != null) && !(cacheDir.isDirectory())) {
  56. throw new IllegalArgumentException("Not a directory!");
  57. }
  58. this.stream = stream;
  59. this.cacheFile =
  60. File.createTempFile("imageio", ".tmp", cacheDir);
  61. cacheFile.deleteOnExit();
  62. this.cache = new RandomAccessFile(cacheFile, "rw");
  63. }
  64. /**
  65. * Ensures that at least <code>pos</code> bytes are cached,
  66. * or the end of the source is reached. The return value
  67. * is equal to the smaller of <code>pos</code> and the
  68. * length of the source file.
  69. */
  70. private long readUntil(long pos) throws IOException {
  71. // We've already got enough data cached
  72. if (pos < length) {
  73. return pos;
  74. }
  75. // pos >= length but length isn't getting any bigger, so return it
  76. if (foundEOF) {
  77. return length;
  78. }
  79. long len = pos - length;
  80. cache.seek(length);
  81. while (len > 0) {
  82. // Copy a buffer's worth of data from the source to the cache
  83. // BUFFER_LENGTH will always fit into an int so this is safe
  84. int nbytes =
  85. stream.read(buf, 0, (int)Math.min(len, (long)BUFFER_LENGTH));
  86. if (nbytes == -1) {
  87. foundEOF = true;
  88. return length;
  89. }
  90. cache.write(buf, 0, nbytes);
  91. len -= nbytes;
  92. length += nbytes;
  93. }
  94. return pos;
  95. }
  96. public int read() throws IOException {
  97. bitOffset = 0;
  98. long next = streamPos + 1;
  99. long pos = readUntil(next);
  100. if (pos >= next) {
  101. cache.seek(streamPos++);
  102. return cache.read();
  103. } else {
  104. return -1;
  105. }
  106. }
  107. public int read(byte[] b, int off, int len) throws IOException {
  108. if (b == null) {
  109. throw new NullPointerException();
  110. }
  111. // Fix 4430357 - if off + len < 0, overflow occurred
  112. if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {
  113. throw new IndexOutOfBoundsException();
  114. }
  115. if (len == 0) {
  116. return 0;
  117. }
  118. checkClosed();
  119. bitOffset = 0;
  120. long pos = readUntil(streamPos + len);
  121. // len will always fit into an int so this is safe
  122. len = (int)Math.min((long)len, pos - streamPos);
  123. if (len > 0) {
  124. cache.seek(streamPos);
  125. cache.readFully(b, off, len);
  126. streamPos += len;
  127. return len;
  128. } else {
  129. return -1;
  130. }
  131. }
  132. /**
  133. * Returns <code>true</code> since this
  134. * <code>ImageInputStream</code> caches data in order to allow
  135. * seeking backwards.
  136. *
  137. * @return <code>true</code>.
  138. *
  139. * @see #isCachedMemory
  140. * @see #isCachedFile
  141. */
  142. public boolean isCached() {
  143. return true;
  144. }
  145. /**
  146. * Returns <code>true</code> since this
  147. * <code>ImageInputStream</code> maintains a file cache.
  148. *
  149. * @return <code>true</code>.
  150. *
  151. * @see #isCached
  152. * @see #isCachedMemory
  153. */
  154. public boolean isCachedFile() {
  155. return true;
  156. }
  157. /**
  158. * Returns <code>false</code> since this
  159. * <code>ImageInputStream</code> does not maintain a main memory
  160. * cache.
  161. *
  162. * @return <code>false</code>.
  163. *
  164. * @see #isCached
  165. * @see #isCachedFile
  166. */
  167. public boolean isCachedMemory() {
  168. return false;
  169. }
  170. /**
  171. * Closes this <code>FileCacheImageInputStream</code>, closing
  172. * and removing the cache file. The source <code>InputStream</code>
  173. * is not closed.
  174. *
  175. * @exception IOException if an error occurs.
  176. */
  177. public void close() throws IOException {
  178. super.close();
  179. cache.close();
  180. cacheFile.delete();
  181. stream = null;
  182. }
  183. }