1 /*
2 * Copyright (c) 2005-2007 Creative Sphere Limited.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *
10 * Creative Sphere - initial API and implementation
11 *
12 */
13 package org.abstracthorizon.mercury.maildir.file;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.RandomAccessFile;
18 import javax.mail.internet.SharedInputStream;
19
20 /**
21 * <code>SharedInputStream</code> implementation
22 *
23 * @author Daniel Sendula
24 */
25 public class SharedInputStreamImpl extends InputStream implements SharedInputStream {
26
27 /** Next Unique Id - needed for debug purposes only */
28 protected static int num = 0;
29
30 /** Unique Id - needed for debug purposes only */
31 protected int id;
32
33 /** Stream start offset */
34 protected long start;
35
36 /** Stream end offset */
37 protected long end;
38
39 /** Stream's pointer */
40 protected long ptr;
41
42 /** Mark */
43 protected long mark;
44
45 /** Stream's buffer size */
46 public static final int BUFFER_SIZE = 1024 * 16;
47
48 /** Stream's buffer */
49 protected byte[] buffer;
50
51 /** Stream's buffer start pointer */
52 protected long bufptr;
53
54 /** Stream's buffer content len */
55 protected int buflen;
56
57 /** Cached value of file's size */
58 protected long fileSize = -1;
59
60 /** File provider reference */
61 protected FileProvider fileProvider;
62
63 /** Random access file reference */
64 protected RandomAccessFile raf;
65
66 /** Pool that created this object */
67 protected SharedInputStreamPool parent;
68
69 /** Timestamp this stream's file is accessed last time */
70 protected long lastAccessed;
71
72 /**
73 * Constructor.
74 * @param parent pool that is creating this stream
75 * @param fileProvider file provider
76 * @param start stream start
77 * @param end stream end
78 */
79 public SharedInputStreamImpl(SharedInputStreamPool parent, FileProvider fileProvider, long start, long end) {
80 this.fileProvider = fileProvider;
81 this.parent = parent;
82 this.end = end;
83 this.start = start;
84 if (end < 0) {
85 try {
86 fileSize = fileProvider.getFile().length();
87 this.end = fileSize;
88 } catch (IOException ignore) {
89 ignore.printStackTrace();
90 }
91 }
92 mark = start;
93 ptr = start;
94 lastAccessed = System.currentTimeMillis();
95 num = num + 1;
96 id = num;
97 }
98
99 /**
100 * Reads bytes from the underlaying file. It opens it if it wasn't open at the moment
101 * of call. Also, uses internal buffer too.
102 * @param buf buffer to be read into
103 * @param off offset in buffer
104 * @param len length to be read
105 * @return number of actually read bytes
106 * @throws IOException
107 */
108 public int read(byte[] buf, int off, int len) throws IOException {
109 lastAccessed = System.currentTimeMillis();
110 if (len == 0) {
111 return 0;
112 }
113 if (ptr >= end) {
114 return -1;
115 }
116 int rem = (int) (end - ptr);
117 if (len > rem) {
118 len = rem;
119 }
120 int ret = 0;
121 boolean firstRead = true;
122 while ((len > 0) && (ptr < end)) {
123 int l = len;
124 if ((buffer != null) && (ptr >= bufptr) && (ptr < bufptr + buflen)) {
125 // we have part of requested data in buffer
126 int bufoff = (int) (ptr - bufptr);
127 if (l > buflen - bufoff) {
128 l = buflen - bufoff;
129 }
130 System.arraycopy(buffer, bufoff, buf, off, l);
131 } else {
132 synchronized (this) {
133 if (firstRead) {
134 checkOpened();
135 long p = raf.getFilePointer();
136 if ((p != ptr) && (ptr <= getFileSize())) {
137 raf.seek(ptr);
138 }
139 firstRead = false;
140 }
141 // requested is not in buffer
142 if (l >= BUFFER_SIZE) {
143 // resulted read is just in the middle - so save
144 // reading in buffer for last part
145 if ((buffer == null) || (buffer.length < BUFFER_SIZE)) {
146 buffer = new byte[BUFFER_SIZE];
147 }
148 l = raf.read(buf, off, BUFFER_SIZE);
149 if (l < 0) {
150 // Reached unexpeced end of file prematurely
151 ptr = end;
152 l = 0;
153 len = 0;
154 }
155 } else {
156 // Last part
157 rem = (int) (end - ptr);
158 if (rem > BUFFER_SIZE) {
159 rem = BUFFER_SIZE;
160 }
161 if ((buffer == null) || (buffer.length < rem)) {
162 buffer = new byte[rem];
163 }
164 bufptr = ptr;
165 buflen = raf.read(buffer, 0, rem);
166 if (buflen >= 0) {
167 if (l > buflen) {
168 l = buflen;
169 }
170 if (l > 0) {
171 try {
172 System.arraycopy(buffer, 0, buf, off, l);
173 } catch (ArrayIndexOutOfBoundsException e) {
174 throw e;
175 }
176 }
177 } else {
178 // Reached unexpeced end of file prematurely
179 len = 0;
180 ptr = end;
181 l = 0;
182 }
183 }
184 } // synch
185 }
186 off = off + l;
187 ret = ret + l;
188 ptr = ptr + l;
189 len = len - l;
190 } // while
191 if (ptr >= end) {
192 close();
193 }
194 // TODO - check if this is working - should be ok but not tested.
195 // if (bufptr + buflen >= end) {
196 // synchronized (this) {
197 // raf.close();
198 // raf = null;
199 // }
200 // }
201 return ret;
202 } // read
203
204 /**
205 * Reads whole buffer.
206 * @param buf buffer to be read into
207 * @return number of actually read bytes
208 * @throws IOException
209 */
210 public int read(byte[] buf) throws IOException {
211 return read(buf, 0, buf.length);
212 } // read
213
214 /**
215 * Reads one byte or returns -1 if EOF is reached (end of stream really).
216 * @return read byte
217 * @throws IOException
218 */
219 public int read() throws IOException {
220 // lastAccessed = System.currentTimeMillis();
221 if (ptr >= end) {
222 return -1;
223 }
224 if ((buffer != null) && (ptr >= bufptr) && (ptr < bufptr + buflen)) {
225 int ret = buffer[(int) (ptr - bufptr)];
226 ptr = ptr + 1;
227 if (ptr >= end) {
228 close();
229 }
230 return ret;
231 }
232 byte[] b = new byte[1];
233 int i = read(b);
234 if (i > 0) {
235 return b[0];
236 } else {
237 return -1;
238 }
239 } // read
240
241 /**
242 * Returns number of available bytes in stream (to end of stream).
243 * @return number of available bytes
244 * @throws IOException
245 */
246 public int available() throws IOException {
247 return (int) (end - ptr);
248 } // available
249
250 /**
251 * Closes the stream and releases allocated resources.
252 * @throws IOException
253 */
254 public void close() throws IOException {
255 if (raf != null) {
256 closeImpl();
257 parent.closed(this);
258 }
259 } // close
260
261 /**
262 * This method actually releases the resources (<code>random access file</code>)
263 * @throws IOException
264 */
265 public synchronized void closeImpl() throws IOException {
266 raf.close();
267 raf = null;
268 buffer = null;
269 } // close
270
271 /**
272 * Returns <code>true</code>
273 * @return <code>true</code>
274 */
275 public boolean markSupported() {
276 return true;
277 } // isMarkSupported
278
279 /**
280 * Sets mark.
281 * @param readlimit ignored
282 */
283 public void mark(int readlimit) {
284 mark = ptr;
285 } // mark
286
287 /**
288 * Resets stream to the mark
289 * @throws IOException
290 */
291 public void reset() throws IOException {
292 ptr = mark;
293 } // reset
294
295 /**
296 * Returns <code>true</code>
297 * @return <code>true</code>
298 */
299 public boolean ready() {
300 return true;
301 } // ready
302
303 /**
304 * Skips number of bytes
305 * @param n number of bytes to be skipped
306 * @return current pointer in stream
307 * @throws IOException
308 */
309 public long skip(long n) throws IOException {
310 lastAccessed = System.currentTimeMillis();
311 if (n > end - ptr) {
312 n = end - ptr;
313 }
314 ptr = ptr + (int) n;
315 if (ptr >= end) {
316 close();
317 }
318 return n;
319 } // skip
320
321 /**
322 * Returns current pointer in stream
323 * @return current pointer in stream
324 */
325 public long getPosition() {
326 return ptr - start;
327 } // getPosition
328
329 /**
330 * Creates new stream from this stream.
331 * @param pos new relative start position
332 * @param end new relative end position
333 * @return new instance with given start and end.
334 */
335 public InputStream newStream(long pos, long end) {
336 pos = this.start + pos;
337 if (end < 0) {
338 return parent.newStream(fileProvider, pos, this.end);
339 } else {
340 end = this.start + end;
341 return parent.newStream(fileProvider, pos, end);
342 }
343 } // newStream
344
345 /**
346 * Checks if underlaying file is opened. It uses <code>FileProvider</code>
347 * to obtain file.
348 * @throws IOException
349 */
350 protected void checkOpened() throws IOException {
351 if (raf == null) {
352 raf = new RandomAccessFile(fileProvider.getFile(), "r");
353 raf.seek(start);
354 parent.opened(this);
355 }
356 }
357
358 /**
359 * Returns file's size. If not cached then caches it using <code>FileProvider</code>
360 * @return file's size.
361 */
362 protected long getFileSize() {
363 if (fileSize >= 0) {
364 return fileSize;
365 }
366 fileSize = fileProvider.getFileSize();
367 if (fileSize < 0) {
368 try {
369 synchronized (this) {
370 checkOpened();
371 fileSize = raf.length();
372 }
373 } catch (IOException ignore) {
374 }
375 }
376 return fileSize;
377 }
378 }