All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mapdb.DB.kt Maven / Gradle / Ivy

Go to download

MapDB provides concurrent Maps, Sets and Queues backed by disk storage or off-heap memory. It is a fast, scalable and easy to use embedded Java database.

There is a newer version: 3.1.0
Show newest version
package org.mapdb

import com.google.common.cache.Cache
import com.google.common.cache.CacheBuilder
import org.eclipse.collections.api.map.primitive.MutableLongLongMap
import org.eclipse.collections.impl.list.mutable.primitive.LongArrayList
import org.mapdb.elsa.*
import org.mapdb.elsa.ElsaSerializerPojo.ClassInfo
import org.mapdb.serializer.GroupSerializer
import org.mapdb.serializer.GroupSerializerObjectArray
import java.io.Closeable
import java.io.DataInput
import java.io.DataOutput
import java.lang.ref.Reference
import java.lang.ref.WeakReference
import java.security.SecureRandom
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.locks.ReentrantReadWriteLock
import java.util.logging.Level

/**
 * A database with easy access to named maps and other collections.
 */
//TODO consistency lock
//TODO rename nemed object
//TODO delete named object
//TOOD metrics logger
open class DB(
        /** Stores all underlying data */
        private val store:Store,
        /** True if store existed before and was opened, false if store was created and is completely empty */
        protected val storeOpened:Boolean,
        override val isThreadSafe:Boolean = true,
        val classLoader:ClassLoader = Thread.currentThread().contextClassLoader,
        /** type of shutdown hook, 0 is disabled, 1 is hard ref, 2 is weak ref*/
        val shutdownHook:Int = 0
): Closeable, ConcurrencyAware {

    companion object{

        protected val NAME_CATALOG_SERIALIZER:Serializer> = object:Serializer>{

            override fun deserialize(input: DataInput2, available: Int): SortedMap? {
                val size = input.unpackInt()
                val ret = TreeMap()
                for(i in 0 until size){
                    ret.put(input.readUTF(), input.readUTF())
                }
                return ret
            }

            override fun serialize(out: DataOutput2, value: SortedMap) {
                out.packInt(value.size)
                value.forEach { e ->
                    out.writeUTF(e.key)
                    out.writeUTF(e.value)
                }
            }
        }

        protected val NAMED_SERIALIZATION_HEADER = 1

        /** list of DB objects to be closed */
        private val shutdownHooks = Collections.synchronizedMap(IdentityHashMap())

        private var shutdownHookInstalled = AtomicBoolean(false)

        protected fun addShutdownHook(ref:Any){
            if(shutdownHookInstalled.compareAndSet(false, true)){
                Runtime.getRuntime().addShutdownHook(object:Thread(){
                    override fun run() {
                        for(o in shutdownHooks.keys.toTypedArray()) { //defensive copy, DB.close() modifies the set
                            try {
                                var a = o
                                if (a is Reference<*>)
                                    a = a.get()
                                if (a is DB)
                                    a.close()
                            } catch(e: Throwable) {
                                //consume all exceptions from this DB object, so other DBs are also closed
                                Utils.LOG.log(Level.SEVERE, "DB.close() thrown exception in shutdown hook.", e)
                            }
                        }
                    }
                })
            }
            shutdownHooks.put(ref, ref)
        }

    }

    fun getStore():Store{
        checkNotClosed()
        return store
    }

    object Keys {
        val type = "#type"

        val keySerializer = "#keySerializer"
        val valueSerializer = "#valueSerializer"
        val serializer = "#serializer"

        val valueInline = "#valueInline"

        val counterRecids = "#counterRecids"

        val hashSeed = "#hashSeed"
        val segmentRecids = "#segmentRecids"

        val expireCreateTTL = "#expireCreateTTL"
        val expireUpdateTTL = "#expireUpdateTTL"
        val expireGetTTL = "#expireGetTTL"

        val expireCreateQueue = "#expireCreateQueue"
        val expireUpdateQueue = "#expireUpdateQueue"
        val expireGetQueue = "#expireGetQueue"


        val rootRecids = "#rootRecids"
        val rootRecid = "#rootRecid"
        /** concurrency shift, 1< = CacheBuilder.newBuilder().concurrencyLevel(1).weakValues().build()


    private val classSingletonCat = IdentityHashMap()
    private val classSingletonRev = HashMap()

    private val unknownClasses = Collections.synchronizedSet(HashSet>())

    private fun namedClasses() = arrayOf(BTreeMap::class.java, HTreeMap::class.java,
            HTreeMap.KeySet::class.java,
            BTreeMapJava.KeySet::class.java,
            Atomic.Integer::class.java,
            Atomic.Long::class.java,
            Atomic.String::class.java,
            Atomic.Boolean::class.java,
            Atomic.Var::class.java,
            IndexTreeList::class.java
    )

    private val nameSer = object:ElsaSerializerBase.Ser(){
        override fun serialize(out: DataOutput, value: Any, objectStack: ElsaStack?) {
            val name = getNameForObject(value)
                    ?: throw DBException.SerializationError("Could not serialize named object, it was not instantiated by this db")

            out.writeUTF(name)
        }
    }

    private val nameDeser = object:ElsaSerializerBase.Deser(){
        override fun deserialize(input: DataInput, objectStack: ElsaStack): Any? {
            val name = input.readUTF()
            return [email protected](name)
        }
    }

    private val elsaSerializer:ElsaSerializerPojo = ElsaSerializerPojo(
            0,
            pojoSingletons(),
            namedClasses().map { Pair(it, nameSer) }.toMap(),
            namedClasses().map { Pair(it, NAMED_SERIALIZATION_HEADER)}.toMap(),
            mapOf(Pair(NAMED_SERIALIZATION_HEADER, nameDeser)),
            ElsaClassCallback { unknownClasses.add(it) },
            object:ElsaClassInfoResolver {
                override fun classToId(className: String): Int {
                    val classInfos = loadClassInfos()
                    classInfos.forEachIndexed { i, classInfo ->
                        if(classInfo.name==className)
                            return i
                    }
                    return -1
                }

                override fun getClassInfo(classId: Int): ElsaSerializerPojo.ClassInfo? {
                    return loadClassInfos()[classId]
                }
            } )

    /**
     * Default serializer used if collection does not specify specialized serializer.
     * It uses Elsa Serializer.
     */
    val defaultSerializer = object: GroupSerializerObjectArray() {

        override fun deserialize(input: DataInput2, available: Int): Any? {
            return elsaSerializer.deserialize(input)
        }

        override fun serialize(out: DataOutput2, value: Any) {
            elsaSerializer.serialize(out, value)
        }

    }


    protected val classInfoSerializer = object : Serializer> {

        override fun serialize(out: DataOutput2, ci: Array) {
            out.packInt(ci.size)
            for(c in ci)
                elsaSerializer.classInfoSerialize(out, c)
        }

        override fun deserialize(input: DataInput2, available: Int): Array {
            return Array(input.unpackInt(), {
                elsaSerializer.classInfoDeserialize(input)
            })
        }

    }

    init{
        if(storeOpened.not()){
            //create new structure
            if(store.isReadOnly){
                throw DBException.WrongConfiguration("Can not create new store in read-only mode")
            }
            //preallocate 16 recids
            val nameCatalogRecid = store.put(TreeMap(), NAME_CATALOG_SERIALIZER)
            if(CC.RECID_NAME_CATALOG != nameCatalogRecid)
                throw DBException.WrongConfiguration("Store does not support Reserved Recids: "+store.javaClass)

            val classCatalogRecid = store.put(arrayOf(), classInfoSerializer)
            if(CC.RECID_CLASS_INFOS != classCatalogRecid)
                throw DBException.WrongConfiguration("Store does not support Reserved Recids: "+store.javaClass)


            for(recid in 3L..CC.RECID_MAX_RESERVED){
                val recid2 = store.put(null, Serializer.LONG_PACKED)
                if(recid!==recid2){
                    throw DBException.WrongConfiguration("Store does not support Reserved Recids: "+store.javaClass)
                }
            }
            store.commit()
        }

        val msgs = nameCatalogVerifyGetMessages().toList()
        if(!msgs.isEmpty())
            throw DBException.NewMapDBFormat("Name Catalog has some new unsupported features: "+msgs.toString());
    }


    init{
        //read all singleton from Serializer fields
        Serializer::class.java.declaredFields.forEach { f ->
            val name = Serializer::class.java.canonicalName + "#"+f.name
            val obj = f.get(null)
            classSingletonCat.put(obj, name)
            classSingletonRev.put(name, obj)
        }
        val defSerName = "org.mapdb.DB#defaultSerializer"
        classSingletonCat.put(defaultSerializer, defSerName)
        classSingletonRev.put(defSerName, defaultSerializer)

    }


    private val shutdownReference:Any? =
            when(shutdownHook){
                0 -> null
                1 -> this@DB
                2 -> WeakReference(this@DB)
                else -> throw IllegalArgumentException()
            }

    init{
        if(shutdownReference!=null){
            DB.addShutdownHook(shutdownReference)
        }
    }


    private fun pojoSingletons():Array{
        // NOTE !!! do not change index of any element!!!
        // it is storage format definition
        return arrayOf(
                this@DB, [email protected],
                Serializer.CHAR, Serializer.STRING_ORIGHASH , Serializer.STRING, Serializer.STRING_DELTA,
                Serializer.STRING_DELTA2, Serializer.STRING_INTERN, Serializer.STRING_ASCII, Serializer.STRING_NOSIZE,
                Serializer.LONG, Serializer.LONG_PACKED, Serializer.LONG_DELTA, Serializer.INTEGER,
                Serializer.INTEGER_PACKED, Serializer.INTEGER_DELTA, Serializer.BOOLEAN, Serializer.RECID,
                Serializer.RECID_ARRAY, Serializer.ILLEGAL_ACCESS, Serializer.BYTE_ARRAY, Serializer.BYTE_ARRAY_DELTA,
                Serializer.BYTE_ARRAY_DELTA2, Serializer.BYTE_ARRAY_NOSIZE, Serializer.CHAR_ARRAY, Serializer.INT_ARRAY,
                Serializer.LONG_ARRAY, Serializer.DOUBLE_ARRAY, Serializer.JAVA, Serializer.ELSA, Serializer.UUID,
                Serializer.BYTE, Serializer.FLOAT, Serializer.DOUBLE, Serializer.SHORT, Serializer.SHORT_ARRAY,
                Serializer.FLOAT_ARRAY, Serializer.BIG_INTEGER, Serializer.BIG_DECIMAL, Serializer.CLASS,
                Serializer.DATE,
                Collections.EMPTY_LIST,
                Collections.EMPTY_SET,
                Collections.EMPTY_MAP
        )

    }

    private fun loadClassInfos():Array{
        return store.get(CC.RECID_CLASS_INFOS, classInfoSerializer)!!
    }




    /** List of executors associated with this database. Those will be terminated on close() */
    protected val executors:MutableSet = Collections.synchronizedSet(LinkedHashSet())

    fun nameCatalogLoad():SortedMap {
        return Utils.lockRead(lock){
            checkNotClosed()
            nameCatalogLoadLocked()
        }

    }
    protected fun nameCatalogLoadLocked():SortedMap {
        if(CC.ASSERT)
            Utils.assertReadLock(lock)
        return store.get(CC.RECID_NAME_CATALOG, NAME_CATALOG_SERIALIZER)
                ?: throw DBException.WrongConfiguration("Could not open store, it has no Named Catalog");
    }

    fun nameCatalogSave(nameCatalog: SortedMap) {
        Utils.lockWrite(lock){
            checkNotClosed()
            nameCatalogSaveLocked(nameCatalog)
        }
    }

    protected fun nameCatalogSaveLocked(nameCatalog: SortedMap) {
        if(CC.ASSERT)
            Utils.assertWriteLock(lock)
        store.update(CC.RECID_NAME_CATALOG, nameCatalog, NAME_CATALOG_SERIALIZER)
    }


    private val nameRegex = "[A-Z0-9._-]".toRegex()

    protected fun checkName(name: String) {
        if(name.contains('#'))
            throw DBException.WrongConfiguration("Name contains illegal character, '#' is not allowed.")
        if(!name.matches(nameRegex))
            throw DBException.WrongConfiguration("Name contains illegal character")
    }

    protected fun nameCatalogGet(name: String): String? {
        return nameCatalogLoadLocked()[name]
    }


    fun  nameCatalogPutClass(
            nameCatalog: SortedMap,
            key: String,
            obj: Any
    ) {
        val value:String? = classSingletonCat[obj]

        if(value== null){
            //not in singletons, try to resolve public no ARG constructor of given class
            //TODO get public no arg constructor if exist
        }

        if(value!=null)
            nameCatalog.put(key, value)
    }

    fun  nameCatalogGetClass(
            nameCatalog: SortedMap,
            key: String
    ):E?{
        val clazz = nameCatalog.get(key)
                ?: return null

        val singleton = classSingletonRev.get(clazz)
        if(singleton!=null)
            return singleton as E

        throw DBException.WrongConfiguration("Could not load object: "+clazz)
    }

    fun nameCatalogParamsFor(name: String): Map {
        val ret = TreeMap()
        ret.putAll(nameCatalogLoad().filter {
            it.key.startsWith(name+"#")
        })
        return Collections.unmodifiableMap(ret)
    }


    private fun unknownClassesSave(){
        if(CC.ASSERT)
            Utils.assertWriteLock(lock)
        //TODO batch class dump
        unknownClasses.forEach {
            defaultSerializerRegisterClass_noLock(it)
        }
        unknownClasses.clear()
    }

    fun commit(){
        Utils.lockWrite(lock) {
            checkNotClosed()
            unknownClassesSave()
            store.commit()
        }
    }

    fun rollback(){
        if(store !is StoreTx)
            throw UnsupportedOperationException("Store does not support rollback")

        Utils.lockWrite(lock) {
            checkNotClosed()
            unknownClasses.clear()
            store.rollback()
        }
    }

    fun isClosed() = closed.get();

    override fun close(){
        if(closed.compareAndSet(false,true).not())
            return

        // do not close this DB from JVM shutdown hook
        if(shutdownReference!=null)
            shutdownHooks.remove(shutdownReference)

        Utils.lockWrite(lock) {
            unknownClassesSave()

            //shutdown running executors if any
            executors.forEach { it.shutdown() }
            //await termination on all
            executors.forEach {
                // TODO LOG this could use some warnings, if background tasks fails to shutdown
                while (!it.awaitTermination(1, TimeUnit.DAYS)) {
                }
            }
            executors.clear()
            store.close()
        }
    }

    fun  get(name:String):E{
        Utils.lockWrite(lock) {
            checkNotClosed()
            val type = nameCatalogGet(name + Keys.type)
            return when (type) {
                "HashMap" -> hashMap(name).open()
                "HashSet" -> hashSet(name).open()
                "TreeMap" -> treeMap(name).open()
                "TreeSet" -> treeSet(name).open()

                "AtomicBoolean" -> atomicBoolean(name).open()
                "AtomicInteger" -> atomicInteger(name).open()
                "AtomicVar" -> atomicVar(name).open()
                "AtomicString" -> atomicString(name).open()
                "AtomicLong" -> atomicLong(name).open()

                "IndexTreeList" -> indexTreeList(name).open()
                "IndexTreeLongLongMap" -> indexTreeLongLongMap(name).open()

                null -> null
                else -> DBException.WrongConfiguration("Collection has unknown type: "+type)
            } as E
        }
    }

    fun getNameForObject(e:Any):String? =
            namesInstanciated.asMap().filterValues { it===e }.keys.firstOrNull()

    fun exists(name: String): Boolean {
        Utils.lockRead(lock) {
            checkNotClosed()
            return nameCatalogGet(name + Keys.type) != null
        }
    }

    fun getAllNames():Iterable{
        return nameCatalogLoad().keys
                .filter { it.endsWith(Keys.type) }
                .map {it.split("#")[0]}
    }

    fun getAll():Map{
        val ret = TreeMap();
        getAllNames().forEach { ret.put(it, get(it)) }
        return ret
    }
//
//
//    /** rename named record into newName
//
//     * @param oldName current name of record/collection
//     * *
//     * @param newName new name of record/collection
//     * *
//     * @throws NoSuchElementException if oldName does not exist
//     */
//    @Synchronized fun rename(oldName: String, newName: String) {
//        if (oldName == newName) return
//        //$DELAY$
//        val sub = catalog.tailMap(oldName)
//        val toRemove = ArrayList()
//        //$DELAY$
//        for (param in sub.keys) {
//            if (!param.startsWith(oldName)) break
//
//            val suffix = param.substring(oldName.length)
//            catalog.put(newName + suffix, catalog.get(param))
//            toRemove.add(param)
//        }
//        if (toRemove.isEmpty()) throw NoSuchElementException("Could not rename, name does not exist: " + oldName)
//        //$DELAY$
//        val old = namesInstanciated.remove(oldName)
//        if (old != null) {
//            val old2 = old!!.get()
//            if (old2 != null) {
//                namesLookup.remove(IdentityWrapper(old2))
//                namedPut(newName, old2)
//            }
//        }
//        for (param in toRemove) catalog.remove(param)
//    }


    class HashMapMaker(
            protected override val db:DB,
            protected override val name:String,
            protected val hasValues:Boolean=true,
            protected val _storeFactory:(segment:Int)->Store = {i-> db.store}
    ):Maker>(){

        override val type = "HashMap"
        private var _keySerializer:Serializer = db.defaultSerializer as Serializer
        private var _valueSerializer:Serializer = db.defaultSerializer as Serializer
        private var _valueInline = false

        private var _concShift = CC.HTREEMAP_CONC_SHIFT
        private var _dirShift = CC.HTREEMAP_DIR_SHIFT
        private var _levels = CC.HTREEMAP_LEVELS

        private var _hashSeed:Int? = null
        private var _expireCreateTTL:Long = 0L
        private var _expireUpdateTTL:Long = 0L
        private var _expireGetTTL:Long = 0L
        private var _expireExecutor:ScheduledExecutorService? = null
        private var _expireExecutorPeriod:Long = 10000
        private var _expireMaxSize:Long = 0
        private var _expireStoreSize:Long = 0
        private var _expireCompactThreshold:Double? = null

        private var _counterEnable: Boolean = false

        private var _valueLoader:((key:K)->V?)? = null
        private var _modListeners:MutableList> = ArrayList()
        private var _expireOverflow:MutableMap? = null;
        private var _removeCollapsesIndexTree:Boolean = true


        fun  keySerializer(keySerializer:Serializer):HashMapMaker{
            _keySerializer = keySerializer as Serializer
            return this as HashMapMaker
        }

        fun  valueSerializer(valueSerializer:Serializer):HashMapMaker{
            _valueSerializer = valueSerializer as Serializer
            return this as HashMapMaker
        }


        fun valueInline():HashMapMaker{
            _valueInline = true
            return this
        }


        fun removeCollapsesIndexTreeDisable():HashMapMaker{
            _removeCollapsesIndexTree = false
            return this
        }

        fun hashSeed(hashSeed:Int):HashMapMaker{
            _hashSeed = hashSeed
            return this
        }

        fun layout(concurrency:Int, dirSize:Int, levels:Int):HashMapMaker{
            fun toShift(value:Int):Int{
                return 31 - Integer.numberOfLeadingZeros(DataIO.nextPowTwo(Math.max(1,value)))
            }
            _concShift = toShift(concurrency)
            _dirShift = toShift(dirSize)
            _levels = levels
            return this
        }

        fun expireAfterCreate():HashMapMaker{
            return expireAfterCreate(-1)
        }

        fun expireAfterCreate(ttl:Long):HashMapMaker{
            _expireCreateTTL = ttl
            return this
        }


        fun expireAfterCreate(ttl:Long, unit:TimeUnit):HashMapMaker {
            return expireAfterCreate(unit.toMillis(ttl))
        }

        fun expireAfterUpdate():HashMapMaker{
            return expireAfterUpdate(-1)
        }


        fun expireAfterUpdate(ttl:Long):HashMapMaker{
            _expireUpdateTTL = ttl
            return this
        }

        fun expireAfterUpdate(ttl:Long, unit:TimeUnit):HashMapMaker {
            return expireAfterUpdate(unit.toMillis(ttl))
        }

        fun expireAfterGet():HashMapMaker{
            return expireAfterGet(-1)
        }

        fun expireAfterGet(ttl:Long):HashMapMaker{
            _expireGetTTL = ttl
            return this
        }


        fun expireAfterGet(ttl:Long, unit:TimeUnit):HashMapMaker {
            return expireAfterGet(unit.toMillis(ttl))
        }


        fun expireExecutor(executor: ScheduledExecutorService?):HashMapMaker{
            _expireExecutor = executor;
            return this
        }

        fun expireExecutorPeriod(period:Long):HashMapMaker{
            _expireExecutorPeriod = period
            return this
        }

        fun expireCompactThreshold(freeFraction: Double):HashMapMaker{
            _expireCompactThreshold = freeFraction
            return this
        }


        fun expireMaxSize(maxSize:Long):HashMapMaker{
            _expireMaxSize = maxSize;
            return counterEnable()
        }

        fun expireStoreSize(storeSize:Long):HashMapMaker{
            _expireStoreSize = storeSize;
            return this
        }

        fun expireOverflow(overflowMap:MutableMap):HashMapMaker{
            _expireOverflow = overflowMap
            return this
        }



        fun valueLoader(valueLoader:(key:K)->V):HashMapMaker{
            _valueLoader = valueLoader
            return this
        }

        fun counterEnable():HashMapMaker{
            _counterEnable = true
            return this;
        }

        fun modificationListener(listener:MapModificationListener):HashMapMaker{
            if(_modListeners==null)
                _modListeners = ArrayList()
            _modListeners?.add(listener)
            return this;
        }

        override fun verify(){
            if (_expireOverflow != null && _valueLoader != null)
                throw DBException.WrongConfiguration("ExpireOverflow and ValueLoader can not be used at the same time")

            val expireOverflow = _expireOverflow
            if (expireOverflow != null) {
                //load non existing values from overflow
                _valueLoader = { key -> expireOverflow[key] }

                //forward modifications to overflow
                val listener = MapModificationListener { key, oldVal, newVal, triggered ->
                    if (!triggered && newVal == null && oldVal != null) {
                        //removal, also remove from overflow map
                        val oldVal2 = expireOverflow.remove(key)
                        if (oldVal2 != null && _valueSerializer.equals(oldVal as V, oldVal2 as V)) {
                            Utils.LOG.warning { "Key also removed from overflow Map, but value in overflow Map differs" }
                        }
                    } else if (triggered && newVal == null) {
                        // triggered by eviction, put evicted entry into overflow map
                        expireOverflow.put(key, oldVal)
                    }
                }
                _modListeners.add(listener)
            }

            if (_expireExecutor != null)
                db.executors.add(_expireExecutor!!)
        }

        override fun create2(catalog: SortedMap): HTreeMap {
            val segmentCount = 1.shl(_concShift)
            val hashSeed = _hashSeed ?: SecureRandom().nextInt()
            val stores = Array(segmentCount, _storeFactory)

            val rootRecids = LongArray(segmentCount)
            var rootRecidsStr = "";
            for (i in 0 until segmentCount) {
                val rootRecid = stores[i].put(IndexTreeListJava.dirEmpty(), IndexTreeListJava.dirSer)
                rootRecids[i] = rootRecid
                rootRecidsStr += (if (i == 0) "" else ",") + rootRecid
            }

            db.nameCatalogPutClass(catalog, name + if(hasValues) Keys.keySerializer else Keys.serializer, _keySerializer)
            if(hasValues) {
                db.nameCatalogPutClass(catalog, name + Keys.valueSerializer, _valueSerializer)
            }
            if(hasValues)
                catalog[name + Keys.valueInline] = _valueInline.toString()

            catalog[name + Keys.rootRecids] = rootRecidsStr
            catalog[name + Keys.hashSeed] = hashSeed.toString()
            catalog[name + Keys.concShift] = _concShift.toString()
            catalog[name + Keys.dirShift] = _dirShift.toString()
            catalog[name + Keys.levels] = _levels.toString()
            catalog[name + Keys.removeCollapsesIndexTree] = _removeCollapsesIndexTree.toString()

            val counterRecids = if (_counterEnable) {
                val cr = LongArray(segmentCount, { segment ->
                    stores[segment].put(0L, Serializer.LONG_PACKED)
                })
                catalog[name + Keys.counterRecids] = LongArrayList.newListWith(*cr).makeString("", ",", "")
                cr
            } else {
                catalog[name + Keys.counterRecids] = ""
                null
            }

            catalog[name + Keys.expireCreateTTL] = _expireCreateTTL.toString()
            if(hasValues)
                catalog[name + Keys.expireUpdateTTL] = _expireUpdateTTL.toString()
            catalog[name + Keys.expireGetTTL] = _expireGetTTL.toString()

            var createQ = LongArrayList()
            var updateQ = LongArrayList()
            var getQ = LongArrayList()


            fun emptyLongQueue(segment: Int, qq: LongArrayList): QueueLong {
                val store = stores[segment]
                val q = store.put(null, QueueLong.Node.SERIALIZER);
                val tailRecid = store.put(q, Serializer.RECID)
                val headRecid = store.put(q, Serializer.RECID)
                val headPrevRecid = store.put(0L, Serializer.RECID)
                qq.add(tailRecid)
                qq.add(headRecid)
                qq.add(headPrevRecid)
                return QueueLong(store = store, tailRecid = tailRecid, headRecid = headRecid, headPrevRecid = headPrevRecid)
            }

            val expireCreateQueues =
                    if (_expireCreateTTL == 0L) null
                    else Array(segmentCount, { emptyLongQueue(it, createQ) })

            val expireUpdateQueues =
                    if (_expireUpdateTTL == 0L) null
                    else Array(segmentCount, { emptyLongQueue(it, updateQ) })
            val expireGetQueues =
                    if (_expireGetTTL == 0L) null
                    else Array(segmentCount, { emptyLongQueue(it, getQ) })

            catalog[name + Keys.expireCreateQueue] = createQ.makeString("", ",", "")
            if(hasValues)
                catalog[name + Keys.expireUpdateQueue] = updateQ.makeString("", ",", "")
            catalog[name + Keys.expireGetQueue] = getQ.makeString("", ",", "")

            val indexTrees = Array(1.shl(_concShift), { segment ->
                IndexTreeLongLongMap(
                        store = stores[segment],
                        rootRecid = rootRecids[segment],
                        dirShift = _dirShift,
                        levels = _levels,
                        collapseOnRemove = _removeCollapsesIndexTree
                )
            })

            return HTreeMap(
                    keySerializer = _keySerializer,
                    valueSerializer = _valueSerializer,
                    valueInline = _valueInline,
                    concShift = _concShift,
                    dirShift = _dirShift,
                    levels = _levels,
                    stores = stores,
                    indexTrees = indexTrees,
                    hashSeed = hashSeed,
                    counterRecids = counterRecids,
                    expireCreateTTL = _expireCreateTTL,
                    expireUpdateTTL = _expireUpdateTTL,
                    expireGetTTL = _expireGetTTL,
                    expireMaxSize = _expireMaxSize,
                    expireStoreSize = _expireStoreSize,
                    expireCreateQueues = expireCreateQueues,
                    expireUpdateQueues = expireUpdateQueues,
                    expireGetQueues = expireGetQueues,
                    expireExecutor = _expireExecutor,
                    expireExecutorPeriod = _expireExecutorPeriod,
                    expireCompactThreshold = _expireCompactThreshold,
                    isThreadSafe = db.isThreadSafe,
                    valueLoader = _valueLoader,
                    modificationListeners = if (_modListeners.isEmpty()) null else _modListeners.toTypedArray(),
                    closeable = db,
                    hasValues = hasValues
            )
        }

        override fun open2(catalog: SortedMap): HTreeMap {
            _concShift = catalog[name + Keys.concShift]!!.toInt()

            val segmentCount = 1.shl(_concShift)
            val stores = Array(segmentCount, _storeFactory)

            _keySerializer =
                    db.nameCatalogGetClass(catalog, name + if(hasValues)Keys.keySerializer else Keys.serializer)
                            ?: _keySerializer
            _valueSerializer = if(!hasValues) BTreeMap.NO_VAL_SERIALIZER as Serializer
            else {
                db.nameCatalogGetClass(catalog, name + Keys.valueSerializer)?: _valueSerializer
            }
            _valueInline = if(hasValues) catalog[name + Keys.valueInline]!!.toBoolean() else true

            val hashSeed = catalog[name + Keys.hashSeed]!!.toInt()
            val rootRecids = catalog[name + Keys.rootRecids]!!.split(",").map { it.toLong() }.toLongArray()
            val counterRecidsStr = catalog[name + Keys.counterRecids]!!
            val counterRecids =
                    if ("" == counterRecidsStr) null
                    else counterRecidsStr.split(",").map { it.toLong() }.toLongArray()

            _dirShift = catalog[name + Keys.dirShift]!!.toInt()
            _levels = catalog[name + Keys.levels]!!.toInt()
            _removeCollapsesIndexTree = catalog[name + Keys.removeCollapsesIndexTree]!!.toBoolean()


            _expireCreateTTL = catalog[name + Keys.expireCreateTTL]!!.toLong()
            _expireUpdateTTL = if(hasValues)catalog[name + Keys.expireUpdateTTL]!!.toLong() else 0L
            _expireGetTTL = catalog[name + Keys.expireGetTTL]!!.toLong()


            fun queues(ttl: Long, queuesName: String): Array? {
                if (ttl == 0L)
                    return null
                val rr = catalog[queuesName]!!.split(",").map { it.toLong() }.toLongArray()
                if (rr.size != segmentCount * 3)
                    throw DBException.WrongConfiguration("wrong segment count");
                return Array(segmentCount, { segment ->
                    QueueLong(store = stores[segment],
                            tailRecid = rr[segment * 3 + 0], headRecid = rr[segment * 3 + 1], headPrevRecid = rr[segment * 3 + 2]
                    )
                })
            }

            val expireCreateQueues = queues(_expireCreateTTL, name + Keys.expireCreateQueue)
            val expireUpdateQueues = queues(_expireUpdateTTL, name + Keys.expireUpdateQueue)
            val expireGetQueues = queues(_expireGetTTL, name + Keys.expireGetQueue)

            val indexTrees = Array(1.shl(_concShift), { segment ->
                IndexTreeLongLongMap(
                        store = stores[segment],
                        rootRecid = rootRecids[segment],
                        dirShift = _dirShift,
                        levels = _levels,
                        collapseOnRemove = _removeCollapsesIndexTree
                )
            })
            return HTreeMap(
                    keySerializer = _keySerializer,
                    valueSerializer = _valueSerializer,
                    valueInline = _valueInline,
                    concShift = _concShift,
                    dirShift = _dirShift,
                    levels = _levels,
                    stores = stores,
                    indexTrees = indexTrees,
                    hashSeed = hashSeed,
                    counterRecids = counterRecids,
                    expireCreateTTL = _expireCreateTTL,
                    expireUpdateTTL = _expireUpdateTTL,
                    expireGetTTL = _expireGetTTL,
                    expireMaxSize = _expireMaxSize,
                    expireStoreSize = _expireStoreSize,
                    expireCreateQueues = expireCreateQueues,
                    expireUpdateQueues = expireUpdateQueues,
                    expireGetQueues = expireGetQueues,
                    expireExecutor = _expireExecutor,
                    expireExecutorPeriod = _expireExecutorPeriod,
                    expireCompactThreshold = _expireCompactThreshold,
                    isThreadSafe = db.isThreadSafe,
                    valueLoader = _valueLoader,
                    modificationListeners = if (_modListeners.isEmpty()) null else _modListeners.toTypedArray(),
                    closeable = db,
                    hasValues = hasValues
            )
        }

        override fun create(): HTreeMap {
            return super.create()
        }

        override fun createOrOpen(): HTreeMap {
            return super.createOrOpen()
        }

        override fun open(): HTreeMap {
            return super.open()
        }
    }

    fun hashMap(name:String):HashMapMaker<*,*> = HashMapMaker(this, name)
    fun  hashMap(name:String, keySerializer: Serializer, valueSerializer: Serializer) =
            HashMapMaker(this, name)
                    .keySerializer(keySerializer)
                    .valueSerializer(valueSerializer)

    abstract class TreeMapSink:Pump.Sink, BTreeMap>(){

        fun put(key:K, value:V) {
            put(Pair(key, value))
        }

        fun putAll(map:SortedMap){
            map.forEach { e ->
                put(e.key, e.value)
            }
        }
    }

    class TreeMapMaker(
            protected override val db:DB,
            protected override val name:String,
            protected val hasValues:Boolean=true
    ):Maker>(){

        override val type = "TreeMap"

        private var _keySerializer:GroupSerializer = db.defaultSerializer as GroupSerializer
        private var _valueSerializer:GroupSerializer =
                (if(hasValues) db.defaultSerializer else BTreeMap.NO_VAL_SERIALIZER) as GroupSerializer
        private var _maxNodeSize = CC.BTREEMAP_MAX_NODE_SIZE
        private var _counterEnable: Boolean = false
        private var _valueLoader:((key:K)->V)? = null
        private var _modListeners:MutableList>? = null

        private var _rootRecidRecid:Long? = null
        private var _counterRecid:Long? = null
        private var _valueInline:Boolean = true

        fun  keySerializer(keySerializer:GroupSerializer):TreeMapMaker{
            _keySerializer = keySerializer as GroupSerializer
            return this as TreeMapMaker
        }

        fun  valueSerializer(valueSerializer:GroupSerializer):TreeMapMaker{
            if(!hasValues)
                throw DBException.WrongConfiguration("Set, no vals")
            _valueSerializer = valueSerializer as GroupSerializer
            return this as TreeMapMaker
        }
//
//        fun valueLoader(valueLoader:(key:K)->V):TreeMapMaker{
//            //TODO BTree value loader
//            _valueLoader = valueLoader
//            return this
//        }


        fun maxNodeSize(size:Int):TreeMapMaker{
            _maxNodeSize = size
            return this;
        }

        fun counterEnable():TreeMapMaker{
            _counterEnable = true
            return this;
        }

        fun valuesOutsideNodesEnable():TreeMapMaker{
            _valueInline = false
            return this;
        }

        fun modificationListener(listener:MapModificationListener):TreeMapMaker{
            if(_modListeners==null)
                _modListeners = ArrayList()
            _modListeners?.add(listener)
            return this;
        }


        fun createFrom(iterator:Iterator>):BTreeMap{
            val consumer = createFromSink()
            while(iterator.hasNext()){
                consumer.put(iterator.next())
            }
            return consumer.create()
        }

        fun createFromSink(): TreeMapSink{

            val consumer = Pump.treeMap(
                    store = db.store,
                    keySerializer = _keySerializer,
                    valueSerializer = _valueSerializer,
                    //TODO add custom comparator, once its enabled
                    dirNodeSize = _maxNodeSize *3/4,
                    leafNodeSize = _maxNodeSize *3/4,
                    valueInline = _valueInline
            )

            return object: TreeMapSink(){

                override fun put(e: Pair) {
                    consumer.put(e)
                }

                override fun create(): BTreeMap {
                    consumer.create()
                    this@TreeMapMaker._rootRecidRecid = consumer.rootRecidRecid
                            ?: throw AssertionError()
                    this@TreeMapMaker._counterRecid =
                            if(_counterEnable) db.store.put(consumer.counter, Serializer.LONG)
                            else 0L
                    return [email protected](create=true)
                }

            }
        }

        override fun create2(catalog: SortedMap): BTreeMap {
            db.nameCatalogPutClass(catalog, name +
                    (if(hasValues)Keys.keySerializer else Keys.serializer), _keySerializer)
            if(hasValues) {
                db.nameCatalogPutClass(catalog, name + Keys.valueSerializer, _valueSerializer)
                catalog[name + Keys.valueInline] = _valueInline.toString()
            }

            val rootRecidRecid2 = _rootRecidRecid
                    ?: BTreeMap.putEmptyRoot(db.store, _keySerializer , _valueSerializer)
            catalog[name + Keys.rootRecidRecid] = rootRecidRecid2.toString()

            val counterRecid2 =
                    if (_counterEnable) _counterRecid ?: db.store.put(0L, Serializer.LONG)
                    else 0L
            catalog[name + Keys.counterRecid] = counterRecid2.toString()

            catalog[name + Keys.maxNodeSize] = _maxNodeSize.toString()

            return BTreeMap(
                    keySerializer = _keySerializer,
                    valueSerializer = _valueSerializer,
                    rootRecidRecid = rootRecidRecid2,
                    store = db.store,
                    maxNodeSize = _maxNodeSize,
                    comparator = _keySerializer, //TODO custom comparator
                    isThreadSafe = db.isThreadSafe,
                    counterRecid = counterRecid2,
                    hasValues = hasValues,
                    valueInline = _valueInline,
                    modificationListeners = if(_modListeners==null) null else _modListeners!!.toTypedArray()
            )
        }

        override fun open2(catalog: SortedMap): BTreeMap {
            val rootRecidRecid2 = catalog[name + Keys.rootRecidRecid]!!.toLong()

            _keySerializer =
                    db.nameCatalogGetClass(catalog, name +
                            if(hasValues)Keys.keySerializer else Keys.serializer)
                            ?: _keySerializer
            _valueSerializer =
                    if(!hasValues) {
                        BTreeMap.NO_VAL_SERIALIZER as GroupSerializer
                    }else {
                        db.nameCatalogGetClass(catalog, name + Keys.valueSerializer) ?: _valueSerializer
                    }

            val counterRecid2 = catalog[name + Keys.counterRecid]!!.toLong()
            _maxNodeSize = catalog[name + Keys.maxNodeSize]!!.toInt()

            //TODO compatibility with older versions, remove before stable version
            if(_valueSerializer!= BTreeMap.Companion.NO_VAL_SERIALIZER &&
                    catalog[name + Keys.valueInline]==null
                    && db.store.isReadOnly.not()){
                //patch store with default value
                catalog[name + Keys.valueInline] = "true"
                db.nameCatalogSaveLocked(catalog)
            }

            _valueInline = (catalog[name + Keys.valueInline]?:"true").toBoolean()
            return BTreeMap(
                    keySerializer = _keySerializer,
                    valueSerializer = _valueSerializer,
                    rootRecidRecid = rootRecidRecid2,
                    store = db.store,
                    maxNodeSize = _maxNodeSize,
                    comparator = _keySerializer, //TODO custom comparator
                    isThreadSafe = db.isThreadSafe,
                    counterRecid = counterRecid2,
                    hasValues = hasValues,
                    valueInline = _valueInline,
                    modificationListeners = if(_modListeners==null)null else _modListeners!!.toTypedArray()
            )
        }

        override fun create(): BTreeMap {
            return super.create()
        }

        override fun createOrOpen(): BTreeMap {
            return super.createOrOpen()
        }

        override fun open(): BTreeMap {
            return super.open()
        }
    }

    class TreeSetMaker(
            protected override val db:DB,
            protected override val name:String
    ) :Maker>(){

        protected val maker = TreeMapMaker(db, name, hasValues = false)


        fun  serializer(serializer:GroupSerializer):TreeSetMaker{
            maker.keySerializer(serializer)
            return this as TreeSetMaker
        }

        fun maxNodeSize(size:Int):TreeSetMaker{
            maker.maxNodeSize(size)
            return this;
        }

        fun counterEnable():TreeSetMaker{
            maker.counterEnable()
            return this;
        }

        override fun verify() {
            maker.`%%%verify`()
        }

        override fun open2(catalog: SortedMap): NavigableSet {
            return maker.`%%%open2`(catalog).keys as NavigableSet
        }

        override fun create2(catalog: SortedMap): NavigableSet {
            return maker.`%%%create2`(catalog).keys as NavigableSet
        }

        override val type = "TreeSet"
    }

    fun treeMap(name:String):TreeMapMaker<*,*> = TreeMapMaker(this, name)
    fun  treeMap(name:String, keySerializer: GroupSerializer, valueSerializer: GroupSerializer) =
            TreeMapMaker(this, name)
                    .keySerializer(keySerializer)
                    .valueSerializer(valueSerializer)

    fun treeSet(name:String):TreeSetMaker<*> = TreeSetMaker(this, name)
    fun  treeSet(name:String, serializer: GroupSerializer) =
            TreeSetMaker(this, name)
                    .serializer(serializer)



    class HashSetMaker(
            protected override val db:DB,
            protected override val name:String,
            protected val _storeFactory:(segment:Int)->Store = {i-> db.store}

    ) :Maker>(){

        protected val maker = HashMapMaker(db, name, hasValues=false, _storeFactory = _storeFactory)

        init{
            maker.valueSerializer(BTreeMap.NO_VAL_SERIALIZER).valueInline()
        }

        fun  serializer(serializer:Serializer):HashSetMaker{
            maker.keySerializer(serializer)
            return this as HashSetMaker
        }

        fun counterEnable():HashSetMaker{
            maker.counterEnable()
            return this;
        }

        fun removeCollapsesIndexTreeDisable():HashSetMaker{
            maker.removeCollapsesIndexTreeDisable()
            return this
        }

        fun hashSeed(hashSeed:Int):HashSetMaker{
            maker.hashSeed(hashSeed)
            return this
        }

        fun layout(concurrency:Int, dirSize:Int, levels:Int):HashSetMaker{
            maker.layout(concurrency, dirSize, levels)
            return this
        }

        fun expireAfterCreate():HashSetMaker{
            return expireAfterCreate(-1)
        }

        fun expireAfterCreate(ttl:Long):HashSetMaker{
            maker.expireAfterCreate(ttl)
            return this
        }


        fun expireAfterCreate(ttl:Long, unit:TimeUnit):HashSetMaker {
            return expireAfterCreate(unit.toMillis(ttl))
        }

        fun expireAfterGet():HashSetMaker{
            return expireAfterGet(-1)
        }

        fun expireAfterGet(ttl:Long):HashSetMaker{
            maker.expireAfterGet(ttl)
            return this
        }


        fun expireAfterGet(ttl:Long, unit:TimeUnit):HashSetMaker {
            return expireAfterGet(unit.toMillis(ttl))
        }


        fun expireExecutor(executor: ScheduledExecutorService?):HashSetMaker{
            maker.expireExecutor(executor)
            return this
        }

        fun expireExecutorPeriod(period:Long):HashSetMaker{
            maker.expireExecutorPeriod(period)
            return this
        }

        fun expireCompactThreshold(freeFraction: Double):HashSetMaker{
            maker.expireCompactThreshold(freeFraction)
            return this
        }


        fun expireMaxSize(maxSize:Long):HashSetMaker{
            maker.expireMaxSize(maxSize)
            return this
        }

        fun expireStoreSize(storeSize:Long):HashSetMaker{
            maker.expireStoreSize(storeSize)
            return this
        }

        override fun verify() {
            maker.`%%%verify`()
        }

        override fun open2(catalog: SortedMap): HTreeMap.KeySet {
            return maker.`%%%open2`(catalog).keys
        }

        override fun create2(catalog: SortedMap): HTreeMap.KeySet {
            return maker.`%%%create2`(catalog).keys
        }

        override val type = "HashSet"
    }

    fun hashSet(name:String):HashSetMaker<*> = HashSetMaker(this, name)
    fun  hashSet(name:String, serializer: Serializer) =
            HashSetMaker(this, name)
                    .serializer(serializer)



    abstract class Maker(){
        /**
         * Creates new collection if it does not exist, or throw {@link DBException.WrongConfiguration}
         * if collection already exists.
         */
        open fun create():E = make2( true)

        /**
         * Create new collection or open existing.
         */
        @Deprecated(message="use createOrOpen() method", replaceWith=ReplaceWith("createOrOpen()"))
        open fun make():E = make2(null)

        @Deprecated(message="use createOrOpen() method", replaceWith=ReplaceWith("createOrOpen()"))
        open fun makeOrGet() = make2(null)

        /**
         * Create new collection or open existing.
         */
        open fun createOrOpen():E = make2(null)

        /**
         * Open existing collection, or throw {@link DBException.WrongConfiguration}
         * if collection already exists.
         */
        open fun open():E = make2( false)

        protected fun make2(create:Boolean?):E{
            Utils.lockWrite(db.lock){
                db.checkNotClosed()
                verify()

                val catalog = db.nameCatalogLoad()
                //check existence
                val typeFromDb = catalog[name+Keys.type]
                if (create != null) {
                    if (typeFromDb!=null && create)
                        throw DBException.WrongConfiguration("Named record already exists: $name")
                    if (!create && typeFromDb==null)
                        throw DBException.WrongConfiguration("Named record does not exist: $name")
                }
                //check typeg
                if(typeFromDb!=null && type!=typeFromDb){
                    throw DBException.WrongConfiguration("Wrong type for named record '$name'. Expected '$type', but catalog has '$typeFromDb'")
                }

                val ref = db.namesInstanciated.getIfPresent(name)
                if(ref!=null)
                    return ref as E;

                if(typeFromDb!=null) {
                    val ret = open2(catalog)
                    db.namesInstanciated.put(name,ret)
                    return ret;
                }

                if(db.store.isReadOnly)
                    throw UnsupportedOperationException("Read-only")
                catalog.put(name+Keys.type,type)
                val ret = create2(catalog)
                db.nameCatalogSaveLocked(catalog)
                db.namesInstanciated.put(name,ret)
                return ret
            }
        }

        open protected fun verify(){}
        abstract protected fun create2(catalog:SortedMap):E
        abstract protected fun open2(catalog:SortedMap):E

        //TODO this is hack to make internal methods not accessible from Java. Remove once internal method names are obfuscated in bytecode
        internal fun `%%%verify`(){verify()}
        internal fun `%%%create2`(catalog:SortedMap) = create2(catalog)
        internal fun `%%%open2`(catalog:SortedMap) = open2(catalog)

        abstract protected val db:DB
        abstract protected val name:String
        abstract protected val type:String
    }

    class AtomicIntegerMaker(protected override val db:DB, protected override val name:String, protected val value:Int=0):Maker(){

        override val type = "AtomicInteger"

        override fun create2(catalog: SortedMap): Atomic.Integer {
            val recid = db.store.put(value, Serializer.INTEGER)
            catalog[name+Keys.recid] = recid.toString()
            return Atomic.Integer(db.store, recid)
        }

        override fun open2(catalog: SortedMap): Atomic.Integer {
            val recid = catalog[name+Keys.recid]!!.toLong()
            return Atomic.Integer(db.store, recid)
        }
    }

    fun atomicInteger(name:String) = AtomicIntegerMaker(this, name)

    fun atomicInteger(name:String, value:Int) = AtomicIntegerMaker(this, name, value)



    class AtomicLongMaker(protected override val db:DB, protected override val name:String, protected val value:Long=0):Maker(){

        override val type = "AtomicLong"

        override fun create2(catalog: SortedMap): Atomic.Long {
            val recid = db.store.put(value, Serializer.LONG)
            catalog[name+Keys.recid] = recid.toString()
            return Atomic.Long(db.store, recid)
        }

        override fun open2(catalog: SortedMap): Atomic.Long {
            val recid = catalog[name+Keys.recid]!!.toLong()
            return Atomic.Long(db.store, recid)
        }
    }

    fun atomicLong(name:String) = AtomicLongMaker(this, name)

    fun atomicLong(name:String, value:Long) = AtomicLongMaker(this, name, value)


    class AtomicBooleanMaker(protected override val db:DB, protected override val name:String, protected val value:Boolean=false):Maker(){

        override val type = "AtomicBoolean"

        override fun create2(catalog: SortedMap): Atomic.Boolean {
            val recid = db.store.put(value, Serializer.BOOLEAN)
            catalog[name+Keys.recid] = recid.toString()
            return Atomic.Boolean(db.store, recid)
        }

        override fun open2(catalog: SortedMap): Atomic.Boolean {
            val recid = catalog[name+Keys.recid]!!.toLong()
            return Atomic.Boolean(db.store, recid)
        }
    }

    fun atomicBoolean(name:String) = AtomicBooleanMaker(this, name)

    fun atomicBoolean(name:String, value:Boolean) = AtomicBooleanMaker(this, name, value)


    class AtomicStringMaker(protected override val db:DB, protected override val name:String, protected val value:String?=null):Maker(){

        override val type = "AtomicString"

        override fun create2(catalog: SortedMap): Atomic.String {
            val recid = db.store.put(value, Serializer.STRING_NOSIZE)
            catalog[name+Keys.recid] = recid.toString()
            return Atomic.String(db.store, recid)
        }

        override fun open2(catalog: SortedMap): Atomic.String {
            val recid = catalog[name+Keys.recid]!!.toLong()
            return Atomic.String(db.store, recid)
        }
    }

    fun atomicString(name:String) = AtomicStringMaker(this, name)

    fun atomicString(name:String, value:String?) = AtomicStringMaker(this, name, value)


    class AtomicVarMaker(protected override val db:DB,
                            protected override val name:String,
                            protected val serializer:Serializer = db.defaultSerializer as Serializer,
                            protected val value:E? = null):Maker>(){

        override val type = "AtomicVar"

        override fun create2(catalog: SortedMap): Atomic.Var {
            val recid = db.store.put(value, serializer)
            catalog[name+Keys.recid] = recid.toString()
            db.nameCatalogPutClass(catalog, name+Keys.serializer, serializer)

            return Atomic.Var(db.store, recid, serializer)
        }

        override fun open2(catalog: SortedMap): Atomic.Var {
            val recid = catalog[name+Keys.recid]!!.toLong()
            val serializer = db.nameCatalogGetClass>(catalog, name+Keys.serializer)
                    ?: this.serializer
            return Atomic.Var(db.store, recid, serializer)
        }
    }

    fun atomicVar(name:String) = atomicVar(name, defaultSerializer)
    fun  atomicVar(name:String, serializer:Serializer ) = AtomicVarMaker(this, name, serializer)

    fun  atomicVar(name:String, serializer:Serializer, value:E? ) = AtomicVarMaker(this, name, serializer, value)

    class IndexTreeLongLongMapMaker(
            protected override val db:DB,
            protected override val name:String
    ):Maker(){

        private var _dirShift = CC.HTREEMAP_DIR_SHIFT
        private var _levels = CC.HTREEMAP_LEVELS
        private var _removeCollapsesIndexTree:Boolean = true

        override val type = "IndexTreeLongLongMap"

        fun layout(dirSize:Int, levels:Int):IndexTreeLongLongMapMaker{
            fun toShift(value:Int):Int{
                return 31 - Integer.numberOfLeadingZeros(DataIO.nextPowTwo(Math.max(1,value)))
            }
            _dirShift = toShift(dirSize)
            _levels = levels
            return this
        }


        fun removeCollapsesIndexTreeDisable():IndexTreeLongLongMapMaker{
            _removeCollapsesIndexTree = false
            return this
        }



        override fun create2(catalog: SortedMap): IndexTreeLongLongMap {
            catalog[name+Keys.dirShift] = _dirShift.toString()
            catalog[name+Keys.levels] = _levels.toString()
            catalog[name + Keys.removeCollapsesIndexTree] = _removeCollapsesIndexTree.toString()

            val rootRecid = db.store.put(IndexTreeListJava.dirEmpty(), IndexTreeListJava.dirSer)
            catalog[name+Keys.rootRecid] = rootRecid.toString()
            return IndexTreeLongLongMap(
                    store=db.store,
                    rootRecid = rootRecid,
                    dirShift = _dirShift,
                    levels=_levels,
                    collapseOnRemove = _removeCollapsesIndexTree);
        }

        override fun open2(catalog: SortedMap): IndexTreeLongLongMap {
            return IndexTreeLongLongMap(
                    store = db.store,
                    dirShift = catalog[name+Keys.dirShift]!!.toInt(),
                    levels = catalog[name+Keys.levels]!!.toInt(),
                    rootRecid = catalog[name+Keys.rootRecid]!!.toLong(),
                    collapseOnRemove = catalog[name + Keys.removeCollapsesIndexTree]!!.toBoolean())
        }
    }

    //TODO this is thread unsafe, but locks should not be added directly due to code overhead on HTreeMap
    private fun indexTreeLongLongMap(name: String) = IndexTreeLongLongMapMaker(this, name)


    class IndexTreeListMaker(
            protected override val db:DB,
            protected override val name:String,
            protected val serializer:Serializer
    ):Maker>(){

        private var _dirShift = CC.HTREEMAP_DIR_SHIFT
        private var _levels = CC.HTREEMAP_LEVELS
        private var _removeCollapsesIndexTree:Boolean = true

        override val type = "IndexTreeList"

        fun layout(dirSize:Int, levels:Int):IndexTreeListMaker{
            fun toShift(value:Int):Int{
                return 31 - Integer.numberOfLeadingZeros(DataIO.nextPowTwo(Math.max(1,value)))
            }
            _dirShift = toShift(dirSize)
            _levels = levels
            return this
        }


        fun removeCollapsesIndexTreeDisable():IndexTreeListMaker{
            _removeCollapsesIndexTree = false
            return this
        }

        override fun create2(catalog: SortedMap): IndexTreeList {
            catalog[name+Keys.dirShift] = _dirShift.toString()
            catalog[name+Keys.levels] = _levels.toString()
            catalog[name + Keys.removeCollapsesIndexTree] = _removeCollapsesIndexTree.toString()
            db.nameCatalogPutClass(catalog, name + Keys.serializer, serializer)

            val counterRecid = db.store.put(0L, Serializer.LONG_PACKED)
            catalog[name+Keys.counterRecid] = counterRecid.toString()
            val rootRecid = db.store.put(IndexTreeListJava.dirEmpty(), IndexTreeListJava.dirSer)
            catalog[name+Keys.rootRecid] = rootRecid.toString()
            val map = IndexTreeLongLongMap(
                    store=db.store,
                    rootRecid = rootRecid,
                    dirShift = _dirShift,
                    levels=_levels,
                    collapseOnRemove = _removeCollapsesIndexTree);

            return IndexTreeList(
                    store = db.store,
                    map = map,
                    serializer = serializer,
                    isThreadSafe = db.isThreadSafe,
                    counterRecid = counterRecid
            )
        }

        override fun open2(catalog: SortedMap): IndexTreeList {
            val map =  IndexTreeLongLongMap(
                    store = db.store,
                    dirShift = catalog[name+Keys.dirShift]!!.toInt(),
                    levels = catalog[name+Keys.levels]!!.toInt(),
                    rootRecid = catalog[name+Keys.rootRecid]!!.toLong(),
                    collapseOnRemove = catalog[name + Keys.removeCollapsesIndexTree]!!.toBoolean())
            return IndexTreeList(
                    store = db.store,
                    map = map,
                    serializer =  db.nameCatalogGetClass(catalog, name + Keys.serializer)?: serializer,
                    isThreadSafe = db.isThreadSafe,
                    counterRecid = catalog[name+Keys.counterRecid]!!.toLong()
            )
        }
    }

    fun  indexTreeList(name: String, serializer:Serializer) = IndexTreeListMaker(this, name, serializer)
    fun indexTreeList(name: String) = indexTreeList(name, defaultSerializer)


    override fun checkThreadSafe() {
        super.checkThreadSafe()
        if(store.isThreadSafe.not())
            throw AssertionError()
    }

    /**
     * Register Class with default POJO serializer. Class structure will be stored in store,
     * and will save space for collections which do not use specialized serializer.
     */
    fun defaultSerializerRegisterClass(clazz:Class<*>){
        Utils.lockWrite(lock) {
            checkNotClosed()
            defaultSerializerRegisterClass_noLock(clazz)
        }
    }
    private fun defaultSerializerRegisterClass_noLock(clazz:Class<*>) {
        if(CC.ASSERT)
            Utils.assertWriteLock(lock)
        var infos = loadClassInfos()
        val className = clazz.name
        if (infos.find { it.name == className } != null)
            return; //class is already present
        //add as last item to an array
        infos = Arrays.copyOf(infos, infos.size + 1)
        infos[infos.size - 1] = elsaSerializer.makeClassInfo(className)
        //and save
        store.update(CC.RECID_CLASS_INFOS, infos, classInfoSerializer)
    }

    protected data class CatVal(val msg:(String)->String?, val required:Boolean=true)

    private fun nameCatalogVerifyTree():Map> {

        val all = {s:String->null}
        val recid = {s:String->
            try{
                val l = s.toLong()
                if(l<=0)
                    "Recid must be greater than 0"
                else
                    null
            }catch(e:Exception){
                "Recid must be a number"
            }
        }

        val recidOptional = {s:String->
            try{
                val l = s.toLong()
                if(l<0)
                    "Recid can not be negative"
                else
                    null
            }catch(e:Exception){
                "Recid must be a number"
            }
        }

        val long = { s: String ->
            try {
                s.toLong()
                null
            } catch(e: Exception) {
                "Must be a number"
            }
        }


        val int = { s: String ->
            try {
                s.toInt()
                null
            } catch(e: Exception) {
                "Must be a number"
            }
        }

        val recidArray = all

        val serializer = all
        val boolean = {s:String ->
            if(s!="true" && s!="false")
                "Not boolean"
            else
                null
        }

        return mapOf(
                Pair("HashMap", mapOf(
                        Pair(Keys.keySerializer, CatVal(serializer, required=false)),
                        Pair(Keys.valueSerializer,CatVal(serializer, required=false)),
                        Pair(Keys.rootRecids,CatVal(recidArray)),
                        Pair(Keys.valueInline, CatVal(boolean)),
                        Pair(Keys.hashSeed, CatVal(int)),
                        Pair(Keys.concShift, CatVal(int)),
                        Pair(Keys.levels, CatVal(int)),
                        Pair(Keys.dirShift, CatVal(int)),
                        Pair(Keys.removeCollapsesIndexTree, CatVal(boolean)),
                        Pair(Keys.counterRecids, CatVal(recidArray)),
                        Pair(Keys.expireCreateQueue, CatVal(all)),
                        Pair(Keys.expireUpdateQueue, CatVal(all)),
                        Pair(Keys.expireGetQueue, CatVal(all)),
                        Pair(Keys.expireCreateTTL, CatVal(long)),
                        Pair(Keys.expireUpdateTTL, CatVal(long)),
                        Pair(Keys.expireGetTTL, CatVal(long))
                )),
                Pair("HashSet", mapOf(
                        Pair(Keys.serializer, CatVal(serializer, required=false)),
                        Pair(Keys.rootRecids, CatVal(recidArray)),
                        Pair(Keys.hashSeed, CatVal(int)),
                        Pair(Keys.concShift, CatVal(int)),
                        Pair(Keys.dirShift, CatVal(int)),
                        Pair(Keys.levels, CatVal(int)),
                        Pair(Keys.removeCollapsesIndexTree, CatVal(boolean)),
                        Pair(Keys.counterRecids, CatVal(recidArray)),
                        Pair(Keys.expireCreateQueue, CatVal(all)),
                        Pair(Keys.expireGetQueue, CatVal(all)),
                        Pair(Keys.expireCreateTTL, CatVal(long)),
                        Pair(Keys.expireGetTTL, CatVal(long))
                )),
                Pair("TreeMap", mapOf(
                        Pair(Keys.keySerializer, CatVal(serializer, required=false)),
                        Pair(Keys.valueSerializer, CatVal(serializer, required=false)),
                        Pair(Keys.rootRecidRecid, CatVal(recid)),
                        Pair(Keys.counterRecid, CatVal(recidOptional)),
                        Pair(Keys.maxNodeSize, CatVal(int)),
                        Pair(Keys.valueInline, CatVal(boolean))
                )),
                Pair("TreeSet", mapOf(
                        Pair(Keys.serializer, CatVal(serializer, required=false)),
                        Pair(Keys.rootRecidRecid, CatVal(recid)),
                        Pair(Keys.counterRecid, CatVal(recidOptional)),
                        Pair(Keys.maxNodeSize, CatVal(int))
                )),
                Pair("AtomicBoolean", mapOf(
                        Pair(Keys.recid, CatVal(recid))
                )),
                Pair("AtomicInteger", mapOf(
                        Pair(Keys.recid, CatVal(recid))
                )),
                Pair("AtomicVar", mapOf(
                        Pair(Keys.recid, CatVal(recid)),
                        Pair(Keys.serializer, CatVal(serializer, false))
                )),
                Pair("AtomicString", mapOf(
                        Pair(Keys.recid, CatVal(recid))
                )),
                Pair("AtomicLong", mapOf(
                        Pair(Keys.recid, CatVal(recid))
                )),
                Pair("IndexTreeList", mapOf(
                        Pair(Keys.serializer, CatVal(serializer, required=false)),
                        Pair(Keys.dirShift, CatVal(int)),
                        Pair(Keys.levels, CatVal(int)),
                        Pair(Keys.removeCollapsesIndexTree, CatVal(boolean)),
                        Pair(Keys.counterRecid, CatVal(recid)),
                        Pair(Keys.rootRecid, CatVal(recid))
                )),
                Pair("IndexTreeLongLongMap", mapOf(
                        Pair(Keys.dirShift, CatVal(int)),
                        Pair(Keys.levels, CatVal(int)),
                        Pair(Keys.removeCollapsesIndexTree, CatVal(boolean)),
                        Pair(Keys.rootRecid, CatVal(recid))
                ))
        )
    }

    /** verifies name catalog is valid (all parameters are known and have required values). If there are problems, it return list of messages */
    fun nameCatalogVerifyGetMessages():Iterable{
        val ret = ArrayList()

        val ver = nameCatalogVerifyTree()
        val catalog = nameCatalogLoad()
        val names = catalog.keys.filter{it.endsWith(Keys.type)}.map{it.substring(0, it.lastIndexOf('#'))}.toSet()

        val known = HashSet()

        //iterate over names, check all required parameters are present
        nameLoop@ for(name in names){

            //get type
            known+=name+Keys.type
            val type = catalog[name+Keys.type]
            val reqParams = ver[type]
            if(reqParams==null){
                ret+=name+Keys.type+": unknown type '$type'"
                continue@nameLoop
            }
            paramLoop@ for((param, catVal) in reqParams){
                known+=name+param
                val value = catalog[name+param]
                if(value==null) {
                    if(catVal.required)
                        ret += name + param+": required parameter not found"
                    continue@paramLoop
                }
                val msg = catVal.msg(value) //validate value, get msg if not valid
                if(msg!=null)
                    ret+=name+param+": "+msg
            }
        }

        //check for extra params which are not known
        for(param in catalog.keys)
            if(known.contains(param).not())
                ret+=param+": unknown parameter"

        return ret;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy