/** * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal */ import * as Data from './data-model' import Column, { createAndFillArray } from '../../../mol-base/collections/column' /** * A schema defines the shape of categories and fields. * * @example: * const atom_site = { * '@alias': '_atom_site', * label_atom_id: Field.str(), * Cartn_x: Field.float(), * Cartn_y: Field.float(), * Cartn_z: Field.float(), * } * * const mmCIF = { atom_site }; */ ////////////////////////////////////////////// export function toTypedFrame = TypedFrame>(schema: Schema, frame: Data.Frame): Frame { return createTypedFrame(schema, frame) as Frame; } export function toTypedCategory(schema: Schema, category: Data.Category): TypedCategory { return new _TypedCategory(category, schema, true) as TypedCategory; } export type FrameSchema = { [category: string]: CategorySchema } export type TypedFrameShape = { [C in keyof Schema]: TypedCategoryShape } export type TypedFrame = { readonly _header?: string, readonly _frame: Data.Frame } & { [C in keyof Schema]: TypedCategory } export type CategorySchema = { [field: string]: Field.Schema } export type TypedCategoryShape = { [F in keyof Schema]: Column } export type TypedCategory = { readonly _rowCount: number, readonly _isDefined: boolean, readonly _category: Data.Category } & { [F in keyof Schema]: Column } export namespace Field { export interface Schema { T: T, ctor: (field: Data.Field, category: Data.Category, key: string) => Column, undefinedField: (c: number) => Data.Field, alias?: string }; export interface Spec { undefinedField?: (c: number) => Data.Field, alias?: string } export function alias(name: string): Schema { return { alias: name } as any; } export function str(spec?: Spec) { return createSchema(spec, Str); } export function int(spec?: Spec) { return createSchema(spec, Int); } export function float(spec?: Spec) { return createSchema(spec, Float); } export function vector(rows: number, spec?: Spec) { return createSchema(spec, Vector(rows)); } export function matrix(rows: number, cols: number, spec?: Spec) { return createSchema(spec, Matrix(rows, cols)); } function create(type: Column.Type, field: Data.Field, value: (row: number) => T, toArray: Column['toArray']): Column { return { '@type': type, '@array': field['@array'], isDefined: field.isDefined, rowCount: field.rowCount, value, valueKind: field.valueKind, stringEquals: field.stringEquals, areValuesEqual: field.areValuesEqual, toArray }; } function Str(field: Data.Field) { return create(Column.Type.str, field, field.str, field.toStringArray); } function Int(field: Data.Field) { return create(Column.Type.int, field, field.int, field.toIntArray); } function Float(field: Data.Field) { return create(Column.Type.float, field, field.float, field.toFloatArray); } function Vector(rows: number) { return function(field: Data.Field, category: Data.Category, key: string) { const value = (row: number) => Data.getVector(category, key, rows, row); return create(Column.Type.vector, field, value, params => createAndFillArray(field.rowCount, value, params)); } } function Matrix(rows: number, cols: number) { return function(field: Data.Field, category: Data.Category, key: string) { const value = (row: number) => Data.getMatrix(category, key, rows, cols, row); return create(Column.Type.matrix, field, value, params => createAndFillArray(field.rowCount, value, params)); } } // spec argument is to allow for specialised implementation for undefined fields function createSchema(spec: Spec | undefined, ctor: (field: Data.Field, category: Data.Category, key: string) => Column): Schema { return { T: 0 as any, ctor, undefinedField: (spec && spec.undefinedField) || Data.DefaultUndefinedField, alias: spec && spec.alias }; } } class _TypedFrame implements TypedFrame { // tslint:disable-line:class-name header = this._frame.header; constructor(public _frame: Data.Frame, schema: FrameSchema) { for (const k of Object.keys(schema)) { Object.defineProperty(this, k, { value: createTypedCategory(k, schema[k], _frame), enumerable: true, writable: false, configurable: false }); } } } class _TypedCategory implements TypedCategory { // tslint:disable-line:class-name _rowCount = this._category.rowCount; constructor(public _category: Data.Category, schema: CategorySchema, public _isDefined: boolean) { const fieldKeys = Object.keys(schema).filter(k => k !== '@alias'); const cache = Object.create(null); for (const k of fieldKeys) { const s = schema[k]; Object.defineProperty(this, k, { get: function() { if (cache[k]) return cache[k]; const name = s.alias || k; const field = _category.getField(name) || s.undefinedField(_category.rowCount); cache[k] = s.ctor(field, _category, name); return cache[k]; }, enumerable: true, configurable: false }); } } } function createTypedFrame(schema: FrameSchema, frame: Data.Frame): any { return new _TypedFrame(frame, schema); } function createTypedCategory(key: string, schema: CategorySchema, frame: Data.Frame) { const alias = (schema['@alias'] && schema['@alias'].alias) || key; const name = alias[0] === '_' ? alias : '_' + alias; const cat = frame.categories[name]; return new _TypedCategory(cat || Data.Category.Empty, schema, !!cat); }