/** * GeDBIT.mckoi.store.AbstractBufferedFile 24 Jan 2003 * * Mckoi SQL Database ( http://www.mckoi.com/database ) * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * Version 2 as published by the Free Software Foundation. * * This program 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 General Public License Version 2 for more details. * * You should have received a copy of the GNU General Public License * Version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Change Log: * * */ package GeDBIT.mckoi.store; import java.io.RandomAccessFile; import java.io.IOException; /** * An abstract implementation of a FileBufferAccessor that implements a paging * strategy for caching access to the underlying RandomAccessFile. This * abstraction is designed such that the actual mechanism for fetching and * caching the pages are left to derived classes. This allows us to implement a * paging system that can exploit the Java 1.4 NIO memory mapping facilities as * well as an implementation that remains compatible with the 1.2 API. * * @author Tobias Downer */ abstract class AbstractBufferedFile implements FileBufferAccessor { /** * The size of the hashmap. */ private static final int HASH_SIZE = 64; /** * A unique id given to this object by the buffer manager. */ protected final int unique_id; /** * The RandomAccessFile whose access to we want to buffer. */ protected final RandomAccessFile file; /** * A reference to the BufferManager object that manages the memory * allocation for the pages in this buffer. */ protected final BufferManager buffer_manager; /** * The page size of pages in this buffered file. */ protected final int page_size; /** * A hash containing the pages that are currently cached for this file. */ private final PageBuffer[] page_map; /** * A lock mutex used when accessing the page_map. */ private final Object page_map_lock = new Object(); /** * Constructs the buffered file. */ public AbstractBufferedFile(int unique_id, RandomAccessFile file, BufferManager manager) { this.unique_id = unique_id; this.file = file; this.buffer_manager = manager; this.page_size = buffer_manager.getPageSizeFor(unique_id, file); page_map = new PageBuffer[HASH_SIZE]; } /** * Returns a new PageBuffer implementation that contains the information in * the given page in the file. For example, if the page size is 4096 and the * page number is 0, this will return a buffer that contains all the * information between position 0 and 4095 in the file. */ protected abstract PageBuffer createPageFor(long position, int length); /** * Asks if the given page is in the page map and if it is returns it, or * otherwise returns null. */ protected final PageBuffer getPageFromCache(long page_number) { synchronized (page_map_lock) { // The (very simple) hashing algorithm. int hash_index = (int) (page_number % HASH_SIZE); PageBuffer page = page_map[hash_index]; PageBuffer prev = null; // Search the hash key list for the page while (page != null && page.getPageNumber() != page_number) { prev = page; page = page.next_page_in_hash; } // If the page is found then move the page to the start of the list. if (prev != null && page != null) { // Move this page to the start of this hash key prev.next_page_in_hash = page.next_page_in_hash; page.next_page_in_hash = page_map[hash_index]; page_map[hash_index] = page; } return page; } } /** * Puts the given page into the cache. */ private final void putPageInCache(long page_number, PageBuffer page) { synchronized (page_map_lock) { // The (very simple) hashing algorithm. int hash_index = (int) (page_number % HASH_SIZE); page.next_page_in_hash = page_map[hash_index]; page_map[hash_index] = page; } } /** * Fetches the PageBuffer for the given page from the underlying file. If * the page is cached it is fetched from the cache. If it's not cached the * page is created and added to the cache. This always adds a new reference * to the reference count. */ private final PageBuffer fetchPage(long page_num_val) throws IOException { boolean page_created = false; PageBuffer page; synchronized (page_map_lock) { // Get the page from the cache page = getPageFromCache(page_num_val); // If it's not in the cache ... if (page == null) { // ... then create it page = createPageFor(page_num_val * page_size, page_size); page.setPageNumber(page_num_val); page.setFileUniqueID(unique_id); // Add 2 to the reference count // 1 reference for the cache and one for the fetch page.referenceAdd(); page.referenceAdd(); // and put it in the cache putPageInCache(page_num_val, page); page_created = true; } else { // This will cause a block until other read/write operations on // the // page have completed. synchronized (page) { // If the page is not in use then setup an initial // reference. if (page.notInUse()) { page.referenceAdd(); page_created = true; } // Make a reference for this fetch page.referenceAdd(); } } } // Notify the buffer manager that this page has been created or has been // accessed. if (page_created) { buffer_manager.pageCreated(page); } else { buffer_manager.pageAccessed(page); } return page; } /** * Reads a single byte from this buffered file at the given position. */ public final int readByte(long position) throws IOException { final long page_number = position / page_size; int v; PageBuffer page = fetchPage(page_number); synchronized (page) { try { page.initialize(); v = ((int) page.read((int) (position % page_size))) & 0x0FF; } finally { page.dispose(); } } return v; } /** * Reads an array from this buffered file at the given position. */ public final int readByteArray(long position, byte[] buf, int off, int len) throws IOException { final int orig_len = len; long page_number = position / page_size; int start_offset = (int) (position % page_size); int to_read = Math.min(len, page_size - start_offset); PageBuffer page = fetchPage(page_number); synchronized (page) { try { page.initialize(); page.read(start_offset, buf, off, to_read); } finally { page.dispose(); } } len -= to_read; while (len > 0) { off += to_read; position += to_read; ++page_number; to_read = Math.min(len, page_size); page = fetchPage(page_number); synchronized (page) { try { page.initialize(); page.read(0, buf, off, to_read); } finally { page.dispose(); } } len -= to_read; } return orig_len; } /** * Writes a single byte to this buffered file at the given position. */ public final void writeByte(long position, byte val) throws IOException { final long page_number = position / page_size; PageBuffer page = fetchPage(page_number); synchronized (page) { try { page.initialize(); page.write((int) (position % page_size), val); } finally { page.dispose(); } } } /** * Writes an array to the buffered file at the given position. */ public final int writeByteArray(long position, byte[] buf, int off, int len) throws IOException { final int orig_len = len; long page_number = position / page_size; int start_offset = (int) (position % page_size); int to_write = Math.min(len, page_size - start_offset); PageBuffer page = fetchPage(page_number); synchronized (page) { try { page.initialize(); page.write(start_offset, buf, off, to_write); } finally { page.dispose(); } } len -= to_write; while (len > 0) { off += to_write; position += to_write; ++page_number; to_write = Math.min(len, page_size); page = fetchPage(page_number); synchronized (page) { try { page.initialize(); page.write(0, buf, off, to_write); } finally { page.dispose(); } } len -= to_write; } return orig_len; } /** * Flushes any pending bytes that were written to the buffer to the * underlying disk. */ public final void flush() throws IOException { synchronized (page_map_lock) { for (int i = 0; i < HASH_SIZE; ++i) { PageBuffer prev = null; PageBuffer page = page_map[i]; while (page != null) { synchronized (page) { // Flush the page page.flush(); // Remove this page if it is not in use if (page.notInUse()) { if (prev == null) { page_map[i] = page.next_page_in_hash; } else { prev.next_page_in_hash = page.next_page_in_hash; } } } prev = page; page = page.next_page_in_hash; } } } } /** * Notifies this file that the underlying RandomAccessFile has changed size. * This would normally cause the last page in the cache to be purged but the * behaviour may be different depending on the implementation. */ public abstract void sizeChange(long old_size, long new_size) throws IOException; }