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

org.apache.pdfbox.io.ScratchFileBuffer Maven / Gradle / Ivy

Go to download

The Apache PDFBox library is an open source Java tool for working with PDF documents.

There is a newer version: 3.0.2
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.pdfbox.io;

import java.io.EOFException;
import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.cos.COSStream;

/**
 * Implementation of {@link RandomAccess} as sequence of multiple fixed size pages handled
 * by {@link ScratchFile}.
 */
class ScratchFileBuffer implements RandomAccess
{
    private final int pageSize;
    /**
     * The underlying page handler.
     */
    private ScratchFile pageHandler;
    /**
     * The number of bytes of content in this buffer.
     */
    private long size = 0;
    /**
     * Index of current page in {@link #pageIndexes} (the nth page within this buffer).
     */
    private int currentPagePositionInPageIndexes;
    /**
     * The offset of the current page within this buffer.
     */
    private long currentPageOffset;
    /**
     * The current page data.
     */
    private byte[] currentPage;
    /**
     * The current position (for next read/write) of the buffer as an offset in the current page.
     */
    private int positionInPage;
    /** 
     * true if current page was changed by a write method
     */
    private boolean currentPageContentChanged = false;

    /** contains ordered list of pages with the index the page is known by page handler ({@link ScratchFile}) */
    private int[] pageIndexes = new int[16];
    /** number of pages held by this buffer */
    private int pageCount = 0;
    
    private static final Log LOG = LogFactory.getLog(ScratchFileBuffer.class);
    
    /**
     * Creates a new buffer using pages handled by provided {@link ScratchFile}.
     * 
     * @param pageHandler The {@link ScratchFile} managing the pages to be used by this buffer.
     * 
     * @throws IOException If getting first page failed.
     */
    ScratchFileBuffer(ScratchFile pageHandler) throws IOException
    {
        pageHandler.checkClosed();

        this.pageHandler = pageHandler;
        
        pageSize = this.pageHandler.getPageSize();
        
        addPage();
    }

    /**
     * Checks if this buffer, or the underlying {@link ScratchFile} have been closed,
     * throwing {@link IOException} if so.
     * 
     * @throws IOException If either this buffer, or the underlying {@link ScratchFile} have been closed.
     */
    private void checkClosed() throws IOException
    {
        if (pageHandler == null)
        {
            throw new IOException("Buffer already closed");
        }
        pageHandler.checkClosed();
    }

    /**
     * Adds a new page and positions all pointers to start of new page.
     * 
     * @throws IOException if requesting a new page fails
     */
    private void addPage() throws IOException
    {
        if (pageCount+1 >= pageIndexes.length)
        {
            int newSize = pageIndexes.length*2;
            // check overflow
            if (newSizeIf this is not the case we go to next page (writing
     * current one if changed). If current buffer has no more
     * pages we add a new one.

* * @param addNewPageIfNeeded if true it is allowed to add a new page in case * we are currently at end of last buffer page * * @return true if we were successful positioning pointer before end of page; * we might return false if it is not allowed to add another page * and current pointer points at end of last page * * @throws IOException */ private boolean ensureAvailableBytesInPage(boolean addNewPageIfNeeded) throws IOException { if (positionInPage >= pageSize) { // page full if (currentPageContentChanged) { // write page pageHandler.writePage(pageIndexes[currentPagePositionInPageIndexes], currentPage); currentPageContentChanged = false; } // get new page if (currentPagePositionInPageIndexes+1 < pageCount) { // we already have more pages assigned (there was a backward seek before) currentPage = pageHandler.readPage(pageIndexes[++currentPagePositionInPageIndexes]); currentPageOffset = ((long)currentPagePositionInPageIndexes) * pageSize; positionInPage = 0; } else if (addNewPageIfNeeded) { // need new page addPage(); } else { // we are at last page and are not allowed to add new page return false; } } return true; } /** * {@inheritDoc} */ @Override public void write(int b) throws IOException { checkClosed(); ensureAvailableBytesInPage(true); currentPage[positionInPage++] = (byte) b; currentPageContentChanged = true; if(currentPageOffset + positionInPage > size) { size = currentPageOffset + positionInPage; } } /** * {@inheritDoc} */ @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } /** * {@inheritDoc} */ @Override public void write(byte[] b, int off, int len) throws IOException { checkClosed(); int remain = len; int bOff = off; while (remain > 0) { ensureAvailableBytesInPage(true); int bytesToWrite = Math.min(remain, pageSize-positionInPage); System.arraycopy(b, bOff, currentPage, positionInPage, bytesToWrite); positionInPage += bytesToWrite; currentPageContentChanged = true; bOff += bytesToWrite; remain -= bytesToWrite; } if(currentPageOffset + positionInPage > size) { size = currentPageOffset + positionInPage; } } /** * {@inheritDoc} */ @Override public final void clear() throws IOException { checkClosed(); // keep only the first page, discard all other pages pageHandler.markPagesAsFree(pageIndexes, 1, pageCount - 1); pageCount = 1; // change to first page if we are not already there if (currentPagePositionInPageIndexes > 0) { currentPage = pageHandler.readPage(pageIndexes[0]); currentPagePositionInPageIndexes = 0; currentPageOffset = 0; } positionInPage = 0; size = 0; currentPageContentChanged = false; } /** * {@inheritDoc} */ @Override public long getPosition() throws IOException { checkClosed(); return currentPageOffset + positionInPage; } /** * {@inheritDoc} */ @Override public void seek(long seekToPosition) throws IOException { checkClosed(); /* * for now we won't allow to seek past end of buffer; this can be changed by adding new pages as needed */ if (seekToPosition > size) { throw new EOFException(); } if (seekToPosition < 0) { throw new IOException("Negative seek offset: " + seekToPosition); } if ((seekToPosition >= currentPageOffset) && (seekToPosition <= currentPageOffset + pageSize)) { // within same page positionInPage = (int) (seekToPosition - currentPageOffset); } else { // have to go to another page // check if current page needs to be written to file if (currentPageContentChanged) { pageHandler.writePage(pageIndexes[currentPagePositionInPageIndexes], currentPage); currentPageContentChanged = false; } int newPagePosition = (int) (seekToPosition / pageSize); currentPage = pageHandler.readPage(pageIndexes[newPagePosition]); currentPagePositionInPageIndexes = newPagePosition; currentPageOffset = ((long)currentPagePositionInPageIndexes) * pageSize; positionInPage = (int) (seekToPosition - currentPageOffset); } } /** * {@inheritDoc} */ @Override public boolean isClosed() { return pageHandler == null; } /** * {@inheritDoc} */ @Override public int peek() throws IOException { int result = read(); if (result != -1) { rewind(1); } return result; } /** * {@inheritDoc} */ @Override public void rewind(int bytes) throws IOException { seek(currentPageOffset + positionInPage - bytes); } /** * {@inheritDoc} */ @Override public byte[] readFully(int len) throws IOException { byte[] b = new byte[len]; int n = 0; do { int count = read(b, n, len - n); if (count < 0) { throw new EOFException(); } n += count; } while (n < len); return b; } /** * {@inheritDoc} */ @Override public boolean isEOF() throws IOException { checkClosed(); return currentPageOffset + positionInPage >= size; } /** * {@inheritDoc} */ @Override public int available() throws IOException { checkClosed(); return (int) Math.min(size - (currentPageOffset + positionInPage), Integer.MAX_VALUE); } /** * {@inheritDoc} */ @Override public int read() throws IOException { checkClosed(); if (currentPageOffset + positionInPage >= size) { return -1; } if (! ensureAvailableBytesInPage(false)) { // should not happen, we checked it before throw new IOException("Unexpectedly no bytes available for read in buffer."); } return currentPage[positionInPage++] & 0xff; } /** * {@inheritDoc} */ @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } /** * {@inheritDoc} */ @Override public int read(byte[] b, int off, int len) throws IOException { checkClosed(); if (currentPageOffset + positionInPage >= size) { return -1; } int remain = (int) Math.min(len, size - (currentPageOffset + positionInPage)); int totalBytesRead = 0; int bOff = off; while (remain > 0) { if (! ensureAvailableBytesInPage(false)) { // should not happen, we checked it before throw new IOException("Unexpectedly no bytes available for read in buffer."); } int readBytes = Math.min(remain, pageSize - positionInPage); System.arraycopy(currentPage, positionInPage, b, bOff, readBytes); positionInPage += readBytes; totalBytesRead += readBytes; bOff += readBytes; remain -= readBytes; } return totalBytesRead; } /** * {@inheritDoc} */ @Override public void close() throws IOException { if (pageHandler != null) { pageHandler.markPagesAsFree(pageIndexes, 0, pageCount); pageHandler = null; pageIndexes = null; currentPage = null; currentPageOffset = 0; currentPagePositionInPageIndexes = -1; positionInPage = 0; size = 0; } } /** * While calling finalize is normally discouraged we will have to * use it here as long as closing a scratch file buffer is not * done in every case. Currently {@link COSStream} creates new * buffers without closing the old one - which might still be * used. * *

Enabling debugging one will see if there are still cases * where the buffer is not closed.

*/ @Override protected void finalize() throws Throwable { try { if ((pageHandler != null) && LOG.isDebugEnabled()) { LOG.debug("ScratchFileBuffer not closed!"); } close(); } finally { super.finalize(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy