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