1 /*
   2  * Copyright (c) 2000, 2006, Oracle and/or its affiliates. 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.imageio.stream;
  27 
  28 import java.io.IOException;
  29 import java.io.OutputStream;
  30 
  31 /**
  32  * An implementation of <code>ImageOutputStream</code> that writes its
  33  * output to a regular <code>OutputStream</code>.  A memory buffer is
  34  * used to cache at least the data between the discard position and
  35  * the current write position.  The only constructor takes an
  36  * <code>OutputStream</code>, so this class may not be used for
  37  * read/modify/write operations.  Reading can occur only on parts of
  38  * the stream that have already been written to the cache and not
  39  * yet flushed.
  40  *
  41  */
  42 public class MemoryCacheImageOutputStream extends ImageOutputStreamImpl {
  43 
  44     private OutputStream stream;
  45 
  46     private MemoryCache cache = new MemoryCache();
  47 
  48     /**
  49      * Constructs a <code>MemoryCacheImageOutputStream</code> that will write
  50      * to a given <code>OutputStream</code>.
  51      *
  52      * @param stream an <code>OutputStream</code> to write to.
  53      *
  54      * @exception IllegalArgumentException if <code>stream</code> is
  55      * <code>null</code>.
  56      */
  57     public MemoryCacheImageOutputStream(OutputStream stream) {
  58         if (stream == null) {
  59             throw new IllegalArgumentException("stream == null!");
  60         }
  61         this.stream = stream;
  62     }
  63 
  64     public int read() throws IOException {
  65         checkClosed();
  66 
  67         bitOffset = 0;
  68 
  69         int val = cache.read(streamPos);
  70         if (val != -1) {
  71             ++streamPos;
  72         }
  73         return val;
  74     }
  75 
  76     public int read(byte[] b, int off, int len) throws IOException {
  77         checkClosed();
  78 
  79         if (b == null) {
  80             throw new NullPointerException("b == null!");
  81         }
  82         // Fix 4467608: read([B,I,I) works incorrectly if len<=0
  83         if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {
  84             throw new IndexOutOfBoundsException
  85                 ("off < 0 || len < 0 || off+len > b.length || off+len < 0!");
  86         }
  87 
  88         bitOffset = 0;
  89 
  90         if (len == 0) {
  91             return 0;
  92         }
  93 
  94         // check if we're already at/past EOF i.e.
  95         // no more bytes left to read from cache
  96         long bytesLeftInCache = cache.getLength() - streamPos;
  97         if (bytesLeftInCache <= 0) {
  98             return -1; // EOF
  99         }
 100 
 101         // guaranteed by now that bytesLeftInCache > 0 && len > 0
 102         // and so the rest of the error checking is done by cache.read()
 103         // NOTE that alot of error checking is duplicated
 104         len = (int)Math.min(bytesLeftInCache, (long)len);
 105         cache.read(b, off, len, streamPos);
 106         streamPos += len;
 107         return len;
 108     }
 109 
 110     public void write(int b) throws IOException {
 111         flushBits(); // this will call checkClosed() for us
 112         cache.write(b, streamPos);
 113         ++streamPos;
 114     }
 115 
 116     public void write(byte[] b, int off, int len) throws IOException {
 117         flushBits(); // this will call checkClosed() for us
 118         cache.write(b, off, len, streamPos);
 119         streamPos += len;
 120     }
 121 
 122     public long length() {
 123         try {
 124             checkClosed();
 125             return cache.getLength();
 126         } catch (IOException e) {
 127             return -1L;
 128         }
 129     }
 130 
 131     /**
 132      * Returns <code>true</code> since this
 133      * <code>ImageOutputStream</code> caches data in order to allow
 134      * seeking backwards.
 135      *
 136      * @return <code>true</code>.
 137      *
 138      * @see #isCachedMemory
 139      * @see #isCachedFile
 140      */
 141     public boolean isCached() {
 142         return true;
 143     }
 144 
 145     /**
 146      * Returns <code>false</code> since this
 147      * <code>ImageOutputStream</code> does not maintain a file cache.
 148      *
 149      * @return <code>false</code>.
 150      *
 151      * @see #isCached
 152      * @see #isCachedMemory
 153      */
 154     public boolean isCachedFile() {
 155         return false;
 156     }
 157 
 158     /**
 159      * Returns <code>true</code> since this
 160      * <code>ImageOutputStream</code> maintains a main memory cache.
 161      *
 162      * @return <code>true</code>.
 163      *
 164      * @see #isCached
 165      * @see #isCachedFile
 166      */
 167     public boolean isCachedMemory() {
 168         return true;
 169     }
 170 
 171     /**
 172      * Closes this <code>MemoryCacheImageOutputStream</code>.  All
 173      * pending data is flushed to the output, and the cache
 174      * is released.  The destination <code>OutputStream</code>
 175      * is not closed.
 176      */
 177     public void close() throws IOException {
 178         long length = cache.getLength();
 179         seek(length);
 180         flushBefore(length);
 181         super.close();
 182         cache.reset();
 183         cache = null;
 184         stream = null;
 185     }
 186 
 187     public void flushBefore(long pos) throws IOException {
 188         long oFlushedPos = flushedPos;
 189         super.flushBefore(pos); // this will call checkClosed() for us
 190 
 191         long flushBytes = flushedPos - oFlushedPos;
 192         cache.writeToStream(stream, oFlushedPos, flushBytes);
 193         cache.disposeBefore(flushedPos);
 194         stream.flush();
 195     }
 196 }