Nickolay - KiokuJS-0.01

Documentation | Source
Class('KiokuJS.Collapser', {
    
    isa         : 'Data.Visitor',
    
    
    has : {
        refCounts           : Joose.I.Object,
        nodes               : Joose.I.Object, //vertexes, addressed by __REFADR__
        
        backend             : null,
        scope               : { required : true },
        
        isShallow           : false,
        setRoot             : true,
        
        rootObjects         : Joose.I.Object
    },
    
        
    
    before : {
        
        markSeenAs    : function (object) {
            this.refCounts[ object.__REFADR__ ] = 1
        }
        
    },
    
    
    methods : {
        
        initialize : function () {
            this.backend = this.scope.getBackend()
        },
        
        
        visitSeen : function (object, seen) {
            var refAdr = object.__REFADR__
            
            this.refCounts[ refAdr ]++
            
            // return either the node from `this.nodes` - for nodes being collapsed
            // or "seen" node - for nodes which are skipped during shallow collapsing
            return this.nodes[ refAdr ] || seen
        },
        
        
        // calls `func` with (object, desiredID) signature for each passed argument
        eachArgument : function (wIDs, woIDs, func, scope) {
            scope = scope || this
            
            Joose.O.each(wIDs, func, scope)
            
            Joose.A.each(woIDs, function (argument) {
                func.call(scope, argument)
            })
        },
        
        
        collapse : function (wIDs, woIDs) {
            
            // sanity checks
            
            this.eachArgument(wIDs, woIDs, function (argument) {
                if (argument == null || (typeof argument != 'object' && typeof argument != 'function') ) throw "Invalid argument [" + argument + "]  to 'collapse'. Can only collapse objects, not values."
                
                var refAdr = this.assignRefAdrTo(argument)
                
                this.rootObjects[ refAdr ] = true
            })
            
            
            // recurse through the graph, accumulating nodes and counting refs
            
            this.visit(wIDs, woIDs)
            

            // all objects are from root set, so we need to acquire IDs for them
            
            var nodes               = this.nodes
            
            this.eachArgument(wIDs, woIDs, function (argument, desiredId) {
                var node = nodes[ argument.__REFADR__ ]
                
                node.acquireID(desiredId)
                if (this.setRoot) node.isRoot = true
            })


            // also marks shared nodes (refCount > 1) and Joose instances (unless intrinsic) as first class 
            
            var refCounts           = this.refCounts
            var firstClassNodes     = []
            var me                  = this
            
            Joose.O.each(nodes, function (node, refadr) {
                
                var object      = node.object
                var isExtrinsic = (refCounts[ refadr ] > 1 || Joose.O.isInstance(object) || node.extrinsic || node.lazy) && !node.isIntrinsic()
                
                if ( me.belongsToRootSet(object) || isExtrinsic) {
                    // this makes this node `firstClass` (but not root)
                    if (!node.isFirstClass()) node.acquireID()
                    
                    if (!node.immutable) firstClassNodes.push(node)
                }
            })
            
            
            return firstClassNodes
        },
     
        
        visitArray : function (instance, className) {
            return this.visitObject(instance, className)
        },
        
        
        belongsToRootSet : function (object) {
            return this.rootObjects[ object.__REFADR__ ]
        },
        
        
        visitObject : function (instance, className) {
            var scope           = this.scope
            var nodes           = this.nodes
            
            var node            = scope.objectToNode(instance)
            var refAdr          = instance.__REFADR__
            
            // if this is a shallow collapsing, and we found the instance which already has the node
            // then stop collapsing at this point and do not recurse
            // also do not add the node into `this.nodes` to prevent it from returning as a result of `collapse`
            if (node && (this.isShallow && !this.belongsToRootSet(instance) || node.immutable)) return node
                
            if (node) {
                node.clearEntry()
                
                nodes[ refAdr ] = node
            } else
                // need to create the node before recursing through the instance's data, to handle circular-references correctly
                nodes[ refAdr ] = node = this.backend.createNodeFromObject(instance)
            
            // now that node is already in the `this.nodes` we can collect the data
            node.collapse(this)
            
            return node
        }
    }

})