import { PathCursor } from './path-cursor' ; import { PathCache } from './path-cache' ; import { QueryTree } from '../query-tree/query-tree' ; import { ResultTreeNode } from './result-tree-node' ; import { ResultTreeHandler } from './result-tree-handler' ; import { Query } from '../query-tree/query' ; import { rgraphql } from 'rgraphql' ; import { QueryTreeNode } from '../query-tree/query-tree-node' ; import { QueryNodePurgeHandler } from '../query-tree/query-tree-handler' ; interface IAttachedHandler { handler : ResultTreeHandler ; cursors : PathCursor [ ] ; flush ? : ( ) => void ; } interface IAttachedCursor { resultTreeNode : ResultTreeNode ; cursors : PathCursor [ ] ; } export class ResultTree { private cursor : PathCursor | null = null ; private pathCache : PathCache ; private root : ResultTreeNode ; private rootCursor : PathCursor ; private handlers : IAttachedHandler [ ] = [ ] ; private cachedCursors : IAttachedCursor [ ] = [ ] ; private qtPurgeHandler : QueryNodePurgeHandler ; constructor ( private qtree , cacheStrategy , cacheSize ) { if ( cacheStrategy !== rgraphql . RGQLValueInit . CacheStrategy . CACHE_LRU ) { throw new Error ( 'unsupported cache strategy: ' + cacheStrategy ) ; } this . root = new ResultTreeNode ( { } ) ; this . rootCursor = new PathCursor ( qtree . getRoot ( ) , this . root ) ; this . pathCache = new PathCache ( cacheSize , this . handleCursorEvict . bind ( this ) ) ; this . qtPurgeHandler = this . handleQtNodePurge . bind ( this ) ; qtree . attachQtNodePurgeHandler ( this . qtPurgeHandler ) ; } public reset ( ) { this . cachedCursors . length = 0 ; this . handlers . length = 0 ; this . root . children . length = 0 ; this . rootCursor . resultHandlers . length = 0 ; this . rootCursor . outOfBounds = false ; this . pathCache . reset ( ) ; this . cursor = null ; } public detach ( ) { this . qtree . detachQtNodePurgeHandler ( this . qtPurgeHandler ) ; } public getRoot ( ) { return this . root ; } public handleValue ( val ) { let isFirst = ! this . cursor ; if ( isFirst ) { let posID = val . posIdentifier ; if ( posID ) { let posIDCursor = this . pathCache . get ( posID ) ; if ( ! posIDCursor ) { throw new Error ( 'unknown position id referenced: ' + posID ) ; } this . cursor = posIDCursor . clone ( ) ; delete val . posIdentifier ; } else { this . cursor = this . rootCursor . clone ( ) ; } } if ( ! this . cursor ) { throw new Error ( 'expected non-null cursor' ) ; } this . cursor . apply ( val ) ; let nPosID = val . posIdentifier ; if ( nPosID ) { this . pathCache . set ( nPosID , this . cursor . clone ( ) ) ; } if ( val . value ) { this . cursor = null ; } } public addResultHandler ( handler , flush ? ) { this . handlers . push ( { handler , cursors : [ ] , flush } ) ; this . rootCursor . resultHandlers . push ( handler ) ; this . root . callHandler ( handler , ( rtn , rth ) => { let attachedCursors ; for ( let cPair of this . cachedCursors ) { if ( cPair . resultTreeNode === rtn ) { attachedCursors = cPair . cursors ; break ; } } if ( ! attachedCursors ) { return ; } for ( let hPair of this . handlers ) { if ( hPair . handler === rth ) { hPair . cursors = hPair . cursors . concat ( attachedCursors ) ; for ( let cursor of hPair . cursors ) { cursor . resultHandlers . push ( rth ) ; } break ; } } } ) ; if ( flush ) { flush ( ) ; } } public flushHandlers ( ) { for ( let handler of this . handlers ) { if ( handler . flush ) { handler . flush ( ) ; } } } public removeResultHandler ( handler ) { let hPair ; for ( let i = 0 ; i < this . handlers . length ; i ++ ) { if ( this . handlers [ i ] . handler === handler ) { hPair = this . handlers [ i ] ; this . handlers [ i ] = this . handlers [ this . handlers . length - 1 ] ; this . handlers . splice ( this . handlers . length - 1 , 1 ) ; break ; } } if ( ! hPair ) { return ; } const rmFromResultHandlers = ( rhs ) => { for ( let i = 0 ; i < rhs . length ; i ++ ) { if ( rhs [ i ] === handler ) { rhs [ i ] = rhs [ rhs . length - 1 ] ; rhs . splice ( rhs . length - 1 , 1 ) ; break ; } } } ; for ( let cursor of hPair . cursors ) { rmFromResultHandlers ( cursor . resultHandlers ) ; } rmFromResultHandlers ( this . rootCursor . resultHandlers ) ; } private handleCursorEvict ( cursor ) { this . purgeCursor ( cursor ) ; } private handleQtNodePurge ( nod ) { let purgeQnIDs = [ ] ; while ( nod ) { purgeQnIDs . push ( nod . getID ( ) ) ; nod = nod . getParent ( ) ; } let allHandlers = [ ] ; for ( let rth of this . handlers ) { allHandlers . push ( rth . handler ) ; } this . purgeQtNodesRecursive ( purgeQnIDs . length - 2 , purgeQnIDs , this . root , allHandlers ) ; } private purgeQtNodesRecursive ( idx , purgeQnIDs , rnode , handlers ) { let qnid = purgeQnIDs [ idx ] ; let getNextHandlers = ( rnode ) : ResultTreeHandler [ ] => { let nh = [ ] ; for ( let handler of handlers ) { if ( ! handler ) continue ; let nnh = handler ( rnode . value ) ; if ( nnh ) { nh . push ( nnh ) ; } } return nh ; } ; for ( let i = 0 ; i < rnode . children . length ; i ++ ) { let rchild = rnode . children [ i ] ; if ( rchild . value . queryNodeId === qnid ) { if ( idx === 0 ) { delete rchild . value . value ; rnode . children [ i ] = rnode . children [ rnode . children . length - 1 ] ; rnode . children . splice ( rnode . children . length - 1 , 1 ) ; i -- ; for ( let handler of handlers ) { if ( ! handler ) continue ; let nh = handler ( rchild . value ) ; if ( nh ) { nh ( undefined ) ; } } } else { this . purgeQtNodesRecursive ( idx - 1 , purgeQnIDs , rchild , getNextHandlers ( rchild ) ) ; } } else if ( rchild . value . arrayIndex ) { this . purgeQtNodesRecursive ( idx , purgeQnIDs , rchild , getNextHandlers ( rchild ) ) ; } } } private purgeCursor ( cursor ) { if ( cursor . rnode ) { for ( let i = 0 ; i < this . cachedCursors . length ; i ++ ) { let cachedCursor = this . cachedCursors [ i ] ; if ( cachedCursor . resultTreeNode === cursor . rnode ) { for ( let ci = 0 ; ci < cachedCursor . cursors . length ; ci ++ ) { if ( cachedCursor . cursors [ ci ] === cursor ) { cachedCursor . cursors [ ci ] = cachedCursor . cursors [ cachedCursor . cursors . length - 1 ] ; cachedCursor . cursors . splice ( cachedCursor . cursors . length - 1 , 1 ) ; break ; } } if ( cachedCursor . cursors . length === 0 ) { this . cachedCursors [ i ] = this . cachedCursors [ this . cachedCursors . length - 1 ] ; this . cachedCursors . splice ( this . cachedCursors . length - 1 , 1 ) ; } break ; } } } for ( let handler of cursor . resultHandlers ) { for ( let handlerPair of this . handlers ) { if ( handlerPair . handler === handler ) { for ( let ri = 0 ; ri < handlerPair . cursors . length ; ri ++ ) { if ( handlerPair . cursors [ ri ] === cursor ) { handlerPair . cursors [ ri ] = handlerPair . cursors [ handlerPair . cursors . length - 1 ] ; handlerPair . cursors . splice ( handlerPair . cursors . length - 1 , 1 ) ; break ; } } break ; } } } } }