View Javadoc

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 }