KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > imageio > stream > MemoryCache


1 /*
2  * @(#)MemoryCache.java 1.15 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
8 package javax.imageio.stream;
9
10 import java.util.ArrayList JavaDoc;
11 import java.io.InputStream JavaDoc;
12 import java.io.OutputStream JavaDoc;
13 import java.io.IOException JavaDoc;
14
15 /**
16  * Package-visible class consolidating common code for
17  * <code>MemoryCacheImageInputStream</code> and
18  * <code>MemoryCacheImageOutputStream</code>.
19  * This class keeps an <code>ArrayList</code> of 8K blocks,
20  * loaded sequentially. Blocks may only be disposed of
21  * from the index 0 forward. As blocks are freed, the
22  * corresponding entries in the array list are set to
23  * <code>null</code>, but no compacting is performed.
24  * This allows the index for each block to never change,
25  * and the length of the cache is always the same as the
26  * total amount of data ever cached. Cached data is
27  * therefore always contiguous from the point of last
28  * disposal to the current length.
29  *
30  * <p> The total number of blocks resident in the cache must not
31  * exceed <code>Integer.MAX_VALUE</code>. In practice, the limit of
32  * available memory will be exceeded long before this becomes an
33  * issue, since a full cache would contain 8192*2^31 = 16 terabytes of
34  * data.
35  *
36  * A <code>MemoryCache</code> may be reused after a call
37  * to <code>reset()</code>.
38  */

39 class MemoryCache {
40
41     private static final int BUFFER_LENGTH = 8192;
42
43     private ArrayList JavaDoc cache = new ArrayList JavaDoc();
44
45     private long cacheStart = 0L;
46
47     /**
48      * The largest position ever written to the cache.
49      */

50     private long length = 0L;
51
52     private byte[] getCacheBlock(long blockNum) throws IOException JavaDoc {
53         long blockOffset = blockNum - cacheStart;
54         if (blockOffset > Integer.MAX_VALUE) {
55             // This can only happen when the cache hits 16 terabytes of
56
// contiguous data...
57
throw new IOException JavaDoc("Cache addressing limit exceeded!");
58         }
59         return (byte[])cache.get((int)blockOffset);
60     }
61
62     /**
63      * Ensures that at least <code>pos</code> bytes are cached,
64      * or the end of the source is reached. The return value
65      * is equal to the smaller of <code>pos</code> and the
66      * length of the source.
67      */

68     public long loadFromStream(InputStream JavaDoc stream, long pos)
69         throws IOException JavaDoc {
70         // We've already got enough data cached
71
if (pos < length) {
72             return pos;
73         }
74
75         int offset = (int)(length % BUFFER_LENGTH);
76         byte [] buf = null;
77
78         long len = pos - length;
79         if (offset != 0) {
80             buf = getCacheBlock(length/BUFFER_LENGTH);
81         }
82
83         while (len > 0) {
84             if (buf == null) {
85                 try {
86                     buf = new byte[BUFFER_LENGTH];
87                 } catch (OutOfMemoryError JavaDoc e) {
88                     throw new IOException JavaDoc("No memory left for cache!");
89                 }
90                 offset = 0;
91             }
92
93             int left = BUFFER_LENGTH - offset;
94             int nbytes = (int)Math.min(len, (long)left);
95             nbytes = stream.read(buf, offset, nbytes);
96             if (nbytes == -1) {
97                 return length; // EOF
98
}
99
100             if (offset == 0) {
101                 cache.add(buf);
102             }
103
104             len -= nbytes;
105             length += nbytes;
106             offset += nbytes;
107
108             if (offset >= BUFFER_LENGTH) {
109                 // we've filled the current buffer, so a new one will be
110
// allocated next time around (and offset will be reset to 0)
111
buf = null;
112             }
113         }
114
115         return pos;
116     }
117
118     /**
119      * Writes out a portion of the cache to an <code>OutputStream</code>.
120      * This method preserves no state about the output stream, and does
121      * not dispose of any blocks containing bytes written. To dispose
122      * blocks, use {@link #disposeBefore <code>disposeBefore()</code>}.
123      *
124      * @exception IndexOutOfBoundsException if any portion of
125      * the requested data is not in the cache (including if <code>pos</code>
126      * is in a block already disposed), or if either <code>pos</code> or
127      * <code>len</code> is < 0.
128      */

129     public void writeToStream(OutputStream JavaDoc stream, long pos, long len)
130         throws IOException JavaDoc {
131         if (pos + len > length) {
132             throw new IndexOutOfBoundsException JavaDoc("Argument out of cache");
133         }
134         if ((pos < 0) || (len < 0)) {
135             throw new IndexOutOfBoundsException JavaDoc("Negative pos or len");
136         }
137         if (len == 0) {
138             return;
139         }
140
141         long bufIndex = pos/BUFFER_LENGTH;
142         if (bufIndex < cacheStart) {
143             throw new IndexOutOfBoundsException JavaDoc("pos already disposed");
144         }
145         int offset = (int)(pos % BUFFER_LENGTH);
146
147         byte[] buf = getCacheBlock(bufIndex++);
148         while (len > 0) {
149             if (buf == null) {
150                 buf = getCacheBlock(bufIndex++);
151                 offset = 0;
152             }
153             int nbytes = (int)Math.min(len, (long)(BUFFER_LENGTH - offset));
154             stream.write(buf, offset, nbytes);
155             buf = null;
156             len -= nbytes;
157         }
158     }
159
160     /**
161      * Ensure that there is space to write a byte at the given position.
162      */

163     private void pad(long pos) throws IOException JavaDoc {
164         long currIndex = cacheStart + cache.size() - 1;
165         long lastIndex = pos/BUFFER_LENGTH;
166         long numNewBuffers = lastIndex - currIndex;
167         for (long i = 0; i < numNewBuffers; i++) {
168             try {
169                 cache.add(new byte[BUFFER_LENGTH]);
170             } catch (OutOfMemoryError JavaDoc e) {
171                 throw new IOException JavaDoc("No memory left for cache!");
172             }
173         }
174     }
175
176     /**
177      * Overwrites and/or appends the cache from a byte array.
178      * The length of the cache will be extended as needed to hold
179      * the incoming data.
180      *
181      * @param b an array of bytes containing data to be written.
182      * @param off the starting offset withing the data array.
183      * @param len the number of bytes to be written.
184      * @param pos the cache position at which to begin writing.
185      *
186      * @exception NullPointerException if <code>b</code> is <code>null</code>.
187      * @exception IndexOutOfBoundsException if <code>off</code>,
188      * <code>len</code>, or <code>pos</code> are negative,
189      * or if <code>off+len > b.length</code>.
190      */

191     public void write(byte[] b, int off, int len, long pos)
192         throws IOException JavaDoc {
193         if (b == null) {
194             throw new NullPointerException JavaDoc("b == null!");
195         }
196         // Fix 4430357 - if off + len < 0, overflow occurred
197
if ((off < 0) || (len < 0) || (pos < 0) ||
198             (off + len > b.length) || (off + len < 0)) {
199             throw new IndexOutOfBoundsException JavaDoc();
200         }
201
202         // Ensure there is space for the incoming data
203
long lastPos = pos + len - 1;
204         if (lastPos >= length) {
205             pad(lastPos);
206             length = lastPos + 1;
207         }
208
209         // Copy the data into the cache, block by block
210
int offset = (int)(pos % BUFFER_LENGTH);
211         while (len > 0) {
212             byte[] buf = getCacheBlock(pos/BUFFER_LENGTH);
213             int nbytes = Math.min(len, BUFFER_LENGTH - offset);
214             System.arraycopy(b, off, buf, offset, nbytes);
215
216             pos += nbytes;
217             off += nbytes;
218             len -= nbytes;
219             offset = 0; // Always after the first time
220
}
221     }
222
223     /**
224      * Overwrites or appends a single byte to the cache.
225      * The length of the cache will be extended as needed to hold
226      * the incoming data.
227      *
228      * @param b an <code>int</code> whose 8 least significant bits
229      * will be written.
230      * @param pos the cache position at which to begin writing.
231      *
232      * @exception IndexOutOfBoundsException if <code>pos</code> is negative.
233      */

234     public void write(int b, long pos) throws IOException JavaDoc {
235         if (pos < 0) {
236             throw new ArrayIndexOutOfBoundsException JavaDoc("pos < 0");
237         }
238
239         // Ensure there is space for the incoming data
240
if (pos >= length) {
241             pad(pos);
242             length = pos + 1;
243         }
244
245         // Insert the data.
246
byte[] buf = getCacheBlock(pos/BUFFER_LENGTH);
247         int offset = (int)(pos % BUFFER_LENGTH);
248         buf[offset] = (byte)b;
249     }
250
251     /**
252      * Returns the total length of data that has been cached,
253      * regardless of whether any early blocks have been disposed.
254      * This value will only ever increase.
255      */

256     public long getLength() {
257         return length;
258     }
259
260     /**
261      * Returns the single byte at the given position, as an
262      * <code>int</code>. Returns -1 if this position has
263      * not been cached or has been disposed.
264      */

265     public int read(long pos) throws IOException JavaDoc {
266         if (pos >= length) {
267             return -1;
268         }
269
270         byte[] buf = getCacheBlock(pos/BUFFER_LENGTH);
271         if (buf == null) {
272             return -1;
273         }
274
275         return buf[(int)(pos % BUFFER_LENGTH)] & 0xff;
276     }
277
278     /**
279      * Copy <code>len</code> bytes from the cache, starting
280      * at cache position <code>pos</code>, into the array
281      * <code>b</code> at offset <code>off</code>.
282      *
283      * @exception NullPointerException if b is <code>null</code>
284      * @exception IndexOutOfBoundsException if <code>off</code>,
285      * <code>len</code> or <code>pos</code> are negative or if
286      * <code>off + len > b.length</code> or if any portion of the
287      * requested data is not in the cache (including if
288      * <code>pos</code> is in a block that has already been disposed).
289      */

290     public void read(byte[] b, int off, int len, long pos)
291         throws IOException JavaDoc {
292         if (b == null) {
293             throw new NullPointerException JavaDoc("b == null!");
294         }
295         // Fix 4430357 - if off + len < 0, overflow occurred
296
if ((off < 0) || (len < 0) || (pos < 0) ||
297             (off + len > b.length) || (off + len < 0)) {
298             throw new IndexOutOfBoundsException JavaDoc();
299         }
300         if (pos + len > length) {
301             throw new IndexOutOfBoundsException JavaDoc();
302         }
303         
304         long index = pos/BUFFER_LENGTH;
305         int offset = (int)pos % BUFFER_LENGTH;
306         while (len > 0) {
307             int nbytes = Math.min(len, BUFFER_LENGTH - offset);
308             byte[] buf = getCacheBlock(index++);
309             System.arraycopy(buf, offset, b, off, nbytes);
310
311             len -= nbytes;
312             off += nbytes;
313             offset = 0; // Always after the first time
314
}
315     }
316
317     /**
318      * Free the blocks up to the position <code>pos</code>.
319      * The byte at <code>pos</code> remains available.
320      *
321      * @exception IndexOutOfBoundsException if <code>pos</code>
322      * is in a block that has already been disposed.
323      */

324     public void disposeBefore(long pos) {
325         long index = pos/BUFFER_LENGTH;
326         if (index < cacheStart) {
327             throw new IndexOutOfBoundsException JavaDoc("pos already disposed");
328         }
329         long numBlocks = Math.min(index - cacheStart, cache.size());
330         for (long i = 0; i < numBlocks; i++) {
331             cache.remove(0);
332         }
333         this.cacheStart = index;
334     }
335
336     /**
337      * Erase the entire cache contents and reset the length to 0.
338      * The cache object may subsequently be reused as though it had just
339      * been allocated.
340      */

341     public void reset() {
342         cache.clear();
343         cacheStart = 0;
344         length = 0L;
345     }
346  }
347
Popular Tags