/*
* Copyright 2011 Future Systems
*
* 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.krakenapps.btree;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import org.krakenapps.btree.types.CompositeKeyFactory;
import org.krakenapps.btree.types.IntegerKeyFactory;
import org.krakenapps.btree.types.LongKeyFactory;
import org.krakenapps.btree.types.StringKeyFactory;
public class PageFile {
private static final String FORMAT_MARKER = "Kraken Page";
/**
* format marker + page size + root page
*/
private static final int FILE_HEADER_SIZE = 64;
private RandomAccessFile raf;
private Schema schema;
private int rootPage;
public static PageFile create(File file, Schema schema) throws IOException {
if (schema.getPageSize() < 0)
throw new IllegalArgumentException("cannot use negative page size");
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
try {
// write file header
byte[] b = FORMAT_MARKER.getBytes("utf-8");
byte[] pad = new byte[16 - FORMAT_MARKER.length()];
raf.write(b);
raf.write(pad);
raf.writeShort(schema.getPageSize());
raf.writeInt(1); // root page
int keyCount = schema.getKeyTypes().length;
raf.write(keyCount);
for (int i = 0; i < keyCount; i++)
raf.write(encodeKeyType(schema.getKeyTypes()[i]));
// create root page (force zero padding)
raf.seek(FILE_HEADER_SIZE);
raf.writeShort(PageType.LEAF);
raf.seek(FILE_HEADER_SIZE + schema.getPageSize() - 1);
raf.writeByte(0);
} finally {
raf.close();
}
return new PageFile(file);
}
private static int encodeKeyType(Class<?> clazz) {
if (clazz == Integer.class)
return 1;
else if (clazz == Long.class)
return 2;
else if (clazz == String.class)
return 3;
return 0;
}
private static Class<?> decodeKeyType(int type) {
switch (type) {
case 1:
return Integer.class;
case 2:
return Long.class;
case 3:
return String.class;
}
throw new IllegalArgumentException("illegal key type: " + type);
}
public PageFile(File file) throws IOException {
if (!file.exists())
throw new FileNotFoundException();
this.raf = new RandomAccessFile(file, "rwd");
readFileHeader();
}
public Schema getSchema() {
return schema;
}
public int getRootPage() {
return rootPage;
}
public void setRootPage(int pageNumber) throws IOException {
raf.seek(18);
raf.writeInt(pageNumber);
rootPage = pageNumber;
}
public void setRowValueFactory(RowValueFactory valueFactory) {
this.schema.setRowValueFactory(valueFactory);
}
public int getPageCount() throws IOException {
long length = raf.length() - FILE_HEADER_SIZE;
return (int) Math.ceil(length / (float) schema.getPageSize());
}
private void readFileHeader() throws IOException {
// marker: 16
// page size: 2
// root page: 4
// key count (1) + keys
byte[] b = new byte[FORMAT_MARKER.length()];
raf.read(b);
String mark = new String(b, "utf-8");
if (!mark.equals(FORMAT_MARKER))
throw new IOException("invalid file format");
raf.seek(16);
int pageSize = raf.readShort() & 0xffff;
this.rootPage = raf.readInt();
int keyCount = raf.read();
Class<?>[] keys = new Class<?>[keyCount];
for (int i = 0; i < keyCount; i++) {
keys[i] = decodeKeyType(raf.read());
}
this.schema = new Schema(pageSize, keys);
setupRowKeyFactory(keys);
}
private void setupRowKeyFactory(Class<?>[] keys) {
if (keys.length == 1) {
Class<?> type = keys[0];
if (type == Integer.class)
schema.setRowKeyFactory(new IntegerKeyFactory());
else if (type == Long.class)
schema.setRowKeyFactory(new LongKeyFactory());
else if (type == String.class)
schema.setRowKeyFactory(new StringKeyFactory());
} else {
schema.setRowKeyFactory(new CompositeKeyFactory(keys));
}
}
public void updateRootPage(int pageNumber) throws IOException {
raf.seek(18);
raf.writeInt(pageNumber);
}
public void write(Page page) throws IOException {
if (page.getNumber() <= 0)
throw new IllegalArgumentException("page number should be positive: " + page.getNumber());
Schema schema = page.getSchema();
byte[] data = page.getData();
raf.seek(FILE_HEADER_SIZE + (page.getNumber() - 1) * schema.getPageSize());
raf.write(data, 0, data.length);
}
public Page read(int pageNumber) throws IOException {
if (pageNumber <= 0)
throw new IllegalArgumentException("page number should be positive: " + pageNumber);
int pageSize = schema.getPageSize();
byte[] b = new byte[pageSize];
raf.seek(FILE_HEADER_SIZE + (pageNumber - 1) * pageSize);
raf.read(b);
return new Page(pageNumber, schema, b);
}
public void close() throws IOException {
raf.close();
}
}