StorageImpl.java :  » Database-DBMS » perst » org » garret » perst » impl » Java Open Source

Java Open Source » Database DBMS » perst 
perst » org » garret » perst » impl » StorageImpl.java
package org.garret.perst.impl;

import org.garret.perst.*;
import java.lang.reflect.*;
import java.util.*;
import java.io.*;

public class StorageImpl implements Storage { 
    /**
     * Initialial database index size - increasing it reduce number of inde reallocation but increase
     * initial database size. Should be set before openning connection.
     */
    static final int  dbDefaultInitIndexSize = 1024;

    /**
     * Initial capacity of object hash
     */
    static final int  dbDefaultObjectCacheInitSize = 1319;

    /**
     * Database extension quantum. Memory is allocate by scanning bitmap. If there is no
     * large enough hole, then database is extended by the value of dbDefaultExtensionQuantum 
     * This parameter should not be smaller than dbFirstUserId
     */
    static final long dbDefaultExtensionQuantum = 1024*1024;

    static final long dbDefaultPagePoolLruLimit = 1L << 60;

    static final int  dbDatabaseOidBits = 31;          // up to 2 milliards of objects
    static final int  dbDatabaseOffsetBits = 32;       // up to 4 gigabyte
    static final int  dbLargeDatabaseOffsetBits = 40;  // up to 1 terabyte
    static final int  dbMaxObjectOid = (1 << dbDatabaseOidBits) - 1;

    static final int  dbAllocationQuantumBits = 5;
    static final int  dbAllocationQuantum = 1 << dbAllocationQuantumBits;
    static final int  dbBitmapSegmentBits = Page.pageSizeLog + 3 + dbAllocationQuantumBits;
    static final int  dbBitmapSegmentSize = 1 << dbBitmapSegmentBits;
    static final int  dbBitmapPages = 1 << (dbDatabaseOffsetBits-dbBitmapSegmentBits);
    static final int  dbLargeBitmapPages = 1 << (dbLargeDatabaseOffsetBits-dbBitmapSegmentBits);
    static final int  dbHandlesPerPageBits = Page.pageSizeLog - 3;
    static final int  dbHandlesPerPage = 1 << dbHandlesPerPageBits;
    static final int  dbDirtyPageBitmapSize = 1 << (dbDatabaseOidBits-dbHandlesPerPageBits-3);

    static final int  dbInvalidId   = 0;
    static final int  dbBitmapId    = 1;
    static final int  dbFirstUserId = dbBitmapId + dbBitmapPages;
    
    static final int  dbPageObjectFlag = 1;
    static final int  dbModifiedFlag   = 2;
    static final int  dbFreeHandleFlag = 4;
    static final int  dbFlagsMask      = 7;
    static final int  dbFlagsBits      = 3;

    /**
     * Current version of database format. 0 means that database is not initilized.
     * Used to provide backward compatibility of Perst releases.
     */
    static final byte dbDatabaseFormatVersion = (byte)2;

    final int getBitmapPageId(int i) { 
        return i < dbBitmapPages ? dbBitmapId + i : header.root[1-currIndex].bitmapExtent + i - bitmapExtentBase;
    }

    final long getPos(int oid) { 
        synchronized (objectCache) {
            if (oid == 0 || oid >= currIndexSize) { 
                throw new StorageError(StorageError.INVALID_OID);
            }
            Page pg = pool.getPage(header.root[1-currIndex].index 
                                   + ((long)(oid >>> dbHandlesPerPageBits) << Page.pageSizeLog));
            long pos = Bytes.unpack8(pg.data, (oid & (dbHandlesPerPage-1)) << 3);
            pool.unfix(pg);
            return pos;
        }
    }
    
    final void setPos(int oid, long pos) { 
        synchronized (objectCache) {
            dirtyPagesMap[oid >>> (dbHandlesPerPageBits+5)] 
                |= 1 << ((oid >>> dbHandlesPerPageBits) & 31);
            Page pg = pool.putPage(header.root[1-currIndex].index 
                                   + ((long)(oid >>> dbHandlesPerPageBits) << Page.pageSizeLog));
            Bytes.pack8(pg.data, (oid & (dbHandlesPerPage-1)) << 3, pos);
            pool.unfix(pg);
        }
    }

    final byte[] get(int oid) { 
        long pos = getPos(oid);
        if ((pos & (dbFreeHandleFlag|dbPageObjectFlag)) != 0) { 
            throw new StorageError(StorageError.INVALID_OID);
        }
        return pool.get(pos & ~dbFlagsMask);
    }
    
    final Page getPage(int oid) {  
        long pos = getPos(oid);
        if ((pos & (dbFreeHandleFlag|dbPageObjectFlag)) != dbPageObjectFlag) { 
            throw new StorageError(StorageError.DELETED_OBJECT);
        }
        return pool.getPage(pos & ~dbFlagsMask);
    }

    final Page putPage(int oid) {  
        synchronized (objectCache) {
            long pos = getPos(oid);
            if ((pos & (dbFreeHandleFlag|dbPageObjectFlag)) != dbPageObjectFlag) { 
                throw new StorageError(StorageError.DELETED_OBJECT);
            }
            if ((pos & dbModifiedFlag) == 0) { 
                dirtyPagesMap[oid >>> (dbHandlesPerPageBits+5)] 
                    |= 1 << ((oid >>> dbHandlesPerPageBits) & 31);
                allocate(Page.pageSize, oid);
                cloneBitmap(pos & ~dbFlagsMask, Page.pageSize);
                pos = getPos(oid);
            }
            modified = true;
            return pool.putPage(pos & ~dbFlagsMask);
        }
    }


    int allocatePage() { 
        int oid = allocateId();
        setPos(oid, allocate(Page.pageSize, 0) | dbPageObjectFlag | dbModifiedFlag);
        return oid;
    }

    public/*protected*/ synchronized void deallocateObject(IPersistent obj) 
    {
        synchronized (objectCache) {
            if (obj.getOid() == 0) { 
                return;
            }
            if (useSerializableTransactions) { 
                ThreadTransactionContext ctx = getTransactionContext();
                if (ctx.nested != 0) { // serializable transaction
                    ctx.deleted.add(obj);  
                    return;
                }
            }
            deallocateObject0(obj);
        }
    }

    public void throwObject(IPersistent obj) 
    {
        objectCache.remove(obj.getOid());
    }

    private void deallocateObject0(IPersistent obj)
    {
        int oid = obj.getOid();
        long pos = getPos(oid);
        objectCache.remove(oid);
        int offs = (int)pos & (Page.pageSize-1);
        if ((offs & (dbFreeHandleFlag|dbPageObjectFlag)) != 0) { 
            throw new StorageError(StorageError.DELETED_OBJECT);
        }
        Page pg = pool.getPage(pos - offs);
        offs &= ~dbFlagsMask;
        int size = ObjectHeader.getSize(pg.data, offs);
        pool.unfix(pg);
        freeId(oid);
        CustomAllocator allocator = (customAllocatorMap != null) 
            ? getCustomAllocator(obj.getClass()) : null;
        if (allocator != null) { 
            allocator.free(pos & ~dbFlagsMask, size);
        } else { 
            if ((pos & dbModifiedFlag) != 0) { 
                free(pos & ~dbFlagsMask, size);
            } else { 
                cloneBitmap(pos, size);
            }
        }
        obj.assignOid(null, 0, false);
    }
    

    final void freePage(int oid) {
        long pos = getPos(oid);
        Assert.that((pos & (dbFreeHandleFlag|dbPageObjectFlag)) == dbPageObjectFlag);
        if ((pos & dbModifiedFlag) != 0) { 
            free(pos & ~dbFlagsMask, Page.pageSize);
        } else { 
            cloneBitmap(pos & ~dbFlagsMask, Page.pageSize);
        } 
        freeId(oid);
    }

    int allocateId() {
        synchronized (objectCache) { 
            int oid;
            int curr = 1-currIndex;
            setDirty();
            if ((oid = header.root[curr].freeList) != 0) { 
                header.root[curr].freeList = (int)(getPos(oid) >> dbFlagsBits);
                Assert.that(header.root[curr].freeList >= 0);
                dirtyPagesMap[oid >>> (dbHandlesPerPageBits+5)] 
                    |= 1 << ((oid >>> dbHandlesPerPageBits) & 31);
                return oid;
            }

            if (currIndexSize > dbMaxObjectOid) { 
                throw new StorageError(StorageError.TOO_MUCH_OBJECTS);
            }
            if (currIndexSize >= header.root[curr].indexSize) {
                int oldIndexSize = header.root[curr].indexSize;
                int newIndexSize = oldIndexSize << 1;
                if (newIndexSize < oldIndexSize) { 
                    newIndexSize = Integer.MAX_VALUE & ~(dbHandlesPerPage-1);
                    if (newIndexSize <= oldIndexSize) { 
                        throw new StorageError(StorageError.NOT_ENOUGH_SPACE);
                    }
                }
                long newIndex = allocate(newIndexSize*8L, 0);
                if (currIndexSize >= header.root[curr].indexSize) {
                    long oldIndex = header.root[curr].index;
                    pool.copy(newIndex, oldIndex, currIndexSize*8L);
                    header.root[curr].index = newIndex;
                    header.root[curr].indexSize = newIndexSize;
                    free(oldIndex, oldIndexSize*8L);
                } else { 
                    // index was already reallocated
                    free(newIndex, newIndexSize*8L);
                }
            }
            oid = currIndexSize;
            header.root[curr].indexUsed = ++currIndexSize;
            return oid;
        }
    }
    
    void freeId(int oid)
    {
        synchronized (objectCache) { 
            setPos(oid, ((long)(header.root[1-currIndex].freeList) << dbFlagsBits)
                   | dbFreeHandleFlag);
            header.root[1-currIndex].freeList = oid;
        }
    }
    
    static final int pageBits = Page.pageSize*8;
    static final int inc = Page.pageSize/dbAllocationQuantum/8;

    static final void memset(Page pg, int offs, int pattern, int len) { 
        byte[] arr = pg.data;
        byte pat = (byte)pattern;
        while (--len >= 0) { 
            arr[offs++] = pat;
        }
    }

    final void extend(long size)
    {
        if (size > header.root[1-currIndex].size) { 
            header.root[1-currIndex].size = size;
        }
    }

    public long getUsedSize() { 
        return usedSize;
    }

    public long getDatabaseSize() { 
        return header.root[1-currIndex].size;
    }

    static class Location { 
        long     pos;
        long     size;
        Location next;
    }

    final boolean wasReserved(long pos, long size) 
    {
        for (Location location = reservedChain; location != null; location = location.next) { 
            if ((pos >= location.pos && pos - location.pos < location.size) 
                || (pos <= location.pos && location.pos - pos < size)) 
            {
                return true;
            }
        }
        return false;
    }

    final void reserveLocation(long pos, long size)
    {
        Location location = new Location();
        location.pos = pos;
        location.size = size;
        location.next = reservedChain;
        reservedChain = location;
    }

    final void commitLocation()
    {
        reservedChain = reservedChain.next;
    }


    final void setDirty() 
    {
        modified = true;
        if (!header.dirty) { 
            header.dirty = true;
            Page pg = pool.putPage(0);
            header.pack(pg.data);
            pool.flush();
            pool.unfix(pg);
        }
    }

    protected boolean isDirty() { 
        return header.dirty;
    }

    final Page putBitmapPage(int i) { 
        return putPage(getBitmapPageId(i));
    }

    final Page getBitmapPage(int i) { 
        return getPage(getBitmapPageId(i));
    }


    final long allocate(long size, int oid)
    {
        synchronized (objectCache) {
            setDirty();
            size = (size + dbAllocationQuantum-1) & ~(dbAllocationQuantum-1);
            Assert.that(size != 0);
            allocatedDelta += size;
            if (allocatedDelta > gcThreshold) {
                gc0();
            }
            int  objBitSize = (int)(size >> dbAllocationQuantumBits);
            Assert.that(objBitSize == (size >> dbAllocationQuantumBits));
            long pos;    
            int  holeBitSize = 0;
            int  alignment = (int)size & (Page.pageSize-1);
            int  offs, firstPage, lastPage, i, j;
            int  holeBeforeFreePage  = 0;
            int  freeBitmapPage = 0;
            int  curr = 1 - currIndex;
            Page pg;


            lastPage = header.root[curr].bitmapEnd - dbBitmapId;
            usedSize += size;

            if (alignment == 0) { 
                firstPage = currPBitmapPage;
                offs = (currPBitmapOffs+inc-1) & ~(inc-1);
            } else { 
                firstPage = currRBitmapPage;
                offs = currRBitmapOffs;
            }
        
            while (true) { 
                if (alignment == 0) { 
                    // allocate page object 
                    for (i = firstPage; i < lastPage; i++){
                        int spaceNeeded = objBitSize - holeBitSize < pageBits 
                            ? objBitSize - holeBitSize : pageBits;
                        if (bitmapPageAvailableSpace[i] <= spaceNeeded) {
                            holeBitSize = 0;
                            offs = 0;
                            continue;
                        }
                        pg = getBitmapPage(i);
                        int startOffs = offs;   
                        while (offs < Page.pageSize) { 
                            if (pg.data[offs++] != 0) { 
                                offs = (offs + inc - 1) & ~(inc-1);
                                holeBitSize = 0;
                            } else if ((holeBitSize += 8) == objBitSize) { 
                                pos = (((long)i*Page.pageSize + offs)*8 - holeBitSize) 
                                    << dbAllocationQuantumBits;
                                if (wasReserved(pos, size)) { 
                                    startOffs = offs = (offs + inc - 1) & ~(inc-1);
                                    holeBitSize = 0;
                                    continue;
                                }       
                                reserveLocation(pos, size);
                                currPBitmapPage = i;
                                currPBitmapOffs = offs;
                                extend(pos + size);
                                if (oid != 0) { 
                                    long prev = getPos(oid);
                                    int marker = (int)prev & dbFlagsMask;
                                    pool.copy(pos, prev - marker, size);
                                    setPos(oid, pos | marker | dbModifiedFlag);
                                }
                                pool.unfix(pg);
                                pg = putBitmapPage(i);
                                int holeBytes = holeBitSize >> 3;
                                if (holeBytes > offs) { 
                                    memset(pg, 0, 0xFF, offs);
                                    holeBytes -= offs;
                                    pool.unfix(pg);
                                    pg = putBitmapPage(--i);
                                    offs = Page.pageSize;
                                }
                                while (holeBytes > Page.pageSize) { 
                                    memset(pg, 0, 0xFF, Page.pageSize);
                                    holeBytes -= Page.pageSize;
                                    bitmapPageAvailableSpace[i] = 0;
                                    pool.unfix(pg);
                                    pg = putBitmapPage(--i);
                                }
                                memset(pg, offs-holeBytes, 0xFF, holeBytes);
                                commitLocation();
                                pool.unfix(pg);
                                return pos;
                            }
                        }
                        if (startOffs == 0 && holeBitSize == 0
                            && spaceNeeded < bitmapPageAvailableSpace[i]) 
                        { 
                            bitmapPageAvailableSpace[i] = spaceNeeded;
                        }
                        offs = 0;
                        pool.unfix(pg);
                    }
                } else { 
                    for (i = firstPage; i < lastPage; i++){
                        int spaceNeeded = objBitSize - holeBitSize < pageBits 
                            ? objBitSize - holeBitSize : pageBits;
                        if (bitmapPageAvailableSpace[i] <= spaceNeeded) {
                            holeBitSize = 0;
                            offs = 0;
                            continue;
                        }
                        pg = getBitmapPage(i);
                        int startOffs = offs;
                        while (offs < Page.pageSize) { 
                            int mask = pg.data[offs] & 0xFF; 
                            if (holeBitSize + Bitmap.firstHoleSize[mask] >= objBitSize) { 
                                pos = (((long)i*Page.pageSize + offs)*8 
                                       - holeBitSize) << dbAllocationQuantumBits;
                                if (wasReserved(pos, size)) {                       
                                    startOffs = offs += 1;
                                    holeBitSize = 0;
                                    continue;
                                }       
                                reserveLocation(pos, size);
                                currRBitmapPage = i;
                                currRBitmapOffs = offs;
                                extend(pos + size);
                                if (oid != 0) { 
                                    long prev = getPos(oid);
                                    int marker = (int)prev & dbFlagsMask;
                                    pool.copy(pos, prev - marker, size);
                                    setPos(oid, pos | marker | dbModifiedFlag);
                                }
                                pool.unfix(pg);
                                pg = putBitmapPage(i);
                                pg.data[offs] |= (byte)((1 << (objBitSize - holeBitSize)) - 1); 
                                if (holeBitSize != 0) { 
                                    if (holeBitSize > offs*8) { 
                                        memset(pg, 0, 0xFF, offs);
                                        holeBitSize -= offs*8;
                                        pool.unfix(pg);
                                        pg = putBitmapPage(--i);
                                        offs = Page.pageSize;
                                    }
                                    while (holeBitSize > pageBits) { 
                                        memset(pg, 0, 0xFF, Page.pageSize);
                                        holeBitSize -= pageBits;
                                        bitmapPageAvailableSpace[i] = 0;
                                        pool.unfix(pg);
                                        pg = putBitmapPage(--i);
                                    }
                                    while ((holeBitSize -= 8) > 0) { 
                                        pg.data[--offs] = (byte)0xFF; 
                                    }
                                    pg.data[offs-1] |= (byte)~((1 << -holeBitSize) - 1);
                                }
                                pool.unfix(pg);
                                commitLocation();
                                return pos;
                            } else if (Bitmap.maxHoleSize[mask] >= objBitSize) { 
                                int holeBitOffset = Bitmap.maxHoleOffset[mask];
                                pos = (((long)i*Page.pageSize + offs)*8 + holeBitOffset) << dbAllocationQuantumBits;
                                if (wasReserved(pos, size)) { 
                                    startOffs = offs += 1;
                                    holeBitSize = 0;
                                    continue;
                                }       
                                reserveLocation(pos, size);
                                currRBitmapPage = i;
                                currRBitmapOffs = offs;
                                extend(pos + size);
                                if (oid != 0) { 
                                    long prev = getPos(oid);
                                    int marker = (int)prev & dbFlagsMask;
                                    pool.copy(pos, prev - marker, size);
                                    setPos(oid, pos | marker | dbModifiedFlag);
                                }
                                pool.unfix(pg);
                                pg = putBitmapPage(i);
                                pg.data[offs] |= (byte)(((1<<objBitSize) - 1) << holeBitOffset);
                                pool.unfix(pg);
                                commitLocation();
                                return pos;
                            }
                            offs += 1;
                            if (Bitmap.lastHoleSize[mask] == 8) { 
                                holeBitSize += 8;
                            } else { 
                                holeBitSize = Bitmap.lastHoleSize[mask];
                            }
                        }
                        if (startOffs == 0 && holeBitSize == 0
                            && spaceNeeded < bitmapPageAvailableSpace[i]) 
                        {
                            bitmapPageAvailableSpace[i] = spaceNeeded;
                        }
                        offs = 0;
                        pool.unfix(pg);
                    }
                }
                if (firstPage == 0) { 
                    if (freeBitmapPage > i) { 
                        i = freeBitmapPage;
                        holeBitSize = holeBeforeFreePage;
                    }
                    objBitSize -= holeBitSize;
                    // number of bits reserved for the object and aligned on page boundary
                    int skip = (objBitSize + Page.pageSize/dbAllocationQuantum - 1) 
                        & ~(Page.pageSize/dbAllocationQuantum - 1);
                    // page aligned position after allocated object
                    pos = ((long)i << dbBitmapSegmentBits) + ((long)skip << dbAllocationQuantumBits);

                    long extension = (size > extensionQuantum) ? size : extensionQuantum;
                    int oldIndexSize = 0;
                    long oldIndex = 0;
                    int morePages = (int)((extension + Page.pageSize*(dbAllocationQuantum*8-1) - 1)
                                          / (Page.pageSize*(dbAllocationQuantum*8-1)));
                    if (i + morePages > dbLargeBitmapPages) { 
                        throw new StorageError(StorageError.NOT_ENOUGH_SPACE);
                    }
                    if (i <= dbBitmapPages && i + morePages > dbBitmapPages) {   
                        // We are out of space mapped by memory default allocation bitmap
                        oldIndexSize = header.root[curr].indexSize;
                        if (oldIndexSize <= currIndexSize + dbLargeBitmapPages - dbBitmapPages) {
                            int newIndexSize = oldIndexSize;
                            oldIndex = header.root[curr].index;
                            do { 
                                newIndexSize <<= 1;                    
                                if (newIndexSize < 0) { 
                                    newIndexSize = Integer.MAX_VALUE & ~(dbHandlesPerPage-1);
                                    if (newIndexSize < currIndexSize + dbLargeBitmapPages - dbBitmapPages) {
                                        throw new StorageError(StorageError.NOT_ENOUGH_SPACE);
                                    }
                                    break;
                                }
                            } while (newIndexSize <= currIndexSize + dbLargeBitmapPages - dbBitmapPages);

                            if (size + newIndexSize*8L > extensionQuantum) { 
                                extension = size + newIndexSize*8L;
                                morePages = (int)((extension + Page.pageSize*(dbAllocationQuantum*8-1) - 1)
                                                  / (Page.pageSize*(dbAllocationQuantum*8-1)));
                            }
                            extend(pos + (long)morePages*Page.pageSize + newIndexSize*8L);
                            long newIndex = pos + (long)morePages*Page.pageSize;                        
                            fillBitmap(pos + (skip>>3) + (long)morePages * (Page.pageSize/dbAllocationQuantum/8),
                                       newIndexSize >>> dbAllocationQuantumBits);
                            pool.copy(newIndex, oldIndex, oldIndexSize*8L);
                            header.root[curr].index = newIndex;
                            header.root[curr].indexSize = newIndexSize;
                        }
                        int[] newBitmapPageAvailableSpace = new int[dbLargeBitmapPages];
                        System.arraycopy(bitmapPageAvailableSpace, 0, newBitmapPageAvailableSpace, 0, dbBitmapPages);
                        for (j = dbBitmapPages; j < dbLargeBitmapPages; j++) { 
                            newBitmapPageAvailableSpace[j] = Integer.MAX_VALUE;
                        }
                        bitmapPageAvailableSpace = newBitmapPageAvailableSpace;
                        
                        for (j = 0; j < dbLargeBitmapPages - dbBitmapPages; j++) { 
                            setPos(currIndexSize + j, dbFreeHandleFlag);
                        }

                        header.root[curr].bitmapExtent = currIndexSize;
                        header.root[curr].indexUsed = currIndexSize += dbLargeBitmapPages - dbBitmapPages;
                    }
                    extend(pos + (long)morePages*Page.pageSize);
                    long adr = pos;
                    int len = objBitSize >> 3;
                    // fill bitmap pages used for allocation of object space with 0xFF 
                    while (len >= Page.pageSize) { 
                        pg = pool.putPage(adr);
                        memset(pg, 0, 0xFF, Page.pageSize);
                        pool.unfix(pg);
                        adr += Page.pageSize;
                        len -= Page.pageSize;
                    }
                    // fill part of last page responsible for allocation of object space
                    pg = pool.putPage(adr);
                    memset(pg, 0, 0xFF, len);
                    pg.data[len] = (byte)((1 << (objBitSize&7))-1);
                    pool.unfix(pg);

                    // mark in bitmap newly allocated object
                    fillBitmap(pos + (skip>>3), morePages * (Page.pageSize/dbAllocationQuantum/8));
                    
                    j = i;
                    while (--morePages >= 0) { 
                        setPos(getBitmapPageId(j++), pos | dbPageObjectFlag | dbModifiedFlag);
                        pos += Page.pageSize;
                    }
                    header.root[curr].bitmapEnd = j + dbBitmapId;
                    j = i + objBitSize / pageBits; 
                    if (alignment != 0) { 
                        currRBitmapPage = j;
                        currRBitmapOffs = 0;
                    } else { 
                        currPBitmapPage = j;
                        currPBitmapOffs = 0;
                    }
                    while (j > i) { 
                        bitmapPageAvailableSpace[--j] = 0;
                    }
                
                    pos = ((long)i*Page.pageSize*8 - holeBitSize)  << dbAllocationQuantumBits;
                    if (oid != 0) { 
                        long prev = getPos(oid);
                        int marker = (int)prev & dbFlagsMask;
                        pool.copy(pos, prev - marker, size);
                        setPos(oid, pos | marker | dbModifiedFlag);
                    }
                
                    if (holeBitSize != 0) { 
                        reserveLocation(pos, size);
                        while (holeBitSize > pageBits) { 
                            holeBitSize -= pageBits;
                            pg = putBitmapPage(--i);
                            memset(pg, 0, 0xFF, Page.pageSize);
                            bitmapPageAvailableSpace[i] = 0;
                            pool.unfix(pg);
                        }
                        pg = putBitmapPage(--i);
                        offs = Page.pageSize;
                        while ((holeBitSize -= 8) > 0) { 
                            pg.data[--offs] = (byte)0xFF; 
                        }
                        pg.data[offs-1] |= (byte)~((1 << -holeBitSize) - 1);
                        pool.unfix(pg);
                        commitLocation();
                    }
                    if (oldIndex != 0) { 
                        free(oldIndex, oldIndexSize*8L);
                    }
                    return pos;
                } 
                if (gcThreshold != Long.MAX_VALUE && !gcDone && !gcActive) {
                    allocatedDelta -= size;
                    usedSize -= size;
                    gc0();
                    currRBitmapPage = currPBitmapPage = 0;
                    currRBitmapOffs = currPBitmapOffs = 0;                
                    return allocate(size, oid);
                }
                freeBitmapPage = i;
                holeBeforeFreePage = holeBitSize;
                holeBitSize = 0;
                lastPage = firstPage + 1;
                firstPage = 0;
                offs = 0;
            }
        }
    } 


    final void fillBitmap(long adr, int len) { 
        while (true) { 
            int off = (int)adr & (Page.pageSize-1);
            Page pg = pool.putPage(adr - off);
            if (Page.pageSize - off >= len) { 
                memset(pg, off, 0xFF, len);
                pool.unfix(pg);
                break;
            } else { 
                memset(pg, off, 0xFF, Page.pageSize - off);
                pool.unfix(pg);
                adr += Page.pageSize - off;
                len -= Page.pageSize - off;
            }
        }
    }

    final void free(long pos, long size)
    {
        synchronized (objectCache) {
            Assert.that(pos != 0 && (pos & (dbAllocationQuantum-1)) == 0);
            long quantNo = pos >>> dbAllocationQuantumBits;
            int  objBitSize = (int)((size+dbAllocationQuantum-1) >>> dbAllocationQuantumBits);
            int  pageId = (int)(quantNo >>> (Page.pageSizeLog+3));
            int  offs = (int)(quantNo & (Page.pageSize*8-1)) >> 3;
            Page pg = putBitmapPage(pageId);
            int  bitOffs = (int)quantNo & 7;

            allocatedDelta -= (long)objBitSize << dbAllocationQuantumBits;
            usedSize -= (long)objBitSize << dbAllocationQuantumBits;

            if ((pos & (Page.pageSize-1)) == 0 && size >= Page.pageSize) { 
                if (pageId == currPBitmapPage && offs < currPBitmapOffs) { 
                    currPBitmapOffs = offs;
                }
            }
            if (pageId == currRBitmapPage && offs < currRBitmapOffs) { 
                currRBitmapOffs = offs;
            }
            bitmapPageAvailableSpace[pageId] = Integer.MAX_VALUE;
        
            if (objBitSize > 8 - bitOffs) { 
                objBitSize -= 8 - bitOffs;
                pg.data[offs++] &= (1 << bitOffs) - 1;
                while (objBitSize + offs*8 > Page.pageSize*8) { 
                    memset(pg, offs, 0, Page.pageSize - offs);
                    pool.unfix(pg);
                    pg = putBitmapPage(++pageId);
                    bitmapPageAvailableSpace[pageId] = Integer.MAX_VALUE;
                    objBitSize -= (Page.pageSize - offs)*8;
                    offs = 0;
                }
                while ((objBitSize -= 8) > 0) { 
                    pg.data[offs++] = (byte)0;
                }
                pg.data[offs] &= (byte)~((1 << (objBitSize + 8)) - 1);
            } else { 
                pg.data[offs] &= (byte)~(((1 << objBitSize) - 1) << bitOffs); 
            }
            pool.unfix(pg);
        }
    }

    static class CloneNode {
        long      pos;
        CloneNode next;

        CloneNode(long pos, CloneNode list) { 
            this.pos = pos;
            this.next = list;
        }
    }

    final void cloneBitmap(long pos, long size)
    {
        synchronized (objectCache) {
            if (insideCloneBitmap) { 
                Assert.that(size == Page.pageSize);
                cloneList = new CloneNode(pos, cloneList);
            } else { 
                insideCloneBitmap = true;
                while (true) { 
                    long quantNo = pos >>> dbAllocationQuantumBits;
                    int  objBitSize = (int)((size+dbAllocationQuantum-1) >>> dbAllocationQuantumBits);
                    int  pageId = (int)(quantNo >>> (Page.pageSizeLog + 3));
                    int  offs = (int)(quantNo & (Page.pageSize*8-1)) >> 3;
                    int  bitOffs = (int)quantNo & 7;
                    int  oid = getBitmapPageId(pageId);
                    pos = getPos(oid);
                    if ((pos & dbModifiedFlag) == 0) { 
                        dirtyPagesMap[oid >>> (dbHandlesPerPageBits+5)] 
                            |= 1 << ((oid >>> dbHandlesPerPageBits) & 31);
                        allocate(Page.pageSize, oid);
                        cloneBitmap(pos & ~dbFlagsMask, Page.pageSize);
                    }
                    
                    if (objBitSize > 8 - bitOffs) { 
                        objBitSize -= 8 - bitOffs;
                        offs += 1;
                        while (objBitSize + offs*8 > Page.pageSize*8) { 
                            oid = getBitmapPageId(++pageId);
                            pos = getPos(oid);
                            if ((pos & dbModifiedFlag) == 0) { 
                                dirtyPagesMap[oid >>> (dbHandlesPerPageBits+5)] 
                                    |= 1 << ((oid >>> dbHandlesPerPageBits) & 31);
                                allocate(Page.pageSize, oid);
                                cloneBitmap(pos & ~dbFlagsMask, Page.pageSize);
                            }
                            objBitSize -= (Page.pageSize - offs)*8;
                            offs = 0;
                        }
                    }
                    if (cloneList == null) { 
                        break;
                    }
                    pos = cloneList.pos;
                    size = Page.pageSize;
                    cloneList = cloneList.next;
                }
                insideCloneBitmap = false;
            }
        }
    }

    public void open(String filePath) {
        open(filePath, DEFAULT_PAGE_POOL_SIZE);
    }

    public void open(IFile file) {
        open(file, DEFAULT_PAGE_POOL_SIZE);
    }

    public synchronized void open(String filePath, int pagePoolSize) {
        IFile file = filePath.startsWith("@") 
            ? (IFile)new MultiFile(filePath.substring(1), readOnly, noFlush)
            : (IFile)new OSFile(filePath, readOnly, noFlush);      
        try {
            open(file, pagePoolSize);
        } catch (StorageError ex) {
            file.close();            
            throw ex;
        }
    }

    public synchronized void open(String filePath, int pagePoolSize, String cryptKey) {
        Rc4File file = new Rc4File(filePath, readOnly, noFlush, cryptKey);      
        try {
            open(file, pagePoolSize);
        } catch (StorageError ex) {
            file.close();            
            throw ex;
        }
    }

        
    public void clearObjectCache() { 
        objectCache.clear();
    }

     protected OidHashTable createObjectCache(String kind, int pagePoolSize, int objectCacheSize) 
    { 
        if (pagePoolSize == INFINITE_PAGE_POOL || "strong".equals(kind)) {
            return new StrongHashTable(objectCacheSize);
        }
        if ("soft".equals(kind)) { 
            return new SoftHashTable(objectCacheSize);
        }
        if ("weak".equals(kind)) { 
            return new WeakHashTable(objectCacheSize);
        }
        if ("pinned".equals(kind)) { 
            return new PinWeakHashTable(objectCacheSize);
        }
        return new LruObjectCache(objectCacheSize);
    }
        

    protected void initialize(IFile file, int pagePoolSize) { 
        this.file = file;
        if (lockFile && !multiclientSupport) { 
            if (!file.tryLock(false)) { 
                throw new StorageError(StorageError.STORAGE_IS_USED);
            }
        }
        dirtyPagesMap = new int[dbDirtyPageBitmapSize/4+1];
        gcThreshold = Long.MAX_VALUE;
        backgroundGcMonitor = new Object();
        backgroundGcStartMonitor = new Object();
        gcThread = null;
        gcActive = false;
        gcDone = false;
        allocatedDelta = 0;

        reservedChain = null;
        cloneList = null;
        insideCloneBitmap = false;

        nNestedTransactions = 0;
        nBlockedTransactions = 0;
        nCommittedTransactions = 0;
        scheduledCommitTime = Long.MAX_VALUE;
        transactionMonitor = new Object();
        transactionLock = new PersistentResource();

        modified = false; 

        objectCache = createObjectCache(cacheKind, pagePoolSize, objectCacheInitSize);

        classDescMap = new HashMap();
        descList = null;
        
        header = new Header();
        pool = new PagePool(pagePoolSize/Page.pageSize, pagePoolLruLimit);
        pool.open(file);
    }        

    public synchronized void open(IFile file, int pagePoolSize) {
        Page pg;
        int i;

        if (opened) {
            throw new StorageError(StorageError.STORAGE_ALREADY_OPENED);
        }
        initialize(file, pagePoolSize);

        if (multiclientSupport) { 
            beginThreadTransaction(READ_WRITE_TRANSACTION);
        }            
        byte[] buf = new byte[Header.sizeof];
        int rc = file.read(0, buf);
        if (rc > 0 && rc < Header.sizeof) { 
            throw new StorageError(StorageError.DATABASE_CORRUPTED);
        }
        header.unpack(buf);
        if (header.curr < 0 || header.curr > 1) { 
            throw new StorageError(StorageError.DATABASE_CORRUPTED);
        }
        transactionId = header.transactionId;
        if (header.databaseFormatVersion == 0) { // database not initialized
            int indexSize = initIndexSize;
            if (indexSize < dbFirstUserId) { 
                indexSize = dbFirstUserId;
            }
            indexSize = (indexSize + dbHandlesPerPage - 1) & ~(dbHandlesPerPage-1);

            bitmapExtentBase = dbBitmapPages;

            header.curr = currIndex = 0;
            long used = Page.pageSize;
            header.root[0].index = used;
            header.root[0].indexSize = indexSize;
            header.root[0].indexUsed = dbFirstUserId;
            header.root[0].freeList = 0;
            used += indexSize*8L;
            header.root[1].index = used;
            header.root[1].indexSize = indexSize;
            header.root[1].indexUsed = dbFirstUserId;
            header.root[1].freeList = 0;
            used += indexSize*8L;
        
            header.root[0].shadowIndex = header.root[1].index;
            header.root[1].shadowIndex = header.root[0].index;
            header.root[0].shadowIndexSize = indexSize;
            header.root[1].shadowIndexSize = indexSize;
            
            int bitmapPages = 
                (int)((used + Page.pageSize*(dbAllocationQuantum*8-1) - 1)
                      / (Page.pageSize*(dbAllocationQuantum*8-1)));
            long bitmapSize = (long)bitmapPages*Page.pageSize;
            int usedBitmapSize = (int)((used + bitmapSize) >>> (dbAllocationQuantumBits + 3));

            for (i = 0; i < bitmapPages; i++) { 
                pg = pool.putPage(used + (long)i*Page.pageSize);
                byte[] bitmap = pg.data;
                int n = usedBitmapSize > Page.pageSize ? Page.pageSize : usedBitmapSize;
                for (int j = 0; j < n; j++) { 
                    bitmap[j] = (byte)0xFF;
                }
                usedBitmapSize -= Page.pageSize;
                pool.unfix(pg);
            }
            int bitmapIndexSize = 
                ((dbBitmapId + dbBitmapPages)*8 + Page.pageSize - 1)
                & ~(Page.pageSize - 1);
            byte[] index = new byte[bitmapIndexSize];
            Bytes.pack8(index, dbInvalidId*8, dbFreeHandleFlag);
            for (i = 0; i < bitmapPages; i++) { 
                Bytes.pack8(index, (dbBitmapId+i)*8, used | dbPageObjectFlag);
                used += Page.pageSize;
            }
            header.root[0].bitmapEnd = dbBitmapId + i;
            header.root[1].bitmapEnd = dbBitmapId + i;
            while (i < dbBitmapPages) { 
                Bytes.pack8(index, (dbBitmapId+i)*8, dbFreeHandleFlag);
                i += 1;
            }
            header.root[0].size = used;
            header.root[1].size = used;
            usedSize = used;
            committedIndexSize = currIndexSize = dbFirstUserId;

            pool.write(header.root[1].index, index);
            pool.write(header.root[0].index, index);

            header.dirty = true;
            header.root[0].size = header.root[1].size;
            pg = pool.putPage(0);
            header.pack(pg.data);
            pool.flush();
            pool.modify(pg);
            header.databaseFormatVersion = dbDatabaseFormatVersion;
            header.pack(pg.data);
            pool.unfix(pg);
            pool.flush();
        } else {
            int curr = header.curr;
            currIndex = curr;
            if (header.root[curr].indexSize != header.root[curr].shadowIndexSize) {
                throw new StorageError(StorageError.DATABASE_CORRUPTED);
            }           
            bitmapExtentBase = (header.databaseFormatVersion < 2) ? 0 : dbBitmapPages;

            if (isDirty()) { 
                if (listener != null) {
                    listener.databaseCorrupted();
                }
                System.err.println("Database was not normally closed: start recovery");
                header.root[1-curr].size = header.root[curr].size;
                header.root[1-curr].indexUsed = header.root[curr].indexUsed; 
                header.root[1-curr].freeList = header.root[curr].freeList; 
                header.root[1-curr].index = header.root[curr].shadowIndex;
                header.root[1-curr].indexSize = header.root[curr].shadowIndexSize;
                header.root[1-curr].shadowIndex = header.root[curr].index;
                header.root[1-curr].shadowIndexSize = header.root[curr].indexSize;
                header.root[1-curr].bitmapEnd = header.root[curr].bitmapEnd;
                header.root[1-curr].rootObject = header.root[curr].rootObject;
                header.root[1-curr].classDescList = header.root[curr].classDescList;
                header.root[1-curr].bitmapExtent = header.root[curr].bitmapExtent;

                pg = pool.putPage(0);
                header.pack(pg.data);
                pool.unfix(pg);

                pool.copy(header.root[1-curr].index, header.root[curr].index, 
                          (header.root[curr].indexUsed*8L + Page.pageSize - 1) & ~(Page.pageSize-1));
                if (listener != null) {
                    listener.recoveryCompleted();
                }
                System.err.println("Recovery completed");
            } 
            currIndexSize = header.root[1-curr].indexUsed;
            committedIndexSize = currIndexSize;
            usedSize = header.root[curr].size;
        }
        int bitmapSize = header.root[1-currIndex].bitmapExtent == 0 ? dbBitmapPages : dbLargeBitmapPages;
        bitmapPageAvailableSpace = new int[bitmapSize];
        for (i = 0; i < bitmapPageAvailableSpace.length; i++) { 
            bitmapPageAvailableSpace[i] = Integer.MAX_VALUE;
        }        
        currRBitmapPage = currPBitmapPage = 0;
        currRBitmapOffs = currPBitmapOffs = 0;

        opened = true;
        reloadScheme();

        if (multiclientSupport) { 
            endThreadTransaction();
        }            
    }

    public boolean isOpened() { 
        return opened;
    }

    static void checkIfFinal(ClassDescriptor desc) {
        Class cls = desc.cls;
        if (cls != null) { 
            for (ClassDescriptor next = desc.next; next != null; next = next.next) { 
                next.load();
                if (cls.isAssignableFrom(next.cls)) { 
                    desc.hasSubclasses = true;
                } else if (next.cls.isAssignableFrom(cls)) { 
                    next.hasSubclasses = true;
                }
            }
        }
    }
        


    void reloadScheme() {
        classDescMap.clear();
        customAllocatorMap = null;
        customAllocatorList = null;
        defaultAllocator = new DefaultAllocator(this);
        int descListOid = header.root[1-currIndex].classDescList;
        classDescMap.put(ClassDescriptor.class, 
                         new ClassDescriptor(this, ClassDescriptor.class));
        classDescMap.put(ClassDescriptor.FieldDescriptor.class, 
                         new ClassDescriptor(this, ClassDescriptor.FieldDescriptor.class));
        if (descListOid != 0) {             
            ClassDescriptor desc;
            descList = findClassDescriptor(descListOid);
            for (desc = descList; desc != null; desc = desc.next) { 
                desc.load();
            }
            for (desc = descList; desc != null; desc = desc.next) { 
                if (findClassDescriptor(desc.cls) == desc) { 
                    desc.resolve();
                }
                if (desc.allocator != null) {
                    if (customAllocatorMap == null) { 
                        customAllocatorMap = new HashMap();
                        customAllocatorList = new ArrayList();
                    }
                    desc.allocator.load();
                    customAllocatorMap.put(desc.cls, desc.allocator);
                    customAllocatorList.add(desc.allocator);
                }
                checkIfFinal(desc);
            }
        } else { 
            descList = null;
        }
    }

    final void assignOid(IPersistent obj, int oid) { 
        obj.assignOid(this, oid, false);
    }

    final void registerClassDescriptor(ClassDescriptor desc) { 
        classDescMap.put(desc.cls, desc);
        desc.next = descList;
        descList = desc;
        checkIfFinal(desc);
        storeObject0(desc, false);
        header.root[1-currIndex].classDescList = desc.getOid();
        modified = true;
    }        

    final ClassDescriptor findClassDescriptor(Class cls) { 
        return (ClassDescriptor)classDescMap.get(cls);
    }

    final ClassDescriptor getClassDescriptor(Class cls) { 
        ClassDescriptor desc = findClassDescriptor(cls);
        if (desc == null) { 
            desc = new ClassDescriptor(this, cls);
            registerClassDescriptor(desc);
        }
        return desc;
    }


    public synchronized IPersistent getRoot() {
        if (!opened) {
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        int rootOid = header.root[1-currIndex].rootObject;
        return (rootOid == 0) ? null : lookupObject(rootOid, null);
    }
    
    public synchronized void setRoot(IPersistent root) {
        if (!opened) {
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        if (root == null) { 
            header.root[1-currIndex].rootObject = 0;
        } else { 
            if (!root.isPersistent()) { 
                storeObject0(root, false);
            }
            header.root[1-currIndex].rootObject = root.getOid();
        }
        modified = true;
    }
 
    public void commit() {
        synchronized (backgroundGcMonitor) { 
            synchronized (this) { 
                if (!opened) {
                    throw new StorageError(StorageError.STORAGE_NOT_OPENED);
                }
                objectCache.flush();
                if (customAllocatorList != null) { 
                    Iterator iterator = customAllocatorList.iterator();
                    while (iterator.hasNext()) {
                        CustomAllocator alloc = (CustomAllocator)iterator.next();
                        if (alloc.isModified()) {                             
                            alloc.store();
                        }
                        alloc.commit();
                    }
                }
                if (!modified) { 
                    return;
                }
                commit0();
                modified = false;
            }
        }
    }

    private final void commit0() 
    {
        int i, j, n;
        int curr = currIndex;
        int[] map = dirtyPagesMap;
        int oldIndexSize = header.root[curr].indexSize;
        int newIndexSize = header.root[1-curr].indexSize;
        int nPages = committedIndexSize >>> dbHandlesPerPageBits;
        Page pg;

        if (newIndexSize > oldIndexSize) { 
            cloneBitmap(header.root[curr].index, oldIndexSize*8L);
            long newIndex;
            while (true) { 
                newIndex = allocate(newIndexSize*8L, 0);
                if (newIndexSize == header.root[1-curr].indexSize) { 
                    break;
                }
                free(newIndex, newIndexSize*8L);
                newIndexSize = header.root[1-curr].indexSize;
            }
            header.root[1-curr].shadowIndex = newIndex;
            header.root[1-curr].shadowIndexSize = newIndexSize;
            free(header.root[curr].index, oldIndexSize*8L);
        }
        long currSize = header.root[1-curr].size;
        for (i = 0; i < nPages; i++) { 
            if ((map[i >> 5] & (1 << (i & 31))) != 0) { 
                Page srcIndex = pool.getPage(header.root[1-curr].index + (long)i*Page.pageSize);
                Page dstIndex = pool.getPage(header.root[curr].index + (long)i*Page.pageSize);
                for (j = 0; j < Page.pageSize; j += 8) {
                    long pos = Bytes.unpack8(dstIndex.data, j);
                    if (Bytes.unpack8(srcIndex.data, j) != pos && pos < currSize) { 
                        if ((pos & dbFreeHandleFlag) == 0) {
                            if ((pos & dbPageObjectFlag) != 0) {  
                                free(pos & ~dbFlagsMask, Page.pageSize);
                            } else if (pos != 0) { 
                                int offs = (int)pos & (Page.pageSize-1);
                                pg = pool.getPage(pos-offs);
                                free(pos, ObjectHeader.getSize(pg.data, offs));
                                pool.unfix(pg);
                            }
                        }
                    }
                }
                pool.unfix(srcIndex);
                pool.unfix(dstIndex);
            }
        }
        n = committedIndexSize & (dbHandlesPerPage-1);
        if (n != 0 && (map[i >> 5] & (1 << (i & 31))) != 0) { 
            Page srcIndex = pool.getPage(header.root[1-curr].index + (long)i*Page.pageSize);
            Page dstIndex = pool.getPage(header.root[curr].index + (long)i*Page.pageSize);
            j = 0;
            do { 
                long pos = Bytes.unpack8(dstIndex.data, j);
                if (Bytes.unpack8(srcIndex.data, j) != pos && pos < currSize) { 
                    if ((pos & dbFreeHandleFlag) == 0) {
                        if ((pos & dbPageObjectFlag) != 0) { 
                            free(pos & ~dbFlagsMask, Page.pageSize);
                        } else if (pos != 0) { 
                            int offs = (int)pos & (Page.pageSize-1);
                            pg = pool.getPage(pos - offs);
                            free(pos, ObjectHeader.getSize(pg.data, offs));
                            pool.unfix(pg);
                        }
                    }
                }
                j += 8;
            } while (--n != 0);
            pool.unfix(srcIndex);
            pool.unfix(dstIndex);
        }
        for (i = 0; i <= nPages; i++) { 
            if ((map[i >> 5] & (1 << (i & 31))) != 0) { 
                pg = pool.putPage(header.root[1-curr].index + (long)i*Page.pageSize);
                for (j = 0; j < Page.pageSize; j += 8) {
                    Bytes.pack8(pg.data, j, Bytes.unpack8(pg.data, j) & ~dbModifiedFlag);
                }
                pool.unfix(pg);
            }
        }
        if (currIndexSize > committedIndexSize) { 
            long page = (header.root[1-curr].index 
                         + committedIndexSize*8L) & ~(Page.pageSize-1);
            long end = (header.root[1-curr].index + Page.pageSize - 1
                        + currIndexSize*8L) & ~(Page.pageSize-1);
            while (page < end) { 
                pg = pool.putPage(page);
                for (j = 0; j < Page.pageSize; j += 8) {
                    Bytes.pack8(pg.data, j, Bytes.unpack8(pg.data, j) & ~dbModifiedFlag);
                }
                pool.unfix(pg);
                page += Page.pageSize;
            }
        }
        header.root[1-curr].usedSize = usedSize;
        pg = pool.putPage(0);
        header.pack(pg.data);
        pool.flush();
        pool.modify(pg);
        Assert.that(header.transactionId == transactionId);
        header.transactionId = ++transactionId;
        header.curr = curr ^= 1;
        header.dirty = true;
        header.pack(pg.data);
        pool.unfix(pg);
        pool.flush();
        header.root[1-curr].size = header.root[curr].size;
        header.root[1-curr].indexUsed = currIndexSize; 
        header.root[1-curr].freeList  = header.root[curr].freeList; 
        header.root[1-curr].bitmapEnd = header.root[curr].bitmapEnd; 
        header.root[1-curr].rootObject = header.root[curr].rootObject; 
        header.root[1-curr].classDescList = header.root[curr].classDescList; 
        header.root[1-curr].bitmapExtent = header.root[curr].bitmapExtent; 
        if (currIndexSize == 0 || newIndexSize != oldIndexSize) {
            header.root[1-curr].index = header.root[curr].shadowIndex;
            header.root[1-curr].indexSize = header.root[curr].shadowIndexSize;
            header.root[1-curr].shadowIndex = header.root[curr].index;
            header.root[1-curr].shadowIndexSize = header.root[curr].indexSize;
            pool.copy(header.root[1-curr].index, header.root[curr].index,
                      currIndexSize*8L);
            i = (currIndexSize+dbHandlesPerPage*32-1) >>> (dbHandlesPerPageBits+5);
            while (--i >= 0) { 
                map[i] = 0;
            }
        } else { 
            for (i = 0; i < nPages; i++) { 
                if ((map[i >> 5] & (1 << (i & 31))) != 0) { 
                    map[i >> 5] -= (1 << (i & 31));
                    pool.copy(header.root[1-curr].index + (long)i*Page.pageSize,
                              header.root[curr].index + (long)i*Page.pageSize,
                              Page.pageSize);
                }
            }
            if (currIndexSize > i*dbHandlesPerPage &&
                ((map[i >> 5] & (1 << (i & 31))) != 0
                 || currIndexSize != committedIndexSize))
            {
                pool.copy(header.root[1-curr].index + (long)i*Page.pageSize,
                          header.root[curr].index + (long)i*Page.pageSize,
                          8L*currIndexSize - (long)i*Page.pageSize);
                j = i>>>5;
                n = (currIndexSize + dbHandlesPerPage*32 - 1) >>> (dbHandlesPerPageBits+5); 
                while (j < n) { 
                    map[j++] = 0;
                }
            }
        }
        gcDone = false;
        currIndex = curr;
        committedIndexSize = currIndexSize;
        if (multiclientSupport) { 
            pool.flush();
            pg = pool.putPage(0);
            header.dirty = false;
            header.pack(pg.data);
            pool.unfix(pg);
            pool.flush();
        }
    }

    public synchronized void rollback() {
        if (!opened) {
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        objectCache.invalidate();
        if (!modified) { 
            return;
        }
        rollback0();
        modified = false;
    }

    private final void rollback0() {
        int curr = currIndex;
        int[] map = dirtyPagesMap;
        if (header.root[1-curr].index != header.root[curr].shadowIndex) { 
            pool.copy(header.root[curr].shadowIndex, header.root[curr].index, 8L*committedIndexSize);
        } else { 
            int nPages = (committedIndexSize + dbHandlesPerPage - 1) >>> dbHandlesPerPageBits;
            for (int i = 0; i < nPages; i++) { 
                if ((map[i >> 5] & (1 << (i & 31))) != 0) { 
                    pool.copy(header.root[curr].shadowIndex + (long)i*Page.pageSize,
                              header.root[curr].index + (long)i*Page.pageSize,
                              Page.pageSize);
                }
            }
        }
        for (int j = (currIndexSize+dbHandlesPerPage*32-1) >>> (dbHandlesPerPageBits+5);
             --j >= 0;
             map[j] = 0);
        header.root[1-curr].index = header.root[curr].shadowIndex;
        header.root[1-curr].indexSize = header.root[curr].shadowIndexSize;
        header.root[1-curr].indexUsed = committedIndexSize;
        header.root[1-curr].freeList  = header.root[curr].freeList; 
        header.root[1-curr].bitmapEnd = header.root[curr].bitmapEnd; 
        header.root[1-curr].size = header.root[curr].size;
        header.root[1-curr].rootObject = header.root[curr].rootObject;
        header.root[1-curr].classDescList = header.root[curr].classDescList;
        header.root[1-curr].bitmapExtent = header.root[curr].bitmapExtent;
        usedSize = header.root[curr].size;
        currIndexSize = committedIndexSize;
        currRBitmapPage = currPBitmapPage = 0;
        currRBitmapOffs = currPBitmapOffs = 0;
        reloadScheme();
    }

    public synchronized void backup(OutputStream out) throws java.io.IOException
    {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        objectCache.flush();

        int   curr = 1-currIndex;
        final int nObjects = header.root[curr].indexUsed;
        long  indexOffs = header.root[curr].index;
        int   i, j, k;
        int   nUsedIndexPages = (nObjects + dbHandlesPerPage - 1) / dbHandlesPerPage;
        int   nIndexPages = (int)((header.root[curr].indexSize + dbHandlesPerPage - 1) / dbHandlesPerPage);
        long  totalRecordsSize = 0;
        long  nPagedObjects = 0;
        int   bitmapExtent = header.root[curr].bitmapExtent;
        final long[] index = new long[nObjects];
        final int[]  oids = new int[nObjects];
            
        if (bitmapExtent == 0) { 
            bitmapExtent = Integer.MAX_VALUE;
        }
        for (i = 0, j = 0; i < nUsedIndexPages; i++) {
            Page pg = pool.getPage(indexOffs + (long)i*Page.pageSize);
            for (k = 0; k < dbHandlesPerPage && j < nObjects; k++, j++) { 
                long pos = Bytes.unpack8(pg.data, k*8);
                index[j] = pos;
                oids[j] = j;
                if ((pos & dbFreeHandleFlag) == 0) { 
                    if ((pos & dbPageObjectFlag) != 0) {
                        nPagedObjects += 1;
                    } else if (pos != 0) { 
                        int offs = (int)pos & (Page.pageSize-1);
                        Page op = pool.getPage(pos - offs);
                        int size = ObjectHeader.getSize(op.data, offs & ~dbFlagsMask);
                        size = (size + dbAllocationQuantum-1) & ~(dbAllocationQuantum-1);
                        totalRecordsSize += size; 
                        pool.unfix(op);
                    }
                }
            }
            pool.unfix(pg);
        
        } 
        Header newHeader = new Header();
        newHeader.curr = 0;
        newHeader.dirty = false;
        newHeader.databaseFormatVersion = header.databaseFormatVersion;
        long newFileSize = (long)(nPagedObjects + nIndexPages*2 + 1)*Page.pageSize + totalRecordsSize;
        newFileSize = (newFileSize + Page.pageSize-1) & ~(Page.pageSize-1);     
        newHeader.root = new RootPage[2];
        newHeader.root[0] = new RootPage();
        newHeader.root[1] = new RootPage();
        newHeader.root[0].size = newHeader.root[1].size = newFileSize;
        newHeader.root[0].index = newHeader.root[1].shadowIndex = Page.pageSize;
        newHeader.root[0].shadowIndex = newHeader.root[1].index = Page.pageSize + (long)nIndexPages*Page.pageSize;
        newHeader.root[0].shadowIndexSize = newHeader.root[0].indexSize = 
            newHeader.root[1].shadowIndexSize = newHeader.root[1].indexSize = nIndexPages*dbHandlesPerPage;
        newHeader.root[0].indexUsed = newHeader.root[1].indexUsed = nObjects;
        newHeader.root[0].freeList = newHeader.root[1].freeList = header.root[curr].freeList;
        newHeader.root[0].bitmapEnd = newHeader.root[1].bitmapEnd = header.root[curr].bitmapEnd;

        newHeader.root[0].rootObject = newHeader.root[1].rootObject = header.root[curr].rootObject;
        newHeader.root[0].classDescList = newHeader.root[1].classDescList = header.root[curr].classDescList;
        newHeader.root[0].bitmapExtent = newHeader.root[1].bitmapExtent = header.root[curr].bitmapExtent;
        byte[] page = new byte[Page.pageSize];
        newHeader.pack(page);
        out.write(page);
        
        long pageOffs = (long)(nIndexPages*2 + 1)*Page.pageSize;
        long recOffs = (long)(nPagedObjects + nIndexPages*2 + 1)*Page.pageSize;
        GenericSort.sort(new GenericSortArray() { 
                public int size() { 
                    return nObjects;
                }
                public int compare(int i, int j) { 
                    return index[i] < index[j] ? -1 : index[i] == index[j] ? 0 : 1;
                }
                public void swap(int i, int j) { 
                    long t1 = index[i];
                    index[i] = index[j];
                    index[j] = t1;
                    int t2 = oids[i];
                    oids[i] = oids[j];
                    oids[j] = t2;
                }
            }
        );
        byte[] newIndex = new byte[nIndexPages*dbHandlesPerPage*8];
        for (i = 0; i < nObjects; i++) {
            long pos = index[i];
            int oid = oids[i];
            if ((pos & dbFreeHandleFlag) == 0) { 
                if ((pos & dbPageObjectFlag) != 0) {
                    Bytes.pack8(newIndex, oid*8, pageOffs | dbPageObjectFlag);
                    pageOffs += Page.pageSize;
                } else if (pos != 0) { 
                    Bytes.pack8(newIndex, oid*8, recOffs);
                    int offs = (int)pos & (Page.pageSize-1);
                    Page op = pool.getPage(pos - offs);
                    int size = ObjectHeader.getSize(op.data, offs & ~dbFlagsMask);
                    size = (size + dbAllocationQuantum-1) & ~(dbAllocationQuantum-1);
                    recOffs += size; 
                    pool.unfix(op);
                }
            } else { 
                Bytes.pack8(newIndex, oid*8, pos);
            }
        }
        out.write(newIndex);
        out.write(newIndex);

        for (i = 0; i < nObjects; i++) {
            long pos = index[i];
            if (((int)pos & (dbFreeHandleFlag|dbPageObjectFlag)) == dbPageObjectFlag) { 
                if (oids[i] < dbBitmapId + dbBitmapPages 
                    || (oids[i] >= bitmapExtent && oids[i] < bitmapExtent + dbLargeBitmapPages - dbBitmapPages))
                { 
                    int pageId = oids[i] < dbBitmapId + dbBitmapPages 
                        ? oids[i] - dbBitmapId : oids[i] - bitmapExtent + bitmapExtentBase;
                    long mappedSpace = (long)pageId*Page.pageSize*8*dbAllocationQuantum;
                    if (mappedSpace >= newFileSize) { 
                        Arrays.fill(page, (byte)0);
                    } else if (mappedSpace + Page.pageSize*8*dbAllocationQuantum <= newFileSize) { 
                        Arrays.fill(page, (byte)-1);
                    } else { 
                        int nBits = (int)((newFileSize - mappedSpace) >> dbAllocationQuantumBits);
                        Arrays.fill(page, 0, nBits >> 3, (byte)-1);
                        page[nBits >> 3] = (byte)((1 << (nBits & 7)) - 1);
                        Arrays.fill(page, (nBits >> 3) + 1, Page.pageSize, (byte)0);
                    }
                    out.write(page);
                } else {                        
                    Page pg = pool.getPage(pos & ~dbFlagsMask);
                    out.write(pg.data);
                    pool.unfix(pg);
                }
            }
        }
        for (i = 0; i < nObjects; i++) {
            long pos = index[i];
            if (pos != 0 && ((int)pos & (dbFreeHandleFlag|dbPageObjectFlag)) == 0) { 
                pos &= ~dbFlagsMask;
                int offs = (int)pos & (Page.pageSize-1);
                Page pg = pool.getPage(pos - offs);
                int size = ObjectHeader.getSize(pg.data, offs);
                size = (size + dbAllocationQuantum-1) & ~(dbAllocationQuantum-1);

                while (true) { 
                    if (Page.pageSize - offs >= size) { 
                        out.write(pg.data, offs, size);
                        break;
                    }
                    out.write(pg.data, offs, Page.pageSize - offs);
                    size -= Page.pageSize - offs;
                    pos += Page.pageSize - offs;
                    offs = 0;
                    pool.unfix(pg); 
                    pg = pool.getPage(pos);
                }
                pool.unfix(pg);
            }
        }
        if (recOffs != newFileSize) {       
            Assert.that(newFileSize - recOffs < Page.pageSize);
            int align = (int)(newFileSize - recOffs);
            Arrays.fill(page, 0, align, (byte)0);
            out.write(page, 0, align);
        }        
    }

    public <T> Query<T> createQuery() { 
        return new QueryImpl<T>(this);
    }

    public synchronized <T extends IPersistent> IPersistentSet<T> createScalableSet() {
        return createScalableSet(8);
    }

    public synchronized <T extends IPersistent> IPersistentSet<T> createScalableSet(int initialSize) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        return new ScalableSet(this, initialSize);
    }

    public <T extends IPersistent> IPersistentList<T> createList() {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        return new PersistentListImpl<T>(this);
    }

    public <T extends IPersistent> IPersistentList<T> createScalableList() {
        return createScalableList(8);
    }

    public <T extends IPersistent> IPersistentList<T> createScalableList(int initialSize) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        return new ScalableList<T>(this, initialSize);
    }

    public <K extends Comparable, V extends IPersistent> IPersistentMap<K, V> createMap(Class keyType) {
        return createMap(keyType, 4);
    }

    public <K extends Comparable, V extends IPersistent> IPersistentMap<K, V> createMap(Class keyType, int initialSize) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        return new PersistentMapImpl<K,V>(this, keyType, initialSize);
    }

    public synchronized <T extends IPersistent> IPersistentSet<T> createSet() {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        IPersistentSet<T> set = alternativeBtree 
            ? (IPersistentSet<T>)new AltPersistentSet<T>()
            : (IPersistentSet<T>)new PersistentSet<T>();
        set.assignOid(this, 0, false);
        return set;
    }
        
    public synchronized <T extends IPersistent> BitIndex<T> createBitIndex() {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        BitIndex index = new BitIndexImpl<T>();
        index.assignOid(this, 0, false);
        return index;
    }

    public synchronized <T extends IPersistent> Index<T> createIndex(Class keyType, boolean unique) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        Index<T> index = alternativeBtree 
            ? (Index<T>)new AltBtree<T>(keyType, unique)
            : (Index<T>)new Btree<T>(keyType, unique);
        index.assignOid(this, 0, false);
        return index;
    }

    public synchronized <T extends IPersistent> Index<T> createIndex(Class[] keyTypes, boolean unique) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        Index<T> index = alternativeBtree 
            ? (Index<T>)new AltBtreeCompoundIndex<T>(keyTypes, unique)
            : (Index<T>)new BtreeCompoundIndex<T>(keyTypes, unique);
        index.assignOid(this, 0, false);
        return index;
    }

    public synchronized <T extends IPersistent> MultidimensionalIndex<T> createMultidimensionalIndex(MultidimensionalComparator<T> comparator)
    {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        return new KDTree<T>(this, comparator);
    }      

    public synchronized <T extends IPersistent> MultidimensionalIndex<T> createMultidimensionalIndex(Class type, String[] fieldNames, boolean treateZeroAsUndefinedValue)
    {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        return new KDTree<T>(this, type, fieldNames, treateZeroAsUndefinedValue);
    }      

    public synchronized <T extends IPersistent> Index<T> createThickIndex(Class keyType) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        return new ThickIndex<T>(keyType, this);
    }      

    public synchronized <T extends IPersistent> SpatialIndex<T> createSpatialIndex() {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        return new Rtree<T>();
    }

    public synchronized <T extends IPersistent> SpatialIndexR2<T> createSpatialIndexR2() {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        return new RtreeR2<T>(this);
    }

    public <T extends IPersistent> FieldIndex<T> createFieldIndex(Class type, String fieldName, boolean unique) {
        return this.<T>createFieldIndex(type, fieldName, unique, false);
    }

    public synchronized <T extends IPersistent> FieldIndex<T> createFieldIndex(Class type, String fieldName, boolean unique, boolean caseInsensitive) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }        
        FieldIndex<T> index = caseInsensitive
            ? alternativeBtree
                 ? (FieldIndex<T>)new AltBtreeCaseInsensitiveFieldIndex<T>(type, fieldName, unique)
                 : (FieldIndex<T>)new BtreeCaseInsensitiveFieldIndex<T>(type, fieldName, unique)
            : alternativeBtree
                 ? (FieldIndex<T>)new AltBtreeFieldIndex<T>(type, fieldName, unique)
                 : (FieldIndex<T>)new BtreeFieldIndex<T>(type, fieldName, unique);
        index.assignOid(this, 0, false);
        return index;
    }

    public <T extends IPersistent> FieldIndex<T> createFieldIndex(Class type, String[] fieldNames, boolean unique) {
        return this.<T>createFieldIndex(type, fieldNames, unique, false);
    }

    public synchronized <T extends IPersistent> FieldIndex<T> createFieldIndex(Class type, String[] fieldNames, boolean unique, boolean caseInsensitive) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }        
        FieldIndex<T> index = caseInsensitive
            ? alternativeBtree
                ? (FieldIndex<T>)new AltBtreeCaseInsensitiveMultiFieldIndex(type, fieldNames, unique)
                : (FieldIndex<T>)new BtreeCaseInsensitiveMultiFieldIndex(type, fieldNames, unique)
            : alternativeBtree
                ? (FieldIndex<T>)new AltBtreeMultiFieldIndex(type, fieldNames, unique)
                : (FieldIndex<T>)new BtreeMultiFieldIndex(type, fieldNames, unique);
        index.assignOid(this, 0, false);
        return index;
    }

    public synchronized <T extends IPersistent> Index<T> createRandomAccessIndex(Class keyType, boolean unique) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        Index<T> index = new RndBtree<T>(keyType, unique);
        index.assignOid(this, 0, false);
        return index;
    }

    public synchronized  <T extends IPersistent> Index<T> createRandomAccessIndex(Class[] keyTypes, boolean unique) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        Index<T> index = new RndBtreeCompoundIndex<T>(keyTypes, unique);
        index.assignOid(this, 0, false);
        return index;
    }


    public <T extends IPersistent> FieldIndex<T> createRandomAccessFieldIndex(Class type, String fieldName, boolean unique) {    
        return this.<T>createRandomAccessFieldIndex(type, fieldName, unique, false);
    }

    public synchronized <T extends IPersistent> FieldIndex<T> createRandomAccessFieldIndex(Class type, String fieldName, boolean unique, boolean caseInsensitive) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }        
        FieldIndex<T> index = caseInsensitive
            ? (FieldIndex)new RndBtreeCaseInsensitiveFieldIndex<T>(type, fieldName, unique)
            : (FieldIndex)new RndBtreeFieldIndex<T>(type, fieldName, unique);
        index.assignOid(this, 0, false);
        return index;
    }

    public <T extends IPersistent> FieldIndex<T> createRandomAccessFieldIndex(Class type, String[] fieldNames, boolean unique) {
        return this.<T>createRandomAccessFieldIndex(type, fieldNames, unique, false);
    }
        
    public synchronized <T extends IPersistent> FieldIndex<T> createRandomAccessFieldIndex(Class type, String[] fieldNames, boolean unique, boolean caseInsensitive) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }        
        FieldIndex<T> index = caseInsensitive
            ? (FieldIndex)new RndBtreeCaseInsensitiveMultiFieldIndex(type, fieldNames, unique)
            : (FieldIndex)new RndBtreeMultiFieldIndex(type, fieldNames, unique);
        index.assignOid(this, 0, false);
        return index;
    }

    public <T extends IPersistent> SortedCollection<T> createSortedCollection(PersistentComparator<T> comparator, boolean unique) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }        
        return new Ttree<T>(comparator, unique);
    }
        
    public <T extends IPersistent&Comparable> SortedCollection<T> createSortedCollection(boolean unique) {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }        
        return new Ttree<T>(new DefaultPersistentComparator<T>(), unique);
    }
        
    public <T extends IPersistent> Link<T> createLink() {
        return createLink(8);
    }

    public <T extends IPersistent> Link<T> createLink(int initialSize) {
        return new LinkImpl<T>(initialSize);
    }

    public <M extends IPersistent, O extends IPersistent> Relation<M,O> createRelation(O owner) {
        return new RelationImpl<M,O>(owner);
    }

    public Blob createBlob() { 
        return new BlobImpl(this, Page.pageSize - BlobImpl.headerSize);
    }

    public Blob createRandomAccessBlob() { 
        return new RandomAccessBlobImpl(this);
    }

    public <T extends TimeSeries.Tick> TimeSeries<T> createTimeSeries(Class blockClass, long maxBlockTimeInterval) {
        return new TimeSeriesImpl<T>(this, blockClass, maxBlockTimeInterval);
    }

    public <T extends IPersistent> PatriciaTrie<T> createPatriciaTrie() { 
        return new PTrie<T>();
    }

    final long getGCPos(int oid) { 
        Page pg = pool.getPage(header.root[currIndex].index 
                               + ((long)(oid >>> dbHandlesPerPageBits) << Page.pageSizeLog));
        long pos = Bytes.unpack8(pg.data, (oid & (dbHandlesPerPage-1)) << 3);
        pool.unfix(pg);
        return pos;
    }
        
    final void markOid(int oid) { 
        if (oid != 0) {  
            long pos = getGCPos(oid);
            if ((pos & (dbFreeHandleFlag|dbPageObjectFlag)) != 0) { 
                throw new StorageError(StorageError.INVALID_OID);
            }
            if (pos < header.root[currIndex].size) { 
                // object was not allocated by custom allocator
                int bit = (int)(pos >>> dbAllocationQuantumBits);
                if ((blackBitmap[bit >>> 5] & (1 << (bit & 31))) == 0) { 
                    greyBitmap[bit >>> 5] |= 1 << (bit & 31);
                }
            }
        }
    }

    final Page getGCPage(int oid) {  
        return pool.getPage(getGCPos(oid) & ~dbFlagsMask);
    }

    public void setGcThreshold(long maxAllocatedDelta) {
        gcThreshold = maxAllocatedDelta;
    }

    private void mark() { 
        int bitmapSize = (int)(header.root[currIndex].size >>> (dbAllocationQuantumBits + 5)) + 1;
        boolean existsNotMarkedObjects;
        long pos;
        int  i, j;
        
        if (listener != null) { 
            listener.gcStarted();
        }           

        greyBitmap = new int[bitmapSize];
        blackBitmap = new int[bitmapSize];
        int rootOid = header.root[currIndex].rootObject;
        if (rootOid != 0) { 
            markOid(rootOid);
            do { 
                existsNotMarkedObjects = false;
                for (i = 0; i < bitmapSize; i++) { 
                    if (greyBitmap[i] != 0) { 
                        existsNotMarkedObjects = true;
                        for (j = 0; j < 32; j++) { 
                            if ((greyBitmap[i] & (1 << j)) != 0) { 
                                pos = (((long)i << 5) + j) << dbAllocationQuantumBits;
                                greyBitmap[i] &= ~(1 << j);
                                blackBitmap[i] |= 1 << j;
                                int offs = (int)pos & (Page.pageSize-1);
                                Page pg = pool.getPage(pos - offs);
                                int typeOid = ObjectHeader.getType(pg.data, offs);
                                if (typeOid != 0) { 
                                    ClassDescriptor desc = findClassDescriptor(typeOid);
                                    if (Btree.class.isAssignableFrom(desc.cls)) { 
                                        Btree btree = new Btree(pg.data, ObjectHeader.sizeof + offs);
                                        btree.assignOid(this, 0, false);
                                        btree.markTree();
                                    } else if (desc.hasReferences) { 
                                        markObject(pool.get(pos), ObjectHeader.sizeof, desc);
                                    }
                                }
                                pool.unfix(pg);                                
                            }
                        }
                    }
                }
            } while (existsNotMarkedObjects);
        }    
    }

    private int sweep() { 
        int nDeallocated = 0;
        long pos;
        gcDone = true;
        for (int i = dbFirstUserId, j = committedIndexSize; i < j; i++) {
            pos = getGCPos(i);
            if (pos != 0 && ((int)pos & (dbPageObjectFlag|dbFreeHandleFlag)) == 0) {
                int bit = (int)(pos >>> dbAllocationQuantumBits);
                if ((blackBitmap[bit >>> 5] & (1 << (bit & 31))) == 0) { 
                    // object is not accessible
                    if (getPos(i) == pos) { 
                        int offs = (int)pos & (Page.pageSize-1);
                        Page pg = pool.getPage(pos - offs);
                        int typeOid = ObjectHeader.getType(pg.data, offs);
                        if (typeOid != 0) { 
                            ClassDescriptor desc = findClassDescriptor(typeOid);
                            nDeallocated += 1;
                            if (Btree.class.isAssignableFrom(desc.cls)) { 
                                Btree btree = new Btree(pg.data, ObjectHeader.sizeof + offs);
                                pool.unfix(pg);
                                btree.assignOid(this, i, false);
                                btree.deallocate();
                            } else { 
                                int size = ObjectHeader.getSize(pg.data, offs);
                                pool.unfix(pg);
                                freeId(i);
                                objectCache.remove(i);                        
                                cloneBitmap(pos, size);
                            }
                            if (listener != null) { 
                                listener.deallocateObject(desc.cls, i);
                            }
                        }
                    }
                }
            }   
        }

        greyBitmap = null;
        blackBitmap = null;
        allocatedDelta = 0;
        gcActive = false;

        if (listener != null) {
            listener.gcCompleted(nDeallocated);
        }
        return nDeallocated;
    }   
     
    class GcThread extends Thread { 
        private boolean go;

        GcThread() { 
            start();
        }

        void activate() { 
            synchronized(backgroundGcStartMonitor) { 
                go = true;
                backgroundGcStartMonitor.notify();
            }
        }

        public void run() { 
            try { 
                while (true) { 
                    synchronized(backgroundGcStartMonitor) { 
                        while (!go && opened) { 
                            backgroundGcStartMonitor.wait();
                        }
                        if (!opened) { 
                            return;
                        }
                        go = false;
                    }
                    synchronized(backgroundGcMonitor) { 
                        if (!opened) { 
                            return;
                        }
                        mark();
                        synchronized (StorageImpl.this) { 
                            synchronized (objectCache) { 
                                sweep();
                            }
                        }                        
                    }
                }
            } catch (InterruptedException x) { 
            }    
        }
    }

    public synchronized int gc() { 
        return gc0();
    }

    private int gc0() { 
        synchronized (objectCache) { 
            if (!opened) {
                throw new StorageError(StorageError.STORAGE_NOT_OPENED);
            }
            if (gcDone || gcActive) { 
                return 0;
            }
            gcActive = true;
            if (backgroundGc) { 
                if (gcThread == null) { 
                    gcThread = new GcThread();
                }
                gcThread.activate();
                return 0;
            }
            // System.out.println("Start GC, allocatedDelta=" + allocatedDelta + ", header[" + currIndex + "].size=" + header.root[currIndex].size + ", gcTreshold=" + gcThreshold);
                        
            mark();
            return sweep();
        }
    }


    public synchronized HashMap getMemoryDump() { 
        synchronized (objectCache) { 
            if (!opened) {
                throw new StorageError(StorageError.STORAGE_NOT_OPENED);
            }
            int bitmapSize = (int)(header.root[currIndex].size >>> (dbAllocationQuantumBits + 5)) + 1;
            boolean existsNotMarkedObjects;
            long pos;
            int  i, j, n;

            // mark
            greyBitmap = new int[bitmapSize];
            blackBitmap = new int[bitmapSize];
            int rootOid = header.root[currIndex].rootObject;
            HashMap map = new HashMap();

            if (rootOid != 0) { 
                MemoryUsage indexUsage = new MemoryUsage(Index.class);
                MemoryUsage fieldIndexUsage = new MemoryUsage(FieldIndex.class);
                MemoryUsage classUsage = new MemoryUsage(Class.class);

                markOid(rootOid);
                do { 
                    existsNotMarkedObjects = false;
                    for (i = 0; i < bitmapSize; i++) { 
                        if (greyBitmap[i] != 0) { 
                            existsNotMarkedObjects = true;
                            for (j = 0; j < 32; j++) { 
                                if ((greyBitmap[i] & (1 << j)) != 0) { 
                                    pos = (((long)i << 5) + j) << dbAllocationQuantumBits;
                                    greyBitmap[i] &= ~(1 << j);
                                    blackBitmap[i] |= 1 << j;
                                    int offs = (int)pos & (Page.pageSize-1);
                                    Page pg = pool.getPage(pos - offs);
                                    int typeOid = ObjectHeader.getType(pg.data, offs);
                                    int objSize = ObjectHeader.getSize(pg.data, offs);
                                    int alignedSize = (objSize + dbAllocationQuantum - 1) & ~(dbAllocationQuantum-1);                                    
                                    if (typeOid != 0) { 
                                        markOid(typeOid);
                                        ClassDescriptor desc = findClassDescriptor(typeOid);
                                        if (Btree.class.isAssignableFrom(desc.cls)) { 
                                            Btree btree = new Btree(pg.data, ObjectHeader.sizeof + offs);
                                            btree.assignOid(this, 0, false);
                                            int nPages = btree.markTree();
                                            if (FieldIndex.class.isAssignableFrom(desc.cls)) { 
                                                fieldIndexUsage.nInstances += 1;
                                                fieldIndexUsage.totalSize += (long)nPages*Page.pageSize + objSize;
                                                fieldIndexUsage.allocatedSize += (long)nPages*Page.pageSize + alignedSize;
                                            } else {
                                                indexUsage.nInstances += 1;
                                                indexUsage.totalSize += (long)nPages*Page.pageSize + objSize;
                                                indexUsage.allocatedSize += (long)nPages*Page.pageSize + alignedSize;
                                            }
                                        } else { 
                                            MemoryUsage usage = (MemoryUsage)map.get(desc.cls);
                                            if (usage == null) { 
                                                usage = new MemoryUsage(desc.cls);
                                                map.put(desc.cls, usage);
                                            }
                                            usage.nInstances += 1;
                                            usage.totalSize += objSize;
                                            usage.allocatedSize += alignedSize;
                                                      
                                            if (desc.hasReferences) { 
                                                markObject(pool.get(pos), ObjectHeader.sizeof, desc);
                                            }
                                        }
                                    } else { 
                                        classUsage.nInstances += 1;
                                        classUsage.totalSize += objSize;
                                        classUsage.allocatedSize += alignedSize;
                                    }
                                    pool.unfix(pg);                                
                                }
                            }
                        }
                    }
                } while (existsNotMarkedObjects);
                
                if (indexUsage.nInstances != 0) { 
                    map.put(Index.class, indexUsage);
                }
                if (fieldIndexUsage.nInstances != 0) { 
                    map.put(FieldIndex.class, fieldIndexUsage);
                }
                if (classUsage.nInstances != 0) { 
                    map.put(Class.class, classUsage);
                }
                MemoryUsage system = new MemoryUsage(Storage.class);
                system.totalSize += header.root[0].indexSize*8L;
                system.totalSize += header.root[1].indexSize*8L;
                system.totalSize += (long)(header.root[currIndex].bitmapEnd-dbBitmapId)*Page.pageSize;
                system.totalSize += Page.pageSize; // root page

                if (header.root[currIndex].bitmapExtent != 0) { 
                    system.allocatedSize = getBitmapUsedSpace(dbBitmapId, dbBitmapId+dbBitmapPages)
                        + getBitmapUsedSpace(header.root[currIndex].bitmapExtent + dbBitmapPages - bitmapExtentBase, 
                                             header.root[currIndex].bitmapExtent + header.root[currIndex].bitmapEnd - dbBitmapId - bitmapExtentBase);
                } else { 
                    system.allocatedSize = getBitmapUsedSpace(dbBitmapId, header.root[currIndex].bitmapEnd);
                }
                system.nInstances = header.root[currIndex].indexSize;
                map.put(Storage.class, system);
            } 
            return map;
        }
    }
        
    final long getBitmapUsedSpace(int from, int till) { 
        long allocated = 0;
        while (from < till) {
            Page pg = getGCPage(from);
            for (int j = 0; j < Page.pageSize; j++) {
                int mask = pg.data[j] & 0xFF;
                while (mask != 0) { 
                    if ((mask & 1) != 0) { 
                        allocated += dbAllocationQuantum;
                    }
                    mask >>= 1;
                }
            }
            pool.unfix(pg);
            from += 1;
        }
        return allocated;
    }

    final int markObject(byte[] obj, int offs,  ClassDescriptor desc)
    { 
        ClassDescriptor.FieldDescriptor[] all = desc.allFields;

        for (int i = 0, n = all.length; i < n; i++) { 
            ClassDescriptor.FieldDescriptor fd = all[i];
            switch (fd.type) { 
                case ClassDescriptor.tpBoolean:
                case ClassDescriptor.tpByte:
                    offs += 1;
                    continue;
                case ClassDescriptor.tpChar:
                case ClassDescriptor.tpShort:
                    offs += 2;
                    continue;
                case ClassDescriptor.tpInt:
                case ClassDescriptor.tpEnum:
                case ClassDescriptor.tpFloat:
                    offs += 4;
                    continue;
                case ClassDescriptor.tpLong:
                case ClassDescriptor.tpDouble:
                case ClassDescriptor.tpDate:
                    offs += 8;
                    continue;
                case ClassDescriptor.tpString:
                {
                    int strlen = Bytes.unpack4(obj, offs);
                    offs += 4;
                    if (strlen > 0) { 
                        offs += strlen*2;
                    } else if (strlen < -1) {
                        offs -= strlen+2;
                    }
                    continue;
                }
                case ClassDescriptor.tpObject:
                    markOid(Bytes.unpack4(obj, offs));
                    offs += 4;
                    continue;
                case ClassDescriptor.tpValue:
                    offs = markObject(obj, offs, fd.valueDesc);
                    continue;
                case ClassDescriptor.tpRaw:
                {
                    int len = Bytes.unpack4(obj, offs);
                    offs += 4;
                    if (len > 0) { 
                        offs += len;
                    } else if (len == -2-ClassDescriptor.tpObject) { 
                        markOid(Bytes.unpack4(obj, offs));
                        offs += 4;
                    } else if (len < -1) { 
                        offs += ClassDescriptor.sizeof[-2-len];
                    }
                    continue;
                }
                case ClassDescriptor.tpCustom:
                {
                    try { 
                        ByteArrayInputStream in = new ByteArrayInputStream(obj, offs, obj.length - offs);
                        serializer.unpack(in);
                        offs = obj.length - in.available();
                    } catch (IOException x) { 
                        throw new StorageError(StorageError.ACCESS_VIOLATION, x);
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfByte:
                case ClassDescriptor.tpArrayOfBoolean:
                {
                    int len = Bytes.unpack4(obj, offs);
                    offs += 4;
                    if (len > 0) { 
                        offs += len;
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfShort:
                case ClassDescriptor.tpArrayOfChar:
                {
                    int len = Bytes.unpack4(obj, offs);
                    offs += 4;
                    if (len > 0) { 
                        offs += len*2;
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfInt:
                case ClassDescriptor.tpArrayOfEnum:
                case ClassDescriptor.tpArrayOfFloat:
                {
                    int len = Bytes.unpack4(obj, offs);
                    offs += 4;
                    if (len > 0) { 
                        offs += len*4;
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfLong:
                case ClassDescriptor.tpArrayOfDouble:
                case ClassDescriptor.tpArrayOfDate:
                {
                    int len = Bytes.unpack4(obj, offs);
                    offs += 4;
                    if (len > 0) { 
                        offs += len*8;
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfString:
                {
                    int len = Bytes.unpack4(obj, offs);
                    offs += 4;
                    while (--len >= 0) {
                        int strlen = Bytes.unpack4(obj, offs);
                        offs += 4;
                        if (strlen > 0) { 
                            offs += strlen*2;
                        } else if (strlen < -1) {
                            offs -= strlen+2;
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfObject:
                case ClassDescriptor.tpLink:
                {
                    int len = Bytes.unpack4(obj, offs);
                    offs += 4;
                    while (--len >= 0) {
                        markOid(Bytes.unpack4(obj, offs));
                        offs += 4;
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfValue:
                {
                    int len = Bytes.unpack4(obj, offs);
                    offs += 4;
                    ClassDescriptor valueDesc = fd.valueDesc;
                    while (--len >= 0) {
                        offs = markObject(obj, offs, valueDesc);
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfRaw:
                {
                    int len = Bytes.unpack4(obj, offs);
                    offs += 4;
                    while (--len >= 0) {
                        int rawlen = Bytes.unpack4(obj, offs);
                        offs += 4;
                        if (rawlen >= 0) { 
                            offs += rawlen;
                        } else if (rawlen == -2-ClassDescriptor.tpObject) { 
                            markOid(Bytes.unpack4(obj, offs));
                            offs += 4;
                        } else if (rawlen < -1) { 
                            offs += ClassDescriptor.sizeof[-2-rawlen];
                        }
                        continue;
                    }
                    continue;
                }
            }
        }
        return offs;
    }

    /**
     * This method is used internally by Perst to get transaction context associated with current thread.
     * But it can be also used by application to get transaction context, store it in some variable and
     * use in another thread. I will make it possible to share one transaction between multiple threads.
     * @return transaction context associated with current thread
     */     
    public ThreadTransactionContext getTransactionContext() { 
        return (ThreadTransactionContext)transactionContext.get();
    }

    /**
     * Associate transaction context with the thread
     * This method can be used by application to share the same transaction between multiple threads
     * @param ctx new transaction context 
     * @return transaction context previously associated with this thread
     */     
    public ThreadTransactionContext setTransactionContext(ThreadTransactionContext ctx) { 
        ThreadTransactionContext oldCtx = (ThreadTransactionContext)transactionContext.get();
        transactionContext.set(ctx);
        return oldCtx;
    }

    public void beginThreadTransaction(int mode)
    {
        switch (mode) {
        case SERIALIZABLE_TRANSACTION:
            if (multiclientSupport) { 
                throw new IllegalArgumentException("Illegal transaction mode");
            }
            useSerializableTransactions = true;
            getTransactionContext().nested += 1;;
            break;
        case EXCLUSIVE_TRANSACTION:
        case COOPERATIVE_TRANSACTION:
            if (multiclientSupport) { 
                if (mode ==  EXCLUSIVE_TRANSACTION) { 
                    transactionLock.exclusiveLock();
                } else {
                    transactionLock.sharedLock();
                }
                synchronized (transactionMonitor) {
                    if (nNestedTransactions++ == 0) { 
                        file.lock(mode == READ_ONLY_TRANSACTION);
                        byte[] buf = new byte[Header.sizeof];
                        int rc = file.read(0, buf);
                        if (rc > 0 && rc < Header.sizeof) { 
                            throw new StorageError(StorageError.DATABASE_CORRUPTED);
                        }
                        header.unpack(buf);
                        int curr = header.curr;
                        currIndex = curr;
                        currIndexSize = header.root[1-curr].indexUsed;
                        committedIndexSize = currIndexSize;
                        usedSize = header.root[curr].size;
                        
                        if (header.transactionId != transactionId) { 
                            if (bitmapPageAvailableSpace != null) { 
                                for (int i = 0; i < bitmapPageAvailableSpace.length; i++) { 
                                    bitmapPageAvailableSpace[i] = Integer.MAX_VALUE;
                                }       
                            } 
                            objectCache.clear();
                            pool.clear();
                            transactionId = header.transactionId;
                        }
                    }
                }
            } else { 
                synchronized (transactionMonitor) {
                    if (scheduledCommitTime != Long.MAX_VALUE) { 
                        nBlockedTransactions += 1;
                        while (System.currentTimeMillis() >= scheduledCommitTime) { 
                            try { 
                                transactionMonitor.wait();
                            } catch (InterruptedException x) {}
                        }
                        nBlockedTransactions -= 1;
                    }
                    nNestedTransactions += 1;
                }           
                if (mode == EXCLUSIVE_TRANSACTION) { 
                    transactionLock.exclusiveLock();
                } else { 
                    transactionLock.sharedLock();
                }
            }
            break;
        default:
            throw new IllegalArgumentException("Illegal transaction mode");
        }
    }

    public void endThreadTransaction() { 
        endThreadTransaction(Integer.MAX_VALUE);
    }

    public void endThreadTransaction(int maxDelay)
    {
        if (multiclientSupport) { 
            if (maxDelay != Integer.MAX_VALUE) { 
                throw new IllegalArgumentException("Delay is not supported for global transactions");
            }
            synchronized (transactionMonitor) { 
                transactionLock.unlock();
                if (nNestedTransactions != 0) { // may be everything is already aborted
                    if (--nNestedTransactions == 0) { 
                        commit();
                        pool.flush();
                        file.unlock();
                    }
                }
            }       
            return;
        }  
        ThreadTransactionContext ctx = getTransactionContext();
        if (ctx.nested != 0) { // serializable transaction
            if (--ctx.nested == 0) { 
                ArrayList modified = ctx.modified;
                ArrayList deleted = ctx.deleted;
                Map locked = ctx.locked;
                synchronized (backgroundGcMonitor) { 
                    synchronized(this) { 
                        synchronized (objectCache) { 
                            for (int i = modified.size(); --i >= 0;) { 
                                ((IPersistent)modified.get(i)).store();
                            } 
                            for (int i = deleted.size(); --i >= 0;) { 
                                deallocateObject0((IPersistent)deleted.get(i));
                            } 
                            if (modified.size() + deleted.size() > 0) { 
                                commit0();
                            }
                        }
                    }
                }
                Iterator iterator = locked.values().iterator();
                while (iterator.hasNext()) { 
                    ((IResource)iterator.next()).reset();
                }
                modified.clear();
                deleted.clear();
                locked.clear();
            } 
        } else { // exclusive or cooperative transaction        
            synchronized (transactionMonitor) { 
                transactionLock.unlock();
                
                if (nNestedTransactions != 0) { // may be everything is already aborted
                    if (--nNestedTransactions == 0) { 
                        nCommittedTransactions += 1;
                        commit();
                        scheduledCommitTime = Long.MAX_VALUE;
                        if (nBlockedTransactions != 0) { 
                            transactionMonitor.notifyAll();
                        }
                    } else {
                        if (maxDelay != Integer.MAX_VALUE) { 
                            long nextCommit = System.currentTimeMillis() + maxDelay;
                            if (nextCommit < scheduledCommitTime) { 
                                scheduledCommitTime = nextCommit;
                            }
                            if (maxDelay == 0) { 
                                int n = nCommittedTransactions;
                                nBlockedTransactions += 1;
                                do { 
                                try { 
                                    transactionMonitor.wait();
                                } catch (InterruptedException x) {}
                                } while (nCommittedTransactions == n);
                                nBlockedTransactions -= 1;
                            }                                   
                        }
                    }
                }
            }
        }
    }


    public void rollbackThreadTransaction()
    {
        if (multiclientSupport) { 
            synchronized (transactionMonitor) { 
                transactionLock.reset();
                nNestedTransactions = 0;
                rollback();
                file.unlock();
            }       
            return;
        }  
        ThreadTransactionContext ctx = getTransactionContext();
        if (ctx.nested != 0) { // serializable transaction
            ArrayList modified = ctx.modified;
            Map locked = ctx.locked;
            synchronized (this) { 
                synchronized (objectCache) {
                    int i = modified.size();
                    while (--i >= 0) { 
                        IPersistent obj = (IPersistent)modified.get(i);
                        int oid = obj.getOid();
                        if (getPos(oid) == 0) {
                            freeId(oid);
                        }
                        obj.invalidate();
                    }
                }
            }
            Iterator iterator = locked.values().iterator();
            while (iterator.hasNext()) { 
                ((IResource)iterator.next()).reset();
            }
            ctx.nested = 0; 
            modified.clear();
            ctx.deleted.clear();
            locked.clear();
        } else { 
            synchronized (transactionMonitor) { 
                transactionLock.reset();
                nNestedTransactions = 0;
                if (nBlockedTransactions != 0) { 
                    transactionMonitor.notifyAll();
                }
                rollback();
            }
        }
    }
            
    public/*protected*/  boolean lockObject(IPersistent obj) { 
        if (useSerializableTransactions) { 
            ThreadTransactionContext ctx = getTransactionContext();
            if (ctx.nested != 0) { // serializable transaction
                return ctx.locked.put(obj, obj) == null;
            }
        }
        return true;
    }
         
    public void close() 
    {
        synchronized (backgroundGcMonitor) { 
            commit();
            opened = false;
        }
        if (gcThread != null) {             
            gcThread.activate();
            try { 
                gcThread.join();
            } catch (InterruptedException x) {}
        }
        if (isDirty()) { 
            Page pg = pool.putPage(0);
            header.pack(pg.data);
            pool.flush();
            pool.modify(pg);
            header.dirty = false;
            header.pack(pg.data);
            pool.unfix(pg);
            pool.flush();
        }
        pool.close();
        // make GC easier
        pool = null;
        objectCache = null;
        classDescMap = null;
        bitmapPageAvailableSpace = null;
        dirtyPagesMap  = null;
        descList = null;
    }

    public synchronized void exportXML(java.io.Writer writer) throws java.io.IOException
    {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        objectCache.flush();
        int rootOid = header.root[1-currIndex].rootObject;
        if (rootOid != 0) { 
            XMLExporter xmlExporter = new XMLExporter(this, writer);
            xmlExporter.exportDatabase(rootOid);
        }
    }

    public synchronized void importXML(java.io.Reader reader) throws XMLImportException
    {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        XMLImporter xmlImporter = new XMLImporter(this, reader);
        xmlImporter.importDatabase();
    }

    private boolean getBooleanValue(Object value) { 
        if (value instanceof Boolean) { 
            return ((Boolean)value).booleanValue();
        } else if (value instanceof String) {
            String s = (String)value;
            if ("true".equalsIgnoreCase(s) || "t".equalsIgnoreCase(s) || "1".equals(s)) { 
                return true;
            } else if ("false".equalsIgnoreCase(s) || "f".equalsIgnoreCase(s) || "0".equals(s)) { 
                return false;
            }
        }
        throw new StorageError(StorageError.BAD_PROPERTY_VALUE);
    }

    private long getIntegerValue(Object value) { 
        if (value instanceof Number) { 
            return ((Number)value).longValue();
        } else if (value instanceof String) {
            try { 
                return Long.parseLong((String)value, 10);
            } catch (NumberFormatException x) {}
        }
        throw new StorageError(StorageError.BAD_PROPERTY_VALUE);
    }

     
    public void setProperties(Properties props) 
    {
        String value;
        properties.putAll(props);
        if ((value = props.getProperty("perst.implicit.values")) != null) { 
            ClassDescriptor.treateAnyNonPersistentObjectAsValue = getBooleanValue(value);
        } 
        if ((value = props.getProperty("perst.serialize.transient.objects")) != null) { 
            ClassDescriptor.serializeNonPersistentObjects = getBooleanValue(value);
        } 
        if ((value = props.getProperty("perst.object.cache.init.size")) != null) { 
            objectCacheInitSize = (int)getIntegerValue(value);
            if (objectCacheInitSize <= 0) { 
                throw new IllegalArgumentException("Initial object cache size should be positive");
            }            
        }
        if ((value = props.getProperty("perst.object.cache.kind")) != null) { 
            cacheKind = value;
        }
        if ((value = props.getProperty("perst.object.index.init.size")) != null) { 
            initIndexSize = (int)getIntegerValue(value);
        }
        if ((value = props.getProperty("perst.extension.quantum")) != null) { 
            extensionQuantum = getIntegerValue(value);
        } 
        if ((value = props.getProperty("perst.gc.threshold")) != null) { 
            gcThreshold = getIntegerValue(value);
        }
        if ((value = props.getProperty("perst.file.readonly")) != null) { 
            readOnly = getBooleanValue(value);
        }
        if ((value = props.getProperty("perst.file.noflush")) != null) { 
            noFlush = getBooleanValue(value);
        }
        if ((value = props.getProperty("perst.alternative.btree")) != null) { 
            alternativeBtree = getBooleanValue(value);
        }
        if ((value = props.getProperty("perst.background.gc")) != null) { 
            backgroundGc = getBooleanValue(value);
        }
        if ((value = props.getProperty("perst.string.encoding")) != null) { 
            encoding = value;
        }
        if ((value = props.getProperty("perst.lock.file")) != null) { 
            lockFile = getBooleanValue(value);
        }
        if ((value = props.getProperty("perst.replication.ack")) != null) { 
            replicationAck = getBooleanValue(value);
        }
        if ((value = props.getProperty("perst.concurrent.iterator")) != null) { 
            concurrentIterator = getBooleanValue(value);
        }
        if ((value = props.getProperty("perst.slave.connection.timeout")) != null) { 
            slaveConnectionTimeout = (int)getIntegerValue(value);
        }
        if ((value = props.getProperty("perst.force.store")) != null) { 
            forceStore = getBooleanValue(value);
        }
        if ((value = props.getProperty("perst.page.pool.lru.limit")) != null) { 
            pagePoolLruLimit = getIntegerValue(value);
        }
        if ((value = props.getProperty("perst.multiclient.support")) != null) { 
            multiclientSupport = getBooleanValue(value);
        }
        if (multiclientSupport && backgroundGc) { 
            throw new IllegalArgumentException("In mutliclient access mode bachround GC is not supported");
        }
    }

    public void setProperty(String name, Object value)
    {
        properties.put(name, value);
        if (name.equals("perst.implicit.values")) { 
            ClassDescriptor.treateAnyNonPersistentObjectAsValue = getBooleanValue(value);
        } else if (name.equals("perst.serialize.transient.objects")) { 
            ClassDescriptor.serializeNonPersistentObjects = getBooleanValue(value);
        } else if (name.equals("perst.object.cache.init.size")) { 
            objectCacheInitSize = (int)getIntegerValue(value);
            if (objectCacheInitSize <= 0) { 
                throw new IllegalArgumentException("Initial object cache size should be positive");
            }            
        } else if (name.equals("perst.object.cache.kind")) { 
            cacheKind = (String)value;
        } else if (name.equals("perst.object.index.init.size")) { 
            initIndexSize = (int)getIntegerValue(value);
        } else if (name.equals("perst.extension.quantum")) { 
            extensionQuantum = getIntegerValue(value);
        } else if (name.equals("perst.gc.threshold")) { 
            gcThreshold = getIntegerValue(value);
        } else if (name.equals("perst.file.readonly")) { 
            readOnly = getBooleanValue(value);
        } else if (name.equals("perst.file.noflush")) { 
            noFlush = getBooleanValue(value);
        } else if (name.equals("perst.alternative.btree")) { 
            alternativeBtree = getBooleanValue(value);
        } else if (name.equals("perst.background.gc")) {
            backgroundGc = getBooleanValue(value);
        } else if (name.equals("perst.string.encoding")) { 
            encoding = (value == null) ? null : value.toString();
        } else if (name.equals("perst.lock.file")) { 
            lockFile = getBooleanValue(value);
        } else if (name.equals("perst.replication.ack")) { 
            replicationAck = getBooleanValue(value);
        } else if (name.equals("perst.concurrent.iterator")) { 
            concurrentIterator = getBooleanValue(value);
        } else if (name.equals("perst.slave.connection.timeout")) { 
            slaveConnectionTimeout = (int)getIntegerValue(value);
        } else if (name.equals("perst.force.store")) { 
            forceStore = getBooleanValue(value);
        } else if (name.equals("perst.page.pool.lru.limit")) {
            pagePoolLruLimit = getIntegerValue(value);
        } else if (name.equals("perst.multiclient.support")) { 
            multiclientSupport = getBooleanValue(value);
        } else { 
            throw new StorageError(StorageError.NO_SUCH_PROPERTY);
        }
        if (multiclientSupport && backgroundGc) { 
            throw new IllegalArgumentException("In mutliclient access mode bachround GC is not supported");
        }
    }

    public Object getProperty(String name)
    {
        return properties.get(name);
    }

    public Properties getProperties()
    {
        return properties;
    }
    

    public StorageListener setListener(StorageListener listener)
    {
        StorageListener prevListener = this.listener;
        this.listener = listener;
        return prevListener;
    }

    public synchronized IPersistent getObjectByOID(int oid)
    {
        return oid == 0 ? null : lookupObject(oid, null);
    }

    public/*protected*/ synchronized void modifyObject(IPersistent obj) {
        synchronized(objectCache) { 
            if (!obj.isModified()) { 
                if (useSerializableTransactions) { 
                    ThreadTransactionContext ctx = getTransactionContext();
                    if (ctx.nested != 0) { // serializable transaction
                        ctx.modified.add(obj);
                        return;
                    }
                }
                objectCache.setDirty(obj);
            }
        }
    }
    
    public/*protected*/ synchronized void storeObject(IPersistent obj) 
    {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        synchronized (objectCache) { 
            storeObject0(obj, false);
        }
    }

    public/*protected*/ void storeFinalizedObject(IPersistent obj) 
    {
        if (opened) { 
            synchronized (objectCache) { 
                if (obj.getOid() != 0) { 
                    storeObject0(obj, true);
                }
            }
        }
    }

    public synchronized int makePersistent(IPersistent obj) 
    {
        if (!opened) { 
            throw new StorageError(StorageError.STORAGE_NOT_OPENED);
        }
        if (obj == null) {
            return 0;
        }
        int oid = obj.getOid();
        if (oid != 0) {
            return oid;
        }
        if (forceStore && (!useSerializableTransactions || getTransactionContext().nested == 0)) {
            synchronized (objectCache) { 
                storeObject0(obj, false);
            }
            return obj.getOid();
        } else { 
            synchronized (objectCache) {
                oid = allocateId();
                obj.assignOid(this, oid, false);
                setPos(oid, 0);
                objectCache.put(oid, obj);
                obj.modify();
                return oid;
            }
        }
    }

    private final CustomAllocator getCustomAllocator(Class cls) { 
        Object a = customAllocatorMap.get(cls);
        if (a != null) { 
            return a == defaultAllocator ? null : (CustomAllocator)a;
        }
        Class superclass = cls.getSuperclass();
        if (superclass != null) { 
            CustomAllocator alloc = getCustomAllocator(superclass);
            if (alloc != null) { 
                customAllocatorMap.put(cls, alloc);
                return alloc;
            }
        }
        Class[] interfaces = cls.getInterfaces();
        for (int i = 0; i < interfaces.length; i++) { 
            CustomAllocator alloc = getCustomAllocator(interfaces[i]);
            if (alloc != null) { 
                customAllocatorMap.put(cls, alloc);
                return alloc;
            }
        }
        customAllocatorMap.put(cls, defaultAllocator);
        return null;
    }

    private final void storeObject0(IPersistent obj, boolean finalized) 
    {
        obj.onStore();
        int oid = obj.getOid();
        boolean newObject = false;
        if (oid == 0) { 
            oid = allocateId();
            if (!finalized) { 
                objectCache.put(oid, obj);
            }
            obj.assignOid(this, oid, false);
            newObject = true;
        } else if (obj.isModified()) {
            objectCache.clearDirty(obj);
        }
        byte[] data = packObject(obj, finalized);
        long pos;
        int newSize = ObjectHeader.getSize(data, 0);
        CustomAllocator allocator = (customAllocatorMap != null) ? getCustomAllocator(obj.getClass()) : null;
        if (newObject || (pos = getPos(oid)) == 0) { 
            pos = allocator != null ? allocator.allocate(newSize) : allocate(newSize, 0);
            setPos(oid, pos | dbModifiedFlag);
        } else {
            int offs = (int)pos & (Page.pageSize-1);
            if ((offs & (dbFreeHandleFlag|dbPageObjectFlag)) != 0) { 
                throw new StorageError(StorageError.DELETED_OBJECT);
            }
            Page pg = pool.getPage(pos - offs);
            int size = ObjectHeader.getSize(pg.data, offs & ~dbFlagsMask);
            pool.unfix(pg);
            if ((pos & dbModifiedFlag) == 0) { 
                if (allocator != null) { 
                    allocator.free(pos & ~dbFlagsMask, size);
                    pos = allocator.allocate(newSize);
                } else { 
                    cloneBitmap(pos & ~dbFlagsMask, size);
                    pos = allocate(newSize, 0);
                }
                setPos(oid, pos | dbModifiedFlag);
            } else {
                pos &= ~dbFlagsMask;
                if (newSize != size) { 
                    if (allocator != null) {
                        long newPos = allocator.reallocate(pos, size, newSize);
                        if (newPos != pos) { 
                            pos = newPos;
                            setPos(oid, pos | dbModifiedFlag);                   
                        } else if (newSize < size) { 
                            ObjectHeader.setSize(data, 0, size);
                        }
                    } else { 
                        if (((newSize + dbAllocationQuantum - 1) & ~(dbAllocationQuantum-1))
                            > ((size + dbAllocationQuantum - 1) & ~(dbAllocationQuantum-1)))
                        { 
                            long newPos = allocate(newSize, 0);
                            cloneBitmap(pos, size);
                            free(pos, size);
                            pos = newPos;
                            setPos(oid, pos | dbModifiedFlag);
                        } else if (newSize < size) { 
                            ObjectHeader.setSize(data, 0, size);
                        }
                    }
                }
            }
        }        
        modified = true;
        pool.put(pos, data, newSize);
    }

    public/*protected*/ synchronized void loadObject(IPersistent obj) {
        if (obj.isRaw()) { 
            loadStub(obj.getOid(), obj, obj.getClass());
        }
    }

    final synchronized IPersistent lookupObject(int oid, Class cls) {
        IPersistent obj = objectCache.get(oid);
        if (obj == null || obj.isRaw()) { 
            obj = loadStub(oid, obj, cls);
        }
        return obj;
    }
 
    protected int swizzle(IPersistent obj, boolean finalized) { 
        int oid = 0;
        if (obj != null) { 
            if (!obj.isPersistent()) { 
                storeObject0(obj, finalized);
            }
            oid = obj.getOid();
        }
        return oid;
    }
        
    final ClassDescriptor findClassDescriptor(int oid) { 
        return (ClassDescriptor)lookupObject(oid, ClassDescriptor.class);
    }

    protected IPersistent unswizzle(int oid, Class cls, boolean recursiveLoading) { 
        if (oid == 0) { 
            return null;
        } 
        if (recursiveLoading) {
            return lookupObject(oid, cls);
        }
        IPersistent stub = objectCache.get(oid);
        if (stub != null) { 
            return stub;
        }
        ClassDescriptor desc;
        if (cls == Persistent.class
            || (desc = findClassDescriptor(cls)) == null
            || desc.hasSubclasses) 
        { 
            long pos = getPos(oid);
            int offs = (int)pos & (Page.pageSize-1);
            if ((offs & (dbFreeHandleFlag|dbPageObjectFlag)) != 0) { 
                throw new StorageError(StorageError.DELETED_OBJECT);
            }
            Page pg = pool.getPage(pos - offs);
            int typeOid = ObjectHeader.getType(pg.data, offs & ~dbFlagsMask);
            pool.unfix(pg);
            desc = findClassDescriptor(typeOid);
        }
        stub = (IPersistent)desc.newInstance();
        stub.assignOid(this, oid, true);
        objectCache.put(oid, stub);
        return stub;
    }

    final IPersistent loadStub(int oid, IPersistent obj, Class cls)
    {
        long pos = getPos(oid);
        if ((pos & (dbFreeHandleFlag|dbPageObjectFlag)) != 0) { 
            throw new StorageError(StorageError.DELETED_OBJECT);
        }
        byte[] body = pool.get(pos & ~dbFlagsMask);
        ClassDescriptor desc;
        int typeOid = ObjectHeader.getType(body, 0);
        if (typeOid == 0) { 
            desc = findClassDescriptor(cls);
        } else { 
            desc = findClassDescriptor(typeOid);
        }
        if (obj == null) { 
            obj = (IPersistent)desc.newInstance();
            objectCache.put(oid, obj);
        }
        obj.assignOid(this, oid, false);
        if (obj instanceof FastSerializable) { 
            ((FastSerializable)obj).unpack(body, ObjectHeader.sizeof, encoding);
        } else { 
            try { 
                unpackObject(obj, desc, obj.recursiveLoading(), body, ObjectHeader.sizeof, obj);
            } catch (Exception x) { 
                throw new StorageError(StorageError.ACCESS_VIOLATION, x);
            }
        }
        obj.onLoad();
        return obj;
    }

    class PersistentObjectInputStream extends ObjectInputStream { 
        PersistentObjectInputStream(InputStream in) throws IOException {
            super(in);
            enableResolveObject(true);
        }
        
        protected Object resolveObject(Object obj) throws IOException {
            if (obj instanceof IPersistent) { 
                int oid = ((IPersistent)obj).getOid();
                if (oid != 0) { 
                    return lookupObject(oid, obj.getClass());
                }
            }
            return obj;
        }
        
        protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
            String classLoaderName = null;
            if (loaderMap != null) { 
                classLoaderName = (String)readObject();
            }
            ClassLoader cl = (classLoaderName != null) 
                ? findClassLoader(classLoaderName) : loader;
            if (cl != null) { 
                try { 
                    return Class.forName(desc.getName(), false, cl);
                } catch (ClassNotFoundException x) {}
            } 
            return super.resolveClass(desc);
        }
    }

    public class PersistentObjectOutputStream extends ObjectOutputStream { 
        PersistentObjectOutputStream(OutputStream out) throws IOException {
            super(out);
        }
        
        public Storage getStorage() { 
            return StorageImpl.this;
        }
    }

    public class AnnotatedPersistentObjectOutputStream extends PersistentObjectOutputStream { 
        AnnotatedPersistentObjectOutputStream(OutputStream out) throws IOException {
            super(out);
        }

        protected void annotateClass(Class cls) throws IOException {
            ClassLoader loader = cls.getClassLoader();
            writeObject((loader instanceof INamedClassLoader) 
                        ? ((INamedClassLoader)loader).getName() : null);
        }
    }       

    final int unpackObject(Object obj, ClassDescriptor desc, boolean recursiveLoading, byte[] body, int offs, IPersistent po) 
      throws Exception
    {
        ClassDescriptor.FieldDescriptor[] all = desc.allFields;
        ReflectionProvider provider = ClassDescriptor.getReflectionProvider();
        int len;

        for (int i = 0, n = all.length; i < n; i++) { 
            ClassDescriptor.FieldDescriptor fd = all[i];
            Field f = fd.field;

            if (f == null || obj == null) { 
                switch (fd.type) { 
                case ClassDescriptor.tpBoolean:
                case ClassDescriptor.tpByte:
                    offs += 1;
                    continue;
                case ClassDescriptor.tpChar:
                case ClassDescriptor.tpShort:
                    offs += 2;
                    continue;
                case ClassDescriptor.tpInt:
                case ClassDescriptor.tpFloat:
                case ClassDescriptor.tpObject:
                case ClassDescriptor.tpEnum:
                    offs += 4;
                    continue;
                case ClassDescriptor.tpLong:
                case ClassDescriptor.tpDouble:
                case ClassDescriptor.tpDate:
                    offs += 8;
                    continue;
                case ClassDescriptor.tpString:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len > 0) { 
                        offs += len*2;
                    } else if (len < -1) {
                        offs -= len+2;
                    } 
                    continue;
                case ClassDescriptor.tpValue:
                    offs = unpackObject(null, fd.valueDesc, recursiveLoading, body, offs, po);
                    continue;
                case ClassDescriptor.tpRaw:
                case ClassDescriptor.tpArrayOfByte:
                case ClassDescriptor.tpArrayOfBoolean:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len > 0) { 
                        offs += len;
                    } else if (len < -1) { 
                        offs += ClassDescriptor.sizeof[-2-len];
                    }
                    continue;
                case ClassDescriptor.tpCustom:
                {
                    ByteArrayInputStream in = new ByteArrayInputStream(body, offs, body.length - offs);
                    serializer.unpack(in);
                    offs = body.length - in.available();
                    continue;
                }
                case ClassDescriptor.tpArrayOfShort:
                case ClassDescriptor.tpArrayOfChar:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len > 0) { 
                        offs += len*2;
                    }
                    continue;
                case ClassDescriptor.tpArrayOfInt:
                case ClassDescriptor.tpArrayOfEnum:
                case ClassDescriptor.tpArrayOfFloat:
                case ClassDescriptor.tpArrayOfObject:
                case ClassDescriptor.tpLink:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len > 0) { 
                        offs += len*4;
                    }
                    continue;
                case ClassDescriptor.tpArrayOfLong:
                case ClassDescriptor.tpArrayOfDouble:
                case ClassDescriptor.tpArrayOfDate:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len > 0) { 
                        offs += len*8;
                    }
                    continue;
                case ClassDescriptor.tpArrayOfString:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len > 0) { 
                        for (int j = 0; j < len; j++) {
                            int strlen = Bytes.unpack4(body, offs);
                            offs += 4;
                            if (strlen > 0) {
                                offs += strlen*2;
                            } else if (strlen < -1) {
                                offs -= strlen+2;
                            }
                        }
                    }
                    continue;
                case ClassDescriptor.tpArrayOfValue:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len > 0) { 
                        ClassDescriptor valueDesc = fd.valueDesc;
                        for (int j = 0; j < len; j++) { 
                            offs = unpackObject(null, valueDesc, recursiveLoading, body, offs, po);
                        }
                    }
                    continue;
                case ClassDescriptor.tpArrayOfRaw:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len > 0) { 
                        for (int j = 0; j < len; j++) {
                            int rawlen = Bytes.unpack4(body, offs);
                            offs += 4;
                            if (rawlen > 0) {
                                offs += rawlen;
                            } else if (rawlen < -1) { 
                                offs += ClassDescriptor.sizeof[-2-rawlen];
                            }
                        }
                    }
                    continue;
                }                
            } else if (offs < body.length) {                 
                switch (fd.type) { 
                case ClassDescriptor.tpBoolean:
                    provider.setBoolean(f, obj, body[offs++] != 0);
                    continue;
                case ClassDescriptor.tpByte:
                    provider.setByte(f, obj, body[offs++]);
                    continue;
                case ClassDescriptor.tpChar:
                    provider.setChar(f, obj, (char)Bytes.unpack2(body, offs));
                    offs += 2;
                    continue;
                case ClassDescriptor.tpShort:
                    provider.setShort(f, obj, Bytes.unpack2(body, offs));
                    offs += 2;
                    continue;
                case ClassDescriptor.tpInt:
                    provider.setInt(f, obj, Bytes.unpack4(body, offs));
                    offs += 4;
                    continue;
                case ClassDescriptor.tpLong:
                    provider.setLong(f, obj, Bytes.unpack8(body, offs));
                    offs += 8;
                    continue;
                case ClassDescriptor.tpFloat:
                    provider.setFloat(f, obj, Float.intBitsToFloat(Bytes.unpack4(body, offs)));
                    offs += 4;
                    continue;
                case ClassDescriptor.tpDouble:
                    provider.setDouble(f, obj, Double.longBitsToDouble(Bytes.unpack8(body, offs)));
                    offs += 8;
                    continue;
                case ClassDescriptor.tpEnum:
                {
                    int index = Bytes.unpack4(body, offs);
                    if (index >= 0) {
                        provider.set(f, obj, fd.field.getType().getEnumConstants()[index]);
                    } else {
                        provider.set(f, obj, null);
                    }
                    offs += 4;
                    continue;
                }
                case ClassDescriptor.tpString:
                {
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    String str = null;
                    if (len >= 0) { 
                        char[] chars = new char[len];
                        for (int j = 0; j < len; j++) { 
                            chars[j] = (char)Bytes.unpack2(body, offs);
                            offs += 2;
                        }
                        str = new String(chars);
                    } else if (len < -1) {
                        if (encoding != null) { 
                            str = new String(body, offs, -2-len, encoding);
                        } else { 
                            str = new String(body, offs, -2-len);
                        }
                        offs -= 2+len;
                    } 
                    provider.set(f, obj, str);
                    continue;
                }
                case ClassDescriptor.tpDate:
                {
                    long msec = Bytes.unpack8(body, offs);
                    offs += 8;
                    Date date = null;
                    if (msec >= 0) { 
                        date = new Date(msec);
                    }
                    provider.set(f, obj, date);
                    continue;
                }
                case ClassDescriptor.tpObject:
                {
                    provider.set(f, obj, unswizzle(Bytes.unpack4(body, offs), f.getType(), recursiveLoading));
                    offs += 4;
                    continue;
                }
                case ClassDescriptor.tpValue:
                {
                    Object value = fd.valueDesc.newInstance();
                    offs = unpackObject(value, fd.valueDesc, recursiveLoading, body, offs ,po);
                    provider.set(f, obj, value);
                    continue;
                }
                case ClassDescriptor.tpRaw:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len >= 0) { 
                        ByteArrayInputStream bin = new ByteArrayInputStream(body, offs, len);
                        ObjectInputStream in = new PersistentObjectInputStream(bin);
                        provider.set(f, obj, in.readObject());
                        in.close();
                        offs += len;
                    } else if (len < 0) { 
                        Object val = null;
                        switch (-2-len) { 
                        case ClassDescriptor.tpBoolean:
                            val = Boolean.valueOf(body[offs++] != 0);
                            break;
                        case ClassDescriptor.tpByte:
                            val = new Byte(body[offs++]);
                            break;                            
                        case ClassDescriptor.tpChar:
                            val = new Character((char)Bytes.unpack2(body, offs));
                            offs += 2;
                            break;                            
                        case ClassDescriptor.tpShort:
                            val = new Short(Bytes.unpack2(body, offs));
                            offs += 2;
                            break;                            
                        case ClassDescriptor.tpInt:
                            val = new Integer(Bytes.unpack4(body, offs));
                            offs += 4;
                            break;                            
                        case ClassDescriptor.tpLong:
                            val = new Long(Bytes.unpack8(body, offs));
                            offs += 8;
                            break;                            
                        case ClassDescriptor.tpFloat:
                            val = new Float(Float.intBitsToFloat(Bytes.unpack4(body, offs)));
                            offs += 4;
                            break;                            
                        case ClassDescriptor.tpDouble:
                            val = new Double(Double.longBitsToDouble(Bytes.unpack8(body, offs)));
                            offs += 8;
                            break;                            
                        case ClassDescriptor.tpDate:
                            val = new Date(Bytes.unpack8(body, offs));
                            offs += 8;
                            break;                                                       
                        case ClassDescriptor.tpObject:
                            val = unswizzle(Bytes.unpack4(body, offs), Persistent.class, recursiveLoading);
                            offs += 4;
                        }
                        provider.set(f, obj, val);
                    }
                    continue;
                case ClassDescriptor.tpCustom:
                {
                    ByteArrayInputStream in = new ByteArrayInputStream(body, offs, body.length - offs);
                    provider.set(f, obj, serializer.unpack(in));
                    offs = body.length - in.available();
                    continue;
                }
                case ClassDescriptor.tpArrayOfByte:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        byte[] arr = new byte[len];
                        System.arraycopy(body, offs, arr, 0, len);
                        offs += len;
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfBoolean:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        boolean[] arr = new boolean[len];
                        for (int j = 0; j < len; j++) { 
                            arr[j] = body[offs++] != 0;
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfShort:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        short[] arr = new short[len];
                        for (int j = 0; j < len; j++) { 
                            arr[j] = Bytes.unpack2(body, offs);
                            offs += 2;
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfChar:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        char[] arr = new char[len];
                        for (int j = 0; j < len; j++) { 
                            arr[j] = (char)Bytes.unpack2(body, offs);
                            offs += 2;
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfInt:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        int[] arr = new int[len];
                        for (int j = 0; j < len; j++) { 
                            arr[j] = Bytes.unpack4(body, offs);
                            offs += 4;
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfEnum:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        f.set(obj, null);
                    } else {
                        Class elemType = f.getType().getComponentType();
                        Enum[] enumConstants = (Enum[])elemType.getEnumConstants();
                        Enum[] arr = (Enum[])Array.newInstance(elemType, len);
                        for (int j = 0; j < len; j++) { 
                            int index = Bytes.unpack4(body, offs);
                            if (index >= 0) {
        arr[j] = enumConstants[index];
          }
                            offs += 4;
                        }
                        f.set(obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfLong:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        long[] arr = new long[len];
                        for (int j = 0; j < len; j++) { 
                            arr[j] = Bytes.unpack8(body, offs);
                            offs += 8;
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfFloat:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        float[] arr = new float[len];
                        for (int j = 0; j < len; j++) { 
                            arr[j] = Float.intBitsToFloat(Bytes.unpack4(body, offs));
                            offs += 4;
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfDouble:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        double[] arr = new double[len];
                        for (int j = 0; j < len; j++) { 
                            arr[j] = Double.longBitsToDouble(Bytes.unpack8(body, offs));
                            offs += 8;
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfDate:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        Date[] arr = new Date[len];
                        for (int j = 0; j < len; j++) { 
                            long msec = Bytes.unpack8(body, offs);
                            offs += 8;
                            if (msec >= 0) { 
                                arr[j] = new Date(msec);
                            }
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfString:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        String[] arr = new String[len];
                        for (int j = 0; j < len; j++) {
                            int strlen = Bytes.unpack4(body, offs);
                            offs += 4;
                            if (strlen >= 0) {
                                char[] chars = new char[strlen];
                                for (int k = 0; k < strlen; k++) { 
                                    chars[k] = (char)Bytes.unpack2(body, offs);
                                    offs += 2;
                                }
                                arr[j] = new String(chars);
                            } else if (strlen < -1) {
                                if (encoding != null) { 
                                    arr[j] = new String(body, offs, -2-strlen, encoding);
                                } else {
                                    arr[j] = new String(body, offs, -2-strlen);
                                }
                                offs -= 2+strlen;
                            }
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfObject:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        Class elemType = f.getType().getComponentType();
                        IPersistent[] arr = (IPersistent[])Array.newInstance(elemType, len);
                        for (int j = 0; j < len; j++) { 
                            arr[j] = unswizzle(Bytes.unpack4(body, offs), elemType, recursiveLoading);
                            offs += 4;
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfValue:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        Class elemType = f.getType().getComponentType();
                        Object[] arr = (Object[])Array.newInstance(elemType, len);
                        ClassDescriptor valueDesc = fd.valueDesc;
                        for (int j = 0; j < len; j++) { 
                            Object value = valueDesc.newInstance();
                            offs = unpackObject(value, valueDesc, recursiveLoading, body, offs, po);
                            arr[j] = value;
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpArrayOfRaw:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        Class elemType = f.getType().getComponentType();
                        Object[] arr = (Object[])Array.newInstance(elemType, len);
                        for (int j = 0; j < len; j++) { 
                            int rawlen = Bytes.unpack4(body, offs);
                            offs += 4;
                            if (rawlen >= 0) {
                                ByteArrayInputStream bin = new ByteArrayInputStream(body, offs, rawlen);
                                ObjectInputStream in = new PersistentObjectInputStream(bin);
                                arr[j] = in.readObject();
                                in.close();
                                offs += rawlen;
                            } else {
                                Object val = null;
                                switch (-2-rawlen) { 
                                case ClassDescriptor.tpBoolean:
                                    val = Boolean.valueOf(body[offs++] != 0);
                                    break;
                                case ClassDescriptor.tpByte:
                                    val = new Byte(body[offs++]);
                                    break;                            
                                case ClassDescriptor.tpChar:
                                    val = new Character((char)Bytes.unpack2(body, offs));
                                    offs += 2;
                                    break;                            
                                case ClassDescriptor.tpShort:
                                    val = new Short(Bytes.unpack2(body, offs));
                                    offs += 2;
                                    break;                            
                                case ClassDescriptor.tpInt:
                                    val = new Integer(Bytes.unpack4(body, offs));
                                    offs += 4;
                                    break;                            
                                case ClassDescriptor.tpLong:
                                    val = new Long(Bytes.unpack8(body, offs));
                                    offs += 8;
                                    break;                            
                                case ClassDescriptor.tpFloat:
                                    val = new Float(Float.intBitsToFloat(Bytes.unpack4(body, offs)));
                                    offs += 4;
                                    break;                            
                                case ClassDescriptor.tpDouble:
                                    val = new Double(Double.longBitsToDouble(Bytes.unpack8(body, offs)));
                                    offs += 8;
                                    break;                            
                                case ClassDescriptor.tpDate:
                                    val = new Date(Bytes.unpack8(body, offs));
                                    offs += 8;
                                    break;                                                       
                                case ClassDescriptor.tpObject:
                                    val = unswizzle(Bytes.unpack4(body, offs), Persistent.class, recursiveLoading);
                                    offs += 4;
                                }
                                arr[j] = val;
                            }
                        }
                        provider.set(f, obj, arr);
                    }
                    continue;
                case ClassDescriptor.tpLink:
                    len = Bytes.unpack4(body, offs);
                    offs += 4;
                    if (len < 0) { 
                        provider.set(f, obj, null);
                    } else {
                        IPersistent[] arr = new IPersistent[len];
                        for (int j = 0; j < len; j++) { 
                            int elemOid = Bytes.unpack4(body, offs);
                            offs += 4;
                            if (elemOid != 0) { 
                                arr[j] = new PersistentStub(this, elemOid);
                            }
                        }
                        provider.set(f, obj, new LinkImpl(arr, po));
                    }
                }
            }
        }
        return offs;
    }


    final byte[] packObject(IPersistent obj, boolean finalized) { 
        ByteBuffer buf = new ByteBuffer();
        int offs = ObjectHeader.sizeof;
        buf.extend(offs);
        ClassDescriptor desc = getClassDescriptor(obj.getClass());
        if (obj instanceof FastSerializable) { 
            offs = ((FastSerializable)obj).pack(buf, offs, encoding);
        } else { 
            try {
                offs = packObject(obj, desc, offs, buf, obj, finalized);
            } catch (Exception x) { 
                throw new StorageError(StorageError.ACCESS_VIOLATION, x);
            }        
        }
        ObjectHeader.setSize(buf.arr, 0, offs);
        ObjectHeader.setType(buf.arr, 0, desc.getOid());
        return buf.arr;        
    }

    final int packValue(Object value, int offs, ByteBuffer buf, boolean finalized) throws Exception {
        if (value == null) { 
            buf.extend(offs + 4);
            Bytes.pack4(buf.arr, offs, -1);
            offs += 4;
        } else if (value instanceof IPersistent) { 
            buf.extend(offs + 8);
            Bytes.pack4(buf.arr, offs, -2-ClassDescriptor.tpObject);
            Bytes.pack4(buf.arr, offs+4, swizzle((IPersistent)value, finalized));
            offs += 8;                        
        } else { 
            Class c = value.getClass();
            if (c == Boolean.class) { 
                buf.extend(offs + 5);
                Bytes.pack4(buf.arr, offs, -2-ClassDescriptor.tpBoolean);
                buf.arr[offs+4] = (byte)(((Boolean)value).booleanValue() ? 1 : 0);
                offs += 5;                        
            } else if (c == Character.class) { 
                buf.extend(offs + 6);
                Bytes.pack4(buf.arr, offs, -2-ClassDescriptor.tpChar);
                Bytes.pack2(buf.arr, offs+4, (short)((Character)value).charValue());
                offs += 6;                                                   
            } else if (c == Byte.class) { 
                buf.extend(offs + 5);
                Bytes.pack4(buf.arr, offs, -2-ClassDescriptor.tpByte);
                buf.arr[offs+4] = ((Byte)value).byteValue();
                offs += 5;                        
            } else if (c == Short.class) { 
                buf.extend(offs + 6);
                Bytes.pack4(buf.arr, offs, -2-ClassDescriptor.tpShort);
                Bytes.pack2(buf.arr, offs+4, ((Short)value).shortValue());
                offs += 6;                                                   
            } else if (c == Integer.class) { 
                buf.extend(offs + 8);
                Bytes.pack4(buf.arr, offs, -2-ClassDescriptor.tpInt);
                Bytes.pack4(buf.arr, offs+4, ((Integer)value).intValue());
                offs += 8;                                                   
            } else if (c == Long.class) { 
                buf.extend(offs + 12);
                Bytes.pack4(buf.arr, offs, -2-ClassDescriptor.tpLong);
                Bytes.pack8(buf.arr, offs+4, ((Long)value).longValue());
                offs += 12;                                                   
            } else if (c == Float.class) { 
                buf.extend(offs + 8);
                Bytes.pack4(buf.arr, offs, -2-ClassDescriptor.tpFloat);
                Bytes.pack4(buf.arr, offs+4, Float.floatToIntBits(((Float)value).floatValue()));
                offs += 8;                                                   
            } else if (c == Double.class) { 
                buf.extend(offs + 12);
                Bytes.pack4(buf.arr, offs, -2-ClassDescriptor.tpDouble);
                Bytes.pack8(buf.arr, offs+4, Double.doubleToLongBits(((Double)value).doubleValue()));
                offs += 12;                                                   
            } else if (c == Date.class) { 
                buf.extend(offs + 12);
                Bytes.pack4(buf.arr, offs, -2-ClassDescriptor.tpDate);
                Bytes.pack8(buf.arr, offs+4, ((Date)value).getTime());
                offs += 12;                                                   
            } else {
                ByteArrayOutputStream bout = new ByteArrayOutputStream();
                ObjectOutputStream out = loaderMap != null
                    ? (ObjectOutputStream)new AnnotatedPersistentObjectOutputStream(bout)
                    : (ObjectOutputStream)new PersistentObjectOutputStream(bout);
                out.writeObject(value);
                out.close();
                byte[] arr = bout.toByteArray();
                int len = arr.length;                        
                buf.extend(offs + 4 + len);
                Bytes.pack4(buf.arr, offs, len);
                offs += 4;
                System.arraycopy(arr, 0, buf.arr, offs, len);
                offs += len;
            }
        }
        return offs;
    }


    final int packObject(Object obj, ClassDescriptor desc, int offs, ByteBuffer buf, IPersistent po, boolean finalized) 
        throws Exception 
    { 
        ClassDescriptor.FieldDescriptor[] flds = desc.allFields;
        for (int i = 0, n = flds.length; i < n; i++) {
            ClassDescriptor.FieldDescriptor fd = flds[i];
            Field f = fd.field;
            switch(fd.type) {
                case ClassDescriptor.tpByte:
                    buf.extend(offs + 1);
                    buf.arr[offs++] = f.getByte(obj);
                    continue;
                case ClassDescriptor.tpBoolean:
                    buf.extend(offs + 1);
                    buf.arr[offs++] = (byte)(f.getBoolean(obj) ? 1 : 0);
                    continue;
                case ClassDescriptor.tpShort:
                    buf.extend(offs + 2);
                    Bytes.pack2(buf.arr, offs, f.getShort(obj));
                    offs += 2;
                    continue;
                case ClassDescriptor.tpChar:
                    buf.extend(offs + 2);
                    Bytes.pack2(buf.arr, offs, (short)f.getChar(obj));
                    offs += 2;
                    continue;
                case ClassDescriptor.tpInt:
                    buf.extend(offs + 4);
                    Bytes.pack4(buf.arr, offs, f.getInt(obj));
                    offs += 4;
                    continue;
                case ClassDescriptor.tpLong:
                    buf.extend(offs + 8);
                    Bytes.pack8(buf.arr, offs, f.getLong(obj));
                    offs += 8;
                    continue;
                case ClassDescriptor.tpFloat:
                    buf.extend(offs + 4);
                    Bytes.pack4(buf.arr, offs, Float.floatToIntBits(f.getFloat(obj)));
                    offs += 4;
                    continue;
                case ClassDescriptor.tpDouble:
                    buf.extend(offs + 8);
                    Bytes.pack8(buf.arr, offs, Double.doubleToLongBits(f.getDouble(obj)));
                    offs += 8;
                    continue;
                case ClassDescriptor.tpEnum:
    {
                    Enum e = (Enum)f.get(obj);
                    buf.extend(offs + 4);
                    if (e == null) {
                        Bytes.pack4(buf.arr, offs, -1);
                    } else {
                        Bytes.pack4(buf.arr, offs, e.ordinal());
                    }
                    offs += 4;
                    continue;
    }
                case ClassDescriptor.tpDate:
                {
                    buf.extend(offs + 8);
                    Date d = (Date)f.get(obj);
                    long msec = (d == null) ? -1 : d.getTime();                
                    Bytes.pack8(buf.arr, offs, msec);
                    offs += 8;
                    continue;
                }
                case ClassDescriptor.tpString:
                    offs = buf.packString(offs, (String)f.get(obj), encoding);
                    continue;
                case ClassDescriptor.tpObject:
                {
                    buf.extend(offs + 4);
                    Bytes.pack4(buf.arr, offs, swizzle((IPersistent)f.get(obj), finalized));
                    offs += 4;
                    continue;
                }
                case ClassDescriptor.tpValue:
                {
                    Object value = f.get(obj);
                    if (value == null) { 
                        throw new StorageError(StorageError.NULL_VALUE, fd.fieldName);
                    } else if (value instanceof IPersistent) { 
                        throw new StorageError(StorageError.SERIALIZE_PERSISTENT);
                    }                        
                    offs = packObject(value, fd.valueDesc, offs, buf, po, finalized);
                    continue;
                }
                case ClassDescriptor.tpRaw:
                    offs = packValue(f.get(obj), offs, buf, finalized);
                    continue;
                case ClassDescriptor.tpCustom:
                {
                    serializer.pack((CustomSerializable)f.get(obj), buf.getOutputStream());
                    offs = buf.size();
                    continue;
                }
                case ClassDescriptor.tpArrayOfByte:
                {
                    byte[] arr = (byte[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        System.arraycopy(arr, 0, buf.arr, offs, len);
                        offs += len;
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfBoolean:
                {
                    boolean[] arr = (boolean[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++, offs++) {
                            buf.arr[offs] = (byte)(arr[j] ? 1 : 0);
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfShort:
                {
                    short[] arr = (short[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len*2);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            Bytes.pack2(buf.arr, offs, arr[j]);
                            offs += 2;
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfChar:
                {
                    char[] arr = (char[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len*2);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            Bytes.pack2(buf.arr, offs, (short)arr[j]);
                            offs += 2;
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfInt:
                {
                    int[] arr = (int[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len*4);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            Bytes.pack4(buf.arr, offs, arr[j]);
                            offs += 4;
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfEnum:
                {
                    Enum[] arr = (Enum[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len*4);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            if (arr[j] == null) {
                                Bytes.pack4(buf.arr, offs, -1);
                            } else {
                                Bytes.pack4(buf.arr, offs, arr[j].ordinal());
                            }
                            offs += 4;
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfLong:
                {
                    long[] arr = (long[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len*8);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            Bytes.pack8(buf.arr, offs, arr[j]);
                            offs += 8;
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfFloat:
                {
                    float[] arr = (float[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len*4);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            Bytes.pack4(buf.arr, offs, Float.floatToIntBits(arr[j]));
                            offs += 4;
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfDouble:
                {
                    double[] arr = (double[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len*8);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            Bytes.pack8(buf.arr, offs, Double.doubleToLongBits(arr[j]));
                            offs += 8;
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfDate:
                {
                    Date[] arr = (Date[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len*8);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            Date d = arr[j];
                            long msec = (d == null) ? -1 : d.getTime();                            
                            Bytes.pack8(buf.arr, offs, msec);
                            offs += 8;
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfString:
                {
                    String[] arr = (String[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            offs = buf.packString(offs, (String)arr[j], encoding);
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfObject:
                {
                    IPersistent[] arr = (IPersistent[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4 + len*4);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            Bytes.pack4(buf.arr, offs, swizzle(arr[j], finalized));
                            offs += 4;
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfValue:
                {
                    Object[] arr = (Object[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        ClassDescriptor elemDesc = fd.valueDesc;
                        for (int j = 0; j < len; j++) {
                            Object value = arr[j];
                            if (value == null) { 
                                throw new StorageError(StorageError.NULL_VALUE, fd.fieldName);
                            }
                            offs = packObject(value, elemDesc, offs, buf, po, finalized);
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpArrayOfRaw:
                {
                    Object[] arr = (Object[])f.get(obj);
                    if (arr == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        int len = arr.length;                        
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            offs = packValue(arr[j], offs, buf, finalized);
                        }
                    }
                    continue;
                }
                case ClassDescriptor.tpLink:
                {
                    LinkImpl link = (LinkImpl)f.get(obj);
                    if (link == null) { 
                        buf.extend(offs + 4);
                        Bytes.pack4(buf.arr, offs, -1);
                        offs += 4;
                    } else {
                        link.owner = po;
                        int len = link.size();                        
                        buf.extend(offs + 4 + len*4);
                        Bytes.pack4(buf.arr, offs, len);
                        offs += 4;
                        for (int j = 0; j < len; j++) {
                            Bytes.pack4(buf.arr, offs, swizzle(link.getRaw(j), finalized));
                            offs += 4;
                        }
                        if (!finalized) { 
                            link.unpin();
                        }
                    }
                    continue;
                }
            }
        }
        return offs;
    }
                
    public ClassLoader setClassLoader(ClassLoader loader) 
    { 
        ClassLoader prev = loader;
        this.loader = loader;
        return prev;
    }

    public ClassLoader getClassLoader() {
        return loader;
    }

    public void registerClassLoader(INamedClassLoader loader) { 
        if (loaderMap == null) { 
            loaderMap = new HashMap();
        }
        loaderMap.put(loader.getName(), loader);
    }

    public ClassLoader findClassLoader(String name) { 
        if (loaderMap == null) { 
            return null;
        }
        return (ClassLoader)loaderMap.get(name);
    }


    public void setCustomSerializer(CustomSerializer serializer) { 
        this.serializer = serializer;
    }

    class HashIterator implements PersistentIterator, Iterator
    {
        Iterator oids;

        HashIterator(HashSet result)
        {
            oids = result.iterator();
        }
             
        public Object next() { 
            return lookupObject(nextOid(), null);
        }

        public int nextOid() { 
            return ((Integer)oids.next()).intValue();
        }

        public boolean hasNext() { 
            return oids.hasNext();
        }

        public void remove() { 
            throw new UnsupportedOperationException();
        }
    }

    public Iterator merge(Iterator[] selections) {             
        HashSet result = null;
        for (int i = 0; i < selections.length; i++) { 
            Iterator iterator = selections[i];
            HashSet newResult = new HashSet();
            while (iterator.hasNext()) {
                Integer oid = new Integer(((PersistentIterator)iterator).nextOid());                
                if (result == null || result.contains(oid)) {
                    newResult.add(oid);
                }  
            }
            result = newResult;
            if (result.size() == 0) {
                break;
            }
        }
        if (result == null) {
            result = new HashSet();
        }
        return new HashIterator(result);     
    }

    public Iterator join(Iterator[] selections) {             
        HashSet result = new HashSet();
        for (int i = 0; i < selections.length; i++) { 
            Iterator iterator = selections[i];
            while (iterator.hasNext()) {
                result.add(new Integer(((PersistentIterator)iterator).nextOid()));
            }
        }
        return new HashIterator(result);     
    }

    public synchronized void registerCustomAllocator(Class cls, CustomAllocator allocator) {
        synchronized (objectCache) { 
            ClassDescriptor desc = getClassDescriptor(cls);
            desc.allocator = allocator;
            storeObject0(desc, false);
            if (customAllocatorMap == null) { 
                customAllocatorMap = new HashMap();
                customAllocatorList = new ArrayList();
            }
            customAllocatorMap.put(cls, desc.allocator);
            customAllocatorList.add(desc.allocator);
        }
    }


    public CustomAllocator createBitmapAllocator(int quantum, long base, long extension, long limit) { 
        return new BitmapCustomAllocator(this, quantum, base, extension, limit);
    }

    public int getDatabaseFormatVersion() { 
        return header.databaseFormatVersion;
    }

    private int     initIndexSize = dbDefaultInitIndexSize;
    private int     objectCacheInitSize = dbDefaultObjectCacheInitSize;
    private long    extensionQuantum = dbDefaultExtensionQuantum;
    private String  cacheKind = "lru";
    private boolean readOnly = false;
    private boolean noFlush = false;
    private boolean lockFile = false;
    private boolean multiclientSupport = false;
    private boolean alternativeBtree = false;
    private boolean backgroundGc = false;
    private boolean forceStore = true;
    private long    pagePoolLruLimit = dbDefaultPagePoolLruLimit;
    
    private HashMap   customAllocatorMap;
    private ArrayList customAllocatorList;
    private CustomAllocator defaultAllocator;

    boolean replicationAck = false;
    boolean concurrentIterator = false;
    int     slaveConnectionTimeout = 60; // seconds

    Properties properties = new Properties();

    String    encoding = null; 

    PagePool  pool;
    Header    header;           // base address of database file mapping
    int       dirtyPagesMap[];  // bitmap of changed pages in current index
    boolean   modified;

    int       currRBitmapPage;//current bitmap page for allocating records
    int       currRBitmapOffs;//offset in current bitmap page for allocating 
                              //unaligned records
    int       currPBitmapPage;//current bitmap page for allocating page objects
    int       currPBitmapOffs;//offset in current bitmap page for allocating 
                              //page objects
    Location  reservedChain;
    CloneNode cloneList;
    boolean   insideCloneBitmap;

    int       committedIndexSize;
    int       currIndexSize;

    int       currIndex;  // copy of header.root, used to allow read access to the database 
                          // during transaction commit
    long      usedSize;   // total size of allocated objects since the beginning of the session
    int[]     bitmapPageAvailableSpace;
    boolean   opened;

    int[]     greyBitmap; // bitmap of visited during GC but not yet marked object
    int[]     blackBitmap;    // bitmap of objects marked during GC 
    long      gcThreshold;
    long      allocatedDelta;
    boolean   gcDone;
    boolean   gcActive;
    Object    backgroundGcMonitor;
    Object    backgroundGcStartMonitor;
    GcThread  gcThread;

    int       bitmapExtentBase;

    ClassLoader loader;
    HashMap     loaderMap;

    CustomSerializer serializer;

    StorageListener listener;

    long      transactionId;
    IFile     file;

    int       nNestedTransactions;
    int       nBlockedTransactions;
    int       nCommittedTransactions;
    long      scheduledCommitTime;
    Object    transactionMonitor;
    PersistentResource transactionLock;

    final ThreadLocal transactionContext = new ThreadLocal() {
         protected synchronized Object initialValue() {
             return new ThreadTransactionContext();
         }
    };
    boolean useSerializableTransactions;


    OidHashTable     objectCache;
    HashMap          classDescMap;
    ClassDescriptor  descList;
}

class RootPage { 
    long size;            // database file size
    long index;           // offset to object index
    long shadowIndex;     // offset to shadow index
    long usedSize;        // size used by objects
    int  indexSize;       // size of object index
    int  shadowIndexSize; // size of object index
    int  indexUsed;       // userd part of the index   
    int  freeList;        // L1 list of free descriptors
    int  bitmapEnd;       // index of last allocated bitmap page
    int  rootObject;      // OID of root object
    int  classDescList;   // List of class descriptors
    int  bitmapExtent;    // Offset of extended bitmap pages in object index

    final static int sizeof = 64;
} 

class Header { 
    int      curr;  // current root
    boolean  dirty; // database was not closed normally
    byte     databaseFormatVersion;

    RootPage root[];
     long     transactionId;
   
    final static int sizeof = 3 + RootPage.sizeof*2 + 8;
    
    final void pack(byte[] rec) { 
        int offs = 0;
        rec[offs++] = (byte)curr;
        rec[offs++] = (byte)(dirty ? 1 : 0);
        rec[offs++] = databaseFormatVersion;
        for (int i = 0; i < 2; i++) { 
            Bytes.pack8(rec, offs, root[i].size);
            offs += 8;
            Bytes.pack8(rec, offs, root[i].index);
            offs += 8;
            Bytes.pack8(rec, offs, root[i].shadowIndex);
            offs += 8;
            Bytes.pack8(rec, offs, root[i].usedSize);
            offs += 8;
            Bytes.pack4(rec, offs, root[i].indexSize);
            offs += 4;
            Bytes.pack4(rec, offs, root[i].shadowIndexSize);
            offs += 4;
            Bytes.pack4(rec, offs, root[i].indexUsed);
            offs += 4;
            Bytes.pack4(rec, offs, root[i].freeList);
            offs += 4;
            Bytes.pack4(rec, offs, root[i].bitmapEnd);
            offs += 4;
            Bytes.pack4(rec, offs, root[i].rootObject);
            offs += 4;
            Bytes.pack4(rec, offs, root[i].classDescList);
            offs += 4;
            Bytes.pack4(rec, offs, root[i].bitmapExtent);
            offs += 4;
        }
        Bytes.pack8(rec, offs, transactionId);
        offs += 8;
        Assert.that(offs == sizeof);
    }
    
    final void unpack(byte[] rec) { 
        int offs = 0;
        curr = rec[offs++];
        dirty = rec[offs++] != 0;
        databaseFormatVersion = rec[offs++];
        root = new RootPage[2];
        for (int i = 0; i < 2; i++) { 
            root[i] = new RootPage();
            root[i].size = Bytes.unpack8(rec, offs);
            offs += 8;
            root[i].index = Bytes.unpack8(rec, offs);
            offs += 8;
            root[i].shadowIndex = Bytes.unpack8(rec, offs);
            offs += 8;
            root[i].usedSize = Bytes.unpack8(rec, offs);
            offs += 8;
            root[i].indexSize = Bytes.unpack4(rec, offs);
            offs += 4;
            root[i].shadowIndexSize = Bytes.unpack4(rec, offs);
            offs += 4;
            root[i].indexUsed = Bytes.unpack4(rec, offs);
            offs += 4;
            root[i].freeList = Bytes.unpack4(rec, offs);
            offs += 4;
            root[i].bitmapEnd = Bytes.unpack4(rec, offs);
            offs += 4;
            root[i].rootObject = Bytes.unpack4(rec, offs);
            offs += 4;
            root[i].classDescList = Bytes.unpack4(rec, offs);
            offs += 4;
            root[i].bitmapExtent = Bytes.unpack4(rec, offs);
            offs += 4;
        }
        transactionId = Bytes.unpack8(rec, offs);
        offs += 8;
        Assert.that(offs == sizeof);
    }   
}

java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.