/*$Id$*/
package at.jku.sii.sqlitereader.page;
import java.util.ArrayList;
import java.util.List;
import at.jku.sii.sqlitereader.DumpUtils;
import at.jku.sii.sqlitereader.Dumpable;
import at.jku.sii.sqlitereader.SqliteDataBase;
import at.jku.sii.sqlitereader.io.ArrayDataInput;
public final class PointerMapPage implements Page {
public enum PageType {
ROOT_BTREE, FREELIST, FIRST_PAYLOAD_OVERFLOW, TAIL_PAYLOAD_OVERFLOW, NON_ROOT_BTREE
}
private final int number;
private final List<BackLinkInfo> infos;
private final int pageOffset;
public PointerMapPage(int number, int startPage, List<BackLinkInfo> infos) {
this.infos = infos;
this.pageOffset = startPage;
this.number = number;
}
@Override
public int getNumber() {
return this.number;
}
public BackLinkInfo getInfoFor(int pageNum) {
if (pageNum < this.pageOffset || pageNum > (this.pageOffset + this.infos.size()))
return null;
return this.infos.get(pageNum - this.pageOffset);
}
public List<BackLinkInfo> getInfos() {
return this.infos;
}
public int getPageOffset() {
return this.pageOffset;
}
public static boolean mustHavePointerMapPage(SqliteDataBase db) {
return db.getLargestBTreePage() > 0;
}
public static boolean mustNotHavePointerMapPage(SqliteDataBase db) {
return db.getLargestBTreePage() == 0;
}
private static PointerMapPage read(int number, int pageOffset, int numEntries, ArrayDataInput block) {
List<BackLinkInfo> infos = new ArrayList<BackLinkInfo>();
for (int i = 0; i < numEntries; ++i) {
PageType type = PageType.values()[block.readByte()];
block.annotateLastByte("Pagetype", type);
int pageNumber = block.readInt("pageNumber");
switch (type) {
case ROOT_BTREE:
infos.add(new SimpleBackLinkInfo(type));
break;
case FREELIST:
infos.add(new SimpleBackLinkInfo(type));
break;
case FIRST_PAYLOAD_OVERFLOW:
infos.add(new FirstPayLoadOverflowBackLinkInfo(pageNumber));
break;
case TAIL_PAYLOAD_OVERFLOW:
infos.add(new TailPayLoadOverflowBackLinkInfo(pageNumber));
break;
case NON_ROOT_BTREE:
infos.add(new NonRootBTreeBackLinkInfo(pageNumber));
break;
}
block.annotateLast(5, "BackLinkInfo", infos.get(infos.size() - 1));
}
return new PointerMapPage(number, pageOffset, infos);
}
public static List<PointerMapPage> readPointerMapPages(SqliteDataBase db) {
List<PointerMapPage> pages = new ArrayList<PointerMapPage>();
if (mustNotHavePointerMapPage(db))
return pages;
int j = db.getUsableSize() / 5;
int numPages = db.getNumPages();
int pmpPage = 2; //start at 2
int pageOffset = 3;
while (pmpPage < numPages) {
ArrayDataInput block = db.getPageBlock(pmpPage);
PointerMapPage page = read(pmpPage, pageOffset, Math.min(numPages - pageOffset + 1, j), block);
db.resolvePage(pmpPage, page);
pages.add(page);
pageOffset += j+1;
}
return pages;
}
static BackLinkInfo getBackInfoFor(int pageNum, Iterable<PointerMapPage> pages) {
if (pageNum <= 3)
return null;
for (PointerMapPage p : pages) {
BackLinkInfo info = p.getInfoFor(pageNum);
if (info != null)
return info;
}
return null;
}
public interface BackLinkInfo extends Dumpable {
PageType getType();
}
public static final class SimpleBackLinkInfo implements BackLinkInfo {
private final PageType type;
public SimpleBackLinkInfo(PageType type) {
this.type = type;
}
@Override
public PageType getType() {
return this.type;
}
@Override
public String toString() {
return String.format("BackLinkInfo [type=%s]", this.type);
}
@Override
public void dump(StringBuilder b) {
b.append(this.toString());
}
}
public static final class FirstPayLoadOverflowBackLinkInfo implements BackLinkInfo {
private final int pageNumber;
public FirstPayLoadOverflowBackLinkInfo(int pageNumber) {
this.pageNumber = pageNumber;
}
@Override
public PageType getType() {
return PageType.FIRST_PAYLOAD_OVERFLOW;
}
public int getContainingBTreePageNumber() {
return this.pageNumber;
}
@Override
public String toString() {
return String.format("FirstPayLoadOverflowBackLinkInfo [containingBTreePageNumber=%s]", this.getContainingBTreePageNumber());
}
@Override
public void dump(StringBuilder b) {
b.append(this.toString());
}
}
public static final class TailPayLoadOverflowBackLinkInfo implements BackLinkInfo {
private final int pageNumber;
public TailPayLoadOverflowBackLinkInfo(int pageNumber) {
this.pageNumber = pageNumber;
}
@Override
public PageType getType() {
return PageType.TAIL_PAYLOAD_OVERFLOW;
}
public int getPriorOverflowPageNumber() {
return this.pageNumber;
}
@Override
public String toString() {
return String.format("TailPayLoadOverflowBackLinkInfo [priorOverflowPageNumber=%s]", this.getPriorOverflowPageNumber());
}
@Override
public void dump(StringBuilder b) {
b.append(this.toString());
}
}
public static final class NonRootBTreeBackLinkInfo implements BackLinkInfo {
private final int pageNumber;
public NonRootBTreeBackLinkInfo(int pageNumber) {
this.pageNumber = pageNumber;
}
@Override
public PageType getType() {
return PageType.NON_ROOT_BTREE;
}
public int getParentBTreePageNumber() {
return this.pageNumber;
}
@Override
public String toString() {
return String.format("NonRootBTreeBackLinkInfo [parentBTreePageNumber=%s]", this.getParentBTreePageNumber());
}
@Override
public void dump(StringBuilder b) {
b.append(this.toString());
}
}
@Override
public String toString() {
return String.format("PointerMapPage [infos=%s, pageOffset=%s]", this.infos, this.pageOffset);
}
@Override
public void dump(StringBuilder b) {
b.append("<h3>PointerMap Page ").append(this.number).append("</h3>\n");
b.append("<pre>");
b.append(" pageOffset\t= ").append(this.pageOffset);
b.append(" infos: \n");
DumpUtils.join(this.infos, b, "\n");
b.append("</pre>");
}
}