/*
* Copyright 2014 Alexey Plotnik
*
* 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.stem.db;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;
/**
* Index entry format:
* <p/>
* 0 16 20 24
* +--------------------------------+--------+--------+
* | key | offset | length |
* +--------------------------------+--------+--------+
* <p/>
* Index format for 2 entries:
* <p/>
* 0 24 48 56
* +-------+-------+-------+
* | Entry | Entry | Meta |
* +-------+-------+-------+
* <p/>
* Index meta format:
* <p/>
* 0 4 8
* +--------------+--------------+
* | Index length | CRC32 |
* +--------------+--------------+
*/
public class FatFileIndex {
public static final int LENGTH_SIZE = 4;
public static final int CRC32_SIZE = 4;
private CRC32 crc = new CRC32();
private List<Entry> entries;
public List<Entry> getEntries() {
return entries;
}
public FatFileIndex() {
entries = new ArrayList<Entry>();
}
public void add(Entry entry) {
entries.add(entry);
}
public int getSize() {
return entries.size() * Entry.SIZE;
}
public ByteBuffer serialize() {
ByteBuffer bodyBuf = ByteBuffer.allocate(getSize());
for (Entry entry : entries) {
entry.writeToBuf(bodyBuf);
}
bodyBuf.position(0);
crc.update(bodyBuf.array());
int crc32 = (int) crc.getValue();
Header header = Header.create(getSize(), crc32);
ByteBuffer indexBuf = ByteBuffer.allocate(getSize() + Header.SIZE);
indexBuf.put(bodyBuf);
indexBuf.put(header.serialize());
return indexBuf;
}
public static Entry create(Blob.Header header, int blobOffset, byte deleteFlag) {
return new Entry(header.key, blobOffset, header.length, deleteFlag);
}
public static FatFileIndex deserialize(FileChannel channel, long indexHeaderOffset) throws IOException {
FileLock lock = channel.lock();
try {
// Deserialize header
ByteBuffer out = ByteBuffer.allocate(FatFileIndex.Header.SIZE);
channel.position(indexHeaderOffset);
channel.read(out);
out.position(0);
Header header = Header.deserialize(out);
FatFileIndex index = new FatFileIndex();
out = ByteBuffer.allocate(header.length);
long indexBodyOffset = indexHeaderOffset - header.length;
channel.position(indexBodyOffset);
channel.read(out);
channel.position(0);
CRC32 crc = new CRC32();
crc.update(out.array());
int crc32 = (int) crc.getValue();
if (crc32 != header.crc32) {
throw new IOException(String.format("Wrong index CRC32 checksum (%s but %s expected)", crc32, header.crc32));
}
out.position(0);
while (out.hasRemaining()) {
index.add(Entry.readFromBuf(out));
}
return index;
} finally {
lock.release();
}
}
public Entry findByKey(byte[] key) {
for (Entry entry : entries) {
if (Arrays.equals(entry.key, key))
return entry;
}
return null;
}
public static class Header {
public static final int CRC32_SIZE = 4;
public static final int LENGTH_SIZE = 4;
public static final int SIZE = LENGTH_SIZE + CRC32_SIZE;
int length;
int crc32;
public Header(int length, int crc32) {
this.length = length;
this.crc32 = crc32;
}
public ByteBuffer serialize() {
ByteBuffer buf = ByteBuffer.allocate(SIZE);
buf.putInt(length);
ByteBuffer crc32Buf = ByteBuffer.allocate(CRC32_SIZE).putInt(crc32);
crc32Buf.position(0);
buf.put(crc32Buf);
buf.position(0);
return buf;
}
public static Header deserialize(ByteBuffer buf) {
assert buf.remaining() == SIZE;
int size = buf.getInt();
int crc32 = buf.getInt();
return Header.create(size, crc32);
}
public static Header create(int size, int crc32) {
return new Header(size, crc32);
}
}
public static class Entry {
public static final int KEY_SIZE = 16;
public static final int OFFSET_SIZE = 4;
public static final int LENGTH_SIZE = 4;
public static final byte FLAGS_SIZE = 1;
public static final byte FLAG_LIVE = 0;
public static final byte FLAG_DELETED = 1;
public byte[] key; // 16
public int offset; // 4
public int length; // 4
public byte deleteFlag; // 1
public static final int SIZE = KEY_SIZE + OFFSET_SIZE + LENGTH_SIZE + FLAGS_SIZE;
public Entry(byte[] key, int offset, int length, byte deleteFlag) {
this.key = key;
this.offset = offset;
this.length = length;
this.deleteFlag = deleteFlag;
}
public static Entry readFromBuf(ByteBuffer buf) {
byte[] key = new byte[KEY_SIZE];
buf.get(key);
int offset = buf.getInt();
int length = buf.getInt();
byte deleteFlag = buf.get();
return new Entry(key, offset, length, deleteFlag);
// TODO: validation
}
public void writeToBuf(ByteBuffer buf) {
buf.put(key);
buf.putInt(offset);
buf.putInt(length);
buf.put(deleteFlag);
}
public boolean isLive() {
return FLAG_LIVE == this.deleteFlag;
}
public boolean isDeleted() {
return FLAG_DELETED == this.deleteFlag;
}
}
}