Java tutorial
/* * 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; } } }