/* This file is part of the db4o object database http://www.db4o.com
Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com
db4o is free software; you can redistribute it and/or modify it under
the terms of version 3 of the GNU General Public License as published
by the Free Software Foundation.
db4o 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 this program. If not, see http://www.gnu.org/licenses/. */
package com.db4o.internal;
import com.db4o.foundation.*;
import com.db4o.internal.marshall.*;
import com.db4o.marshall.*;
/**
* @exclude
*/
public class MarshallingBuffer implements WriteBuffer{
private static final int SIZE_NEEDED = Const4.LONG_LENGTH;
private static final int NO_PARENT = - Integer.MAX_VALUE;
private ByteArrayBuffer _delegate;
private int _lastOffSet;
private int _addressInParent = NO_PARENT;
private List4 _children;
private FieldMetadata _indexedField;
public int length() {
return offset();
}
public int offset(){
if(_delegate == null){
return 0;
}
return _delegate.offset();
}
public void writeByte(byte b) {
prepareWrite();
_delegate.writeByte(b);
}
public void writeBytes(byte[] bytes) {
prepareWrite(bytes.length);
_delegate.writeBytes(bytes);
}
public void writeInt(int i) {
prepareWrite();
_delegate.writeInt(i);
}
public void writeLong(long l) {
prepareWrite();
_delegate.writeLong(l);
}
private void prepareWrite(){
prepareWrite(SIZE_NEEDED);
}
public void prepareWrite(int sizeNeeded){
if(_delegate == null){
_delegate = new ByteArrayBuffer(sizeNeeded);
}
_lastOffSet = _delegate.offset();
if(remainingSize() < sizeNeeded){
resize(sizeNeeded);
}
}
private int remainingSize() {
return _delegate.length() - _delegate.offset();
}
private void resize(int sizeNeeded) {
int newSize = _delegate.length() * 2;
if(newSize - _lastOffSet < sizeNeeded){
newSize += sizeNeeded;
}
ByteArrayBuffer temp = new ByteArrayBuffer(newSize);
temp.seek(_lastOffSet);
_delegate.copyTo(temp, 0, 0, _delegate.length());
_delegate = temp;
}
public void transferLastWriteTo(MarshallingBuffer other, boolean storeLengthInLink){
other.addressInParent(_lastOffSet, storeLengthInLink);
int length = _delegate.offset() - _lastOffSet;
other.prepareWrite(length);
int otherOffset = other._delegate.offset();
System.arraycopy(_delegate._buffer, _lastOffSet, other._delegate._buffer, otherOffset, length);
_delegate.seek(_lastOffSet);
other._delegate.seek(otherOffset + length);
other._lastOffSet = otherOffset;
}
private void addressInParent(int offset, boolean storeLengthInLink) {
_addressInParent = storeLengthInLink ? offset : -offset;
}
public void transferContentTo(ByteArrayBuffer buffer){
transferContentTo(buffer, length());
}
public void transferContentTo(ByteArrayBuffer buffer, int length){
if(_delegate == null){
return;
}
System.arraycopy(_delegate._buffer, 0, buffer._buffer, buffer._offset, length);
buffer._offset += length;
}
public ByteArrayBuffer testDelegate(){
return _delegate;
}
public MarshallingBuffer addChild() {
return addChild(true, false);
}
public MarshallingBuffer addChild(boolean reserveLinkSpace, boolean storeLengthInLink) {
MarshallingBuffer child = new MarshallingBuffer();
child.addressInParent(offset(), storeLengthInLink);
_children = new List4(_children, child);
if(reserveLinkSpace){
reserveChildLinkSpace(storeLengthInLink);
}
return child;
}
public void reserveChildLinkSpace(boolean storeLengthInLink) {
int length = storeLengthInLink ? Const4.INT_LENGTH * 2 : Const4.INT_LENGTH;
prepareWrite(length);
_delegate.incrementOffset(length);
}
public void mergeChildren(MarshallingContext context, int masterAddress, int linkOffset) {
mergeChildren(context, masterAddress, this, this, linkOffset);
}
private static void mergeChildren(MarshallingContext context, int masterAddress, MarshallingBuffer writeBuffer, MarshallingBuffer parentBuffer, int linkOffset) {
if(parentBuffer._children == null){
return;
}
Iterator4 i = new Iterator4Impl(parentBuffer._children);
while(i.moveNext()){
merge(context, masterAddress, writeBuffer, parentBuffer, (MarshallingBuffer) i.current(), linkOffset);
}
}
private static void merge(MarshallingContext context, int masterAddress, MarshallingBuffer writeBuffer, MarshallingBuffer parentBuffer, MarshallingBuffer childBuffer, int linkOffset) {
int childPosition = writeBuffer.offset();
writeBuffer.reserve(childBuffer.blockedLength());
mergeChildren(context, masterAddress, writeBuffer, childBuffer, linkOffset);
int savedWriteBufferOffset = writeBuffer.offset();
writeBuffer.seek(childPosition);
childBuffer.transferContentTo(writeBuffer._delegate);
writeBuffer.seek(savedWriteBufferOffset);
parentBuffer.writeLink(childBuffer, childPosition + linkOffset, childBuffer.unblockedLength());
childBuffer.writeIndex(context, masterAddress, childPosition + linkOffset);
}
public void seek(int offset) {
_delegate.seek(offset);
}
public ReservedBuffer reserve(int length) {
prepareWrite(length);
ReservedBuffer reservedBuffer = new ReservedBuffer() {
private final int reservedOffset = _delegate.offset();
public void writeBytes(byte[] bytes) {
int currentOffset = _delegate.offset();
_delegate.seek(reservedOffset);
_delegate.writeBytes(bytes);
_delegate.seek(currentOffset);
}
};
_delegate.seek(_delegate.offset() + length );
return reservedBuffer;
}
private void writeLink(MarshallingBuffer child, int position, int length){
int offset = offset();
_delegate.seek(child.addressInParent());
_delegate.writeInt(position);
if(child.storeLengthInLink()){
_delegate.writeInt(length);
}
_delegate.seek(offset);
}
private void writeIndex(MarshallingContext context, int masterAddress, int position) {
if(_indexedField != null){
// for now this is a String index only, it takes the entire slot.
StatefulBuffer buffer = new StatefulBuffer(context.transaction(), unblockedLength());
int blockedPosition = context.container().blockConverter().bytesToBlocks(position);
int indexID = masterAddress + blockedPosition;
buffer.setID(indexID);
buffer.address(indexID);
transferContentTo(buffer, unblockedLength());
_indexedField.addIndexEntry(context.transaction(), context.objectID(), buffer);
}
}
private int addressInParent() {
if(! hasParent()){
throw new IllegalStateException();
}
if(_addressInParent < 0){
return - _addressInParent;
}
return _addressInParent;
}
public void debugDecrementLastOffset(int count){
_lastOffSet -= count;
}
public boolean hasParent(){
return _addressInParent != NO_PARENT;
}
private boolean storeLengthInLink(){
return _addressInParent > 0;
}
public void requestIndexEntry(FieldMetadata fieldMetadata) {
_indexedField = fieldMetadata;
}
public MarshallingBuffer checkBlockAlignment(MarshallingContext context, MarshallingBuffer precedingBuffer, IntByRef precedingLength) {
_lastOffSet = offset();
if(doBlockAlign()){
precedingBuffer.blockAlign(context, precedingLength.value);
}
if(precedingBuffer != null){
precedingLength.value += precedingBuffer.length();
}
precedingBuffer = this;
if(_children != null){
Iterator4 i = new Iterator4Impl(_children);
while(i.moveNext()){
precedingBuffer = ((MarshallingBuffer) i.current()).checkBlockAlignment(context, precedingBuffer, precedingLength);
}
}
return precedingBuffer;
}
private void blockAlign(MarshallingContext context, int precedingLength) {
int totalLength = context.container().blockConverter().blockAlignedBytes(precedingLength + length());
int newLength = totalLength - precedingLength;
blockAlign(newLength);
}
public int marshalledLength() {
int length = length();
if(_children != null){
Iterator4 i = new Iterator4Impl(_children);
while(i.moveNext()){
length += ((MarshallingBuffer) i.current()).marshalledLength();
}
}
return length;
}
private void blockAlign(int length) {
if(_delegate == null){
return;
}
if(length > _delegate.length()){
int sizeNeeded = length - _delegate.offset();
prepareWrite(sizeNeeded);
}
_delegate.seek(length);
}
private boolean doBlockAlign() {
return hasParent(); // For now we block align every linked entry. Indexes could be created late.
}
private int blockedLength(){
return length();
}
private int unblockedLength(){
// This is only valid after checkBlockAlignMent has been called.
return _lastOffSet;
}
}