/* 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.ids;
import com.db4o.*;
import com.db4o.ext.*;
import com.db4o.foundation.*;
import com.db4o.internal.*;
import com.db4o.internal.freespace.*;
import com.db4o.internal.slots.*;
/**
* @exclude
*/
public class InMemoryIdSystem implements StackableIdSystem {
private final LocalObjectContainer _container;
private IdSlotTree _ids;
private Slot _slot;
private final SequentialIdGenerator _idGenerator;
private int _childId;
/**
* for testing purposes only.
*/
public InMemoryIdSystem(LocalObjectContainer container, final int maxValidId){
_container = container;
_idGenerator = new SequentialIdGenerator(new Function4<Integer, Integer>() {
public Integer apply(Integer start) {
return findFreeId(start, maxValidId);
}
}, _container.handlers().lowestValidId(), maxValidId);
}
public InMemoryIdSystem(LocalObjectContainer container){
this(container, Integer.MAX_VALUE);
readThis();
}
private void readThis() {
SystemData systemData = _container.systemData();
_slot = systemData.idSystemSlot();
if(! Slot.isNull(_slot)){
ByteArrayBuffer buffer = _container.readBufferBySlot(_slot);
_childId = buffer.readInt();
_idGenerator.read(buffer);
_ids = (IdSlotTree) new TreeReader(buffer, new IdSlotTree(0, null)).read();
}
}
public void close() {
// do nothing
}
public void commit(Visitable<SlotChange> slotChanges, FreespaceCommitter freespaceCommitter) {
Slot oldSlot = _slot;
Slot reservedSlot = allocateSlot(false, estimatedSlotLength(estimateMappingCount(slotChanges)));
// No more operations against the FreespaceManager.
// Time to free old slots.
freespaceCommitter.commit();
slotChanges.accept(new Visitor4<SlotChange>() {
public void visit(SlotChange slotChange) {
if(! slotChange.slotModified()){
return;
}
if(slotChange.removeId()){
_ids = (IdSlotTree) Tree.removeLike(_ids, new TreeInt(slotChange._key));
return;
}
if(DTrace.enabled){
DTrace.SLOT_COMMITTED.logLength(slotChange._key, slotChange.newSlot());
}
_ids = Tree.add(_ids, new IdSlotTree(slotChange._key, slotChange.newSlot()));
}
});
writeThis(reservedSlot);
freeSlot(oldSlot);
}
private Slot allocateSlot(boolean appendToFile, int slotLength) {
if(! appendToFile){
Slot slot = _container.freespaceManager().allocateSafeSlot(slotLength);
if(slot != null){
return slot;
}
}
return _container.appendBytes(slotLength);
}
private int estimateMappingCount(Visitable<SlotChange> slotChanges) {
final IntByRef count = new IntByRef();
count.value = _ids == null ? 0 :_ids.size();
slotChanges.accept(new Visitor4<SlotChange>() {
public void visit(SlotChange slotChange) {
if(! slotChange.slotModified() || slotChange.removeId()){
return;
}
count.value++;
}
});
return count.value;
}
private void writeThis(Slot reservedSlot) {
// We need a little dance here to keep filling free slots
// with X bytes. The FreespaceManager would do it immediately
// upon the free call, but then our CrashSimulatingTestCase
// fails because we have the Xses in the file before flushing.
Slot xByteSlot = null;
if(Debug4.xbytes){
xByteSlot = _slot;
}
int slotLength = slotLength();
if (reservedSlot.length() >= slotLength){
_slot = reservedSlot;
reservedSlot = null;
} else{
if(Debug4.xbytes){
_container.freespaceManager().slotFreed(reservedSlot);
}
_slot = allocateSlot(true, slotLength);
}
ByteArrayBuffer buffer = new ByteArrayBuffer(_slot.length());
buffer.writeInt(_childId);
_idGenerator.write(buffer);
TreeInt.write(buffer, _ids);
_container.writeBytes(buffer, _slot.address(), 0);
_container.systemData().idSystemSlot(_slot);
Runnable commitHook = _container.commitHook();
_container.syncFiles(commitHook);
freeSlot(reservedSlot);
if(Debug4.xbytes){
if(! Slot.isNull(xByteSlot)){
_container.freespaceManager().slotFreed(xByteSlot);
}
}
}
private void freeSlot(Slot slot) {
if(Slot.isNull(slot)){
return;
}
FreespaceManager freespaceManager = _container.freespaceManager();
if(freespaceManager == null){
return;
}
freespaceManager.freeSafeSlot(slot);
}
private int slotLength() {
return TreeInt.marshalledLength(_ids) + _idGenerator.marshalledLength() + Const4.ID_LENGTH;
}
private int estimatedSlotLength(int estimatedCount) {
IdSlotTree template = _ids;
if(template == null){
template = new IdSlotTree(0, new Slot(0, 0));
}
return template.marshalledLength(estimatedCount) + _idGenerator.marshalledLength() + Const4.ID_LENGTH;
}
public Slot committedSlot(int id) {
IdSlotTree idSlotMapping = (IdSlotTree) Tree.find(_ids, new TreeInt(id));
if(idSlotMapping == null){
throw new InvalidIDException(id);
}
return idSlotMapping.slot();
}
public void completeInterruptedTransaction(int address,
int length) {
// do nothing
}
public int newId() {
int id = _idGenerator.newId();
_ids = Tree.add(_ids, new IdSlotTree(id, Slot.ZERO));
return id;
}
private int findFreeId(final int start, final int end) {
if(_ids == null){
return start;
}
final IntByRef lastId = new IntByRef();
final IntByRef freeId = new IntByRef();
Tree.traverse(_ids, new TreeInt(start), new CancellableVisitor4<TreeInt>() {
public boolean visit(TreeInt node) {
int id = node._key;
if(lastId.value == 0){
if( id > start){
freeId.value = start;
return false;
}
lastId.value = id;
return true;
}
if(id > lastId.value + 1){
freeId.value = lastId.value + 1;
return false;
}
lastId.value = id;
return true;
}
});
if(freeId.value > 0){
return freeId.value;
}
if(lastId.value < end){
return Math.max(start, lastId.value + 1);
}
return 0;
}
public void returnUnusedIds(Visitable<Integer> visitable) {
visitable.accept(new Visitor4<Integer>() {
public void visit(Integer obj) {
_ids = (IdSlotTree) Tree.removeLike(_ids, new TreeInt(obj));
}
});
}
public int childId() {
return _childId;
}
public void childId(int id) {
_childId = id;
}
@Override
public void traverseOwnSlots(Procedure4<Pair<Integer, Slot>> block) {
block.apply(Pair.of(0, _slot));
}
}