/*
* 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.FreeSpaceManager;
import org.zoodb.internal.util.DBLogger;
public class StorageWriter implements StorageChannelOutput {
private final ByteBuffer buf;
private int currentPage = -1;
private final FreeSpaceManager fsm;
//indicate whether to automatically allocate and move to next page when page end is reached.
private final boolean isAutoPaging;
private boolean isWriting = true; //TODO merge with currentPage=-1
//The header is only written in auto-paging mode
private long classOid = -1;
private final int MAX_POS;
private final StorageChannel root;
private CallbackPageWrite overflowCallback = null;
private final IntBuffer intBuffer;
private final int[] intArray;
private PAGE_TYPE currentDataType;
/**
* Use for creating an additional view on a given file.
* @param fc
* @param pageSize
* @param fsm
*/
StorageWriter(StorageChannel root, FreeSpaceManager fsm, boolean autoPaging) {
this.root = root;
this.MAX_POS = root.getPageSize() - 4;
this.fsm = fsm;
this.isAutoPaging = autoPaging;
isWriting = false;
buf = ByteBuffer.allocateDirect(root.getPageSize());
currentPage = -1;
intBuffer = buf.asIntBuffer();
intArray = new int[intBuffer.capacity()];
}
/**
* Assumes autoPaging=false;
*/
@Override
public void seekPageForWrite(PAGE_TYPE type, int pageId) {
//isAutoPaging = false;
writeData();
isWriting = true;
currentPage = pageId;
buf.clear();
currentDataType = type;
if (type != PAGE_TYPE.DB_HEADER) {
writeHeader();
}
}
/**
* Assumes autoPaging=false;
*/
@Override
public int allocateAndSeek(PAGE_TYPE type, int prevPage) {
//isAutoPaging = false;
currentDataType = type;
return allocateAndSeekPage(prevPage);
}
/**
* Assumes autoPaging=true;
*/
@Override
public int allocateAndSeekAP(PAGE_TYPE type, int prevPage, long header) {
//isAutoPaging = true;
currentDataType = type;
classOid = header;
int pageId = allocateAndSeekPage(prevPage);
//auto-paging is true
return pageId;
}
private int allocateAndSeekPage(int prevPage) {
int pageId = fsm.getNextPage(prevPage);
try {
writeData();
isWriting = true;
currentPage = pageId;
buf.clear();
if (currentDataType != PAGE_TYPE.DB_HEADER) {
writeHeader();
}
} catch (Exception e) {
throw DBLogger.newFatal("Error loading Page: " + pageId, e);
}
return pageId;
}
/**
* Not a true flush, just writes the stuff to StorageChannel.
*/
@Override
public void flush() {
writeData();
//To avoid unnecessary writing during the next flush()
isWriting = false;
}
private void writeData() {
if (isWriting) {
buf.flip();
root.write(buf, currentPage);
}
}
@Override
public void writeString(String string) {
checkPosWrite(4);
buf.putInt(string.length());
//Align for 2-byte writing
int p = buf.position();
if ((p & 0x00000001) == 1) {
buf.position(p+1);
}
CharBuffer cb;
int l = string.length();
int posA = 0; //position in array
while (l > 0) {
checkPosWrite(2);
int putLen = MAX_POS - buf.position();
putLen = putLen >> 1; //TODO loses odd values!
if (putLen > l) {
putLen = l;
}
//This is crazy!!! Unlike ByteBuffer, CharBuffer requires END as third param!!
cb = buf.asCharBuffer();
cb.put(string, posA, posA + putLen);
buf.position(buf.position() + putLen * 2);
posA += putLen;
l -= putLen;
}
}
@Override
public void write(byte[] array) {
int l = array.length;
int posA = 0; //position in array
while (l > 0) {
checkPosWrite(1);
int putLen = MAX_POS - buf.position();
if (putLen > l) {
putLen = l;
}
buf.put(array, posA, putLen);
posA += putLen;
l -= putLen;
}
}
/**
* The no-check methods are thought to be faster, because they don't need range checking.
* Furthermore, they ensure that a page can be filled to the last byte. without a new page
* being allocated.
*/
@Override
public void noCheckWrite(long[] array) {
LongBuffer lb = buf.asLongBuffer();
lb.put(array);
buf.position(buf.position() + S_LONG * array.length);
}
@Override
public void noCheckWrite(int[] array) {
IntBuffer lb = buf.asIntBuffer();
lb.put(array);
buf.position(buf.position() + S_INT * array.length);
}
@Override
public void noCheckWrite(byte[] array) {
buf.put(array);
}
@Override
public void noCheckWrite(long[] array, int len) {
LongBuffer lb = buf.asLongBuffer();
lb.put(array, 0, len);
buf.position(buf.position() + S_LONG * len);
}
@Override
public void noCheckWrite(int[] array, int len) {
IntBuffer lb = buf.asIntBuffer();
lb.put(array, 0, len);
buf.position(buf.position() + S_INT * len);
}
@Override
public void noCheckWrite(byte[] array, int len) {
buf.put(array, 0, len);
}
@Override
public void noCheckWriteAsInt(long[] array, int nElements) {
int pos = buf.position();
if ((pos >> 2) << 2 == pos) {
intBuffer.position(pos >> 2);
} else {
intBuffer.position((pos >> 2)+1);
}
for (int i = 0; i < nElements; i++) {
intArray[i] = (int) array[i];
}
intBuffer.put(intArray, 0, nElements);
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++) {
// buf.putInt( (int) array[i] );
// }
}
@Override
public void writeBoolean(boolean boolean1) {
writeByte((byte) (boolean1 ? 1 : 0));
}
@Override
public void writeByte(byte byte1) {
checkPosWrite(S_BYTE);
buf.put(byte1);
}
@Override
public void writeChar(char char1) {
if (!checkPos(S_CHAR)) {
write(ByteBuffer.allocate(S_CHAR).putChar(char1).array());
return;
}
buf.putChar(char1);
}
@Override
public void writeDouble(double double1) {
if (!checkPos(S_DOUBLE)) {
writeLong(Double.doubleToLongBits(double1));
return;
}
buf.putDouble(double1);
}
@Override
public void writeFloat(float float1) {
if (!checkPos(S_FLOAT)) {
writeInt(Float.floatToIntBits(float1));
return;
}
buf.putFloat(float1);
}
@Override
public void writeInt(int int1) {
if (!checkPos(S_INT)) {
write(ByteBuffer.allocate(S_INT).putInt(int1).array());
return;
}
buf.putInt(int1);
}
@Override
public void writeLong(long long1) {
if (!checkPos(S_LONG)) {
write(ByteBuffer.allocate(S_LONG).putLong(long1).array());
return;
}
buf.putLong(long1);
}
@Override
public void writeShort(short short1) {
if (!checkPos(S_SHORT)) {
write(ByteBuffer.allocate(S_SHORT).putShort(short1).array());
return;
}
buf.putShort(short1);
}
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 checkPosWrite(int delta) {
if (isAutoPaging && buf.position() + delta > MAX_POS) {
int pageId = fsm.getNextPage(0);
buf.putInt(pageId);
//write page
writeData();
currentPage = pageId;
buf.clear();
writeHeader();
if (overflowCallback != null) {
overflowCallback.notifyOverflowWrite(currentPage);
}
}
}
private void writeHeader() {
buf.put(currentDataType.getId());
buf.put((byte) 0); //dummy
buf.putShort(PAGE_FORMAT_VERSION);
buf.putLong(root.getTxId());
if (isAutoPaging) {
buf.putLong(classOid);
}
}
@Override
public int getOffset() {
return buf.position();
}
@Override
public int getPage() {
return currentPage;
}
@Override
public void skipWrite(int nBytes) {
int l = nBytes;
while (l > 0) {
checkPosWrite(1);
int bPos = buf.position();
int putLen = MAX_POS - bPos;
if (putLen > l) {
putLen = l;
}
buf.position(bPos + putLen);
l -= putLen;
}
}
/**
* Set a call-back for this view. Every view has its own call-backs.
*/
@Override
public void setOverflowCallbackWrite(CallbackPageWrite overflowCallback) {
if (this.overflowCallback!=null) {
throw new IllegalStateException();
}
this.overflowCallback = overflowCallback;
}
}