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

com.adobe.internal.io.RandomAccessFileByteWriter Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 *
 *  File: RandomAccessFileByteWriter.java
 *
 *
 *  ADOBE CONFIDENTIAL
 *  ___________________
 *
 *  Copyright 2005 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 *  NOTICE: All information contained herein is, and remains the property of
 *  Adobe Systems Incorporated and its suppliers, if any. The intellectual
 *  and technical concepts contained herein are proprietary to Adobe Systems
 *  Incorporated and its suppliers and may be covered by U.S. and Foreign
 *  Patents, patents in process, and are protected by trade secret or
 *  copyright law. Dissemination of this information or reproduction of this
 *  material is strictly forbidden unless prior written permission is obtained
 *  from Adobe Systems Incorporated.
 *
 */
package com.adobe.internal.io;

import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * An implementation of the ByteWriter interface that provides access to a
 * RandomAccessFile.  This implementation also uses buffering to limit
 * the number of calls to the methods of the {@link java.io.RandomAccessFile RandomAccessFile}
 * class.  This is because such methods use JNI and any calls to native code from Java
 * are very slow.
 * 
 * This class is not threadsafe.  It is not safe to pass an instance of this class
 * to multiple threads.  It is not safe to pass an instance of this class to multiple users even
 * if in the same thread.  It is not safe to give the same RandomAccessFile to multiple instances
 * of this class.
 */
public final class RandomAccessFileByteWriter implements ByteWriter
{
	private boolean closed = false;
	private long fileLength = -1;
	private static final int DEFAULT_BUFFERSIZE = 4096;
	private static final int DEFAULT_NUMBERBUFFERS = 4;
	
	private int numberOfBuffers;
	private int bufferSize;
	private RandomAccessFile file;
	
	private Buffer[] buffers;
	private long counter;
	private Buffer mru;
	
	// basic performance stats
	// to activate uncomment all other blocks marked with "basic performance stats"
//	private long singleReadAccess;
//	private long bulkReadAccess;
//	private long overlapReads;
//	
//	private long singleWriteAccess;
//	private long bulkWriteAccess;
//	private long overlapWrites;
	
	private class Buffer
	{
		private long base = Long.MAX_VALUE;
		private long references;
		private boolean isDirty = false;
		private int bytesUsed; // number of bytes in the buffer that are used
		private byte data[];
		
		Buffer()
		{
			this.data = new byte[bufferSize];
		}
		
		void loadBuffer(long position) 
		throws IOException
		{
			// need to align buffer to boundary and then load it
			this.base = this.calculateBufferBase(position);
			this.references = ++counter;		
			this.bytesUsed = (int) Math.min(bufferSize, Math.max(fileLength - this.base, 0));
			
			
			if (this.base >= fileLength)
			{
				// no need to read if the buffer is beyond the current end of the file
				return;
			}
			file.seek(this.base);
			long bytesRead = file.read(this.data, 0, this.bytesUsed);
			if (    (bytesRead != this.bytesUsed)
				&& !((bytesRead == -1) && (this.bytesUsed == 0)))
			{
				throw new IOException("Didn't read enough bytes from the file.  Expected = " + this.bytesUsed + ", Actual = " + bytesRead);
			}
		}
		
		void flushBuffer() 
		throws IOException
		{
			file.seek(this.base);
			file.write(this.data, 0, this.bytesUsed);
			this.isDirty = false;
		}
		
		void resetBuffer()
		{
			this.base = Long.MAX_VALUE;
		}
		
		int buffersRequiredForRequest(long position, int length)
		{
			long base = calculateBufferBase(position);
			long initialBufferSpace =  bufferSize - (position - base);
			long overage = Math.max(length - initialBufferSpace, 0);
			int extraBuffers = (int) Math.ceil(((float) overage) / bufferSize);
			return ((length != 0) ? 1 : 0) + extraBuffers;
		}
		
		private long calculateBufferBase(long position)
		{
			 return (position / bufferSize) * bufferSize;
		}
	}
	
	/**
	 * Create a new RandomAccessFileByteReader with the given 
	 * {@link java.io.RandomAccessFile RandomAccessFile}.
	 * The {@link java.io.RandomAccessFile RandomAccessFile} given to this ByteWriter belongs
	 * to it and must not be used after construction of this ByteWriter.  It will be closed
	 * when this ByteWriter is closed.
	 * @param file the location to read bytes from.
	 * @param numberOfBuffers the number of buffers to use
	 * @param bufferSize the size in bytes for the buffers
	 */
	public RandomAccessFileByteWriter(RandomAccessFile file, int numberOfBuffers, int bufferSize)
	{
		if ((numberOfBuffers < 1) || (bufferSize < 1))
		{
			throw new IllegalArgumentException("Invalid buffer size or number of buffers.");
		}
		this.numberOfBuffers = numberOfBuffers;
		this.bufferSize = bufferSize;
		this.file = file;
		
		this.buffers = new Buffer[this.numberOfBuffers];
		for (int i = 0; i < this.numberOfBuffers; i++)
		{
			this.buffers[i] = new Buffer();
		}
		this.mru = this.buffers[0];
	}
	
	/**
	 * Create a new RandomAccessFileByteReader with the given 
	 * {@link java.io.RandomAccessFile RandomAccessFile}.
	 * {@link java.io.RandomAccessFile RandomAccessFile} given to this ByteReader must not
	 * be written to during the time it is being used by this or any ByteReader. 
	 * @param file the location to read bytes from.
	 */
	public RandomAccessFileByteWriter(RandomAccessFile file)
	{
		this(file, DEFAULT_NUMBERBUFFERS, DEFAULT_BUFFERSIZE);
	}
	
	/**
	 * @see com.adobe.internal.io.ByteWriter#write(long, int)
	 */
	public void write(long position, int b) throws IOException 
	{
		if (this.closed)
		{
			throw new IOException("ByteReader was closed");
		}

		// basic performance stats
//		this.singleWriteAccess++;
		
		boolean bufFound = false;
		
		if (this.fileLength == -1)
		{
			this.fileLength = this.file.length();
		}
		if (position < 0)
		{
			throw new IOException("Position is less than zero.");
		}
		
		// write request is in the most recently used buffer?
		if ((position >= this.mru.base) && (position < this.mru.base + this.bufferSize))
		{
			bufFound = true;
		} else {
			// is the the write request in any buffer?
			for (int bufferIndex = 0; bufferIndex < this.numberOfBuffers; bufferIndex++)
			{
				Buffer currentBuffer = this.buffers[bufferIndex];
				if (   (position >= currentBuffer.base)
					&& (position < currentBuffer.base + this.bufferSize))
				{
					this.mru = currentBuffer;
					bufFound = true;
					break;
				}
			}
		}
		
		if (!bufFound)
		{
			// write request isn't in any buffer so load it
			this.mru = loadLRU(position);
			bufFound = true;
		}
		
		this.mru.references = ++this.counter;
		this.mru.data[(int) (position - this.mru.base)] = (byte) (b & 0xFF);
		this.mru.bytesUsed = (int) Math.max(this.mru.bytesUsed, (position - this.mru.base) + 1);
		this.mru.isDirty = true;
		this.fileLength = Math.max(this.fileLength, position + 1);
	}
	
    /**
     * @see com.adobe.internal.io.ByteWriter#write(long, byte[], int, int)
     */
	public void write(long position, byte[] b, int offset, int length) throws IOException 
	{
		if (this.closed)
		{
			throw new IOException("ByteReader was closed");
		}

		// basic performance stats
//		this.bulkWriteAccess++;
		
		boolean bufFound = false;
		
		if (this.fileLength == -1)
		{
			this.fileLength = this.file.length();
		}
		if (position < 0)
		{
			throw new IOException("Position is less than zero.");
		}
		
		// check to see if request is within the last used buffer
		if (   (position >= this.mru.base)
			&& (position + length <= this.mru.base + this.bufferSize))
		{
			bufFound = true;
		} else {
			// if it's not then loop over all loaded buffers
			for (int bufferIndex = 0; bufferIndex < this.numberOfBuffers; bufferIndex++)
			{
				Buffer currentBuffer = this.buffers[bufferIndex];
				// is start position within a buffer?
				if (   (position >= currentBuffer.base)
					&& (position + length <= currentBuffer.base + this.bufferSize))
				{
					this.mru = currentBuffer;
					bufFound = true;
					break;
				}
			}
		}
		
		// if the request isn't in any of the buffers then load a buffer with that region
		if (!bufFound)
		{
			// is the request too big for a buffer?
			if (	this.mru.buffersRequiredForRequest(position, length) != 1)
			{
				// TODO
				// If we want to get really smart we can do two things
				// 1) check which buffers need flushed (overlap with request)
				// 2) split request and put overlapped parts into the buffer
				// must flush to avoid stale buffers
				this.flush();
				file.seek(position);
				file.write(b, offset, length);
				this.fileLength = this.file.length();

				// basic performance stats
//				this.overlapWrites++;

				return;
			} else {
				// not too big - load a buffer
				this.mru = loadLRU(position);
				bufFound = true;
			}
		}
		
		this.mru.references = ++this.counter;
		System.arraycopy(b, offset, this.mru.data, (int) (position - this.mru.base), length);
		this.mru.bytesUsed = (int) Math.max(this.mru.bytesUsed, (position - this.mru.base) + length);
		this.mru.isDirty = true;
		this.fileLength = Math.max(this.fileLength, position + length);
	}
	
	/**
	 * @see com.adobe.internal.io.ByteWriter#length()
	 */
	public long length() throws IOException 
	{
		if (this.closed)
		{
			throw new IOException("ByteReader was closed");
		}
		if(this.fileLength == -1)
		{
			this.fileLength = this.file.length();
		}
		return this.fileLength;
	}
	
	/**
	 * @see com.adobe.internal.io.ByteWriter#flush()
	 */
	public void flush() throws IOException 
	{
		if (this.closed)
		{
			throw new IOException("ByteReader was closed");
		}
		for (int bufferIndex = 0; bufferIndex < this.numberOfBuffers; bufferIndex++)
		{
			if (this.buffers[bufferIndex].isDirty)
			{
				this.buffers[bufferIndex].flushBuffer();
			}
			this.buffers[bufferIndex].resetBuffer();
		}
	}
	
	/**
	 * @see com.adobe.internal.io.ByteWriter#close()
	 */
	public void close() throws IOException
	{
		if (this.closed)
		{
			return;
		}
		flush();
		this.closed = true;
		file.close();
		
		// basic performance stats
//		System.out.println("==== RAFByteWriter closing");
//		System.out.print("\tSingle Reads = " + this.singleReadAccess);
//		System.out.println(", Bulk Reads = " + this.bulkReadAccess);
//		if (this.bulkReadAccess != 0 && this.overlapReads != 0)
//		{
//			float overlapReadPercentage = 
//				(float) (((this.overlapReads * 10000) / this.bulkReadAccess) / 100.0);
//			System.out.println("\tBeyond Buffer Reads = " + this.overlapReads + ", " 
//						+ overlapReadPercentage + "% of bulk reads");
//		}
//		
//		System.out.print("\tSingle Writes = " + this.singleWriteAccess);
//		System.out.println(", Bulk Writes = " + this.bulkWriteAccess);
//		if (this.bulkWriteAccess != 0 && this.overlapWrites != 0)
//		{
//			float overlapWritePercentage = 
//				(float) (((this.overlapWrites * 10000) / this.bulkWriteAccess) / 100.0);
//			System.out.println("\tBeyond Buffer Writes = " + this.overlapWrites + ", " 
//				+ overlapWritePercentage + "% of bulk writes");
//		}
//		System.out.println("");
	}
	
	/*** Reader methods **********************************************************/
	
	/**
	 * @see com.adobe.internal.io.ByteReader#read(long)
	 */
	public int read(long position)
	throws IOException
	{	
		if (this.closed)
		{
			throw new IOException("ByteReader was closed");
		}

		// basic performance stats
//		this.singleReadAccess++;
		
		boolean bufFound = false;
		
		if (this.fileLength == -1)
		{
			this.fileLength = file.length();
		}
		if (position < 0 || position >= this.fileLength)
		{
			return ByteReader.EOF;
		}
		
		// request is in the most recently used buffer?
		if ((position >= this.mru.base) && (position < this.mru.base + this.bufferSize))
		{
			bufFound = true;
		} else {
			// is the request in any buffer?
			for (int bufferIndex = 0; bufferIndex < this.numberOfBuffers; bufferIndex++)
			{
				Buffer currentBuffer = this.buffers[bufferIndex];
				if (   (position >= currentBuffer.base)
					&& (position < currentBuffer.base + this.bufferSize))
				{
					this.mru = currentBuffer;
					bufFound = true;
					break;
				}
			}
		}
		
		if (!bufFound)
		{
			// request isn't in any buffer so load it
			this.mru = loadLRU(position);
			bufFound = true;
		}
		
		this.mru.references = ++this.counter;
		return this.mru.data[(int) (position - this.mru.base)] & 0xFF;
	}
	
	/**
	 * @see com.adobe.internal.io.ByteReader#read(long, byte[], int, int)
	 */
	public int read(long position, byte[] b, int offset, int length)
	throws IOException
	{
		if (this.closed)
		{
			throw new IOException("ByteReader was closed");
		}

		// basic performance stats
//		this.bulkReadAccess++;
		
		boolean bufFound = false;
		
		if (this.fileLength == -1)
		{
			this.fileLength = this.file.length();
		}
		if (position < 0 || position >= this.fileLength)
		{
			return ByteReader.EOF;
		}
				
		// limit the length to the length of the file
		length = (int) Math.min(length, this.fileLength - position);

		// check to see if request is within the last used buffer
		if (   (position >= this.mru.base)
			&& (position + length <= this.mru.base + this.bufferSize))
		{
			bufFound = true;
		} else {
			// if it's not then loop over all loaded buffers
			for (int bufferIndex = 0; bufferIndex < this.numberOfBuffers; bufferIndex++)
			{
				Buffer currentBuffer = this.buffers[bufferIndex];
				// is start position within a buffer?
				if (   (position >= currentBuffer.base)
					&& (position + length <= currentBuffer.base + this.bufferSize))
				{
					this.mru = currentBuffer;
					bufFound = true;
					break;
				}
			}
		}
		
		// if the request isn't in any of the buffers then load a buffer with that region
		if (!bufFound)
		{
			// is the request too big for a buffer?
			if (	this.mru.buffersRequiredForRequest(position, length) != 1)
			{
				// TODO
				// If we want to get really smart we can do two things
				// 1) check which buffers need flushed (overlap with request)
				// 2) split request and put overlapped parts into the buffer
				// must flush to avoid stale read
				this.flush();
				file.seek(position);
				return file.read(b, offset, length);

				// basic performance stats
//				this.overlapReads++;

			} else {
				// not too big - load a buffer
				this.mru = loadLRU(position);
				bufFound = true;
			}
		}
		
		this.mru.references = ++this.counter;
		System.arraycopy(this.mru.data, (int) (position - this.mru.base), b, offset, length);
		return length;
	}
	
	private Buffer loadLRU(long position)
	throws IOException
	{
		// find the least recently used buffer
		Buffer lru = null;
		long minCounter = 0x7fffffffffffffffL;
		for (int bufferIndex = 0; bufferIndex < this.numberOfBuffers; bufferIndex++)
		{
			if (this.buffers[bufferIndex].references < minCounter)
			{
				lru = this.buffers[bufferIndex];
				minCounter = lru.references;
			}
		}
		
		// if it's dirty we need to write it out first
		if (lru.isDirty)
		{
			lru.flushBuffer();
		}
		
		lru.loadBuffer(position);
		return lru;
	}
	
	
	/**
	 * @see java.lang.Object#toString()
	 */
	public String toString()
	{
		return file.toString();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy