2003/12/07 09:57:15
[org.ibex.core.git] / src / org / xwt / util / CachedInputStream.java
1 // Copyright (C) 2003 Adam Megacz <adam@xwt.org> all rights reserved.
2 //
3 // You may modify, copy, and redistribute this code under the terms of
4 // the GNU Library Public License version 2.1, with the exception of
5 // the portion of clause 6a after the semicolon (aka the "obnoxious
6 // relink clause")
7
8 package org.xwt.util;
9 import java.io.*;
10
11 // FEATURE: don't use a byte[] if we have a diskCache file
12 /**
13  *  Wraps around an InputStream, caching the stream in a byte[] as it
14  *  is read and permitting multiple simultaneous readers
15  */
16 public class CachedInputStream {
17
18     boolean filling = false;               ///< true iff some thread is blocked on us waiting for input
19     boolean eof = false;                   ///< true iff end of stream has been reached
20     byte[] cache = new byte[1024 * 128];
21     int size = 0;
22     final InputStream is;
23     File diskCache;
24
25     public CachedInputStream(InputStream is) { this(is, null); }
26     public CachedInputStream(InputStream is, File diskCache) {
27         this.is = is;
28         this.diskCache = diskCache;
29     }
30     public InputStream getInputStream() throws IOException {
31         System.out.println("diskCache == " + diskCache);
32         System.out.println("diskCache.exists() == " + diskCache.exists());
33         if (diskCache != null && diskCache.exists()) return new FileInputStream(diskCache);
34         return new SubStream();
35     }
36
37     public void grow(int newLength) {
38         if (newLength < cache.length) return;
39         byte[] newCache = new byte[cache.length + 2 * (newLength - cache.length)];
40         System.arraycopy(cache, 0, newCache, 0, size);
41         cache = newCache;
42     }
43
44     synchronized void fillCache(int howMuch) throws IOException {
45         if (filling) { try { wait(); } catch (InterruptedException e) { }; return; }
46         filling = true;
47         grow(size + howMuch);
48         int ret = is.read(cache, size, howMuch);
49         if (ret == -1) {
50             eof = true;
51             // FIXME: probably a race here
52             if (diskCache != null && !diskCache.exists())
53                 try {
54                     File cacheFile = new File(diskCache + ".tmp");
55                     FileOutputStream cacheFileStream = new FileOutputStream(cacheFile);
56                     cacheFileStream.write(cache, 0, size);
57                     cacheFileStream.close();
58                     cacheFile.renameTo(diskCache);
59                 } catch (IOException e) {
60                     Log.log(this, "exception thrown while writing disk cache");
61                     Log.log(this, e);
62                 }
63         }
64         else size += ret;
65         filling = false;
66         notifyAll();
67     }
68
69     private class SubStream extends InputStream implements KnownLength {
70         int pos = 0;
71         public int available() { return Math.max(0, size - pos); }
72         public long skip(long n) throws IOException { pos += (int)n; return n; }     // FEATURE: don't skip past EOF
73         public int getLength() { return eof ? size : is instanceof KnownLength ? ((KnownLength)is).getLength() : 0; }
74         public int read() throws IOException {                                       // FEATURE: be smarter here
75             byte[] b = new byte[1];
76             int ret = read(b, 0, 1);
77             return ret == -1 ? -1 : b[0]&0xff;
78         }
79         public int read(byte[] b, int off, int len) throws IOException {
80             synchronized(CachedInputStream.this) {
81                 while (pos >= size && !eof) fillCache(pos + len - size);
82                 if (eof && pos == size) return -1;
83                 int count = Math.min(size - pos, len);
84                 System.arraycopy(cache, pos, b, off, count);
85                 pos += count;
86                 return count;
87             }
88         }
89     }
90 }