net.sf.jasperreports.engine.util.JRSwapFile.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jasperreports.engine.util.JRSwapFile.java

Source

/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JasperReports is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
 */
package net.sf.jasperreports.engine.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.fill.JRFileVirtualizer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Swap file implementation that can be used as a disk cache for arbitrary binary data.
 * <p>
 * Fixed-size blocks are allocated inside the swap file when a caller wants to write data.
 * The caller receives a handle to the allocated area based on which it can read the data
 * or free the area.
 * <p>
 * The implementation is thread-safe.  I/O operations are performed in synchronized blocks,
 * only one thread would do a read or write at one moment.
 * 
 * @author Lucian Chirita (lucianc@users.sourceforge.net)
 */
public class JRSwapFile {
    private static final Log log = LogFactory.getLog(JRSwapFile.class);

    /**
     * Property that instructs whether {@link File#deleteOnExit() deleteOnExit} is to be requested
     * for swap files.
     * 
     * Swap files are explicitly deleted on {@link #dispose() dispose()} and garbage collection.
     */
    public static final String PROPERTY_DELETE_ON_EXIT = JRFileVirtualizer.PROPERTY_TEMP_FILES_SET_DELETE_ON_EXIT;

    protected final File swapFile;
    protected final RandomAccessFile file;
    private final int blockSize;
    private final int minGrowCount;
    private final LongQueue freeBlocks;

    /**
     * Creates a swap file.
     * 
     * The file name is generated automatically.
     * 
     * @param directory the directory where the file should be created.
     * @param blockSize the size of the blocks allocated by the swap file
     * @param minGrowCount the minimum number of blocks by which the swap file grows when full
     */
    public JRSwapFile(String directory, int blockSize, int minGrowCount) {
        this(DefaultJasperReportsContext.getInstance(), directory, blockSize, minGrowCount);
    }

    /**
     * Creates a swap file.
     * 
     * The file name is generated automatically.
     * 
     * @param jasperReportsContext the JasperReportsContext to read configuration from.
     * @param directory the directory where the file should be created.
     * @param blockSize the size of the blocks allocated by the swap file
     * @param minGrowCount the minimum number of blocks by which the swap file grows when full
     */
    public JRSwapFile(JasperReportsContext jasperReportsContext, String directory, int blockSize,
            int minGrowCount) {
        try {
            String filename = "swap_" + System.identityHashCode(this) + "_" + System.currentTimeMillis();
            swapFile = new File(directory, filename);
            if (log.isDebugEnabled()) {
                log.debug("Creating swap file " + swapFile.getPath());
            }
            boolean fileExists = swapFile.exists();

            boolean deleteOnExit = JRPropertiesUtil.getInstance(jasperReportsContext)
                    .getBooleanProperty(PROPERTY_DELETE_ON_EXIT);
            if (deleteOnExit) {
                swapFile.deleteOnExit();
            }

            file = new RandomAccessFile(swapFile, "rw");//FIXME to this lazily?

            this.blockSize = blockSize;
            this.minGrowCount = minGrowCount;
            freeBlocks = new LongQueue(minGrowCount);

            if (fileExists) {
                file.setLength(0);
                if (log.isDebugEnabled()) {
                    log.debug("Swap file " + swapFile.getPath() + " exists, truncating");
                }
            }
        } catch (FileNotFoundException e) {
            throw new JRRuntimeException(e);
        } catch (IOException e) {
            throw new JRRuntimeException(e);
        }
    }

    @Override
    public String toString() {
        return "JRSwapFile " + swapFile.getAbsolutePath();
    }

    /**
     * Allocates an area in the swap file and writes data in it.
     * 
     * @param data the data for which to allocate an area in the file
     * @return a handle to the allocated area
     * @throws IOException
     */
    public SwapHandle write(byte[] data) throws IOException {
        int blockCount = (data.length - 1) / blockSize + 1;
        long[] offsets = reserveFreeBlocks(blockCount);
        int lastBlockSize = (data.length - 1) % blockSize + 1;
        SwapHandle handle = new SwapHandle(offsets, lastBlockSize);
        for (int i = 0; i < blockCount; ++i) {
            int dataSize = i < blockCount - 1 ? blockSize : lastBlockSize;
            int dataOffset = i * blockSize;
            write(data, dataSize, dataOffset, offsets[i]);
        }

        return handle;
    }

    protected void write(byte[] data, int dataSize, int dataOffset, long fileOffset) throws IOException {
        synchronized (this) {
            file.seek(fileOffset);
            file.write(data, dataOffset, dataSize);
        }
    }

    /**
     * Reads all the data from an allocated area.
     * 
     * @param handle the allocated area handle
     * @param free whether to free the area after reading
     * @return the whole data saved in an allocated area
     * @throws IOException
     */
    public byte[] read(SwapHandle handle, boolean free) throws IOException {
        long[] offsets = handle.getOffsets();
        int totalLength = (offsets.length - 1) * blockSize + handle.getLastSize();
        byte[] data = new byte[totalLength];

        for (int i = 0; i < offsets.length; ++i) {
            int dataOffset = i * blockSize;
            int dataLength = i < offsets.length - 1 ? blockSize : handle.getLastSize();
            read(data, dataOffset, dataLength, offsets[i]);
        }

        if (free) {
            freeBlocks(offsets);
        }

        return data;
    }

    protected void read(byte[] data, int dataOffset, int dataLength, long fileOffset) throws IOException {
        synchronized (this) {
            file.seek(fileOffset);
            file.readFully(data, dataOffset, dataLength);
        }
    }

    /**
     * Frees an allocated area.
     * 
     * @param handle the allocated area handle
     */
    public void free(SwapHandle handle) {
        freeBlocks(handle.getOffsets());
    }

    /**
     * Closes and deletes the swap file.
     */
    public void dispose() {
        synchronized (this) {
            if (swapFile.exists()) {
                if (log.isDebugEnabled()) {
                    log.debug("Disposing swap file " + swapFile.getPath());
                }

                try {
                    file.close();
                } catch (IOException e) {
                    log.warn("Not able to close swap file " + swapFile.getPath());
                }

                if (!swapFile.delete()) {
                    log.warn("Not able to delete swap file " + swapFile.getPath());
                }
            }
        }
    }

    @Override
    protected void finalize() throws Throwable //NOSONAR
    {
        dispose();
        super.finalize();
    }

    protected synchronized long[] reserveFreeBlocks(int blockCount) throws IOException {
        int growCount = blockCount - freeBlocks.size();
        if (growCount > 0) {
            if (growCount < minGrowCount) {
                growCount = minGrowCount;
            }

            long length = file.length();
            long newLength = length + growCount * blockSize;
            if (log.isDebugEnabled()) {
                log.debug("Growing swap file " + swapFile.getPath() + " with " + growCount + " blocks x "
                        + blockSize + " bytes to size " + newLength);
            }
            file.setLength(newLength);

            for (int i = 0; i < growCount; ++i) {
                freeBlocks.addLast(length + i * blockSize);
            }
        }

        long[] offsets = new long[blockCount];
        for (int i = 0; i < blockCount; i++) {
            offsets[i] = freeBlocks.popFirst();
        }
        return offsets;
    }

    protected synchronized void freeBlocks(long[] offsets) {
        for (int i = offsets.length - 1; i >= 0; --i) {
            freeBlocks.addFirst(offsets[i]);
        }
    }

    protected static class LongQueue {
        public static final String EXCEPTION_MESSAGE_KEY_QUEUE_UNDERFLOW = "util.long.queue.underflow";

        private final int minGrowCount;
        private long[] vals;
        private int size;
        private int first;
        private int last;

        public LongQueue(int minGrowCount) {
            this.minGrowCount = minGrowCount;
            vals = new long[minGrowCount];
            size = 0;
            first = 0;
            last = 0;
        }

        public void addFirst(long val) {
            growIfFull();

            --first;
            if (first == -1) {
                first = vals.length - 1;
            }
            vals[first] = val;
            ++size;
        }

        public void addLast(long val) {
            growIfFull();

            vals[last] = val;
            ++size;
            ++last;
            if (last == vals.length) {
                last = 0;
            }
        }

        public long popFirst() {
            if (size == 0) {
                throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_QUEUE_UNDERFLOW, (Object[]) null);
            }

            long val = vals[first];
            ++first;
            if (first == vals.length) {
                first = 0;
            }
            --size;

            return val;
        }

        protected void growIfFull() {
            int valsLength = vals.length;
            if (size == valsLength) {
                int newLength = (valsLength * 3) / 2 + 1;
                if (newLength - valsLength < minGrowCount) {
                    newLength = valsLength + minGrowCount;
                }

                long[] newVals = new long[newLength];
                System.arraycopy(vals, first, newVals, 0, valsLength - first);
                if (last > 0) {
                    System.arraycopy(vals, 0, newVals, valsLength - first, last);
                }

                vals = newVals;
                first = 0;
                last = valsLength;
            }
        }

        public int size() {
            return size;
        }
    }

    public static class SwapHandle {
        private final long[] offsets;
        private final int lastSize;

        public SwapHandle(long[] offsets, int lastSize) {
            this.offsets = offsets;
            this.lastSize = lastSize;
        }

        public long[] getOffsets() {
            return offsets;
        }

        public int getLastSize() {
            return lastSize;
        }
    }
}