/*
* Copyright 2009-2016 Tilmann Zaeschke. All rights reserved.
*
* This file is part of ZooDB.
*
* ZooDB 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 3 of the License, or
* (at your option) any later version.
*
* ZooDB 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.
*
* You should have received a copy of the GNU General Public License
* along with ZooDB. If not, see <http://www.gnu.org/licenses/>.
*
* See the README and COPYING files for further information.
*/
package org.zoodb.internal.server;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import org.zoodb.internal.server.index.BitTools;
import org.zoodb.internal.util.DBLogger;
public class StorageReader implements StorageChannelInput {
private final ByteBuffer buf;
private int currentPage = -1;
//indicate whether to automatically allocate and move to next page when page end is reached.
private final boolean isAutoPaging;
//The header is only written in auto-paging mode
private long headerClassOID = -1;
private long txTimeStamp = -1;
private final int MAX_POS;
private final StorageChannel root;
private final IntBuffer intBuffer;
private final int[] intArray;
private CallbackPageRead overflowCallback = null;
private PAGE_TYPE currentType;
/**
* Use for creating an additional view on a given file.
* @param fc
* @param pageSize
* @param fsm
*/
StorageReader(StorageChannel root, boolean autoPaging) {
this.root = root;
this.MAX_POS = root.getPageSize() - 4;
this.isAutoPaging = autoPaging;
buf = ByteBuffer.allocateDirect(root.getPageSize());
currentPage = -1;
intBuffer = buf.asIntBuffer();
intArray = new int[intBuffer.capacity()];
}
/**
* To be called after every commit, to ensure that pages are reset, in case they have been
* rewritten.
*/
@Override
public void reset() {
currentPage = -1;
}
@Override
public void seekPageForRead(PAGE_TYPE type, int pageId) {
seekPage(type, pageId, 0);
}
@Override
public void seekPosAP(PAGE_TYPE type, long pageAndOffs) {
int page = BitTools.getPage(pageAndOffs);
int offs = BitTools.getOffs(pageAndOffs);
seekPage(type, page, offs);
}
@Override
public void seekPage(PAGE_TYPE type, int pageId, int pageOffset) {
//isAutoPaging = autoPaging;
if (pageId != currentPage) {
currentPage = pageId;
buf.clear();
root.readPage(buf, pageId);
}
currentType = type;
if (type != PAGE_TYPE.DB_HEADER) {
buf.clear();
readHeader();
}
if (pageOffset == 0) {
if (isAutoPaging) {
pageOffset = PAGE_HEADER_SIZE_DATA; //TODO this is dirty...
} else {
if (type != PAGE_TYPE.DB_HEADER) {
pageOffset = PAGE_HEADER_SIZE;
}
}
}
buf.position(pageOffset);
}
@Override
public String readString() {
checkPosRead(4);
int len = buf.getInt();
//Align for 2-byte writing
int p = buf.position();
if ((p & 0x00000001) == 1) {
buf.position(p+1);
}
char[] array = new char[len];
CharBuffer cb = buf.asCharBuffer();
int l = array.length;
int posA = 0; //position in array
while (l > 0) {
checkPosRead(2);
int getLen = MAX_POS - buf.position();
getLen = getLen >> 1;
if (getLen > l) {
getLen = l;
}
cb = buf.asCharBuffer(); //create a buffer at the correct position
cb.get(array, posA, getLen);
buf.position(buf.position()+getLen*2);
posA += getLen;
l -= getLen;
}
return String.valueOf(array);
//return String.copyValueOf(array);
//return new String(array);
}
@Override
public boolean readBoolean() {
return readByte() != 0;
}
@Override
public byte readByte() {
checkPosRead(S_BYTE);
return buf.get();
}
@Override
public char readChar() {
if (!checkPos(S_CHAR)) {
return readByteBuffer(S_CHAR).getChar();
}
return buf.getChar();
}
@Override
public double readDouble() {
if (!checkPos(S_DOUBLE)) {
return Double.longBitsToDouble(readLong());
}
return buf.getDouble();
}
@Override
public float readFloat() {
if (!checkPos(S_FLOAT)) {
return Float.intBitsToFloat(readInt());
}
return buf.getFloat();
}
@Override
public void readFully(byte[] array) {
int l = array.length;
int posA = 0; //position in array
while (l > 0) {
checkPosRead(1);
int getLen = MAX_POS - buf.position();
if (getLen > l) {
getLen = l;
}
buf.get(array, posA, getLen);
posA += getLen;
l -= getLen;
}
}
@Override
public void noCheckRead(long[] array) {
LongBuffer lb = buf.asLongBuffer();
lb.get(array);
buf.position(buf.position() + S_LONG * array.length);
}
@Override
public void noCheckRead(int[] array) {
IntBuffer lb = buf.asIntBuffer();
lb.get(array);
buf.position(buf.position() + S_INT * array.length);
}
@Override
public void noCheckRead(byte[] array) {
buf.get(array);
}
@Override
public void noCheckRead(long[] array, int len) {
LongBuffer lb = buf.asLongBuffer();
lb.get(array, 0, len);
buf.position(buf.position() + S_LONG * len);
}
@Override
public void noCheckRead(int[] array, int len) {
IntBuffer lb = buf.asIntBuffer();
lb.get(array, 0, len);
buf.position(buf.position() + S_INT * len);
}
@Override
public void noCheckRead(byte[] array, int len) {
buf.get(array, 0, len);
}
@Override
public void noCheckReadAsInt(long[] array, int nElements) {
int pos = buf.position();
if ((pos >> 2) << 2 == pos) {
intBuffer.position(pos >> 2);
} else {
intBuffer.position((pos >> 2)+1);
}
intBuffer.get(intArray, 0, nElements);
for (int i = 0; i < nElements; i++) {
array[i] = intArray[i];
}
buf.position(intBuffer.position() * S_INT);
//Alternative implementation (faster according to PerfTest but slower when running JUnit suite
// for (int i = 0; i < nElements; i++) {
// array[i] = buf.getInt();
// }
}
@Override
public int readInt() {
if (!checkPos(S_INT)) {
return readByteBuffer(S_INT).getInt();
}
return buf.getInt();
}
@Override
public long readLong() {
if (!checkPos(S_LONG)) {
return readByteBuffer(S_LONG).getLong();
}
// checkPosRead(S_LONG);
return buf.getLong();
}
@Override
public short readShort() {
if (!checkPos(S_SHORT)) {
return readByteBuffer(S_SHORT).getShort();
}
return buf.getShort();
}
private ByteBuffer readByteBuffer(int len) {
byte[] ba = new byte[len];
readFully(ba);
return ByteBuffer.wrap(ba);
}
@Override
public long getHeaderClassOID() {
return headerClassOID;
}
@Override
public long getHeaderTimestamp() {
return txTimeStamp;
}
private boolean checkPos(int delta) {
//TODO remove autopaging, the indices use anyway the noCheckMethods!!
//TODO -> otherwise, make it final, as it should be known when a view is constructed.
if (isAutoPaging) {
return (buf.position() + delta - MAX_POS) <= 0;
}
return true;
}
private void checkPosRead(int delta) {
final ByteBuffer buf = this.buf;
if (isAutoPaging && buf.position() + delta > MAX_POS) {
final int pageId = buf.getInt();
currentPage = pageId;
buf.clear();
root.readPage(buf, pageId);
buf.rewind();
//read header
readHeader();
if (overflowCallback != null) {
overflowCallback.notifyOverflowRead(currentPage);
}
}
}
private void readHeader() {
byte pageType = buf.get();
buf.get(); //dummy
buf.getShort(); //pageVersion
txTimeStamp = buf.getLong();
if (pageType != currentType.getId()) {
throw DBLogger.newFatalInternal("Page type mismatch, expected " +
currentType.getId() + "/" + currentType + " (tx=" + root.getTxId() +
") but got " + pageType + " (tx=" + txTimeStamp + "). PageId=" + currentPage);
}
buf.position(PAGE_HEADER_SIZE);
if (isAutoPaging) {
headerClassOID = buf.getLong();
}
}
@Override
public int getOffset() {
return buf.position();
}
@Override
public int getPage() {
return currentPage;
}
@Override
public void skipRead(int nBytes) {
int l = nBytes;
while (l > 0) {
checkPosRead(1);
int bPos = buf.position();
int putLen = MAX_POS - bPos;
if (putLen > l) {
putLen = l;
}
buf.position(bPos + putLen);
l -= putLen;
}
}
@Override
public void setOverflowCallbackRead(CallbackPageRead readCallback) {
if (overflowCallback != null) {
throw new IllegalStateException();
}
overflowCallback = readCallback;
}
}