jspurefix
Version:
pure node js fix engine
315 lines (283 loc) • 10.8 kB
text/typescript
import { ILooseObject } from '../../../collections/collection'
import {
SimpleFieldDefinition, GroupFieldDefinition, ComponentFieldDefinition,
MessageDefinition, FixDefinitions
} from '../../definition'
import {
IContainedSet,
ContainedComponentField,
ContainedGroupField,
ContainedSimpleField,
ContainedSetBuilder
} from '../../contained'
import { FixVersion } from '../../fix-versions'
import { GetJsFixLogger, IJsFixLogger } from '../../../config'
import { FixDefinitionSource } from '../../fix-definition-source'
import { ContainedSetType } from '../../contained-set-type'
import { IRepositoryMessage } from './repository-message'
import { IRepositoryEnum } from './repository-enum'
import { IRepositoryDataType } from './repository-data-type'
import { IRepositoryComponent } from './repository-component'
import { IRepositoryField } from './repository-field'
import { IRepositoryAbbreviation } from './repository-abbreviation'
import { IRepositoryMsgContent } from './repository-msg-content'
export class Repository {
public Enums: IRepositoryEnum[]
public Fields: IRepositoryField[]
public DataTypes: IRepositoryDataType[]
public Components: IRepositoryComponent[]
public Messages: IRepositoryMessage[]
public MsgContents: IRepositoryMsgContent[]
public Abbreviations: IRepositoryAbbreviation[]
public includesAbbreviations: boolean
// derived from above
public readonly definitions: FixDefinitions
private readonly groupLookup: Map<string, GroupFieldDefinition> = new Map<string, GroupFieldDefinition>()
private contentLookup: Map<string, IRepositoryMsgContent[]>
private componentLookup: Map<string, IRepositoryComponent>
private dataTypeLookup: Map<string, IRepositoryDataType>
private readonly logger: IJsFixLogger
constructor (public readonly version: FixVersion, public readonly getLogger: GetJsFixLogger) {
this.logger = getLogger('Repository')
this.includesAbbreviations = Repository.doesIncludeAbbreviations(version)
this.definitions = new FixDefinitions(FixDefinitionSource.FixRepo, version)
}
private static doesIncludeAbbreviations (version: FixVersion): boolean {
let includesAbbreviations: boolean
switch (version) {
case FixVersion.FIX44:
case FixVersion.FIX50:
case FixVersion.FIX50SP1:
case FixVersion.FIX50SP2:
includesAbbreviations = true
break
default: {
includesAbbreviations = false
break
}
}
return includesAbbreviations
}
public assign (name: string, data: ILooseObject[]): void {
switch (name) {
case 'Fields': {
this.Fields = data as IRepositoryField[]
break
}
case 'Enums': {
this.Enums = data as IRepositoryEnum[]
break
}
case 'Datatypes': {
this.DataTypes = data as IRepositoryDataType[]
break
}
case 'Components': {
this.Components = data as IRepositoryComponent[]
break
}
case 'Messages': {
this.Messages = data as IRepositoryMessage[]
break
}
case 'MsgContents': {
this.MsgContents = data as IRepositoryMsgContent[]
if (!this.includesAbbreviations) {
this.toDefinitions()
}
break
}
case 'Abbreviations': {
this.Abbreviations = data as IRepositoryAbbreviation[]
this.toDefinitions()
break
}
}
}
private summarise (): void {
const logger = this.logger
const definitions = this.definitions
logger.info(`definitions: ${definitions.simple.size} fields`)
logger.info(`definitions: ${definitions.component.size} components`)
logger.info(`definitions: ${definitions.message.size} messages`)
}
private toDefinitions (): void {
this.fields()
this.complex()
this.header()
this.trailer()
this.summarise()
}
private complex (): void {
this.contentLookup = this.contents()
this.componentLookup = this.components()
this.Components.forEach((c: IRepositoryComponent) => this.resolve(c))
this.Messages.forEach((m: IRepositoryMessage) => {
const msg = this.message(m)
this.logger.debug(`${msg.toString()}`)
this.definitions.addMessage(msg)
})
}
private header (): void {
const h: ComponentFieldDefinition | undefined = this.definitions.component.get('StandardHeader')
if (h) {
this.definitions.component.set('header', h)
}
}
private trailer (): void {
const t: ComponentFieldDefinition | undefined = this.definitions.component.get('StandardTrailer')
if (t) {
this.definitions.component.set('trailer', t)
}
}
private static isNative (f: IRepositoryField): boolean {
return f.Type === 'Boolean' ||
f.Type === 'data' ||
f.Type === 'LocalMktDate' ||
f.Type === 'UTCDateOnly' ||
f.Type === 'UTCTimestamp'
}
private static makeSimple (f: IRepositoryField, type: string): SimpleFieldDefinition {
return new SimpleFieldDefinition(
f.Tag,
f.Name,
f.AbbrName ? f.AbbrName : f.Name,
f.BaseCategory,
f.BaseCategoryAbbrName,
type.toUpperCase(),
f.Description)
}
private getType (f: IRepositoryField): string {
const types = this.dataTypeLookup
const nativeType = Repository.isNative(f)
let type = f.Type
const mapped = !nativeType && types.get(type)
if (f.Type !== 'Length' && mapped) {
type = mapped.BaseType ?? type
}
return type
}
private fieldEnums (): void {
const definitions = this.definitions
for (const e of this.Enums) {
const field: SimpleFieldDefinition = definitions.tagToSimple[parseInt(e.Tag, 10)]
if (field == null) {
continue
}
if (e.Value && e.SymbolicName) {
field.addEnum(e.Value, e.SymbolicName, e.Description)
}
}
}
private fields (): void {
this.dataTypeLookup = this.types()
const definitions = this.definitions
const types = this.dataTypeLookup
types.delete('boolean')
types.delete('data')
this.Fields.forEach((f: IRepositoryField) => {
const type = this.getType(f)
definitions.addSimpleFieldDef(Repository.makeSimple(f, type))
})
this.fieldEnums()
}
private contents (): Map<string, IRepositoryMsgContent[]> {
return this.MsgContents.reduce((a, current) => {
let content: IRepositoryMsgContent[] | undefined = a.get(current.ComponentID)
if (!content) {
content = []
a.set(current.ComponentID, content)
}
content[content.length] = current
return a
}, new Map<string, IRepositoryMsgContent[]>())
}
private resolveToFieldSet (content: IRepositoryMsgContent[], parentSet: IContainedSet): void {
const builder = new ContainedSetBuilder(parentSet)
content.forEach((current: IRepositoryMsgContent) => {
const required: boolean = current.Reqd === '1'
const tag: number = parseInt(current.TagText, 10)
if (!isNaN(tag)) {
const sf: SimpleFieldDefinition = this.definitions.tagToSimple[tag]
if (sf) {
builder.add(new ContainedSimpleField(sf, parentSet.fields.length, required, false))
}
} else {
// is there a definition for this type yet create.
let childSet: IContainedSet | undefined = this.definitions.component.get(current.TagText)
if (!childSet) {
const cl: IRepositoryComponent | undefined = this.componentLookup.get(current.TagText)
if (cl) {
childSet = this.resolve(cl)
}
}
if (childSet) {
switch (childSet.type) {
case ContainedSetType.Component: {
builder.add(new ContainedComponentField(childSet as ComponentFieldDefinition, parentSet.fields.length, required))
break
}
case ContainedSetType.Group: {
builder.add(new ContainedGroupField(childSet as GroupFieldDefinition, parentSet.fields.length, required))
break
}
default: {
throw new Error(`unknown set type ${childSet.type}`)
}
}
}
}
return parentSet
})
}
private resolve (c: IRepositoryComponent): IContainedSet {
switch (c.ComponentType) {
case 'ImplicitBlockRepeating':
case 'BlockRepeating': {
const content: IRepositoryMsgContent[] | undefined = this.contentLookup.get(c.ComponentID) ?? []
const noField: SimpleFieldDefinition = this.definitions.tagToSimple[parseInt(content[0].TagText, 10)]
let def: GroupFieldDefinition | undefined = this.groupLookup.get(c.ComponentID)
if (!def) {
def = new GroupFieldDefinition(c.Name, c.AbbrName, c.CategoryID, noField, c.Description)
this.resolveToFieldSet(content.slice(1), def)
this.groupLookup.set(c.ComponentID, def)
}
return def
}
default: {
const content: IRepositoryMsgContent[] | null = this.contentLookup.get(c.ComponentID) ?? []
let def: ComponentFieldDefinition | undefined = this.definitions.component.get(c.Name)
if (!def) {
def = new ComponentFieldDefinition(c.Name, c.AbbrName, c.CategoryID, c.Description)
this.resolveToFieldSet(content, def)
this.definitions.addComponentFieldDef(def)
}
return def
}
}
}
private message (m: IRepositoryMessage): MessageDefinition {
const definitions = this.definitions
const content: IRepositoryMsgContent[] = this.contentLookup.get(m.ComponentID) ?? []
let def: MessageDefinition | undefined = definitions.message.get(m.Name)
if (!def) {
def = new MessageDefinition(m.Name, m.AbbrName, m.MsgType, m.CategoryID, m.Description)
this.resolveToFieldSet(content, def)
definitions.addComponentFieldDef(def)
}
return def
}
private components (): Map<string, IRepositoryComponent> {
return this.Components.reduce((a: Map<string, IRepositoryComponent>, current: IRepositoryComponent) => {
a.set(current.Name, current)
a.set(current.ComponentID, current)
return a
}, new Map<string, IRepositoryComponent>())
}
private types (): Map<string, IRepositoryDataType> {
return this.DataTypes.reduce((a: Map<string, IRepositoryDataType>, current: IRepositoryDataType) => {
a.set(current.Name, current)
return a
}, new Map<string, IRepositoryDataType>())
}
}