Save This Page
Home » openjdk-7 » javax » imageio » stream » [javadoc | source]
    1   /*
    2    * Copyright 2000-2007 Sun Microsystems, Inc.  All Rights Reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any questions.
   24    */
   25   
   26   package javax.imageio.stream;
   27   
   28   import java.io.File;
   29   import java.io.InputStream;
   30   import java.io.IOException;
   31   import java.io.RandomAccessFile;
   32   import com.sun.imageio.stream.StreamCloser;
   33   import com.sun.imageio.stream.StreamFinalizer;
   34   import sun.java2d.Disposer;
   35   import sun.java2d.DisposerRecord;
   36   
   37   /**
   38    * An implementation of <code>ImageInputStream</code> that gets its
   39    * input from a regular <code>InputStream</code>.  A file is used to
   40    * cache previously read data.
   41    *
   42    */
   43   public class FileCacheImageInputStream extends ImageInputStreamImpl {
   44   
   45       private InputStream stream;
   46   
   47       private File cacheFile;
   48   
   49       private RandomAccessFile cache;
   50   
   51       private static final int BUFFER_LENGTH = 1024;
   52   
   53       private byte[] buf = new byte[BUFFER_LENGTH];
   54   
   55       private long length = 0L;
   56   
   57       private boolean foundEOF = false;
   58   
   59       /** The referent to be registered with the Disposer. */
   60       private final Object disposerReferent;
   61   
   62       /** The DisposerRecord that closes the underlying cache. */
   63       private final DisposerRecord disposerRecord;
   64   
   65       /**
   66        * Constructs a <code>FileCacheImageInputStream</code> that will read
   67        * from a given <code>InputStream</code>.
   68        *
   69        * <p> A temporary file is used as a cache.  If
   70        * <code>cacheDir</code>is non-<code>null</code> and is a
   71        * directory, the file will be created there.  If it is
   72        * <code>null</code>, the system-dependent default temporary-file
   73        * directory will be used (see the documentation for
   74        * <code>File.createTempFile</code> for details).
   75        *
   76        * @param stream an <code>InputStream</code> to read from.
   77        * @param cacheDir a <code>File</code> indicating where the
   78        * cache file should be created, or <code>null</code> to use the
   79        * system directory.
   80        *
   81        * @exception IllegalArgumentException if <code>stream</code> is
   82        * <code>null</code>.
   83        * @exception IllegalArgumentException if <code>cacheDir</code> is
   84        * non-<code>null</code> but is not a directory.
   85        * @exception IOException if a cache file cannot be created.
   86        */
   87       public FileCacheImageInputStream(InputStream stream, File cacheDir)
   88           throws IOException {
   89           if (stream == null) {
   90               throw new IllegalArgumentException("stream == null!");
   91           }
   92           if ((cacheDir != null) && !(cacheDir.isDirectory())) {
   93               throw new IllegalArgumentException("Not a directory!");
   94           }
   95           this.stream = stream;
   96           this.cacheFile =
   97               File.createTempFile("imageio", ".tmp", cacheDir);
   98           this.cache = new RandomAccessFile(cacheFile, "rw");
   99           StreamCloser.addToQueue(this);
  100   
  101           disposerRecord = new StreamDisposerRecord(cacheFile, cache);
  102           if (getClass() == FileCacheImageInputStream.class) {
  103               disposerReferent = new Object();
  104               Disposer.addRecord(disposerReferent, disposerRecord);
  105           } else {
  106               disposerReferent = new StreamFinalizer(this);
  107           }
  108       }
  109   
  110       /**
  111        * Ensures that at least <code>pos</code> bytes are cached,
  112        * or the end of the source is reached.  The return value
  113        * is equal to the smaller of <code>pos</code> and the
  114        * length of the source file.
  115        */
  116       private long readUntil(long pos) throws IOException {
  117           // We've already got enough data cached
  118           if (pos < length) {
  119               return pos;
  120           }
  121           // pos >= length but length isn't getting any bigger, so return it
  122           if (foundEOF) {
  123               return length;
  124           }
  125   
  126           long len = pos - length;
  127           cache.seek(length);
  128           while (len > 0) {
  129               // Copy a buffer's worth of data from the source to the cache
  130               // BUFFER_LENGTH will always fit into an int so this is safe
  131               int nbytes =
  132                   stream.read(buf, 0, (int)Math.min(len, (long)BUFFER_LENGTH));
  133               if (nbytes == -1) {
  134                   foundEOF = true;
  135                   return length;
  136               }
  137   
  138               cache.write(buf, 0, nbytes);
  139               len -= nbytes;
  140               length += nbytes;
  141           }
  142   
  143           return pos;
  144       }
  145   
  146       public int read() throws IOException {
  147           checkClosed();
  148           bitOffset = 0;
  149           long next = streamPos + 1;
  150           long pos = readUntil(next);
  151           if (pos >= next) {
  152               cache.seek(streamPos++);
  153               return cache.read();
  154           } else {
  155               return -1;
  156           }
  157       }
  158   
  159       public int read(byte[] b, int off, int len) throws IOException {
  160           checkClosed();
  161   
  162           if (b == null) {
  163               throw new NullPointerException("b == null!");
  164           }
  165           // Fix 4430357 - if off + len < 0, overflow occurred
  166           if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {
  167               throw new IndexOutOfBoundsException
  168                   ("off < 0 || len < 0 || off+len > b.length || off+len < 0!");
  169           }
  170   
  171           bitOffset = 0;
  172   
  173           if (len == 0) {
  174               return 0;
  175           }
  176   
  177           long pos = readUntil(streamPos + len);
  178   
  179           // len will always fit into an int so this is safe
  180           len = (int)Math.min((long)len, pos - streamPos);
  181           if (len > 0) {
  182               cache.seek(streamPos);
  183               cache.readFully(b, off, len);
  184               streamPos += len;
  185               return len;
  186           } else {
  187               return -1;
  188           }
  189       }
  190   
  191       /**
  192        * Returns <code>true</code> since this
  193        * <code>ImageInputStream</code> caches data in order to allow
  194        * seeking backwards.
  195        *
  196        * @return <code>true</code>.
  197        *
  198        * @see #isCachedMemory
  199        * @see #isCachedFile
  200        */
  201       public boolean isCached() {
  202           return true;
  203       }
  204   
  205       /**
  206        * Returns <code>true</code> since this
  207        * <code>ImageInputStream</code> maintains a file cache.
  208        *
  209        * @return <code>true</code>.
  210        *
  211        * @see #isCached
  212        * @see #isCachedMemory
  213        */
  214       public boolean isCachedFile() {
  215           return true;
  216       }
  217   
  218       /**
  219        * Returns <code>false</code> since this
  220        * <code>ImageInputStream</code> does not maintain a main memory
  221        * cache.
  222        *
  223        * @return <code>false</code>.
  224        *
  225        * @see #isCached
  226        * @see #isCachedFile
  227        */
  228       public boolean isCachedMemory() {
  229           return false;
  230       }
  231   
  232       /**
  233        * Closes this <code>FileCacheImageInputStream</code>, closing
  234        * and removing the cache file.  The source <code>InputStream</code>
  235        * is not closed.
  236        *
  237        * @exception IOException if an error occurs.
  238        */
  239       public void close() throws IOException {
  240           super.close();
  241           disposerRecord.dispose(); // this will close/delete the cache file
  242           stream = null;
  243           cache = null;
  244           cacheFile = null;
  245           StreamCloser.removeFromQueue(this);
  246       }
  247   
  248       /**
  249        * {@inheritDoc}
  250        */
  251       protected void finalize() throws Throwable {
  252           // Empty finalizer: for performance reasons we instead use the
  253           // Disposer mechanism for ensuring that the underlying
  254           // RandomAccessFile is closed/deleted prior to garbage collection
  255       }
  256   
  257       private static class StreamDisposerRecord implements DisposerRecord {
  258           private File cacheFile;
  259           private RandomAccessFile cache;
  260   
  261           public StreamDisposerRecord(File cacheFile, RandomAccessFile cache) {
  262               this.cacheFile = cacheFile;
  263               this.cache = cache;
  264           }
  265   
  266           public synchronized void dispose() {
  267               if (cache != null) {
  268                   try {
  269                       cache.close();
  270                   } catch (IOException e) {
  271                   } finally {
  272                       cache = null;
  273                   }
  274               }
  275               if (cacheFile != null) {
  276                   cacheFile.delete();
  277                   cacheFile = null;
  278               }
  279               // Note: Explicit removal of the stream from the StreamCloser
  280               // queue is not mandatory in this case, as it will be removed
  281               // automatically by GC shortly after this method is called.
  282           }
  283       }
  284   }

Save This Page
Home » openjdk-7 » javax » imageio » stream » [javadoc | source]