248 lines
7.6 KiB
Java
248 lines
7.6 KiB
Java
/*******************************************************************************
|
|
* Copyright 2010 Cees De Groot, Alex Boisvert, Jan Kotek
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
******************************************************************************/
|
|
|
|
|
|
package org.apache.jdbm;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.ByteBuffer;
|
|
|
|
/**
|
|
* This class manages the linked lists of pages that make up a file.
|
|
*/
|
|
final class PageManager {
|
|
// our record file
|
|
final PageFile file;
|
|
|
|
private PageIo headerBuf;
|
|
|
|
/**
|
|
* Creates a new page manager using the indicated record file.
|
|
*/
|
|
PageManager(PageFile file) throws IOException {
|
|
this.file = file;
|
|
|
|
// check the file headerBuf.fileHeader If the magic is 0, we assume a new
|
|
// file. Note that we hold on to the file header node.
|
|
headerBuf = file.get(0);
|
|
headerBuf.ensureHeapBuffer();
|
|
headerBuf.fileHeaderCheckHead(headerBuf.readShort(0) == 0);
|
|
}
|
|
|
|
/**
|
|
* Allocates a page of the indicated type. Returns recid of the
|
|
* page.
|
|
*/
|
|
long allocate(short type) throws IOException {
|
|
|
|
if (type == Magic.FREE_PAGE)
|
|
throw new Error("allocate of free page?");
|
|
|
|
// do we have something on the free list?
|
|
long retval = headerBuf.fileHeaderGetFirstOf(Magic.FREE_PAGE);
|
|
boolean isNew = false;
|
|
|
|
if(type!=Magic.TRANSLATION_PAGE){
|
|
|
|
if (retval != 0) {
|
|
// yes. Point to it and make the next of that page the
|
|
// new first free page.
|
|
headerBuf.fileHeaderSetFirstOf(Magic.FREE_PAGE, getNext(retval));
|
|
} else {
|
|
// nope. make a new record
|
|
retval = headerBuf.fileHeaderGetLastOf(Magic.FREE_PAGE);
|
|
if (retval == 0)
|
|
// very new file - allocate record #1
|
|
retval = 1;
|
|
headerBuf.fileHeaderSetLastOf(Magic.FREE_PAGE, retval + 1);
|
|
isNew = true;
|
|
}
|
|
}else{
|
|
//translation pages have different allocation scheme
|
|
//and also have negative address
|
|
retval = headerBuf.fileHeaderGetLastOf(Magic.TRANSLATION_PAGE) - 1;
|
|
isNew = true;
|
|
}
|
|
|
|
// Cool. We have a record, add it to the correct list
|
|
PageIo pageHdr = file.get(retval);
|
|
if(isNew){
|
|
pageHdr.pageHeaderSetType(type);
|
|
}else{
|
|
if (!pageHdr.pageHeaderMagicOk())
|
|
throw new Error("CRITICAL: page header magic for page "+
|
|
pageHdr.getPageId() + " not OK "+ pageHdr.pageHeaderGetMagic());
|
|
}
|
|
long oldLast = headerBuf.fileHeaderGetLastOf(type);
|
|
|
|
// Clean data.
|
|
pageHdr.writeByteArray(PageFile.CLEAN_DATA, 0, 0, Storage.PAGE_SIZE);
|
|
|
|
pageHdr.pageHeaderSetType(type);
|
|
pageHdr.pageHeaderSetPrev(oldLast);
|
|
pageHdr.pageHeaderSetNext(0);
|
|
|
|
|
|
if (oldLast == 0)
|
|
// This was the first one of this type
|
|
headerBuf.fileHeaderSetFirstOf(type, retval);
|
|
headerBuf.fileHeaderSetLastOf(type, retval);
|
|
file.release(retval, true);
|
|
|
|
// If there's a previous, fix up its pointer
|
|
if (oldLast != 0) {
|
|
pageHdr = file.get(oldLast);
|
|
pageHdr.pageHeaderSetNext(retval);
|
|
file.release(oldLast, true);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Frees a page of the indicated type.
|
|
*/
|
|
void free(short type, long recid) throws IOException {
|
|
if (type == Magic.FREE_PAGE)
|
|
throw new Error("free free page?");
|
|
if (type == Magic.TRANSLATION_PAGE)
|
|
throw new Error("Translation page can not be dealocated");
|
|
|
|
if (recid == 0)
|
|
throw new Error("free header page?");
|
|
|
|
// get the page and read next and previous pointers
|
|
PageIo pageHdr = file.get(recid);
|
|
long prev = pageHdr.pageHeaderGetPrev();
|
|
long next = pageHdr.pageHeaderGetNext();
|
|
|
|
// put the page at the front of the free list.
|
|
pageHdr.pageHeaderSetType(Magic.FREE_PAGE);
|
|
pageHdr.pageHeaderSetNext(headerBuf.fileHeaderGetFirstOf(Magic.FREE_PAGE));
|
|
pageHdr.pageHeaderSetPrev(0);
|
|
|
|
headerBuf.fileHeaderSetFirstOf(Magic.FREE_PAGE, recid);
|
|
file.release(recid, true);
|
|
|
|
// remove the page from its old list
|
|
if (prev != 0) {
|
|
pageHdr = file.get(prev);
|
|
pageHdr.pageHeaderSetNext(next);
|
|
file.release(prev, true);
|
|
} else {
|
|
headerBuf.fileHeaderSetFirstOf(type, next);
|
|
}
|
|
if (next != 0) {
|
|
pageHdr = file.get(next);
|
|
pageHdr.pageHeaderSetPrev(prev);
|
|
file.release(next, true);
|
|
} else {
|
|
headerBuf.fileHeaderSetLastOf(type, prev);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the page following the indicated page
|
|
*/
|
|
long getNext(long page) throws IOException {
|
|
try {
|
|
return file.get(page).pageHeaderGetNext();
|
|
} finally {
|
|
file.release(page, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the page before the indicated page
|
|
*/
|
|
long getPrev(long page) throws IOException {
|
|
try {
|
|
return file.get(page).pageHeaderGetPrev();
|
|
} finally {
|
|
file.release(page, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the first page on the indicated list.
|
|
*/
|
|
long getFirst(short type) throws IOException {
|
|
return headerBuf.fileHeaderGetFirstOf(type);
|
|
}
|
|
|
|
/**
|
|
* Returns the last page on the indicated list.
|
|
*/
|
|
long getLast(short type) throws IOException {
|
|
return headerBuf.fileHeaderGetLastOf(type);
|
|
}
|
|
|
|
|
|
/**
|
|
* Commit all pending (in-memory) data by flushing the page manager.
|
|
* This forces a flush of all outstanding pages (this it's an implicit
|
|
* {@link PageFile#commit} as well).
|
|
*/
|
|
void commit() throws IOException {
|
|
// write the header out
|
|
file.release(headerBuf);
|
|
file.commit();
|
|
|
|
// and obtain it again
|
|
headerBuf = file.get(0);
|
|
headerBuf.ensureHeapBuffer();
|
|
headerBuf.fileHeaderCheckHead(headerBuf.readShort(0) == 0);
|
|
}
|
|
|
|
/**
|
|
* Flushes the page manager. This forces a flush of all outstanding
|
|
* pages (this it's an implicit {@link PageFile#commit} as well).
|
|
*/
|
|
void rollback() throws IOException {
|
|
// release header
|
|
file.discard(headerBuf);
|
|
file.rollback();
|
|
// and obtain it again
|
|
headerBuf = file.get(0);
|
|
headerBuf.ensureHeapBuffer();
|
|
headerBuf.fileHeaderCheckHead(headerBuf.readShort(0) == 0);
|
|
}
|
|
|
|
/**
|
|
* Closes the page manager. This flushes the page manager and releases
|
|
* the lock on the headerBuf.fileHeader
|
|
*/
|
|
void close() throws IOException {
|
|
file.release(headerBuf);
|
|
file.commit();
|
|
headerBuf = null;
|
|
}
|
|
|
|
|
|
/**
|
|
* PageManager permanently locks zero page, and we need this for backups
|
|
*/
|
|
ByteBuffer getHeaderBufData() {
|
|
return headerBuf.getData();
|
|
}
|
|
|
|
public PageIo getFileHeader() {
|
|
return headerBuf;
|
|
}
|
|
}
|