Commit | Line | Data |
---|---|---|
2e7584da NR |
1 | package be.nikiroo.utils; |
2 | ||
3 | import java.io.IOException; | |
4 | import java.io.InputStream; | |
5 | ||
63b46ca9 NR |
6 | /** |
7 | * This {@link InputStream} can be separated into sub-streams (you can process | |
8 | * it as a normal {@link InputStream} but, when it is spent, you can call | |
9 | * {@link NextableInputStream#next()} on it to unlock new data). | |
10 | * <p> | |
11 | * The separation in sub-streams is done via {@link NextableInputStreamStep}. | |
12 | * | |
13 | * @author niki | |
14 | */ | |
2e7584da | 15 | public class NextableInputStream extends InputStream { |
63b46ca9 NR |
16 | private NextableInputStreamStep step; |
17 | private boolean stopped; | |
4098af70 | 18 | |
2e7584da NR |
19 | private InputStream in; |
20 | private boolean eof; | |
21 | private int pos = 0; | |
22 | private int len = 0; | |
23 | private byte[] buffer = new byte[4096]; | |
24 | ||
63b46ca9 NR |
25 | /** |
26 | * Create a new {@link NextableInputStream} that wraps the given | |
27 | * {@link InputStream}. | |
28 | * | |
29 | * @param in | |
30 | * the {@link InputStream} to wrap | |
31 | * @param step | |
32 | * how to separate it into sub-streams (can be NULL, but in that | |
33 | * case it will behave as a normal {@link InputStream}) | |
34 | */ | |
35 | public NextableInputStream(InputStream in, NextableInputStreamStep step) { | |
2e7584da | 36 | this.in = in; |
63b46ca9 | 37 | this.step = step; |
2e7584da NR |
38 | } |
39 | ||
63b46ca9 NR |
40 | /** |
41 | * Unblock the processing of the next sub-stream. | |
42 | * <p> | |
43 | * It can only be called when the "current" stream is spent (i.e., you must | |
44 | * first process the stream until it is spent). | |
45 | * <p> | |
46 | * We consider that when the under-laying {@link InputStream} is also spent, | |
47 | * we cannot have a next sub-stream (it will thus return FALSE). | |
48 | * <p> | |
49 | * {@link IOException}s can happen when we have no data available in the | |
50 | * buffer; in that case, we fetch more data to know if we can have a next | |
51 | * sub-stream or not. | |
52 | * | |
53 | * @return TRUE if we unblocked the next sub-stream, FALSE if not | |
54 | * | |
55 | * @throws IOException | |
56 | * in case of I/O error | |
57 | */ | |
58 | public boolean next() throws IOException { | |
59 | if (!hasMoreData() && stopped) { | |
4098af70 | 60 | len = step.getResumeLen(); |
63b46ca9 | 61 | pos += step.getResumeSkip(); |
4098af70 | 62 | eof = false; |
4098af70 | 63 | |
63b46ca9 NR |
64 | if (!preRead()) { |
65 | checkBuffer(false); | |
66 | } | |
67 | ||
68 | // consider that if EOF, there is no next | |
69 | return hasMoreData(); | |
4098af70 N |
70 | } |
71 | ||
72 | return false; | |
73 | } | |
74 | ||
2e7584da NR |
75 | @Override |
76 | public int read() throws IOException { | |
77 | preRead(); | |
78 | if (eof) { | |
79 | return -1; | |
80 | } | |
81 | ||
82 | return buffer[pos++]; | |
83 | } | |
84 | ||
85 | @Override | |
86 | public int read(byte[] b) throws IOException { | |
87 | return read(b, 0, b.length); | |
88 | } | |
89 | ||
90 | @Override | |
91 | public int read(byte[] b, int boff, int blen) throws IOException { | |
92 | if (b == null) { | |
93 | throw new NullPointerException(); | |
94 | } else if (boff < 0 || blen < 0 || blen > b.length - boff) { | |
95 | throw new IndexOutOfBoundsException(); | |
96 | } else if (blen == 0) { | |
97 | return 0; | |
98 | } | |
99 | ||
100 | int done = 0; | |
4098af70 | 101 | while (hasMoreData() && done < blen) { |
2e7584da | 102 | preRead(); |
4098af70 N |
103 | if (hasMoreData()) { |
104 | for (int i = pos; i < blen && i < len; i++) { | |
105 | b[boff + done] = buffer[i]; | |
106 | pos++; | |
107 | done++; | |
108 | } | |
2e7584da NR |
109 | } |
110 | } | |
111 | ||
112 | return done > 0 ? done : -1; | |
113 | } | |
114 | ||
4098af70 N |
115 | @Override |
116 | public int available() throws IOException { | |
117 | return Math.max(0, len - pos); | |
118 | } | |
119 | ||
63b46ca9 NR |
120 | /** |
121 | * Check if we still have some data in the buffer and, if not, fetch some. | |
122 | * | |
123 | * @return TRUE if we fetched some data, FALSE if there are still some in | |
124 | * the buffer | |
125 | * | |
126 | * @throws IOException | |
127 | * in case of I/O error | |
128 | */ | |
129 | private boolean preRead() throws IOException { | |
130 | boolean hasRead = false; | |
131 | if (!eof && in != null && pos >= len && !stopped) { | |
2e7584da NR |
132 | pos = 0; |
133 | len = in.read(buffer); | |
63b46ca9 NR |
134 | checkBuffer(true); |
135 | hasRead = true; | |
2e7584da NR |
136 | } |
137 | ||
138 | if (pos >= len) { | |
139 | eof = true; | |
140 | } | |
63b46ca9 NR |
141 | |
142 | return hasRead; | |
2e7584da | 143 | } |
4098af70 | 144 | |
63b46ca9 NR |
145 | /** |
146 | * We have more data available in the buffer or we can fetch more. | |
147 | * | |
148 | * @return TRUE if it is the case, FALSE if not | |
149 | */ | |
4098af70 N |
150 | private boolean hasMoreData() { |
151 | return !(eof && pos >= len); | |
152 | } | |
153 | ||
63b46ca9 NR |
154 | /** |
155 | * Check that the buffer didn't overshot to the next item, and fix | |
156 | * {@link NextableInputStream#len} if needed. | |
157 | * <p> | |
158 | * If {@link NextableInputStream#len} is fixed, | |
159 | * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped} | |
160 | * are set to TRUE. | |
161 | * | |
162 | * @param newBuffer | |
163 | * we changed the buffer, we need to clear some information in | |
164 | * the {@link NextableInputStreamStep} | |
165 | */ | |
166 | private void checkBuffer(boolean newBuffer) { | |
167 | if (step != null) { | |
168 | if (newBuffer) { | |
169 | step.clearBuffer(); | |
170 | } | |
4098af70 | 171 | |
63b46ca9 NR |
172 | int stopAt = step.stop(buffer, pos, len); |
173 | if (stopAt >= 0) { | |
174 | len = stopAt; | |
175 | eof = true; | |
176 | stopped = true; | |
4098af70 N |
177 | } |
178 | } | |
179 | } | |
2e7584da | 180 | } |