001: /*
002: * ============================================================================
003: * GNU Lesser General Public License
004: * ============================================================================
005: *
006: * JasperReports - Free Java report-generating library.
007: * Copyright (C) 2001-2006 JasperSoft Corporation http://www.jaspersoft.com
008: *
009: * This library is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU Lesser General Public
011: * License as published by the Free Software Foundation; either
012: * version 2.1 of the License, or (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017: * Lesser General Public License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
022: *
023: * JasperSoft Corporation
024: * 303 Second Street, Suite 450 North
025: * San Francisco, CA 94107
026: * http://www.jaspersoft.com
027: */
028: package net.sf.jasperreports.engine.util;
029:
030: import java.io.File;
031: import java.io.FileNotFoundException;
032: import java.io.IOException;
033: import java.io.RandomAccessFile;
034:
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037:
038: import net.sf.jasperreports.engine.JRRuntimeException;
039:
040: /**
041: * Swap file implementation that can be used as a disk cache for arbitrary binary data.
042: * <p>
043: * Fixed-size blocks are allocated inside the swap file when a caller wants to write data.
044: * The caller receives a handle to the allocated area based on which it can read the data
045: * or free the area.
046: * <p>
047: * The implementation is thread-safe. I/O operations are performed in synchronized blocks,
048: * only one thread would do a read or write at one moment.
049: *
050: * @author Lucian Chirita (lucianc@users.sourceforge.net)
051: * @version $Id: JRSwapFile.java 1744 2007-06-11 14:30:11Z teodord $
052: */
053: public class JRSwapFile {
054: private static final Log log = LogFactory.getLog(JRSwapFile.class);
055:
056: private final File swapFile;
057: protected final RandomAccessFile file;
058: private final int blockSize;
059: private final int minGrowCount;
060: private final LongQueue freeBlocks;
061:
062: /**
063: * Creates a swap file.
064: *
065: * The file name is generated automatically.
066: *
067: * @param directory the directory where the file should be created.
068: * @param blockSize the size of the blocks allocated by the swap file
069: * @param minGrowCount the minimum number of blocks by which the swap file grows when full
070: */
071: public JRSwapFile(String directory, int blockSize, int minGrowCount) {
072: try {
073: String filename = "swap_" + System.identityHashCode(this )
074: + "_" + System.currentTimeMillis();
075: swapFile = new File(directory, filename);
076: if (log.isDebugEnabled()) {
077: log.debug("Creating swap file " + swapFile.getPath());
078: }
079: boolean fileExists = swapFile.exists();
080: swapFile.deleteOnExit();
081: file = new RandomAccessFile(swapFile, "rw");
082:
083: this .blockSize = blockSize;
084: this .minGrowCount = minGrowCount;
085: freeBlocks = new LongQueue(minGrowCount);
086:
087: if (fileExists) {
088: file.setLength(0);
089: if (log.isDebugEnabled()) {
090: log.debug("Swap file " + swapFile.getPath()
091: + " exists, truncating");
092: }
093: }
094: } catch (FileNotFoundException e) {
095: throw new JRRuntimeException(e);
096: } catch (IOException e) {
097: throw new JRRuntimeException(e);
098: }
099: }
100:
101: /**
102: * Allocates an area in the swap file and writes data in it.
103: *
104: * @param data the data for which to allocate an area in the file
105: * @return a handle to the allocated area
106: * @throws IOException
107: */
108: public SwapHandle write(byte[] data) throws IOException {
109: int blockCount = (data.length - 1) / blockSize + 1;
110: long[] offsets = reserveFreeBlocks(blockCount);
111: int lastBlockSize = (data.length - 1) % blockSize + 1;
112: SwapHandle handle = new SwapHandle(offsets, lastBlockSize);
113: for (int i = 0; i < blockCount; ++i) {
114: int dataSize = i < blockCount - 1 ? blockSize
115: : lastBlockSize;
116: int dataOffset = i * blockSize;
117: write(data, dataSize, dataOffset, offsets[i]);
118: }
119:
120: return handle;
121: }
122:
123: protected void write(byte[] data, int dataSize, int dataOffset,
124: long fileOffset) throws IOException {
125: synchronized (this ) {
126: file.seek(fileOffset);
127: file.write(data, dataOffset, dataSize);
128: }
129: }
130:
131: /**
132: * Reads all the data from an allocated area.
133: *
134: * @param handle the allocated area handle
135: * @param free whether to free the area after reading
136: * @return the whole data saved in an allocated area
137: * @throws IOException
138: */
139: public byte[] read(SwapHandle handle, boolean free)
140: throws IOException {
141: long[] offsets = handle.getOffsets();
142: int totalLength = (offsets.length - 1) * blockSize
143: + handle.getLastSize();
144: byte[] data = new byte[totalLength];
145:
146: for (int i = 0; i < offsets.length; ++i) {
147: int dataOffset = i * blockSize;
148: int dataLength = i < offsets.length - 1 ? blockSize
149: : handle.getLastSize();
150: read(data, dataOffset, dataLength, offsets[i]);
151: }
152:
153: if (free) {
154: freeBlocks(offsets);
155: }
156:
157: return data;
158: }
159:
160: protected void read(byte[] data, int dataOffset, int dataLength,
161: long fileOffset) throws IOException {
162: synchronized (this ) {
163: file.seek(fileOffset);
164: file.readFully(data, dataOffset, dataLength);
165: }
166: }
167:
168: /**
169: * Frees an allocated area.
170: *
171: * @param handle the allocated area handle
172: */
173: public void free(SwapHandle handle) {
174: freeBlocks(handle.getOffsets());
175: }
176:
177: /**
178: * Closes and deletes the swap file.
179: */
180: public void dispose() {
181: synchronized (this ) {
182: if (swapFile.exists()) {
183: if (log.isDebugEnabled()) {
184: log.debug("Disposing swap file "
185: + swapFile.getPath());
186: }
187:
188: try {
189: file.close();
190: } catch (IOException e) {
191: log.warn("Not able to close swap file "
192: + swapFile.getPath());
193: }
194:
195: if (!swapFile.delete()) {
196: log.warn("Not able to delete swap file "
197: + swapFile.getPath());
198: }
199: }
200: }
201: }
202:
203: protected void finalize() throws Throwable {
204: dispose();
205: super .finalize();
206: }
207:
208: protected synchronized long[] reserveFreeBlocks(int blockCount)
209: throws IOException {
210: int growCount = blockCount - freeBlocks.size();
211: if (growCount > 0) {
212: if (growCount < minGrowCount) {
213: growCount = minGrowCount;
214: }
215:
216: long length = file.length();
217: long newLength = length + growCount * blockSize;
218: if (log.isDebugEnabled()) {
219: log.debug("Growing swap file " + swapFile.getPath()
220: + " with " + growCount + " blocks x "
221: + blockSize + " bytes to size " + newLength);
222: }
223: file.setLength(newLength);
224:
225: for (int i = 0; i < growCount; ++i) {
226: freeBlocks.addLast(length + i * blockSize);
227: }
228: }
229:
230: long[] offsets = new long[blockCount];
231: for (int i = 0; i < blockCount; i++) {
232: offsets[i] = freeBlocks.popFirst();
233: }
234: return offsets;
235: }
236:
237: protected synchronized void freeBlocks(long[] offsets) {
238: for (int i = offsets.length - 1; i >= 0; --i) {
239: freeBlocks.addFirst(offsets[i]);
240: }
241: }
242:
243: protected static class LongQueue {
244: private final int minGrowCount;
245: private long[] vals;
246: private int size;
247: private int first;
248: private int last;
249:
250: public LongQueue(int minGrowCount) {
251: this .minGrowCount = minGrowCount;
252: vals = new long[minGrowCount];
253: size = 0;
254: first = 0;
255: last = 0;
256: }
257:
258: public void addFirst(long val) {
259: growIfFull();
260:
261: --first;
262: if (first == -1) {
263: first = vals.length - 1;
264: }
265: vals[first] = val;
266: ++size;
267: }
268:
269: public void addLast(long val) {
270: growIfFull();
271:
272: vals[last] = val;
273: ++size;
274: ++last;
275: if (last == vals.length) {
276: last = 0;
277: }
278: }
279:
280: public long popFirst() {
281: if (size == 0) {
282: throw new JRRuntimeException("Queue underflow");
283: }
284:
285: long val = vals[first];
286: ++first;
287: if (first == vals.length) {
288: first = 0;
289: }
290: --size;
291:
292: return val;
293: }
294:
295: protected void growIfFull() {
296: int valsLength = vals.length;
297: if (size == valsLength) {
298: int newLength = (valsLength * 3) / 2 + 1;
299: if (newLength - valsLength < minGrowCount) {
300: newLength = valsLength + minGrowCount;
301: }
302:
303: long[] newVals = new long[newLength];
304: System.arraycopy(vals, first, newVals, 0, valsLength
305: - first);
306: if (last > 0) {
307: System.arraycopy(vals, 0, newVals, valsLength
308: - first, last);
309: }
310:
311: vals = newVals;
312: first = 0;
313: last = valsLength;
314: }
315: }
316:
317: public int size() {
318: return size;
319: }
320: }
321:
322: public static class SwapHandle {
323: private final long[] offsets;
324: private final int lastSize;
325:
326: public SwapHandle(long[] offsets, int lastSize) {
327: this .offsets = offsets;
328: this .lastSize = lastSize;
329: }
330:
331: public long[] getOffsets() {
332: return offsets;
333: }
334:
335: public int getLastSize() {
336: return lastSize;
337: }
338: }
339: }
|