com.izforge.izpack.core.io.FileSpanningInputStream.java Source code

Java tutorial

Introduction

Here is the source code for com.izforge.izpack.core.io.FileSpanningInputStream.java

Source

/*
 * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
 *
 * http://izpack.org/ http://izpack.codehaus.org/
 *
 * Copyright 2007 Dennis Reil
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.izforge.izpack.core.io;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;

/**
 * An <tt>InputStream</tt> which transparently spans over multiple volumes.
 *
 * @author Dennis Reil, <Dennis.Reil@reddot.de>
 * @author Tim Anderson
 * @see FileSpanningOutputStream
 */
public class FileSpanningInputStream extends InputStream {
    /**
     * The spanning input stream. This sits between the volume file input stream and {@link #zippedInputStream}.
     */
    private final SpanningInputStream spanningInputStream;

    /**
     * The sip stream.
     */
    private GZIPInputStream zippedInputStream;

    /**
     * The absolute offset into the volumes.
     */
    private long filePointer;

    /**
     * The logger.
     */
    private static final Logger logger = Logger.getLogger(FileSpanningInputStream.class.getName());

    /**
     * Constructs a <tt>FileSpanningInputStream</tt>.
     *
     * @param volume  the first volume to read
     * @param volumes the no. of volumes
     * @throws CorruptVolumeException if the volume magic no. cannot be read
     * @throws IOException            for any other I/O exception
     */
    public FileSpanningInputStream(File volume, int volumes) throws IOException {
        spanningInputStream = new SpanningInputStream(volume, volumes);
        zippedInputStream = new GZIPInputStream(spanningInputStream);
    }

    /**
     * Sets the volume locator.
     *
     * @param locator the locator. May be <tt>null</tt>
     */
    public void setLocator(VolumeLocator locator) {
        spanningInputStream.setLocator(locator);
    }

    /**
     * (non-Javadoc)
     *
     * @see java.io.InputStream#available()
     */
    @Override
    public int available() throws IOException {
        return zippedInputStream.available();
    }

    /**
     * (non-Javadoc)
     *
     * @see java.io.InputStream#close()
     */
    @Override
    public void close() throws IOException {
        zippedInputStream.close();
        spanningInputStream.close();
    }

    /**
     * (non-Javadoc)
     *
     * @see java.io.InputStream#read()
     */
    @Override
    public int read() throws IOException {
        int read = zippedInputStream.read();
        if (read != -1) {
            ++filePointer;
        }
        return read;
    }

    /**
     * (non-Javadoc)
     *
     * @see java.io.InputStream#read(byte[], int, int)
     */
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int count = -1;
        while (len != 0) {
            int read = zippedInputStream.read(b, off, len);
            if (read == -1) {
                break;
            } else {
                off += read;
                len -= read;
                count = (count == -1) ? read : count + read;
            }
        }
        if (count != -1) {
            filePointer += count;
        }
        return count;
    }

    /**
     * (non-Javadoc)
     *
     * @see java.io.InputStream#skip(long)
     */
    @Override
    public long skip(long n) throws IOException {
        long skipped = zippedInputStream.skip(n);
        long count = skipped;
        while (skipped != -1 && skipped < n) {
            n -= skipped;
            skipped = zippedInputStream.skip(n);
            if (skipped != -1) {
                count += skipped;
            }
        }
        if (count != -1) {
            filePointer += count;
        }
        return count;
    }

    /**
     * Returns the volume being read.
     *
     * @return the volume being read
     */
    public File getVolume() {
        return spanningInputStream.getVolume();
    }

    /**
     * Returns the current position in the file.
     * This is the absolute offset into the volumes.
     *
     * @return the current position in the file
     */
    public long getFilePointer() {
        return filePointer;
    }

    private static final class SpanningInputStream extends InputStream {

        /**
         * The current volume stream.
         */
        private InputStream stream;

        /**
         * The base path to each volume.
         */
        private String basePath;

        /**
         * The index of the current volume.
         */
        private int index = 0;

        /**
         * The total no. of volumes.
         */
        private final int volumes;

        /**
         * The first volume magic number. All subsequent volumes must start with this.
         */
        private final byte[] magicNumber;

        /**
         * The volume locator. May be <tt>null</tt>
         */
        private VolumeLocator locator;

        /**
         * The current volume.
         */
        private File current;

        /**
         * Constructs a <tt>SpanningInputStream</tt>.
         *
         * @param volume  the first volume
         * @param volumes the number of volumes
         * @throws IOException for any I/O error
         */
        public SpanningInputStream(File volume, int volumes) throws IOException {
            basePath = volume.getAbsolutePath();
            stream = new FileInputStream(volume);
            current = volume;
            this.volumes = volumes;

            // read magic number
            magicNumber = new byte[FileSpanningOutputStream.MAGIC_NUMBER_LENGTH];
            if (stream.read(magicNumber) != FileSpanningOutputStream.MAGIC_NUMBER_LENGTH) {
                IOUtils.closeQuietly(stream);
                throw new CorruptVolumeException();
            }
            if (logger.isLoggable(Level.FINE)) {
                logger.fine(
                        "Opened volume=" + volume + ", magic=" + FileSpanningOutputStream.formatMagic(magicNumber));
            }
        }

        /**
         * Sets the volume locator.
         *
         * @param locator the locator. May be <tt>null</tt>
         */
        public void setLocator(VolumeLocator locator) {
            this.locator = locator;
        }

        /**
         * Reads up to <code>len</code> bytes of data from the input stream into an array of bytes.
         * <p/>
         * An attempt is made to read as many as <code>len</code> bytes, but a smaller number may be read.
         *
         * @param b   the buffer into which the data is read.
         * @param off the start offset in array <code>b</code> at which the data is written.
         * @param len the maximum number of bytes to read.
         * @return the total number of bytes read into the buffer, or  <code>-1</code> if there is no more data because
         *         the end of the stream has been reached.
         * @throws IOException for any I/O error
         */
        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            int count = -1;
            while (len != 0) {
                int read = stream.read(b, off, len);
                if (read == -1) {
                    if (!openNextVolume()) {
                        break;
                    }
                } else {
                    off += read;
                    len -= read;
                    count = (count == -1) ? read : count + read;
                }
            }
            return count;
        }

        /**
         * Reads the next byte of data from the input stream.
         *
         * @return the next byte of data, or <code>-1</code> if the end of the stream is reached.
         * @throws java.io.IOException if an I/O error occurs.
         */
        @Override
        public int read() throws IOException {
            int read = stream.read();
            if (read == -1 && openNextVolume()) {
                // read from the next volume
                read = stream.read();
            }
            return read;
        }

        /**
         * Returns the volume being read.
         *
         * @return the volume being read
         */
        public File getVolume() {
            return current;
        }

        /**
         * Closes this input stream and releases any system resources associated
         * with the stream.
         *
         * @throws IOException if an I/O error occurs
         */
        @Override
        public void close() throws IOException {
            stream.close();
        }

        /**
         * Opens the next volume.
         *
         * @return <tt>true</tt> if the next volume was opened, or <tt>false</tt> if there are no more volumes
         * @throws CorruptVolumeException  if the magic no. of the next volume does not match that expected
         * @throws VolumeNotFoundException if the next volume was not found
         */
        private boolean openNextVolume() throws IOException {
            boolean result;
            if (index + 1 >= volumes) {
                logger.fine("Last volume reached");
                result = false;
            } else {
                // the next volume name
                String volumePath = basePath + "." + (index + 1);
                File volume = new File(volumePath);
                boolean found = false;
                while (!found) {
                    if (volume.exists()) {
                        try {
                            // try to open new stream to next volume
                            IOUtils.closeQuietly(stream);
                            stream = new FileInputStream(volume);
                            current = volume;
                            checkMagicNumber();
                            found = true;
                        } catch (CorruptVolumeException exception) {
                            if (locator == null) {
                                throw exception;
                            } else {
                                volume = locator.getVolume(volume.getAbsolutePath(), true);
                            }
                        }
                    } else if (locator != null) {
                        volume = locator.getVolume(volume.getAbsolutePath(), false);
                    } else {
                        throw new VolumeNotFoundException("Volume not found: " + volume.getAbsolutePath(),
                                volume.getAbsolutePath());
                    }
                }

                ++index;
                result = true;
            }
            return result;
        }

        /**
         * Checks if the magic number if the current volume is valid.
         *
         * @throws CorruptVolumeException if the magic number doesn't match that expected
         * @throws IOException            for any I/O error
         */
        private void checkMagicNumber() throws IOException {
            logger.fine("Trying to read magic number");
            byte[] volumeMagicNo = new byte[FileSpanningOutputStream.MAGIC_NUMBER_LENGTH];
            try {
                if (stream.read(volumeMagicNo) != volumeMagicNo.length) {
                    logger.fine("Failed to read magic number");
                    throw new CorruptVolumeException();
                }

                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("Magic number is " + FileSpanningOutputStream.formatMagic(volumeMagicNo));
                    if (!Arrays.equals(magicNumber, volumeMagicNo)) {
                        throw new CorruptVolumeException();
                    }
                }
            } catch (IOException exception) {
                IOUtils.closeQuietly(stream);
                throw exception;
            }
        }

    }
}