Nickolay - KiokuJS-0.01

Documentation | Source
Class('KiokuJS.Scope', {
    
    trait   : 'JooseX.CPS',
    
    use     : [ 
        'KiokuJS.Collapser', 
        'KiokuJS.Exception.LookUp',
        
        'KiokuJS.Collapser.Encoder', 
        'KiokuJS.Linker.Decoder', 
        'KiokuJS.Linker.RefGatherer' 
    ],
    
    
    has : {
        backend         : {
            is          : 'ro',
            required    : true
        },
        
//        parent          : null,        
        
        // only store the first class nodes (with ID) & live ones
        nodesByREFADR   : Joose.I.Object,
        nodesByID       : Joose.I.Object,
        
        encoder         : {
            handles     : 'encodeNodes',
            init        : Joose.I.FutureClass('KiokuJS.Collapser.Encoder')
        },
        decoder         : Joose.I.FutureClass('KiokuJS.Linker.Decoder'),
        
        gatherer        : {
            handles     : 'gatherReferences',
            init        : Joose.I.FutureClass('KiokuJS.Linker.RefGatherer')
        }
    },
    
        
    methods : {
        
        getResolver : function () {
            return this.backend.resolver
        },
        
        
        registerProxy : function (object, ID) {
            
            var node = this.backend.createNodeFromObject(object)
            
            // XXX proxy will currently only work, when linking with the `shallowLevel` == 0
            node.immutable  = true
            
            if (!node.isFirstClass()) node.acquireID(ID)
            
            this.pinNode(node)
        },
        
        
//        deriveChild : function (options) {
//            return new this.constructor(Joose.O.copy({
//                parent          : this,
//                
//                nodesByREFADR   : Joose.O.getMutableCopy(this.nodesByREFADR),
//                nodesByID       : Joose.O.getMutableCopy(this.nodesByID)
//            }, options))
//        },        
        
        
        // node *must* be live
        pinNode : function (node) {
            var nodeID = node.ID
            
//            if (!this.hasID(nodeID) || this.hasOwnID(nodeID)) {
                
                if (!node.isLive()) throw "Can pin only live nodes"
                
                this.nodesByID[ nodeID ] = node
                
                var object  = node.getObject()
                
                this.nodesByREFADR[ object.__REFADR__ ] = node
//            } else
//                // XXX no proto inheritance already
//                this.parent.pinNode(node)
        },
        
        
        unpinNode : function (node) {
            var nodeID = node.ID
            
//            if (this.hasOwnID( nodeID )) {
            
                if (!node.isLive()) throw "Can unpin only live node"
                
                delete this.nodesByID[ nodeID ]
                
                var REFADR  = node.getObject().__REFADR__
                
                delete this.nodesByREFADR[ REFADR ]
//            } else
//                // XXX no proto inheritance already
//                this.parent.unpinNode(node)
        },
        

        unpinID : function (id) {
            var node = this.idToNode(id)
            
            if (node) 
                this.unpinNode(node)
            else
                throw "ID [" + id + "] is not in scope - can't unpin it"
        },
        
        
        objectToNode : function (obj) {
            return this.nodesByREFADR[ obj.__REFADR__ ]
        },
        
        
        nodeToObject : function (node) {
            var ownNode     = this.nodesByID[ node.ID ]
            
            return ownNode && ownNode.getObject()
        },
        
        
        objectToId : function (obj) {
            return obj.__REFADR__ && this.nodesByREFADR[ obj.__REFADR__ ].ID || null
        },
        
        
        idToObject : function (id) {
            var node = this.nodesByID[ id ]
            
            return node && node.getObject()
        },
        
        
        idToNode : function (id) {
            return this.nodesByID[ id ]
        },
        
        
        idPinned : function (id) {
            return this.nodesByID[ id ] != null
        },
        
        
        nodePinned : function (node) {
            return node.ID && this.nodesByID[ node.ID ] != null
        },
        
        
        objectPinned : function (object) {
            return object.__REFADR__ && this.nodesByREFADR[ object.__REFADR__ ] != null || false
        },
        
        
//        getOwnNodes : function () {
//            return Joose.O.copyOwn(this.nodesByID)
//        },
//        
//        
//        
//        hasID : function (id) {
//            return this.nodesByID[ id ] != null
//        },
//        
//        
//        hasOwnID : function (id) {
//            return this.nodesByID.hasOwnProperty( id )
//        },
        
        
        collapse : function (wIDs, woIDs, options) {
            options             = options || {}
            
            options.backend     = this.getBackend()
            options.scope       = this
            
            return new KiokuJS.Collapser(options).collapse(wIDs, woIDs)
        },
        
        
        encodeNode : function (node) {
            return this.encoder.encodeNodes([ node ])[ 0 ]
        },
        
        
        decodeEntry : function (entry) {
            return this.decoder.decodeEntries([ entry ], this.getBackend())[ 0 ]
        },
        
        
        decodeEntries : function (entries) {
            return this.decoder.decodeEntries(entries, this.getBackend())
        },
        
        
        includeNewObjects : function (wIDs, woIDs) {
            var nodes   = this.collapse(wIDs, woIDs, { isShallow : true })
            
            Joose.A.each(nodes, this.pinNode, this)
            
            var customIDs = []
            
            Joose.O.each(wIDs, function (object, id) {
                customIDs.push(id)
            })
            
            return {
                entries     : this.encodeNodes(nodes),
                customIDs   : customIDs,
                IDs         : Joose.A.map(woIDs, this.objectToId, this)
            }
        }
    },
    
    
    continued : {
        
        methods : {
            
            store : function () {
                this.storeObjects({
                    
                    wIDs        : {},
                    woIDs       : Array.prototype.slice.call(arguments),
                    
                    mode        : 'store',
                    shallow     : false
                    
                }).now()
            },
            
            
            storeAs : function () {
                var woIDs = Array.prototype.slice.call(arguments)
                
                this.storeObjects({
                    
                    wIDs        : woIDs.shift(),
                    woIDs       : woIDs,
                    
                    mode        : 'store',
                    shallow     : false
                    
                }).now()
            },
            
            
            update : function () {
                this.storeObjects({
                    
                    wIDs        : {},
                    woIDs       : Array.prototype.slice.call(arguments),
                    
                    mode        : 'update',
                    shallow     : true
                    
                }).now()
            },
            
            
            deepUpdate : function () {
                this.storeObjects({
                    
                    wIDs        : {},
                    woIDs       : Array.prototype.slice.call(arguments),
                    
                    mode        : 'update',
                    shallow     : false
                    
                }).now()
            },
            
            
            insert : function () {
                this.storeObjects({
                    
                    wIDs        : {},
                    woIDs       : Array.prototype.slice.call(arguments),
                    
                    mode        : 'insert',
                    shallow     : true
                    
                }).now()
            },
            
            
            insertAs : function () {
                var woIDs = Array.prototype.slice.call(arguments)
                
                this.storeObjects({
                    
                    wIDs        : woIDs.shift(),
                    woIDs       : woIDs,
                    
                    mode        : 'insert',
                    shallow     : true
                    
                }).now()
            },
            
            
            
            storeObjects : function (args) {
                var wIDs        = args.wIDs     || {}
                var woIDs       = args.woIDs    || []
                var mode        = args.mode     || 'store'    
                var shallow     = args.shallow  || false
                var setRoot     = args.setRoot
                
                
                var resolver    = this.getResolver()
                var backend     = this.getBackend()
                var self        = this
                
                resolver.fetchClasses().andThen(function () {
                    
                    var firstClassNodes = self.collapse(wIDs, woIDs, {
                        isShallow           : shallow,
                        setRoot             : setRoot != null ? setRoot : true
                    })
                    
                    // saving only first-class nodes - by design they'll contain a description of the whole graph
                    backend.insert(self.encodeNodes(firstClassNodes), mode).andThen(function (entries) {
                        
                        // pin nodes only after successfull insert and only those not pinned yet 
                        Joose.A.each(firstClassNodes, function (node, index) {
                            
                            node.consumeEntry(entries[ index ])
                            
                            if (!self.nodePinned(node)) self.pinNode(node)
                        })
                        
                        this.CONTINUE.apply(this, Joose.A.map(woIDs, self.objectToId, self))
                    })
                })
            },
            
            
            remove : function () {
                var me          = this
                
                var entriesOrIds  = Joose.A.map(arguments, function (arg) {
                    // id
                    // trying to replace an ID with entry where possible, as it has more information attached
                    if (typeof arg == 'string') return me.idPinned(arg) ? me.idToNode(arg).getEntry() : arg
                    
                    // object
                    if (!me.objectPinned(arg)) 
                        throw new KiokuJS.Exception.Remove({
                            message : "Can't remove object [" + arg + "] - its not in the scope"
                        })
                    
                    return me.objectToNode(arg).getEntry()
                })
                
                
                var backend  = this.getBackend()
                
                backend.remove(entriesOrIds).andThen(function () {
                    
                    Joose.A.each(entriesOrIds, function (entryOrId) {
                        
                        me.unpinID(typeof entryOrId == 'string' ? entryOrId : entryOrId.ID)
                    })
                    
                    this.CONTINUE()
                })
            },
            
            
            animatePacket : function (packet) {
                var me          = this
                
                var linker = new KiokuJS.Linker({
                    scope       : this,
                    
                    entries     : packet.entries // entries will be converted from Array to Object (by ID)
                })
                
                var entries         = linker.entries
                var notFetchedRefs  = []
                
                Joose.A.each(this.gatherReferences(entries), function (refID) {
                    if (entries[ refID ]) return
                    
                    if (!me.idPinned(refID)) notFetchedRefs.push(refID)
                })
                
                
                linker.link(notFetchedRefs, 0).andThen(function () {
                    
                    var objects = {}
                    
                    Joose.A.each(packet.customIDs, function (id) { 
                        objects[ id ] = me.idToObject(id)
                    })
                    
                    this.CONTINUE.apply(this, [ objects, Joose.A.map(packet.IDs, me.idToObject, me) ])
                })
            },            
            
            
            // `idsToFetch` - array of ids to fetch and materialize from backend 
            // `shallowLevel == 0` - means stop fetching at the nodes already in scope 
            // `shallowLevel == 1` - means fetching the passed `idsToFetch` anyway, but stop on further references 
            // `shallowLevel == 2` - full deep refresh 
            fetch : function (idsToFetch, shallowLevel) {
                var me          = this
                
                // linker will have an access to backend through the scope
                var linker = new KiokuJS.Linker({
                    scope       : this
                })
                
                linker.link(idsToFetch, shallowLevel).andThen(function () {
                    
                    this.CONTINUE.apply(this, Joose.A.map(idsToFetch, me.idToObject, me))
                })
            },
            
            
            lookUp : function () {
                this.fetch(Array.prototype.slice.call(arguments), 0).now()
            },
            
            
            refresh : function () {
                var me              = this
                var idsToFetch      = []
                
                Joose.A.each(arguments, function (object) {
                    if (!me.objectPinned(object)) throw "Can only refresh objects in scope" 
                    
                    idsToFetch.push(me.objectToId(object))
                })
                
                this.fetch(idsToFetch, 1).now()
            },
            
            
            deepRefresh : function () {
                var me              = this
                var idsToFetch      = []
                
                Joose.A.each(arguments, function (object) {
                    if (!me.objectPinned(object)) throw "Can only refresh objects in scope" 
                    
                    idsToFetch.push(me.objectToId(object))
                })
                
                this.fetch(idsToFetch, 2).now()
            },
            
            
            search : function () {
                var backend = this.getBackend()
                
                backend.search(this, arguments).now()
            }
        }
    }

})