1. /*
  2. * @(#)FileCacheImageInputStream.java 1.26 03/01/23
  3. *
  4. * Copyright 2003 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.setLength(cache.length() + nbytes);
  91. cache.write(buf, 0, nbytes);
  92. len -= nbytes;
  93. length += nbytes;
  94. }
  95. return pos;
  96. }
  97. public int read() throws IOException {
  98. bitOffset = 0;
  99. long next = streamPos + 1;
  100. long pos = readUntil(next);
  101. if (pos >= next) {
  102. cache.seek(streamPos++);
  103. return cache.read();
  104. } else {
  105. return -1;
  106. }
  107. }
  108. public int read(byte[] b, int off, int len) throws IOException {
  109. if (b == null) {
  110. throw new NullPointerException();
  111. }
  112. // Fix 4430357 - if off + len < 0, overflow occurred
  113. if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {
  114. throw new IndexOutOfBoundsException();
  115. }
  116. if (len == 0) {
  117. return 0;
  118. }
  119. checkClosed();
  120. bitOffset = 0;
  121. long pos = readUntil(streamPos + len);
  122. // len will always fit into an int so this is safe
  123. len = (int)Math.min((long)len, pos - streamPos);
  124. if (len > 0) {
  125. cache.seek(streamPos);
  126. cache.readFully(b, off, len);
  127. streamPos += len;
  128. return len;
  129. } else {
  130. return -1;
  131. }
  132. }
  133. /**
  134. * Returns <code>true</code> since this
  135. * <code>ImageInputStream</code> caches data in order to allow
  136. * seeking backwards.
  137. *
  138. * @return <code>true</code>.
  139. *
  140. * @see #isCachedMemory
  141. * @see #isCachedFile
  142. */
  143. public boolean isCached() {
  144. return true;
  145. }
  146. /**
  147. * Returns <code>true</code> since this
  148. * <code>ImageInputStream</code> maintains a file cache.
  149. *
  150. * @return <code>true</code>.
  151. *
  152. * @see #isCached
  153. * @see #isCachedMemory
  154. */
  155. public boolean isCachedFile() {
  156. return true;
  157. }
  158. /**
  159. * Returns <code>false</code> since this
  160. * <code>ImageInputStream</code> does not maintain a main memory
  161. * cache.
  162. *
  163. * @return <code>false</code>.
  164. *
  165. * @see #isCached
  166. * @see #isCachedFile
  167. */
  168. public boolean isCachedMemory() {
  169. return false;
  170. }
  171. /**
  172. * Closes this <code>FileCacheImageInputStream</code>, closing
  173. * and removing the cache file. The source <code>InputStream</code>
  174. * is not closed.
  175. *
  176. * @exception IOException if an error occurs.
  177. */
  178. public void close() throws IOException {
  179. super.close();
  180. cache.close();
  181. cacheFile.delete();
  182. stream = null;
  183. }
  184. }