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