schema.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /**
  2. * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import * as Data from './data-model'
  7. import Column, { createAndFillArray } from '../../../mol-base/collections/column'
  8. /**
  9. * A schema defines the shape of categories and fields.
  10. *
  11. * @example:
  12. * const atom_site = {
  13. * '@alias': '_atom_site',
  14. * label_atom_id: Field.str(),
  15. * Cartn_x: Field.float(),
  16. * Cartn_y: Field.float(),
  17. * Cartn_z: Field.float(),
  18. * }
  19. *
  20. * const mmCIF = { atom_site };
  21. */
  22. //////////////////////////////////////////////
  23. export function toTypedFrame<Schema extends FrameSchema, Frame extends TypedFrame<Schema> = TypedFrame<Schema>>(schema: Schema, frame: Data.Frame): Frame {
  24. return createTypedFrame(schema, frame) as Frame;
  25. }
  26. export function toTypedCategory<Schema extends CategorySchema>(schema: Schema, category: Data.Category): TypedCategory<Schema> {
  27. return new _TypedCategory(category, schema, true) as TypedCategory<any>;
  28. }
  29. export type FrameSchema = { [category: string]: CategorySchema }
  30. export type TypedFrameShape<Schema extends FrameSchema> = { [C in keyof Schema]: TypedCategoryShape<Schema[C]> }
  31. export type TypedFrame<Schema extends FrameSchema> = {
  32. readonly _header?: string,
  33. readonly _frame: Data.Frame
  34. } & { [C in keyof Schema]: TypedCategory<Schema[C]> }
  35. export type CategorySchema = { [field: string]: Field.Schema<any> }
  36. export type TypedCategoryShape<Schema extends CategorySchema> = { [F in keyof Schema]: Column<Schema[F]['T']> }
  37. export type TypedCategory<Schema extends CategorySchema> = {
  38. readonly _rowCount: number,
  39. readonly _isDefined: boolean,
  40. readonly _category: Data.Category
  41. } & { [F in keyof Schema]: Column<Schema[F]['T']> }
  42. export namespace Field {
  43. export interface Schema<T> { T: T, ctor: (field: Data.Field, category: Data.Category, key: string) => Column<T>, undefinedField: (c: number) => Data.Field, alias?: string };
  44. export interface Spec { undefinedField?: (c: number) => Data.Field, alias?: string }
  45. export function alias(name: string): Schema<any> { return { alias: name } as any; }
  46. export function str(spec?: Spec) { return createSchema(spec, Str); }
  47. export function int(spec?: Spec) { return createSchema(spec, Int); }
  48. export function float(spec?: Spec) { return createSchema(spec, Float); }
  49. export function vector(rows: number, spec?: Spec) { return createSchema(spec, Vector(rows)); }
  50. export function matrix(rows: number, cols: number, spec?: Spec) { return createSchema(spec, Matrix(rows, cols)); }
  51. function create<T>(type: Column.Type, field: Data.Field, value: (row: number) => T, toArray: Column<T>['toArray']): Column<T> {
  52. return {
  53. '@type': type,
  54. '@array': field['@array'],
  55. isDefined: field.isDefined,
  56. rowCount: field.rowCount,
  57. value,
  58. valueKind: field.valueKind,
  59. stringEquals: field.stringEquals,
  60. areValuesEqual: field.areValuesEqual,
  61. toArray
  62. };
  63. }
  64. function Str(field: Data.Field) { return create(Column.Type.str, field, field.str, field.toStringArray); }
  65. function Int(field: Data.Field) { return create(Column.Type.int, field, field.int, field.toIntArray); }
  66. function Float(field: Data.Field) { return create(Column.Type.float, field, field.float, field.toFloatArray); }
  67. function Vector(rows: number) {
  68. return function(field: Data.Field, category: Data.Category, key: string) {
  69. const value = (row: number) => Data.getVector(category, key, rows, row);
  70. return create(Column.Type.vector, field, value, params => createAndFillArray(field.rowCount, value, params));
  71. }
  72. }
  73. function Matrix(rows: number, cols: number) {
  74. return function(field: Data.Field, category: Data.Category, key: string) {
  75. const value = (row: number) => Data.getMatrix(category, key, rows, cols, row);
  76. return create(Column.Type.matrix, field, value, params => createAndFillArray(field.rowCount, value, params));
  77. }
  78. }
  79. // spec argument is to allow for specialised implementation for undefined fields
  80. function createSchema<T>(spec: Spec | undefined, ctor: (field: Data.Field, category: Data.Category, key: string) => Column<T>): Schema<T> {
  81. return { T: 0 as any, ctor, undefinedField: (spec && spec.undefinedField) || Data.DefaultUndefinedField, alias: spec && spec.alias };
  82. }
  83. }
  84. class _TypedFrame implements TypedFrame<any> { // tslint:disable-line:class-name
  85. header = this._frame.header;
  86. constructor(public _frame: Data.Frame, schema: FrameSchema) {
  87. for (const k of Object.keys(schema)) {
  88. Object.defineProperty(this, k, { value: createTypedCategory(k, schema[k], _frame), enumerable: true, writable: false, configurable: false });
  89. }
  90. }
  91. }
  92. class _TypedCategory implements TypedCategory<any> { // tslint:disable-line:class-name
  93. _rowCount = this._category.rowCount;
  94. constructor(public _category: Data.Category, schema: CategorySchema, public _isDefined: boolean) {
  95. const fieldKeys = Object.keys(schema).filter(k => k !== '@alias');
  96. const cache = Object.create(null);
  97. for (const k of fieldKeys) {
  98. const s = schema[k];
  99. Object.defineProperty(this, k, {
  100. get: function() {
  101. if (cache[k]) return cache[k];
  102. const name = s.alias || k;
  103. const field = _category.getField(name) || s.undefinedField(_category.rowCount);
  104. cache[k] = s.ctor(field, _category, name);
  105. return cache[k];
  106. },
  107. enumerable: true,
  108. configurable: false
  109. });
  110. }
  111. }
  112. }
  113. function createTypedFrame(schema: FrameSchema, frame: Data.Frame): any {
  114. return new _TypedFrame(frame, schema);
  115. }
  116. function createTypedCategory(key: string, schema: CategorySchema, frame: Data.Frame) {
  117. const alias = (schema['@alias'] && schema['@alias'].alias) || key;
  118. const name = alias[0] === '_' ? alias : '_' + alias;
  119. const cat = frame.categories[name];
  120. return new _TypedCategory(cat || Data.Category.Empty, schema, !!cat);
  121. }