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

de.schlichtherle.io.rof.MemoryMappedReadOnlyFile Maven / Gradle / Ivy

/*
 * Copyright (C) 2005-2010 Schlichtherle IT Services
 *
 * 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 de.schlichtherle.io.rof;

import java.io.*;
import java.nio.*;
import java.nio.channels.*;

/**
 * A {@link ReadOnlyFile} implementation using channels to map the underlying
 * file into memory.
 * This class supports files larger than Integer.MAX_VALUE.
 *
 * @deprecated This class does not reliably work on the Windows platform,
 *             and hence its not used in TrueZIP.
 *             The reason is that the mapped file remains allocated until the
 *             garbage collector frees it even if the file channel and/or the
 *             RandomAccessFile has been closed. Subsequent delete/write
 *             operations on the file will then fail. For more information,
 *             please refer to
 *             
 *             http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4715154.
 * @author Christian Schlichtherle
 * @version $Id: MemoryMappedReadOnlyFile.java,v 1.3 2010/08/16 10:55:48 christian_schlichtherle Exp $
 */
public class MemoryMappedReadOnlyFile extends AbstractReadOnlyFile {

    /** The length of the mapped window. */
    private static final int WINDOW_LEN = Integer.MAX_VALUE;

    private FileChannel channel;
    private long windowOff = -1;
    private ByteBuffer window;

    public MemoryMappedReadOnlyFile(File file) throws FileNotFoundException {
        channel = new FileInputStream(file).getChannel();
        try {
            window(0);
        } catch (IOException ioe) {
            FileNotFoundException fnfe = new FileNotFoundException(ioe.toString());
            fnfe.initCause(ioe);
            throw fnfe;
        }
        assert window != null;
        assert windowOff == 0;
    }

    /**
     * Returns the number of bytes available in the floating window.
     * The window is positioned so that at least one byte is available
     * unless the end of the file has been reached.
     *
     * @return The number of bytes available in the floating window.
     *         If zero, the end of the file has been reached.
     */
    private final int available() throws IOException {
        ensureOpen();
        if (window.remaining() <= 0)
            window(windowOff + WINDOW_LEN);
        return window.remaining();
    }

    private void window(long newWindowOff) throws IOException {
        if (windowOff == newWindowOff)
            return;

        final long size = channel.size();
        if (newWindowOff > size)
            newWindowOff = size; // don't move past EOF

        window = channel.map(   FileChannel.MapMode.READ_ONLY, newWindowOff,
                                Math.min(size - newWindowOff, WINDOW_LEN));
        assert window != null;
        windowOff = newWindowOff;
    }

    public long length() throws IOException {
        ensureOpen();
        return channel.size();
    }

    public long getFilePointer() throws IOException {
        ensureOpen();
        return windowOff + window.position();
    }

    public void seek(final long fp) throws IOException {
        ensureOpen();

        if (fp < 0)
            throw new IOException("file pointer must not be negative");
        final long length = length();
        if (fp > length)
            throw new IOException("file pointer (" + fp
                    + ") is larger than file length (" + length + ")");

        window(fp / WINDOW_LEN * WINDOW_LEN); // round down
        window.position((int) (fp % WINDOW_LEN));
    }

    public int read() throws IOException {
        return available() > 0 ? window.get() & 0xff : -1;
    }
    
    public int read(final byte[] buf, final int off, int len)
    throws IOException {
        if (len == 0)
            return 0; // be fault-tolerant and compatible to RandomAccessFile

        // Check state.
        final int avail = available();
        if (avail <= 0)
            return -1; // EOF

        // Check parameters.
        if (buf == null)
            throw new NullPointerException("buf");
        if (off < 0 || len < 0 || off + len > buf.length)
            throw new IndexOutOfBoundsException();

        if (len > avail)
            len = avail;
        window.get(buf, off, len);
        return len;
    }

    public void close() throws IOException {
        // Check state.
        if (channel == null)
            return;

        // Order is important here!
        window = null;
        try {
            channel.close();
        } finally {
            channel = null;
            // Workaround for garbage collection issue with memory mapped files.
            // Note that there's no guarantee that this works: Sometimes it
            // does, sometimes not!
            System.gc(); 
            System.runFinalization();
            // Thread.interrupted(); // cancel pending interrupt
            try {
                Thread.sleep(50);
            } catch (InterruptedException dontCare) {
            }
        }
    }

    /**
     * Ensures that this file is open.
     *
     * @throws IOException If the preconditions do not hold.
     */
    private final void ensureOpen() throws IOException {
        if (channel == null)
            throw new IOException("file is closed");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy