/*
* Copyright (C) 2006 http://www.chaidb.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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 for more details.
*
*/
package org.chaidb.db.index.btree;
import org.chaidb.db.exception.ChaiDBException;
import org.chaidb.db.exception.ErrorCode;
import org.chaidb.db.helper.BitMap;
import org.chaidb.db.index.btree.bufmgr.PageBufferManager;
import org.chaidb.db.index.btree.bufmgr.PageNumber;
import org.chaidb.db.index.btree.bufmgr.StorageProxy;
import java.io.*;
import java.util.*;
public class BTreeChanges {
private final static String EXT = ".trk";
private final static int OBSOLETE_FLAG = 0x9D5ACD59;
private final static int INIT_FLAG = ~OBSOLETE_FLAG;
private final static int MAX_BITMAP_SIZE = 0x10000; // 64k:2G/(4096*8)
private final static int INIT_CAP = MAX_BITMAP_SIZE;
private final static int COUNT_PER = 10;
// position
private final static int FLAG_OFF = 0;
private final static int TREEID_OFF = FLAG_OFF + 4;
private final static int DATA_OFF = TREEID_OFF + 4;
private static PageBufferManager pbm = PageBufferManager.getInstance();
private int treeid;
private LinkedHashMap bitMaps;
private String btreepath;
private String trackName;
private boolean reading = false; // when backup, reading the track file
private static final int BLOCK_SIZE = BTreeSpec.PAGE_SIZE;
public BTreeChanges(int treeid) throws ChaiDBException {
this.treeid = treeid;
bitMaps = new LinkedHashMap(1);
BitMap bitMap = new BitMap(INIT_CAP);
bitMaps.put(new Integer(0), bitMap);
btreepath = pbm.getBTreeName(treeid);
trackName = btreepath + EXT;
// init bitMap from file
try {
init();
} catch (IOException e) {
throw new ChaiDBException(ErrorCode.BTREE_INVALID);
}
}
// for backup use
public BTreeChanges(String btreename) throws ChaiDBException {
bitMaps = new LinkedHashMap(1);
BitMap bitMap = new BitMap(INIT_CAP);
bitMaps.put(new Integer(0), bitMap);
this.btreepath = btreename;
trackName = btreename + EXT;
reading = true;
// init bitMap from file
try {
init();
} catch (IOException e) {
throw new ChaiDBException(ErrorCode.BTREE_INVALID);
}
}
public void setChange(Object change) {
// parameters validate
if (change.getClass() != PageNumber.class) {
// bad parameters
return;
}
PageNumber pno = (PageNumber) change;
if (pno.getTreeId() != treeid) {
return;
}
BitMap bm;
Integer pageInFile = new Integer(pno.getFileNumber());
bm = (BitMap) bitMaps.get(pageInFile);
if (bm == null) {
bm = new BitMap(INIT_CAP);
bitMaps.put(pageInFile, bm);
}
bm.set(pno.getPageInFile());
}
public long getChangeSize() {
Iterator it = bitMaps.values().iterator();
int len = 0;
while (it.hasNext()) {
len += ((BitMap) it.next()).getTrueCount();
}
return len * BTreeSpec.PAGE_SIZE;
}
public String getBTreeName() {
return btreepath;
}
private boolean isRightFile(RandomAccessFile raf) {
try {
raf.seek(0);
int flag = raf.readInt();
if (flag == OBSOLETE_FLAG) return false;
else return true;
} catch (IOException e) {
return false;
}
}
private void init() throws IOException, ChaiDBException {
if (trackName == null || trackName.length() == 0) {
return;
}
/**
* Several cases:
* 1: file not exists
* 2: bad file
* 3: correct file
*/
File f = new File(trackName);
RandomAccessFile raf = null;
int flag;
try {
if (!f.exists()) { // this would not be run
if (reading) {
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
}
raf = new RandomAccessFile(trackName, "rw");
raf.setLength(4);
flag = OBSOLETE_FLAG;
raf.writeInt(flag);
return;
} else {
raf = new RandomAccessFile(trackName, "rw");
if (!isRightFile(raf) && reading) { // when reading track file, it's bad
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
} else {
if (f.length() < 8) {
if (reading) {
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
} else {
raf.setLength(8);
flag = OBSOLETE_FLAG;
raf.seek(FLAG_OFF);
raf.writeInt(flag);
if (!reading) {
raf.writeInt(treeid);
} else { // should read a empty track file when backup, it's bad, throw exception
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
}
}
return;
} else {
// Set tree to obsolete
flag = raf.readInt();
flag = OBSOLETE_FLAG;
raf.seek(FLAG_OFF);
raf.writeInt(flag);
// read treeid
raf.seek(TREEID_OFF);
int treeid = raf.readInt();
if (reading) { // reading track file, init treeid
this.treeid = treeid;
} else { // init a track file, write treeid
raf.seek(TREEID_OFF);
raf.writeInt(this.treeid);
}
BitMap bm = (BitMap) bitMaps.get(new Integer(0));
long len = f.length() - DATA_OFF;
if (len > 0x10000) {
len -= 0x10000;
}
bm.read(raf, (int) len);
return;
}
}
}
} finally {
if (raf != null) {
raf.close();
}
}
}
private void readAFile(int pageInFile, RandomAccessFile raf) throws IOException {
raf.seek(DATA_OFF + MAX_BITMAP_SIZE * pageInFile);
Integer fnum = new Integer(pageInFile);
BitMap bm = (BitMap) bitMaps.get(fnum);
if (bm == null) {
bm = new BitMap(INIT_CAP);
}
bm.read(raf);
}
private void beginFlush(RandomAccessFile raf) throws IOException {
int flag = OBSOLETE_FLAG;
raf.seek(0);
raf.writeInt(flag);
raf.getFD().sync();
}
private void endFlush(RandomAccessFile raf) throws IOException {
int flag = INIT_FLAG;
raf.seek(0);
raf.writeInt(flag);
raf.getFD().sync();
}
public void flush() throws ChaiDBException {
if (trackName == null || trackName.length() == 0) {
return;
}
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(trackName, "rw");
beginFlush(raf);
raf.seek(TREEID_OFF); // not necessary
raf.writeInt(treeid);
Iterator it = bitMaps.values().iterator();
while (it.hasNext()) {
((BitMap) it.next()).flush(raf);
}
endFlush(raf);
} catch (IOException e) {
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
}
}
}
}
public boolean changesOf(String btreename) {
return this.btreepath.equals(btreename);
}
public void backupChanges(DataOutputStream dataOut) throws ChaiDBException {
try {
File f = new File(trackName);
int fileCount = (int) ((f.length() - DATA_OFF) / MAX_BITMAP_SIZE);
// read changes
RandomAccessFile raf = new RandomAccessFile(trackName, "r");
try {
for (int i = 0; i < fileCount; i++) {
readAFile(i, raf);
}
} finally {
raf.close();
}
// generate pagenumbers
Iterator it = bitMaps.entrySet().iterator();
ArrayList pnos = new ArrayList();
int pos[];
while (it.hasNext()) {
Map.Entry ent = (Map.Entry) it.next();
pos = ((BitMap) ent.getValue()).getTruePositions();
for (int i = 0; i < pos.length; i++) {
pnos.add(new PageNumber(treeid, ((Integer) ent.getKey()).intValue(), pos[i]));
}
}
// backup changes
int size = 0; // counter for snap file to 4k blocks
int pnocount = pnos.size();
int readed = 0;
int endpos = 0;
dataOut.writeInt(treeid);
dataOut.writeInt(pnocount);
size += 8;
// write data offset
size += 4 * pnocount;
size += 4; //allocate space to write header size
final int odd = size % BLOCK_SIZE;
int pageRemain = 0;
if (odd != 0) {
pageRemain = BLOCK_SIZE - odd;
}
dataOut.writeInt(size + pageRemain); //write header size
// write pagenumbers
for (int i = 0; i < pnocount; i++) {
dataOut.writeInt(((PageNumber) pnos.get(i)).getPageNumber());
}
for (int i = 0; i < pageRemain; i++) {
dataOut.write(0);
}
// write datum
while (readed < pnocount) {
endpos = readed + COUNT_PER;
if (endpos > pnocount) endpos = pnocount;
List datum = StorageProxy.readPages(btreepath, pnos, readed, endpos - readed);
while (datum.size() > 0) {
dataOut.write((byte[]) datum.remove(0));
}
readed += endpos;
}
//stream will be closed by outer method
} catch (IOException e) {
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
} catch (ChaiDBException e) {
throw e;
}
}
public void backupChanges(String dstpath) throws ChaiDBException {
DataOutputStream out = null;
try {
out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dstpath)));
backupChanges(out);
} catch (FileNotFoundException e) {
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
;
}
}
}
}
public static void backupChanges(String btreepath, String dstpath) throws ChaiDBException {
BTreeChanges bchange = new BTreeChanges(btreepath);
bchange.backupChanges(dstpath);
}
public static void restoreChanges(String btreepath, DataInputStream in) throws ChaiDBException {
try {
int size = 0;
int treeid = in.readInt();
int count = in.readInt();
size += 8;
int data_offset = in.readInt();
size += 4;
ArrayList pnos = new ArrayList(count);
// read page numbers
PageNumber pn;
for (int i = 0; i < count; i++) {
pn = new PageNumber(in.readInt());
pn.setTreeId(treeid);
pnos.add(pn);
}
size += 4 * count;
int remain = data_offset - size;
for (int i = 0; i < remain; i++) {
in.read();
}
// write to btree
int writed = 0, endpos = 0;
ArrayList pages;
byte[] data;
while (writed < count) {
endpos += COUNT_PER;
if (endpos > count) endpos = count;
pages = new ArrayList(COUNT_PER);
for (int i = writed; i < endpos; i++) {
data = new byte[BTreeSpec.PAGE_SIZE];
in.read(data);
pages.add(data);
}
StorageProxy.writePages(btreepath, pnos, writed, pages, 0, endpos - writed);
writed = endpos;
}
in.close();
} catch (IOException e) {
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
} catch (ChaiDBException e) {
throw e;
}
}
public static void restoreChanges(String btreepath, String srcpath) throws ChaiDBException {
try {
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(srcpath)));
restoreChanges(btreepath, in);
} catch (FileNotFoundException e) {
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
}
}
public static String getTrackFileName(String btreename) {
return btreename + EXT;
}
public static boolean isTrackFile(String fname) {
return fname.endsWith(EXT);
}
public static boolean trackFileExist(int treeid) {
try {
return new File(PageBufferManager.getInstance().getBTreeName(treeid) + EXT).exists();
} catch (ChaiDBException e) {
// when the btree is not opened, following code will be excute. that's impossible
// this time return false is ok.
return false;
}
}
public static void createEmptyTrackFile(String btreename) throws ChaiDBException {
try {
RandomAccessFile raf = new RandomAccessFile(getTrackFileName(btreename), "rw");
try {
raf.writeInt(INIT_FLAG);
} finally {
raf.close();
}
} catch (IOException e) {
throw new ChaiDBException(ErrorCode.BTREE_TRACKFILE_IO_ERROR);
}
}
}