All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.durbo.io.RegionFileInputStream Maven / Gradle / Ivy

/*
 * Copyright 1997-2008 Day Management AG
 * Barfuesserplatz 6, 4001 Basel, Switzerland
 * All Rights Reserved.
 *
 * This software is the confidential and proprietary information of
 * Day Management AG, ("Confidential Information"). You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Day.
 */
package com.day.durbo.io;

import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import java.util.zip.ZipException;

/**
 * RegionFileInputStream implements an input stream that streams
 * the contents of a region of a file. The reading is buffered. The file is
 * opened upon the first access. this helps minimizing the total number of
 * open files.
 */
public class RegionFileInputStream extends InputStream {

    /**
     * the maximum size of a buffer
     */
    static final int MAX_BUFFER = 8192;

    /**
     * the buffer for the data
     */
    private byte[] buffer;

    /**
     * one byte buffer for 'read'
     */
    private final byte[] singleByteBuf = new byte[1];

    /**
     * the read offset within the buffer
     */
    private int pos = 0;

    /**
     * the end of valid data available in the buffer
     */
    private int end = 0;

    /**
     * the absolute position of the start of this region
     */
    private final long regionStart;

    /**
     * the absolute position of the end of this region
     */
    private final long regionEnd;

    /**
     * last sync point
     */
    private long lastSyncPoint = -1;

    /**
     * the position at the last sync point
     */
    private long lastSyncPos = 0;

    /**
     * the relative length of this region
     */
    private long length;

    /**
     * the relative position (actual bytes returned, if inflating)
     */
    private long position;

    /**
     * the position when 'mark' was invoked.
     */
    private long markedPosition = 0;

    /**
     * the file this stream was created from
     */
    private File file;

    /**
     * the random access file this stream operates on
     */
    private RandomAccessFile raf;

    /**
     * the inflater to use for compressed data
     */
    private Inflater inflater;

    /**
     * flag indicating if inflater is needed
     */
    private boolean isInflating;

    /**
     * true when eof is reached
     */
    private boolean reachEOF = false;

    /**
     * true when stream is closed
     */
    private IOException closedBy = null;

    /**
     * defines that the region can have multiple zip streams
     */
    private boolean hasMultiStreams = true;

    /**
     * Same as {@link #RegionFileInputStream(File, boolean)} with inflate set to
     * false.
     *
     * @param file the file to read from.
     * @throws FileNotFoundException if the file was not found
     * @throws IOException           if an I/O error occurs
     */
    public RegionFileInputStream(File file)
            throws IOException, FileNotFoundException {
        this(file, false);
    }

    /**
     * Same as {@link #RegionFileInputStream(File, long, long, boolean)} where
     * offset is 0 and len the length of the file.
     *
     * @param file    the file to read from.
     * @param inflate indicates if the file is compressed
     * @throws FileNotFoundException if the file was not found
     * @throws IOException           if an I/O error occurs
     */
    public RegionFileInputStream(File file, boolean inflate)
            throws IOException, FileNotFoundException {
        this(file, 0, file.length(), inflate);
    }

    /**
     * Creates a new input stream that reads for the region [off, off+len] of
     * the given file. If the region  is greater than the length of the file an
     * IOException os thrown.
     *
     * @param file the file to read from.
     * @param off  the absolute offset in the file to read from
     * @param len  the total length of the region
     * @throws FileNotFoundException if the file was not found
     * @throws IOException           if an I/O error occurs
     */
    public RegionFileInputStream(File file, long off, long len)
            throws IOException, FileNotFoundException {
        this(file, off, len, false);
    }

    /**
     * Creates a new input stream that reads for the region [off, off+len] of
     * the given file. If the region  is greater than the length of the file an
     * IOException os thrown.
     *
     * @param file    the file to read from.
     * @param off     the absolute offset in the file to read from
     * @param len     the total length of the region
     * @param inflate indicates if the file is compressed
     * @throws FileNotFoundException if the file was not found
     * @throws IOException           if an I/O error occurs
     */
    public RegionFileInputStream(File file, long off, long len, boolean inflate)
            throws FileNotFoundException, IOException {
        this.file = file;
        regionStart = off;
        regionEnd = off + len;
        length = Integer.MAX_VALUE;
        if (regionEnd > file.length()) {
            throw new EOFException("Region overlaps.");
        }
        // check if we can read from the file
        if (!file.canRead()) {
            throw new FileNotFoundException(file.getPath());
        }

        isInflating = inflate;
        if (!isInflating) {
            length = regionEnd - regionStart;
        }
    }

    /**
     * internally creates a new region input stream that operates on a physical
     * region but seeks to the correct logical position in the decompressed
     * stream.
     *
     * @param file  the file to read from.
     * @param start the (physical) start of the new region
     * @param end   the (physical) end of the new region
     * @param pos   the (logical) position of the region
     * @param len   the (logical) length of the region
     * @throws FileNotFoundException if the file was not found
     * @throws IOException           if an I/O error occurs
     * @throws EOFException          if invalid ranges are specified.
     */
    private RegionFileInputStream(File file, long start, long end, long pos, long len)
            throws IOException, FileNotFoundException, EOFException {
        this(file, start, end - start, true);
        if (skip(pos) < 0) {
            throw new EOFException("error while seeking to " + pos);
        }
        length = position + len;
    }

    /**
     * checks if the stream is property initialized and opens the underlying
     * file if needed.
     *
     * @throws IOException if an I/O error occurs.
     */
    private void ensureOpen() throws IOException {
        if (closedBy != null) {
            throw closedBy;
        }
        if (raf == null) {
            // open raf
            raf = new RandomAccessFile(file, "r");
            if (isInflating) {
                inflater = new Inflater();
            }
            raf.seek(regionStart);

            // create buffer
            buffer = new byte[(int) Math.min(regionEnd - regionStart, MAX_BUFFER)];
        }
    }

    /**
     * {@inheritDoc}
     */
    public int available() throws IOException {
        if (reachEOF) {
            return 0;
        } else {
            return 1;
        }
    }

    /**
     * {@inheritDoc}
     */
    public void close() throws IOException {
        if (raf != null) {
            raf.close();
            raf = null;
        }
        try {
            throw new IOException("RegionFileInputStream already closed. Stack trace is the one of the closer. (file=" + file.getPath() + ", s=" + regionStart + ", e=" + regionEnd);
        } catch (IOException e) {
            closedBy = e;
        }
    }

    /**
     * {@inheritDoc}
     */
    public synchronized void reset() throws IOException {
        if (!markSupported()) {
            throw new IOException("mark not supported or not called.");
        }
        pos = 0;
        end = 0;
        position = markedPosition;
        raf.seek(regionStart + position);
    }

    /**
     * {@inheritDoc}
     */
    public boolean markSupported() {
        return !isInflating;
    }

    /**
     * {@inheritDoc}
     */
    public synchronized void mark(int readlimit) {
        if (!isInflating) {
            try {
                ensureOpen();
                markedPosition = position;
            } catch (IOException e) {
                // ignore
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public long skip(final long n) throws IOException {
        ensureOpen();
        // todo: improve
        final byte[] b = new byte[8192];
        long total = 0;
        while (total < n) {
            final int len = read(b, 0, (int) Math.min((long) b.length, n - total));
            if (len == -1) {
                reachEOF = true;
                break;
            }
            total += len;
        }
        return total;
    }

    /**
     * {@inheritDoc}
     */
    public int read() throws IOException {
        return read(singleByteBuf, 0, 1) == -1 ? -1 : singleByteBuf[0] & 0xff;
    }

    /**
     * {@inheritDoc}
     */
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    /**
     * {@inheritDoc}
     */
    public int read(byte b[], int off, int len) throws IOException {
        ensureOpen();
        // limit length to max
        len = (int) Math.min((long) len, (length - position));
        if (len == 0) {
            return -1;
        }
        int read = inflater == null
                ? readBuffered(b, off, len)
                : readCompressed(b, off, len);
        if (read > 0) {
            position += read;
        }
        return read;
    }

    private int readBuffered(byte b[], int off, int len) throws IOException {
        if (fill() < 0) {
            return -1;
        }
        int read = 0;
        while (len > 0) {
            if (fill() < 0) {
                break;
            }
            int d = Math.min((end - pos), len);
            System.arraycopy(buffer, pos, b, off, d);
            pos += d;
            off += d;
            len -= d;
            read += d;
        }
        return read;
    }

    private int readCompressed(byte[] b, int off, int len) throws IOException {
        try {
            int n;
            while ((n = inflater.inflate(b, off, len)) == 0) {
                if (inflater.finished() || inflater.needsDictionary()) {
                    if (hasMultiStreams && getRemaining() > 0) {
                        int r = inflater.getRemaining();
                        inflater.reset();
                        inflater.setInput(buffer, end - r, r);
                        lastSyncPoint = raf.getFilePointer() - r;
                        lastSyncPos = position;
                    } else {
                        reachEOF = true;
                        return -1;
                    }
                }
                if (inflater.needsInput()) {
                    feed();
                }
            }
            return n;
        } catch (DataFormatException e) {
            String s = e.getMessage();
            throw new ZipException(s != null ? s : "Invalid ZLIB data format");
        }
    }

    /**
     * fills the buffer by at least 1 byte.
     *
     * @return the number of bytes read.
     * @throws IOException if an I/O error occurs.
     */
    private int fill() throws IOException {
        if (end > pos) {
            return end - pos;
        }
        int d = (int) Math.min((long) buffer.length, regionEnd - raf.getFilePointer());
        pos = 0;
        end = 0;
        if (d == 0) {
            reachEOF = true;
            return -1;
        }
        while (d > 0) {
            int read = raf.read(buffer, end, d);
            if (read < 0) {
                throw new EOFException("error while reading past region boundaries");
            }
            end += read;
            d -= read;
        }
        return end - pos;
    }

    /**
     * fills the buffer by at least 1 byte.
     *
     * @return the number of bytes fed into the buffer
     * @throws IOException if an I/O error occurs.
     */
    private int feed() throws IOException {
        int d = (int) Math.min((long) buffer.length, regionEnd - raf.getFilePointer());
        pos = 0;
        end = 0;
        if (d == 0) {
            reachEOF = true;
            return -1;
        }
        while (d > 0) {
            int read = raf.read(buffer, end, d);
            if (read < 0) {
                throw new EOFException("error while reading past region boundaries");
            }
            end += read;
            d -= read;
        }
        inflater.setInput(buffer, 0, end);
        return end;
    }

    /**
     * Returns the underlying file.
     *
     * @return the underlying file.
     */
    public File getFile() {
        return file;
    }

    /**
     * Returns the read pointer position relative to this file.
     *
     * @return the read pointer position relative to this file.
     * @throws IOException if an I/O error occurs.
     */
    public long getAbsolutePosition() throws IOException {
        ensureOpen();
        return regionStart + position;
    }

    /**
     * Returns the read pointer position relative to this region.
     *
     * @return the read pointer position relative to this region.
     * @throws IOException if an I/O error occurs.
     */
    public long getPosition() throws IOException {
        ensureOpen();
        return position;
    }

    /**
     * Returns the remaining bytes available in this region
     *
     * @return the remaining bytes available in this region
     * @throws IOException if an I/O error occurs.
     */
    public long getRemaining() throws IOException {
        ensureOpen();
        // check if length is set
        if (length == Integer.MAX_VALUE) {
            return regionEnd - raf.getFilePointer() + inflater.getRemaining();
        } else {
            return length - position;
        }
    }

    /**
     * Creates a new RegionFileInputStream that is based on this one.
     *
     * @param off relative offset to the current read pointer of this region
     * @param len total length of this new stream.
     * @return the new region stream
     * @throws IOException if an I/O error occurs.
     */
    public RegionFileInputStream substream(long off, long len)
            throws IOException {
        ensureOpen();
        if (inflater == null) {
            return new RegionFileInputStream(file, getAbsolutePosition() + off, len);
        } else {
            // if synced
            if (lastSyncPoint < 0) {
                return new RegionFileInputStream(file, regionStart, regionEnd, position + off, len);
            } else {
                return new RegionFileInputStream(file, lastSyncPoint, regionEnd, position + off - lastSyncPos, len);
            }
        }
    }

    /**
     * Duplicates this stream. If the stream is already consumed or compressed,
     * null is returned.
     *
     * @return a duplicate of this stream or null
     * @throws IOException if an I/O error occurs.
     */
    public RegionFileInputStream duplicate() throws IOException {
        if (raf != null || inflater != null) {
            return null;
        } else {
            return new RegionFileInputStream(file, regionStart, regionEnd - regionStart, false);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy