OSDN Git Service

Add new source files
[armadillo/armadillo1.git] / src / jp / sfjp / armadillo / archive / cab / CabInputStream.java
1 package jp.sfjp.armadillo.archive.cab;
2
3 import java.io.*;
4 import java.nio.*;
5 import java.nio.channels.*;
6 import java.util.zip.*;
7 import jp.sfjp.armadillo.archive.*;
8 import jp.sfjp.armadillo.io.*;
9
10 public final class CabInputStream extends ArchiveInputStream {
11
12     private final CabHeader header;
13
14     private int index = 0;
15
16     public CabInputStream(InputStream is) {
17         super(is);
18         this.header = new CabHeader();
19     }
20
21     public CabInputStream(InputStream is, boolean readHeaderFirst) throws IOException {
22         this(is);
23         if (readHeaderFirst)
24             header.initialize(is);
25     }
26
27     public CabEntry getNextEntry() throws IOException {
28         ensureOpen();
29         CabEntry entry = header.read(in);
30         if (entry == null)
31             return null;
32         final boolean first = index == 0;
33         ++index;
34         remaining = entry.getSize();
35         if (first) {
36             final short imethod = 1;
37             final CabCompressionType type = CabCompressionType.of(imethod);
38             switch (type) {
39                 case No:
40                     frontStream = in;
41                     break;
42                 case MSZIP:
43                     frontStream = new CfDataInflateInputStream(in);
44                     break;
45                 default:
46                     throw new CabException("Unsupported compression type: %s(0x%X)", type, imethod);
47             }
48         }
49         return entry;
50     }
51
52     static final class CfDataInflateInputStream extends FilterInputStream {
53
54         private static final int BLOCK_SIZE = 32768;
55
56         private final Inflater inflater;
57         private byte[] inflaterBuffer;
58
59         CfDataInflateInputStream(InputStream is) {
60             this(is, new Inflater(true));
61         }
62
63         CfDataInflateInputStream(InputStream is, Inflater inflater) {
64             super(is);
65             this.inflater = inflater;
66         }
67
68         @Override
69         public int read() throws IOException {
70             byte[] bytes = new byte[1];
71             if (read(bytes, 0, 1) < 1)
72                 return -1;
73             return bytes[0];
74         }
75
76         @Override
77         public int read(byte[] b, int off, int len) throws IOException {
78             if (inflaterBuffer == null)
79                 refill();
80             int p = off;
81             int q = len;
82             while (q > 0) {
83                 final int r;
84                 try {
85                     r = inflater.inflate(b, p, q);
86                 }
87                 catch (DataFormatException ex) {
88                     throw new CabException("data format error", ex);
89                 }
90                 if (r < 0)
91                     throw new CabException("illegal state");
92                 if (r == 0) {
93                     if (inflater.needsDictionary())
94                         throw new CabException("change dictionary not supported");
95                     if (inflater.finished())
96                         if (inflater.getTotalOut() == BLOCK_SIZE)
97                             refill();
98                         else
99                             break;
100                     if (inflater.needsInput())
101                         refill();
102                 }
103                 p += r;
104                 q -= r;
105             }
106             assert q >= 0;
107             return len - q;
108         }
109
110         void refill() throws IOException {
111             byte[] tmp = new byte[BLOCK_SIZE];
112             final int r = readNextBlock(tmp, 0);
113             if (r < 1)
114                 throw new CabException("illegal state");
115             final int remaining = inflater.getRemaining();
116             byte[] newBuffer = new byte[remaining + r];
117             if (remaining > 0) {
118                 assert inflaterBuffer != null;
119                 final int offset = inflaterBuffer.length - remaining;
120                 System.arraycopy(inflaterBuffer, offset, newBuffer, 0, remaining);
121             }
122             System.arraycopy(tmp, 0, newBuffer, remaining, r);
123             inflater.reset();
124             inflater.setInput(newBuffer);
125             inflaterBuffer = newBuffer;
126         }
127
128         @SuppressWarnings("unused")
129         int readNextBlock(byte[] bytes, int offset) throws IOException {
130             // CFDATA header
131             final int csum; // checksum of this CFDATA entry
132             final short cbData; // number of compressed bytes in this block
133             final short cbUncomp; // number of uncompressed bytes in this block
134             final byte[] abReserve; // (optional) per-datablock reserved area
135             // ---
136             ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
137             buffer.clear();
138             Channels.newChannel(in).read(buffer);
139             if (buffer.position() == 0)
140                 return -1; // EOF?
141             if (buffer.position() != 8)
142                 throw new CabException("reading CFDATA header error: %d", buffer.position());
143             buffer.rewind();
144             csum = buffer.getInt();
145             cbData = buffer.getShort();
146             cbUncomp = buffer.getShort();
147             byte[] tmp = new byte[cbData];
148             final int r = IOUtilities.read(in, tmp, 0, cbData);
149             if (r != cbData)
150                 throw new CabException("failed to inflate (fill)");
151             // skip CK
152             if (tmp[0] != 'C' || tmp[1] != 'K')
153                 throw new CabException("bad CFDATA block (%02X%02X)", tmp[0], tmp[1]);
154             System.arraycopy(tmp, 2, bytes, offset, r - 2);
155             return r - 2;
156         }
157
158     }
159
160 }