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           if (diskCache != null && diskCache.exists()) return new FileInputStream(diskCache);
32           return new SubStream();
33       }
34   
35       public void grow(int newLength) {
36           if (newLength < cache.length) return;
37           byte[] newCache = new byte[cache.length + 2 * (newLength - cache.length)];
38           System.arraycopy(cache, 0, newCache, 0, size);
39           cache = newCache;
40       }
41   
42       synchronized void fillCache(int howMuch) throws IOException {
43           if (filling) { try { wait(); } catch (InterruptedException e) { }; return; }
44           filling = true;
45           grow(size + howMuch);
46           int ret = is.read(cache, size, howMuch);
47           if (ret == -1) {
48               eof = true;
49               // FIXME: probably a race here
50               if (diskCache != null && !diskCache.exists())
51                   try {
52                       File cacheFile = new File(diskCache + ".incomplete");
53                       FileOutputStream cacheFileStream = new FileOutputStream(cacheFile);
54                       cacheFileStream.write(cache, 0, size);
55                       cacheFileStream.close();
56                       cacheFile.renameTo(diskCache);
57                   } catch (IOException e) {
58                       Log.info(this, "exception thrown while writing disk cache");
59                       Log.info(this, e);
60                   }
61           }
62           else size += ret;
63           filling = false;
64           notifyAll();
65       }
66   
67       private class SubStream extends InputStream implements KnownLength {
68           int pos = 0;
69           public int available() { return Math.max(0, size - pos); }
70           public long skip(long n) throws IOException { pos += (int)n; return n; }     // FEATURE: don't skip past EOF
71           public int getLength() { return eof ? size : is instanceof KnownLength ? ((KnownLength)is).getLength() : 0; }
72           public int read() throws IOException {                                       // FEATURE: be smarter here
73               byte[] b = new byte[1];
74               int ret = read(b, 0, 1);
75               return ret == -1 ? -1 : b[0]&0xff;
76           }
77           public int read(byte[] b, int off, int len) throws IOException {
78               synchronized(CachedInputStream.this) {
79                   while (pos >= size && !eof) fillCache(pos + len - size);
80                   if (eof && pos == size) return -1;
81                   int count = Math.min(size - pos, len);
82                   System.arraycopy(cache, pos, b, off, count);
83                   pos += count;
84                   return count;
85               }
86           }
87       }
88   }
89