import * as path from 'path' ; import { TypeDef , TypeRegistry , ObjectFactory } from './object-type' ; import { Uri , ProviderDef , ObjectProvider , ProviderRegistry } from './object-provider' ; import { NamedObjectDef , NamedObject , NamedObjectRegistry , ObjectContextDependency } from './named-object' ; export interface ObjectContext { create ( input ) : any ; get ( name ) : NamedObject ; forEach ( callback ) : void ; baseDir : string ; } export class ScopedObjectContext implements ObjectContext { private _scope : string ; private _parent : ScopedObjectContext ; private _def : ScopedObjectContextDef ; private _objectFactory : ObjectFactory ; private _objectProvider : ObjectProvider ; private _namedObjects : NamedObjectRegistry ; private _baseDir : string ; public constructor ( scopeName , baseDir , parent , definition ) { this . _scope = scopeName ; this . _baseDir = baseDir ; this . _def = definition ; this . _parent = parent ; this . _objectFactory = TypeRegistry . fromDefinition ( definition . typeDefs , baseDir ) ; this . _objectProvider = ProviderRegistry . fromDefinition ( definition . providerDefs , baseDir ) ; this . _namedObjects = NamedObjectRegistry . fromDefinition ( scopeName , definition . namedObjectDefs , this ) ; } public get scope ( ) : string { return this . _scope ; } public get baseDir ( ) : string { return this . _baseDir ; } public get def ( ) : ScopedObjectContextDef { return this . _def ; } public get parent ( ) : ScopedObjectContext { return this . _parent ; } public forEach ( callback ) { let visited = new Set < string > ( ) ; let currentScope = this ; while ( currentScope != null ) { currentScope . _namedObjects . forEach ( object => { if ( ! visited . has ( object . def . name ) ) { visited . add ( object . def . name ) ; callback ( object ) ; } } ) ; currentScope = currentScope . _parent ; } } public create ( input ) { if ( Array . isArray ( input ) ) { if ( input . length == 0 ) { return input ; } if ( typeof input [ 0 ] == 'string' ) { let uris = [ ] ; let success = input . every ( ( uri ) => { let ret = Uri . tryParse ( uri ) ; if ( ret . success ) { uris . push ( ret . uri ) ; } return ret . success ; } ) ; if ( ! success ) { return input ; } let provider = this . selectProvider ( uris [ 0 ] , input ) ; return provider . provide ( uris , this ) ; } else if ( typeof input [ 0 ] === 'object' ) { if ( input [ 0 ] . hasOwnProperty ( '_type' ) ) { let typeName = input [ 0 ] [ '_type' ] ; let factory = this . selectFactory ( typeName , input ) ; return factory . create ( input , this ) ; } } return input ; } else if ( typeof input === 'string' ) { let ret = Uri . tryParse ( input ) ; if ( ret . success ) { let uri = ret . uri ; let provider = this . selectProvider ( uri , input ) ; return provider . provide ( uri , this ) ; } else { return input ; } } else if ( typeof input === 'object' ) { if ( input . hasOwnProperty ( '_type' ) ) { let typeName = input [ '_type' ] ; let factory = this . selectFactory ( typeName , input ) ; return factory . create ( input , this ) ; } } return input ; } public get ( name ) { let namedObject = this . _namedObjects . get ( name ) ; if ( namedObject != null ) { return namedObject ; } let depth = 1 ; let parent = this . _parent ; while ( parent != null ) { namedObject = parent . _namedObjects . get ( name ) ; if ( namedObject != null ) { if ( this . needsUpdate ( namedObject , depth ) ) { namedObject = { def : namedObject . def , value : this . create ( namedObject . def . value ) , scope : this . _scope } ; this . _namedObjects . insert ( namedObject ) ; } return namedObject ; } parent = parent . _parent ; ++ depth ; } return null ; } public needsUpdate ( namedObject , depth ) { if ( this . _parent == null ) { return false ; } let def = namedObject . def ; let overrides = this . _def ; if ( this . _def . typeDefs . length != 0 ) { let typeDeps = def . dependencies . typeDependencies ; for ( let typeDep of typeDeps ) { if ( overrides . getTypeDef ( typeDep , depth - 1 ) != null ) { return true ; } } } if ( this . _def . providerDefs . length != 0 ) { let providerDeps = def . dependencies . protocolDependencies ; for ( let providerDep of providerDeps ) { if ( overrides . getProviderDef ( providerDep , depth - 1 ) != null ) { return true ; } } ; } if ( this . _def . namedObjectDefs . length != 0 ) { let objectDeps = def . dependencies . objectDependencies ; for ( let objectDep of objectDeps ) { if ( overrides . getNamedObjectDef ( objectDep , depth - 1 ) != null ) { return true ; } } } return false ; } private selectProvider ( uri , input ) { if ( uri != null ) { for ( let scope = this ; scope != null ; scope = scope . _parent ) { let provider = scope . _objectProvider ; if ( provider != null && provider . supports ( uri . protocol ) ) { return provider ; } } } throw new Error ( "Cannot create object, URI protocol '" + uri . protocol + "' is not supported. Input=" + JSON . stringify ( input ) ) ; } private selectFactory ( typeName , input ) { for ( let scope = this ; scope != null ; scope = scope . _parent ) { let factory = scope . _objectFactory ; if ( factory != null && factory . supports ( typeName ) ) { return factory ; } } throw new Error ( "Cannot create object, _type '" + typeName + "' is not supported. Input=" + JSON . stringify ( input ) ) ; } } export class ScopedObjectContextDef { private _parent : ScopedObjectContextDef ; private _typeDefs : TypeDef [ ] ; private _typeNameToDef : Map < string , TypeDef > ; private _providerDefs : ProviderDef [ ] ; private _protocolNameToDef : Map < string , ProviderDef > ; private _objectDefs : NamedObjectDef [ ] ; private _objectNameToDef : Map < string , NamedObjectDef > ; public constructor ( parentDefinition , typeDefs , providerDefs , objectDefs , enableDependencyAnalysis ) { this . _parent = parentDefinition ; this . _typeDefs = typeDefs ; this . _providerDefs = providerDefs ; this . _objectDefs = objectDefs ; this . _typeNameToDef = new Map < string , TypeDef > ( ) ; for ( let def of typeDefs ) { this . _typeNameToDef . set ( def . typeName , def ) ; } this . _protocolNameToDef = new Map < string , ProviderDef > ( ) ; for ( let def of providerDefs ) { this . _protocolNameToDef . set ( def . protocol , def ) ; } this . _objectNameToDef = new Map < string , NamedObjectDef > ( ) ; for ( let def of objectDefs ) { this . _objectNameToDef . set ( def . name , def ) ; } if ( enableDependencyAnalysis ) { this . analyzeNamedObjectDependencies ( ) ; } } public get parent ( ) : ScopedObjectContextDef { return this . _parent ; } public get typeDefs ( ) : TypeDef [ ] { return this . _typeDefs ; } public getTypeDef ( typeName , maxDepth = 32 ) { let def = this . _typeNameToDef . get ( typeName ) ; if ( def == null && maxDepth > 0 && this . _parent != null ) { def = this . _parent . getTypeDef ( typeName , maxDepth - 1 ) ; } return def ; } public get providerDefs ( ) : ProviderDef [ ] { return this . _providerDefs ; } public getProviderDef ( protocolName , maxDepth = 32 ) { let def = this . _protocolNameToDef . get ( protocolName ) ; if ( def == null && maxDepth > 0 && this . _parent != null ) { def = this . _parent . getProviderDef ( protocolName , maxDepth - 1 ) ; } return def ; } public get namedObjectDefs ( ) : NamedObjectDef [ ] { return this . _objectDefs ; } public getNamedObjectDef ( name , maxDepth = 32 ) { let def = this . _objectNameToDef . get ( name ) ; if ( def == null && maxDepth > 0 && this . _parent != null ) { def = this . _parent . getNamedObjectDef ( name , maxDepth - 1 ) ; } return def ; } private analyzeNamedObjectDependencies ( ) { for ( let def of this . _objectDefs ) { def . dependencies = new ObjectContextDependency ( ) ; ScopedObjectContextDef . analyzeDirectDependencies ( def . dependencies , def . value ) ; let typeDeps = def . dependencies . typeDependencies ; typeDeps . forEach ( typeDep => { if ( this . getTypeDef ( typeDep ) == null ) { throw new Error ( "Unrecoginized type '" + typeDep + "' found in named object '" + def . name + "'." ) ; } } ) ; let protocolDeps = def . dependencies . protocolDependencies ; protocolDeps . forEach ( protocolDep => { if ( this . getProviderDef ( protocolDep ) == null ) { throw new Error ( "Unrecongized URI protocol '" + protocolDep + "' found in named object '" + def . name + "'." ) ; } } ) ; } let resolved = new Map < string , Set < string > > ( ) ; let toResolve = [ ] ; for ( let def of this . _objectDefs ) { let objectDeps = def . dependencies . objectDependencies ; if ( objectDeps . size != 0 ) { let unresolvedDeps = new Set < string > ( ) ; objectDeps . forEach ( depName => { unresolvedDeps . add ( depName ) ; } ) ; toResolve . push ( { unresolvedDeps : unresolvedDeps , def : def } ) ; } else { resolved . set ( def . name , objectDeps ) ; } } while ( toResolve . length != 0 ) { let remaining = [ ] ; let resolvedThisRound = 0 ; for ( let record of toResolve ) { let unresolvedDeps = Object . keys ( record . unresolvedDeps ) ; for ( let dep of unresolvedDeps ) { let depClosure = resolved . get ( dep ) ; if ( depClosure != null ) { record . unresolvedDeps . delete ( dep ) ; depClosure . forEach ( depName => { record . def . dependencies . setObjectDependency ( depName ) ; } ) ; } } if ( unresolvedDeps . length == 0 ) { resolved . set ( record . def . name , record . def . dependencies . objectDependencies ) ; ++ resolvedThisRound ; } else { remaining . push ( record ) ; } } if ( resolvedThisRound == 0 ) { throw new Error ( "Undefined named object or cyclic dependencies found: '" + toResolve . map ( obj => { return obj . def . name ; } ) . join ( "," ) ) + "'." ; } toResolve = remaining ; } } private static analyzeDirectDependencies ( dep , jsValue ) { if ( typeof jsValue === 'string' ) { let ret = Uri . tryParse ( jsValue ) ; if ( ret . success ) { dep . setProtocolDependency ( ret . uri . protocol ) ; } } else if ( typeof jsValue === 'object' ) { let typeName = jsValue [ '_type' ] ; if ( typeName != null ) { dep . setTypeDependency ( typeName ) ; } let propertyNames = Object . getOwnPropertyNames ( jsValue ) ; for ( let propertyName of propertyNames ) { ScopedObjectContextDef . analyzeDirectDependencies ( dep , jsValue [ propertyName ] ) ; } } } }