1. /*
  2. * @(#)FileCacheImageOutputStream.java 1.22 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.FileNotFoundException;
  11. import java.io.IOException;
  12. import java.io.OutputStream;
  13. import java.io.RandomAccessFile;
  14. /**
  15. * An implementation of <code>ImageOutputStream</code> that writes its
  16. * output to a regular <code>OutputStream</code>. A file is used to
  17. * cache data until it is flushed to the output stream.
  18. *
  19. * @version 0.5
  20. */
  21. public class FileCacheImageOutputStream extends ImageOutputStreamImpl {
  22. private OutputStream stream;
  23. private File cacheFile;
  24. private RandomAccessFile cache;
  25. // Pos after last (rightmost) byte written
  26. private long maxStreamPos = 0L;
  27. /**
  28. * Constructs a <code>FileCacheImageOutputStream</code> that will write
  29. * to a given <code>outputStream</code>.
  30. *
  31. * <p> A temporary file is used as a cache. If
  32. * <code>cacheDir</code>is non-<code>null</code> and is a
  33. * directory, the file will be created there. If it is
  34. * <code>null</code>, the system-dependent default temporary-file
  35. * directory will be used (see the documentation for
  36. * <code>File.createTempFile</code> for details).
  37. *
  38. * @param stream an <code>OutputStream</code> to write to.
  39. * @param cacheDir a <code>File</code> indicating where the
  40. * cache file should be created, or <code>null</code> to use the
  41. * system directory.
  42. *
  43. * @exception IllegalArgumentException if <code>stream</code>
  44. * is <code>null</code>.
  45. * @exception IllegalArgumentException if <code>cacheDir</code> is
  46. * non-<code>null</code> but is not a directory.
  47. * @exception IOException if a cache file cannot be created.
  48. */
  49. public FileCacheImageOutputStream(OutputStream stream, File cacheDir)
  50. throws IOException {
  51. if (stream == null) {
  52. throw new IllegalArgumentException("stream == null!");
  53. }
  54. if ((cacheDir != null) && !(cacheDir.isDirectory())) {
  55. throw new IllegalArgumentException("Not a directory!");
  56. }
  57. this.stream = stream;
  58. this.cacheFile =
  59. File.createTempFile("imageio", ".tmp", cacheDir);
  60. cacheFile.deleteOnExit();
  61. this.cache = new RandomAccessFile(cacheFile, "rw");
  62. }
  63. public int read() throws IOException {
  64. bitOffset = 0;
  65. int val = cache.read();
  66. if (val != -1) {
  67. ++streamPos;
  68. }
  69. return val;
  70. }
  71. public int read(byte[] b, int off, int len) throws IOException {
  72. bitOffset = 0;
  73. int nbytes = cache.read(b, off, len);
  74. if (nbytes != -1) {
  75. streamPos += nbytes;
  76. }
  77. return nbytes;
  78. }
  79. public void write(int b) throws IOException {
  80. flushBits();
  81. cache.write(b);
  82. ++streamPos;
  83. maxStreamPos = Math.max(maxStreamPos, streamPos);
  84. }
  85. public void write(byte[] b, int off, int len) throws IOException {
  86. flushBits();
  87. cache.write(b, off, len);
  88. streamPos += len;
  89. maxStreamPos = Math.max(maxStreamPos, streamPos);
  90. }
  91. public long length() {
  92. try {
  93. return cache.length();
  94. } catch (IOException e) {
  95. return -1L;
  96. }
  97. }
  98. /**
  99. * Sets the current stream position and resets the bit offset to
  100. * 0. It is legal to seek past the end of the file; an
  101. * <code>EOFException</code> will be thrown only if a read is
  102. * performed. The file length will not be increased until a write
  103. * is performed.
  104. *
  105. * @exception IndexOutOfBoundsException if <code>pos</code> is smaller
  106. * than the flushed position.
  107. * @exception IOException if any other I/O error occurs.
  108. */
  109. public void seek(long pos) throws IOException {
  110. checkClosed();
  111. if (pos < flushedPos) {
  112. throw new IndexOutOfBoundsException();
  113. }
  114. cache.seek(pos);
  115. this.streamPos = cache.getFilePointer();
  116. maxStreamPos = Math.max(maxStreamPos, streamPos);
  117. this.bitOffset = 0;
  118. }
  119. /**
  120. * Returns <code>true</code> since this
  121. * <code>ImageOutputStream</code> caches data in order to allow
  122. * seeking backwards.
  123. *
  124. * @return <code>true</code>.
  125. *
  126. * @see #isCachedMemory
  127. * @see #isCachedFile
  128. */
  129. public boolean isCached() {
  130. return true;
  131. }
  132. /**
  133. * Returns <code>true</code> since this
  134. * <code>ImageOutputStream</code> maintains a file cache.
  135. *
  136. * @return <code>true</code>.
  137. *
  138. * @see #isCached
  139. * @see #isCachedMemory
  140. */
  141. public boolean isCachedFile() {
  142. return true;
  143. }
  144. /**
  145. * Returns <code>false</code> since this
  146. * <code>ImageOutputStream</code> does not maintain a main memory
  147. * cache.
  148. *
  149. * @return <code>false</code>.
  150. *
  151. * @see #isCached
  152. * @see #isCachedFile
  153. */
  154. public boolean isCachedMemory() {
  155. return false;
  156. }
  157. /**
  158. * Closes this <code>FileCacheImageOututStream</code>. All
  159. * pending data is flushed to the output, and the cache file
  160. * is closed and removed. The destination <code>OutputStream</code>
  161. * is not closed.
  162. *
  163. * @exception IOException if an error occurs.
  164. */
  165. public void close() throws IOException {
  166. maxStreamPos = cache.length();
  167. seek(maxStreamPos);
  168. flushBefore(maxStreamPos);
  169. super.close();
  170. cache.close();
  171. cacheFile.delete();
  172. stream.flush();
  173. stream = null;
  174. }
  175. public void flushBefore(long pos) throws IOException {
  176. long oFlushedPos = flushedPos;
  177. super.flushBefore(pos);
  178. long flushBytes = flushedPos - oFlushedPos;
  179. if (flushBytes > 0) {
  180. int bufLen = 512;
  181. byte[] buf = new byte[bufLen];
  182. cache.seek(oFlushedPos);
  183. while (flushBytes > 0) {
  184. int len = (int)Math.min(flushBytes, bufLen);
  185. cache.readFully(buf, 0, len);
  186. stream.write(buf, 0, len);
  187. flushBytes -= len;
  188. }
  189. stream.flush();
  190. }
  191. }
  192. }