import { ClassType , KeyType } from '../utils/types' ; import { ClassInfo } from './ClassInfo' ; import { getMetadata } from './metadata' ; import { getSuperClassInfo } from '../utils' ; type ExtendAndInterface = KeyType ; export interface TypeWithInfo { type : ClassType ; info : ClassInfo ; } export class DuplicateRegistrationError extends Error { constructor ( type ) { super ( `Type ${ type . name } is already registration.` ) ; } } export class ClassLoader { constructor ( internal private classInfoMap = new Map ( ) , internal private implementCacheMap = new Map ( ) ) { } /** has class */ hasClass ( type ) { return this . classInfoMap . has ( type ) ; } /** register class */ registerClass ( type , info ? ) { if ( this . classInfoMap . has ( type ) ) { throw new DuplicateRegistrationError ( type ) ; } const metadata = getMetadata ( type ) ; metadata . injectable = true ; info = info || metadata . classInfo ; if ( ! info . name ) { info . name = type . name ; } if ( ! info . extends ?. length ) { const superClasses = getSuperClassInfo ( type ) ; info . extends = superClasses . map ( c => c . class ) ; } this . classInfoMap . set ( type , info ) ; if ( ! info . implements ) { info . implements = [ ] ; } [ ... info . extends , ... info . implements ] . forEach ( ext => { const impls = this . getImplCacheByType ( ext ) ; if ( impls . every ( impl => impl . type !== type ) ) { impls . push ( { type , info , } ) ; } } ) ; return this ; } /** unregister class */ unregisterClass ( type ) { const info = this . classInfoMap . get ( type ) ; if ( info ) { [ ... info . extends , ... info . implements ] . forEach ( ext => { const cache = this . getImplCacheByType ( ext ) ; const index = cache . findIndex ( c => c . type === type ) ; if ( index >= 0 ) { cache . splice ( index , 1 ) ; } } ) ; this . classInfoMap . delete ( type ) ; } } /** get class info */ getClassInfo ( type ) { return this . classInfoMap . get ( type ) ; } /** filter classes by info */ filterClasses ( pattern ) { const classes = [ ] ; this . classInfoMap . forEach ( ( info , type ) => { const typeWithInfo = { type , info } ; if ( pattern ( typeWithInfo ) ) { classes . push ( typeWithInfo ) ; } } ) ; return classes ; } /** classes of extends/implements type */ getImplementClasses ( type ) { return this . getImplCacheByType ( type ) ; } clearAll ( ) { this . classInfoMap . clear ( ) ; this . implementCacheMap . clear ( ) ; return this ; } /** return new instance from this */ clone ( ) { return new ClassLoader ( new Map ( this . classInfoMap ) , this . cloneImplCacheMap ( this . implementCacheMap ) ) ; } param classLoader initWith ( classLoader ) { this . classInfoMap = new Map ( classLoader . classInfoMap ) ; this . implementCacheMap = this . cloneImplCacheMap ( classLoader . implementCacheMap ) ; } private cloneImplCacheMap ( map ) { return new Map ( Array . from ( map . entries ( ) ) . map ( ( [ k , v ] ) => [ k , [ ... v ] ] ) ) ; } private getImplCacheByType ( type ) { if ( ! this . implementCacheMap . has ( type ) ) { this . implementCacheMap . set ( type , [ ] ) ; return this . getImplCacheByType ( type ) ; } return this . implementCacheMap . get ( type ) ; } } export const classLoader = new ClassLoader ( ) ;