001/*
002 * $Id: RandomAccessFileOrArray.java 4882 2011-05-24 16:33:21Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045
046import com.itextpdf.text.Document;
047import java.io.ByteArrayOutputStream;
048import java.io.DataInput;
049import java.io.DataInputStream;
050import java.io.EOFException;
051import java.io.File;
052import java.io.FileInputStream;
053import java.io.IOException;
054import java.io.InputStream;
055import java.io.RandomAccessFile;
056import java.net.URL;
057import java.nio.channels.FileChannel;
058import com.itextpdf.text.error_messages.MessageLocalization;
059/** An implementation of a RandomAccessFile for input only
060 * that accepts a file or a byte array as data source.
061 *
062 * @author Paulo Soares
063 */
064public class RandomAccessFileOrArray implements DataInput {
065        
066    MappedRandomAccessFile rf;
067    RandomAccessFile trf;
068    boolean plainRandomAccess;
069    String filename;
070    byte arrayIn[];
071    int arrayInPtr;
072    byte back;
073    boolean isBack = false;
074    
075    /** Holds value of property startOffset. */
076    private int startOffset = 0;
077
078    public RandomAccessFileOrArray(String filename) throws IOException {
079        this(filename, false, Document.plainRandomAccess);
080    }
081    
082    /**
083     * Constructs a new RandomAccessFileOrArrayObject
084     * @param filename the file to open (can be a file system file or one of hte following url strings: file://, http://, https://, jar:, wsjar:
085     * @param forceRead if true, the entire file will be read into memory
086     * @param plainRandomAccess if true, a regular RandomAccessFile is used to access the file contents.  If false, a memory mapped file will be used, unless the file cannot be mapped into memory, in which case regular RandomAccessFile will be used
087     * @throws IOException if there is a failure opening or reading the file
088     */
089    public RandomAccessFileOrArray(String filename, boolean forceRead, boolean plainRandomAccess) throws IOException {
090        this.plainRandomAccess = plainRandomAccess;
091        File file = new File(filename);
092        if (!file.canRead()) {
093            if (filename.startsWith("file:/") || filename.startsWith("http://") 
094                    || filename.startsWith("https://") || filename.startsWith("jar:") || filename.startsWith("wsjar:")) {
095                InputStream is = new URL(filename).openStream();
096                try {
097                    this.arrayIn = InputStreamToArray(is);
098                    return;
099                }
100                finally {
101                    try {is.close();}catch(IOException ioe){}
102                }
103            }
104            else {
105                InputStream is = BaseFont.getResourceStream(filename);
106                if (is == null)
107                    throw new IOException(MessageLocalization.getComposedMessage("1.not.found.as.file.or.resource", filename));
108                try {
109                    this.arrayIn = InputStreamToArray(is);
110                    return;
111                }
112                finally {
113                    try {is.close();}catch(IOException ioe){}
114                }
115            }
116        }
117        else if (forceRead) {
118            InputStream s = null;
119            try {
120                s = new FileInputStream(file);
121                this.arrayIn = InputStreamToArray(s);
122            }
123            finally {
124                try {if (s != null) {s.close();}}catch(Exception e){}
125            }
126                return;
127        }
128        this.filename = filename;
129        if (plainRandomAccess){
130            trf = new RandomAccessFile(filename, "r");
131        }else{
132            try{
133                rf = new MappedRandomAccessFile(filename, "r");
134            } catch (IOException e){
135                if (exceptionIsMapFailureException(e)){
136                    this.plainRandomAccess = true;
137                    trf = new RandomAccessFile(filename, "r");
138                } else {
139                    throw e;
140                }
141            }
142        }
143    }
144
145    /**
146     * Utility method that determines whether a given IOException is the result
147     * of a failure to map a memory mapped file.  It would be better if the runtime
148     * provided a special exception for this case, but it doesn't, so we have to rely
149     * on parsing the exception message.
150     * @param e the exception to check
151     * @return true if the exception was the result of a failure to map a memory mapped file
152     * @since 5.0.3
153     */
154    private static boolean exceptionIsMapFailureException(IOException e){
155        if (e.getMessage().indexOf("Map failed") >= 0)
156            return true;
157
158        return false;
159    }
160    
161    public RandomAccessFileOrArray(URL url) throws IOException {
162        InputStream is = url.openStream();
163        try {
164            this.arrayIn = InputStreamToArray(is);
165        }
166        finally {
167            try {is.close();}catch(IOException ioe){}
168        }
169    }
170
171    public RandomAccessFileOrArray(InputStream is) throws IOException {
172        this.arrayIn = InputStreamToArray(is);
173    }
174    
175    public static byte[] InputStreamToArray(InputStream is) throws IOException {
176        byte b[] = new byte[8192];
177        ByteArrayOutputStream out = new ByteArrayOutputStream();
178        while (true) {
179            int read = is.read(b);
180            if (read < 1)
181                break;
182            out.write(b, 0, read);
183        }
184        out.close();
185        return out.toByteArray();
186    }
187
188    public RandomAccessFileOrArray(byte arrayIn[]) {
189        this.arrayIn = arrayIn;
190    }
191    
192    public RandomAccessFileOrArray(RandomAccessFileOrArray file) {
193        filename = file.filename;
194        arrayIn = file.arrayIn;
195        startOffset = file.startOffset;
196        plainRandomAccess = file.plainRandomAccess;
197    }
198    
199    public void pushBack(byte b) {
200        back = b;
201        isBack = true;
202    }
203    
204    public int read() throws IOException {
205        if(isBack) {
206            isBack = false;
207            return back & 0xff;
208        }
209        if (arrayIn == null)
210            return plainRandomAccess ? trf.read() : rf.read();
211        else {
212            if (arrayInPtr >= arrayIn.length)
213                return -1;
214            return arrayIn[arrayInPtr++] & 0xff;
215        }
216    }
217    
218    public int read(byte[] b, int off, int len) throws IOException {
219        if (len == 0)
220            return 0;
221        int n = 0;
222        if (isBack) {
223            isBack = false;
224            if (len == 1) {
225                b[off] = back;
226                return 1;
227            }
228            else {
229                n = 1;
230                b[off++] = back;
231                --len;
232            }
233        }
234        if (arrayIn == null) {
235            return (plainRandomAccess ? trf.read(b, off, len) : rf.read(b, off, len)) + n;
236        }
237        else {
238            if (arrayInPtr >= arrayIn.length)
239                return -1;
240            if (arrayInPtr + len > arrayIn.length)
241                len = arrayIn.length - arrayInPtr;
242            System.arraycopy(arrayIn, arrayInPtr, b, off, len);
243            arrayInPtr += len;
244            return len + n;
245        }
246    }
247    
248    public int read(byte b[]) throws IOException {
249        return read(b, 0, b.length);
250    }
251    
252    public void readFully(byte b[]) throws IOException {
253        readFully(b, 0, b.length);
254    }
255    
256    public void readFully(byte b[], int off, int len) throws IOException {
257        int n = 0;
258        do {
259            int count = read(b, off + n, len - n);
260            if (count < 0)
261                throw new EOFException();
262            n += count;
263        } while (n < len);
264    }
265    
266    public long skip(long n) throws IOException {
267        return skipBytes((int)n);
268    }
269    
270    public int skipBytes(int n) throws IOException {
271        if (n <= 0) {
272            return 0;
273        }
274        int adj = 0;
275        if (isBack) {
276            isBack = false;
277            if (n == 1) {
278                return 1;
279            }
280            else {
281                --n;
282                adj = 1;
283            }
284        }
285        int pos;
286        int len;
287        int newpos;
288        
289        pos = getFilePointer();
290        len = length();
291        newpos = pos + n;
292        if (newpos > len) {
293            newpos = len;
294        }
295        seek(newpos);
296        
297        /* return the actual number of bytes skipped */
298        return newpos - pos + adj;
299    }
300    
301    public void reOpen() throws IOException {
302        if (filename != null && rf == null && trf == null) {
303            if (plainRandomAccess)
304                trf = new RandomAccessFile(filename, "r");
305            else
306                rf = new MappedRandomAccessFile(filename, "r");
307        }
308        seek(0);
309    }
310    
311    protected void insureOpen() throws IOException {
312        if (filename != null && rf == null && trf == null) {
313            reOpen();
314        }
315    }
316    
317    public boolean isOpen() {
318        return (filename == null || rf != null || trf != null);
319    }
320    
321    public void close() throws IOException {
322        isBack = false;
323        if (rf != null) {
324            rf.close();
325            rf = null;
326            // it's very expensive to open a memory mapped file and for the usage pattern of this class
327            // in iText it's faster the next re-openings to be done as a plain random access
328            // file
329            plainRandomAccess = true;
330        }
331        else if (trf != null) {
332            trf.close();
333            trf = null;
334        }
335    }
336    
337    public int length() throws IOException {
338        if (arrayIn == null) {
339            insureOpen();
340            return (int)(plainRandomAccess ? trf.length() : rf.length()) - startOffset;
341        }
342        else
343            return arrayIn.length - startOffset;
344    }
345    
346    public void seek(int pos) throws IOException {
347        pos += startOffset;
348        isBack = false;
349        if (arrayIn == null) {
350            insureOpen();
351            if (plainRandomAccess)
352                trf.seek(pos);
353            else
354                rf.seek(pos);
355        }
356        else
357            arrayInPtr = pos;
358    }
359    
360    public void seek(long pos) throws IOException {
361        seek((int)pos);
362    }
363    
364    public int getFilePointer() throws IOException {
365        insureOpen();
366        int n = isBack ? 1 : 0;
367        if (arrayIn == null) {
368            return (int)(plainRandomAccess ? trf.getFilePointer() : rf.getFilePointer()) - n - startOffset;
369        }
370        else
371            return arrayInPtr - n - startOffset;
372    }
373    
374    public boolean readBoolean() throws IOException {
375        int ch = this.read();
376        if (ch < 0)
377            throw new EOFException();
378        return (ch != 0);
379    }
380    
381    public byte readByte() throws IOException {
382        int ch = this.read();
383        if (ch < 0)
384            throw new EOFException();
385        return (byte)(ch);
386    }
387    
388    public int readUnsignedByte() throws IOException {
389        int ch = this.read();
390        if (ch < 0)
391            throw new EOFException();
392        return ch;
393    }
394    
395    public short readShort() throws IOException {
396        int ch1 = this.read();
397        int ch2 = this.read();
398        if ((ch1 | ch2) < 0)
399            throw new EOFException();
400        return (short)((ch1 << 8) + ch2);
401    }
402    
403    /**
404     * Reads a signed 16-bit number from this stream in little-endian order.
405     * The method reads two
406     * bytes from this stream, starting at the current stream pointer.
407     * If the two bytes read, in order, are
408     * <code>b1</code> and <code>b2</code>, where each of the two values is
409     * between <code>0</code> and <code>255</code>, inclusive, then the
410     * result is equal to:
411     * <blockquote><pre>
412     *     (short)((b2 &lt;&lt; 8) | b1)
413     * </pre></blockquote>
414     * <p>
415     * This method blocks until the two bytes are read, the end of the
416     * stream is detected, or an exception is thrown.
417     *
418     * @return     the next two bytes of this stream, interpreted as a signed
419     *             16-bit number.
420     * @exception  EOFException  if this stream reaches the end before reading
421     *               two bytes.
422     * @exception  IOException   if an I/O error occurs.
423     */
424    public final short readShortLE() throws IOException {
425        int ch1 = this.read();
426        int ch2 = this.read();
427        if ((ch1 | ch2) < 0)
428            throw new EOFException();
429        return (short)((ch2 << 8) + (ch1 << 0));
430    }
431    
432    public int readUnsignedShort() throws IOException {
433        int ch1 = this.read();
434        int ch2 = this.read();
435        if ((ch1 | ch2) < 0)
436            throw new EOFException();
437        return (ch1 << 8) + ch2;
438    }
439    
440    /**
441     * Reads an unsigned 16-bit number from this stream in little-endian order.
442     * This method reads
443     * two bytes from the stream, starting at the current stream pointer.
444     * If the bytes read, in order, are
445     * <code>b1</code> and <code>b2</code>, where
446     * <code>0&nbsp;&lt;=&nbsp;b1, b2&nbsp;&lt;=&nbsp;255</code>,
447     * then the result is equal to:
448     * <blockquote><pre>
449     *     (b2 &lt;&lt; 8) | b1
450     * </pre></blockquote>
451     * <p>
452     * This method blocks until the two bytes are read, the end of the
453     * stream is detected, or an exception is thrown.
454     *
455     * @return     the next two bytes of this stream, interpreted as an
456     *             unsigned 16-bit integer.
457     * @exception  EOFException  if this stream reaches the end before reading
458     *               two bytes.
459     * @exception  IOException   if an I/O error occurs.
460     */
461    public final int readUnsignedShortLE() throws IOException {
462        int ch1 = this.read();
463        int ch2 = this.read();
464        if ((ch1 | ch2) < 0)
465            throw new EOFException();
466        return (ch2 << 8) + (ch1 << 0);
467    }
468    
469    public char readChar() throws IOException {
470        int ch1 = this.read();
471        int ch2 = this.read();
472        if ((ch1 | ch2) < 0)
473            throw new EOFException();
474        return (char)((ch1 << 8) + ch2);
475    }
476    
477    /**
478     * Reads a Unicode character from this stream in little-endian order.
479     * This method reads two
480     * bytes from the stream, starting at the current stream pointer.
481     * If the bytes read, in order, are
482     * <code>b1</code> and <code>b2</code>, where
483     * <code>0&nbsp;&lt;=&nbsp;b1,&nbsp;b2&nbsp;&lt;=&nbsp;255</code>,
484     * then the result is equal to:
485     * <blockquote><pre>
486     *     (char)((b2 &lt;&lt; 8) | b1)
487     * </pre></blockquote>
488     * <p>
489     * This method blocks until the two bytes are read, the end of the
490     * stream is detected, or an exception is thrown.
491     *
492     * @return     the next two bytes of this stream as a Unicode character.
493     * @exception  EOFException  if this stream reaches the end before reading
494     *               two bytes.
495     * @exception  IOException   if an I/O error occurs.
496     */
497    public final char readCharLE() throws IOException {
498        int ch1 = this.read();
499        int ch2 = this.read();
500        if ((ch1 | ch2) < 0)
501            throw new EOFException();
502        return (char)((ch2 << 8) + (ch1 << 0));
503    }
504    
505    public int readInt() throws IOException {
506        int ch1 = this.read();
507        int ch2 = this.read();
508        int ch3 = this.read();
509        int ch4 = this.read();
510        if ((ch1 | ch2 | ch3 | ch4) < 0)
511            throw new EOFException();
512        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
513    }
514    
515    /**
516     * Reads a signed 32-bit integer from this stream in little-endian order.
517     * This method reads 4
518     * bytes from the stream, starting at the current stream pointer.
519     * If the bytes read, in order, are <code>b1</code>,
520     * <code>b2</code>, <code>b3</code>, and <code>b4</code>, where
521     * <code>0&nbsp;&lt;=&nbsp;b1, b2, b3, b4&nbsp;&lt;=&nbsp;255</code>,
522     * then the result is equal to:
523     * <blockquote><pre>
524     *     (b4 &lt;&lt; 24) | (b3 &lt;&lt; 16) + (b2 &lt;&lt; 8) + b1
525     * </pre></blockquote>
526     * <p>
527     * This method blocks until the four bytes are read, the end of the
528     * stream is detected, or an exception is thrown.
529     *
530     * @return     the next four bytes of this stream, interpreted as an
531     *             <code>int</code>.
532     * @exception  EOFException  if this stream reaches the end before reading
533     *               four bytes.
534     * @exception  IOException   if an I/O error occurs.
535     */
536    public final int readIntLE() throws IOException {
537        int ch1 = this.read();
538        int ch2 = this.read();
539        int ch3 = this.read();
540        int ch4 = this.read();
541        if ((ch1 | ch2 | ch3 | ch4) < 0)
542            throw new EOFException();
543        return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0));
544    }
545    
546    /**
547     * Reads an unsigned 32-bit integer from this stream. This method reads 4
548     * bytes from the stream, starting at the current stream pointer.
549     * If the bytes read, in order, are <code>b1</code>,
550     * <code>b2</code>, <code>b3</code>, and <code>b4</code>, where
551     * <code>0&nbsp;&lt;=&nbsp;b1, b2, b3, b4&nbsp;&lt;=&nbsp;255</code>,
552     * then the result is equal to:
553     * <blockquote><pre>
554     *     (b1 &lt;&lt; 24) | (b2 &lt;&lt; 16) + (b3 &lt;&lt; 8) + b4
555     * </pre></blockquote>
556     * <p>
557     * This method blocks until the four bytes are read, the end of the
558     * stream is detected, or an exception is thrown.
559     *
560     * @return     the next four bytes of this stream, interpreted as a
561     *             <code>long</code>.
562     * @exception  EOFException  if this stream reaches the end before reading
563     *               four bytes.
564     * @exception  IOException   if an I/O error occurs.
565     */
566    public final long readUnsignedInt() throws IOException {
567        long ch1 = this.read();
568        long ch2 = this.read();
569        long ch3 = this.read();
570        long ch4 = this.read();
571        if ((ch1 | ch2 | ch3 | ch4) < 0)
572            throw new EOFException();
573        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
574    }
575    
576    public final long readUnsignedIntLE() throws IOException {
577        long ch1 = this.read();
578        long ch2 = this.read();
579        long ch3 = this.read();
580        long ch4 = this.read();
581        if ((ch1 | ch2 | ch3 | ch4) < 0)
582            throw new EOFException();
583        return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0));
584    }
585    
586    public long readLong() throws IOException {
587        return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
588    }
589    
590    public final long readLongLE() throws IOException {
591        int i1 = readIntLE();
592        int i2 = readIntLE();
593        return ((long)i2 << 32) + (i1 & 0xFFFFFFFFL);
594    }
595    
596    public float readFloat() throws IOException {
597        return Float.intBitsToFloat(readInt());
598    }
599    
600    public final float readFloatLE() throws IOException {
601        return Float.intBitsToFloat(readIntLE());
602    }
603    
604    public double readDouble() throws IOException {
605        return Double.longBitsToDouble(readLong());
606    }
607    
608    public final double readDoubleLE() throws IOException {
609        return Double.longBitsToDouble(readLongLE());
610    }
611    
612    public String readLine() throws IOException {
613        StringBuffer input = new StringBuffer();
614        int c = -1;
615        boolean eol = false;
616        
617        while (!eol) {
618            switch (c = read()) {
619                case -1:
620                case '\n':
621                    eol = true;
622                    break;
623                case '\r':
624                    eol = true;
625                    int cur = getFilePointer();
626                    if ((read()) != '\n') {
627                        seek(cur);
628                    }
629                    break;
630                default:
631                    input.append((char)c);
632                    break;
633            }
634        }
635        
636        if ((c == -1) && (input.length() == 0)) {
637            return null;
638        }
639        return input.toString();
640    }
641    
642    public String readUTF() throws IOException {
643        return DataInputStream.readUTF(this);
644    }
645    
646    /** Getter for property startOffset.
647     * @return Value of property startOffset.
648     *
649     */
650    public int getStartOffset() {
651        return this.startOffset;
652    }
653    
654    /** Setter for property startOffset.
655     * @param startOffset New value of property startOffset.
656     *
657     */
658    public void setStartOffset(int startOffset) {
659        this.startOffset = startOffset;
660    }
661
662    /**
663     * @since 2.0.8
664     */
665    public java.nio.ByteBuffer getNioByteBuffer() throws IOException {
666        if (filename != null) {
667                FileChannel channel;
668            if (plainRandomAccess)
669                channel = trf.getChannel();
670            else
671                channel = rf.getChannel();
672            return channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
673        }
674        return java.nio.ByteBuffer.wrap(arrayIn);
675    }
676}