2021-08-03 16:55:52 +00:00
|
|
|
//
|
|
|
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
|
|
|
//
|
|
|
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License. You may
|
|
|
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
//
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
//
|
|
|
|
|
2022-03-03 09:01:39 +00:00
|
|
|
import { FindOptions, Lookup, ToClassRefT, WithLookup } from '.'
|
2021-12-01 20:25:28 +00:00
|
|
|
import type { AnyAttribute, Class, Classifier, Doc, Domain, Interface, Mixin, Obj, Ref } from './classes'
|
2021-08-03 16:55:52 +00:00
|
|
|
import { ClassifierKind } from './classes'
|
|
|
|
import core from './component'
|
2022-01-11 09:10:50 +00:00
|
|
|
import { _createMixinProxy, _mixinClass, _toDoc } from './proxy'
|
2022-05-28 04:05:36 +00:00
|
|
|
import type { Tx, TxCreateDoc, TxMixin, TxRemoveDoc, TxUpdateDoc } from './tx'
|
2021-12-01 20:25:28 +00:00
|
|
|
import { TxProcessor } from './tx'
|
2023-01-18 05:14:59 +00:00
|
|
|
import getTypeOf from './typeof'
|
2021-08-03 16:55:52 +00:00
|
|
|
|
2021-08-04 18:02:49 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
2021-08-03 16:55:52 +00:00
|
|
|
export class Hierarchy {
|
2021-12-01 20:25:28 +00:00
|
|
|
private readonly classifiers = new Map<Ref<Classifier>, Classifier>()
|
|
|
|
private readonly attributes = new Map<Ref<Classifier>, Map<string, AnyAttribute>>()
|
2022-05-28 04:05:36 +00:00
|
|
|
private readonly attributesById = new Map<Ref<AnyAttribute>, AnyAttribute>()
|
2021-12-01 20:25:28 +00:00
|
|
|
private readonly descendants = new Map<Ref<Classifier>, Ref<Classifier>[]>()
|
|
|
|
private readonly ancestors = new Map<Ref<Classifier>, Ref<Classifier>[]>()
|
2021-08-03 16:55:52 +00:00
|
|
|
private readonly proxies = new Map<Ref<Mixin<Doc>>, ProxyHandler<Doc>>()
|
|
|
|
|
2023-01-04 17:58:54 +00:00
|
|
|
private readonly classifierProperties = new Map<Ref<Classifier>, Record<string, any>>()
|
|
|
|
|
2021-08-03 16:55:52 +00:00
|
|
|
private createMixinProxyHandler (mixin: Ref<Mixin<Doc>>): ProxyHandler<Doc> {
|
|
|
|
const value = this.getClass(mixin)
|
|
|
|
const ancestor = this.getClass(value.extends as Ref<Class<Obj>>)
|
|
|
|
const ancestorProxy = ancestor.kind === ClassifierKind.MIXIN ? this.getMixinProxyHandler(ancestor._id) : null
|
2022-01-11 09:10:50 +00:00
|
|
|
return _createMixinProxy(value, ancestorProxy)
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private getMixinProxyHandler (mixin: Ref<Mixin<Doc>>): ProxyHandler<Doc> {
|
|
|
|
const handler = this.proxies.get(mixin)
|
|
|
|
if (handler === undefined) {
|
|
|
|
const handler = this.createMixinProxyHandler(mixin)
|
|
|
|
this.proxies.set(mixin, handler)
|
|
|
|
return handler
|
|
|
|
}
|
|
|
|
return handler
|
|
|
|
}
|
|
|
|
|
|
|
|
as<D extends Doc, M extends D>(doc: D, mixin: Ref<Mixin<M>>): M {
|
|
|
|
return new Proxy(doc, this.getMixinProxyHandler(mixin)) as M
|
|
|
|
}
|
|
|
|
|
2021-12-30 09:04:32 +00:00
|
|
|
static toDoc<D extends Doc>(doc: D): D {
|
2022-01-11 09:10:50 +00:00
|
|
|
return _toDoc(doc)
|
2021-12-30 09:04:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-29 05:27:17 +00:00
|
|
|
static mixinClass<D extends Doc, M extends D>(doc: D): Ref<Mixin<M>> | undefined {
|
2022-01-11 09:10:50 +00:00
|
|
|
return _mixinClass(doc)
|
2021-12-30 09:04:32 +00:00
|
|
|
}
|
|
|
|
|
2022-03-01 09:14:30 +00:00
|
|
|
static mixinOrClass<D extends Doc, M extends D>(doc: D): Ref<Mixin<M> | Class<Doc>> {
|
|
|
|
const m = _mixinClass(doc)
|
|
|
|
return m ?? doc._class
|
|
|
|
}
|
|
|
|
|
2021-12-30 09:04:32 +00:00
|
|
|
hasMixin<D extends Doc, M extends D>(doc: D, mixin: Ref<Mixin<M>>): boolean {
|
|
|
|
const d = Hierarchy.toDoc(doc)
|
|
|
|
return typeof (d as any)[mixin] === 'object'
|
|
|
|
}
|
|
|
|
|
2023-04-25 07:34:10 +00:00
|
|
|
classHierarchyMixin<D extends Doc, M extends D>(
|
|
|
|
_class: Ref<Class<D>>,
|
|
|
|
mixin: Ref<Mixin<M>>,
|
|
|
|
filter?: (value: M) => boolean
|
|
|
|
): M | undefined {
|
2023-03-29 09:18:59 +00:00
|
|
|
let clazz = this.getClass(_class)
|
|
|
|
while (true) {
|
|
|
|
if (this.hasMixin(clazz, mixin)) {
|
2023-04-25 07:34:10 +00:00
|
|
|
const m = this.as(clazz, mixin) as any as M
|
|
|
|
if (m !== undefined && (filter?.(m) ?? true)) {
|
|
|
|
return m
|
|
|
|
}
|
2023-03-29 09:18:59 +00:00
|
|
|
}
|
|
|
|
if (clazz.extends === undefined) return
|
|
|
|
clazz = this.getClass(clazz.extends)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-08 04:33:23 +00:00
|
|
|
findClassOrMixinMixin<D extends Doc, M extends D>(doc: Doc, mixin: Ref<Mixin<M>>): M | undefined {
|
|
|
|
const cc = this.classHierarchyMixin(doc._class, mixin)
|
|
|
|
if (cc !== undefined) {
|
|
|
|
return cc
|
|
|
|
}
|
|
|
|
|
|
|
|
const _doc = _toDoc(doc)
|
|
|
|
// Find all potential mixins of doc
|
|
|
|
for (const [k, v] of Object.entries(_doc)) {
|
|
|
|
if (typeof v === 'object' && this.classifiers.has(k as Ref<Classifier>)) {
|
|
|
|
const cc = this.classHierarchyMixin(k as Ref<Mixin<Doc>>, mixin)
|
|
|
|
if (cc !== undefined) {
|
|
|
|
return cc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-30 09:04:32 +00:00
|
|
|
isMixin (_class: Ref<Class<Doc>>): boolean {
|
|
|
|
const data = this.classifiers.get(_class)
|
|
|
|
return data !== undefined && this._isMixin(data)
|
|
|
|
}
|
|
|
|
|
2021-12-01 20:25:28 +00:00
|
|
|
getAncestors (_class: Ref<Classifier>): Ref<Classifier>[] {
|
2021-08-03 16:55:52 +00:00
|
|
|
const result = this.ancestors.get(_class)
|
|
|
|
if (result === undefined) {
|
|
|
|
throw new Error('ancestors not found: ' + _class)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
getClass (_class: Ref<Class<Obj>>): Class<Obj> {
|
2021-12-01 20:25:28 +00:00
|
|
|
const data = this.classifiers.get(_class)
|
|
|
|
if (data === undefined || this.isInterface(data)) {
|
2021-08-03 16:55:52 +00:00
|
|
|
throw new Error('class not found: ' + _class)
|
|
|
|
}
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
2022-09-09 03:44:33 +00:00
|
|
|
getClassOrInterface (_class: Ref<Class<Obj>>): Class<Obj> {
|
|
|
|
const data = this.classifiers.get(_class)
|
|
|
|
if (data === undefined) {
|
|
|
|
throw new Error('class not found: ' + _class)
|
|
|
|
}
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
2021-12-01 20:25:28 +00:00
|
|
|
getInterface (_interface: Ref<Interface<Doc>>): Interface<Doc> {
|
|
|
|
const data = this.classifiers.get(_interface)
|
|
|
|
if (data === undefined || !this.isInterface(data)) {
|
|
|
|
throw new Error('interface not found: ' + _interface)
|
|
|
|
}
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
2021-08-03 16:55:52 +00:00
|
|
|
getDomain (_class: Ref<Class<Obj>>): Domain {
|
2023-01-04 17:58:54 +00:00
|
|
|
const domain = this.findDomain(_class)
|
|
|
|
if (domain === undefined) {
|
|
|
|
throw new Error(`domain not found: ${_class} `)
|
|
|
|
}
|
|
|
|
return domain
|
|
|
|
}
|
|
|
|
|
|
|
|
public findDomain (_class: Ref<Class<Doc>>): Domain | undefined {
|
2021-08-03 16:55:52 +00:00
|
|
|
const klazz = this.getClass(_class)
|
|
|
|
if (klazz.domain !== undefined) {
|
|
|
|
return klazz.domain
|
|
|
|
}
|
2021-12-01 20:25:28 +00:00
|
|
|
|
|
|
|
let _klazz = klazz
|
|
|
|
while (_klazz.extends !== undefined) {
|
|
|
|
_klazz = this.getClass(_klazz.extends)
|
|
|
|
if (_klazz.domain !== undefined) {
|
2023-01-04 17:58:54 +00:00
|
|
|
// Cache for next requests
|
|
|
|
klazz.domain = _klazz.domain
|
2021-12-01 20:25:28 +00:00
|
|
|
return _klazz.domain
|
|
|
|
}
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tx (tx: Tx): void {
|
2021-08-05 12:39:43 +00:00
|
|
|
switch (tx._class) {
|
|
|
|
case core.class.TxCreateDoc:
|
|
|
|
this.txCreateDoc(tx as TxCreateDoc<Doc>)
|
|
|
|
return
|
2022-05-28 04:05:36 +00:00
|
|
|
case core.class.TxUpdateDoc:
|
|
|
|
this.txUpdateDoc(tx as TxUpdateDoc<Doc>)
|
|
|
|
return
|
|
|
|
case core.class.TxRemoveDoc:
|
|
|
|
this.txRemoveDoc(tx as TxRemoveDoc<Doc>)
|
|
|
|
return
|
2021-08-05 12:39:43 +00:00
|
|
|
case core.class.TxMixin:
|
|
|
|
this.txMixin(tx as TxMixin<Doc, Doc>)
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
2021-08-05 12:39:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private txCreateDoc (tx: TxCreateDoc<Doc>): void {
|
2022-04-29 05:27:17 +00:00
|
|
|
if (
|
|
|
|
tx.objectClass === core.class.Class ||
|
|
|
|
tx.objectClass === core.class.Interface ||
|
|
|
|
tx.objectClass === core.class.Mixin
|
|
|
|
) {
|
2021-12-01 20:25:28 +00:00
|
|
|
const _id = tx.objectId as Ref<Classifier>
|
|
|
|
this.classifiers.set(_id, TxProcessor.createDoc2Doc(tx as TxCreateDoc<Classifier>))
|
2022-08-05 07:48:31 +00:00
|
|
|
this.updateAncestors(_id)
|
|
|
|
this.updateDescendant(_id)
|
2021-08-05 12:39:43 +00:00
|
|
|
} else if (tx.objectClass === core.class.Attribute) {
|
2021-08-03 16:55:52 +00:00
|
|
|
const createTx = tx as TxCreateDoc<AnyAttribute>
|
|
|
|
this.addAttribute(TxProcessor.createDoc2Doc(createTx))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-28 04:05:36 +00:00
|
|
|
private txUpdateDoc (tx: TxUpdateDoc<Doc>): void {
|
|
|
|
if (tx.objectClass === core.class.Attribute) {
|
|
|
|
const updateTx = tx as TxUpdateDoc<AnyAttribute>
|
|
|
|
const doc = this.attributesById.get(updateTx.objectId)
|
|
|
|
if (doc === undefined) return
|
|
|
|
this.addAttribute(TxProcessor.updateDoc2Doc(doc, updateTx))
|
2023-01-04 17:58:54 +00:00
|
|
|
|
|
|
|
this.classifierProperties.delete(doc.attributeOf)
|
2022-08-05 07:48:31 +00:00
|
|
|
} else if (tx.objectClass === core.class.Mixin || tx.objectClass === core.class.Class) {
|
|
|
|
const updateTx = tx as TxUpdateDoc<Mixin<Class<Doc>>>
|
|
|
|
const doc = this.classifiers.get(updateTx.objectId)
|
|
|
|
if (doc === undefined) return
|
|
|
|
TxProcessor.updateDoc2Doc(doc, updateTx)
|
2023-01-04 17:58:54 +00:00
|
|
|
this.classifierProperties.delete(doc._id)
|
2022-05-28 04:05:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private txRemoveDoc (tx: TxRemoveDoc<Doc>): void {
|
|
|
|
if (tx.objectClass === core.class.Attribute) {
|
|
|
|
const removeTx = tx as TxRemoveDoc<AnyAttribute>
|
|
|
|
const doc = this.attributesById.get(removeTx.objectId)
|
|
|
|
if (doc === undefined) return
|
|
|
|
const map = this.attributes.get(doc.attributeOf)
|
|
|
|
map?.delete(doc.name)
|
|
|
|
this.attributesById.delete(removeTx.objectId)
|
2022-08-05 07:48:31 +00:00
|
|
|
} else if (tx.objectClass === core.class.Mixin) {
|
|
|
|
const removeTx = tx as TxRemoveDoc<Mixin<Class<Doc>>>
|
|
|
|
this.updateDescendant(removeTx.objectId, false)
|
|
|
|
this.updateAncestors(removeTx.objectId, false)
|
|
|
|
this.classifiers.delete(removeTx.objectId)
|
2022-05-28 04:05:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-05 12:39:43 +00:00
|
|
|
private txMixin (tx: TxMixin<Doc, Doc>): void {
|
2022-01-11 09:05:53 +00:00
|
|
|
if (this.isDerived(tx.objectClass, core.class.Class)) {
|
2021-08-05 12:39:43 +00:00
|
|
|
const obj = this.getClass(tx.objectId as Ref<Class<Obj>>) as any
|
2022-06-19 16:26:05 +00:00
|
|
|
TxProcessor.updateMixin4Doc(obj, tx)
|
2021-08-05 12:39:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-01 20:25:28 +00:00
|
|
|
/**
|
|
|
|
* Check if passed _class is derived from `from` class.
|
|
|
|
* It will iterave over parents.
|
|
|
|
*/
|
2021-08-03 16:55:52 +00:00
|
|
|
isDerived<T extends Obj>(_class: Ref<Class<T>>, from: Ref<Class<T>>): boolean {
|
2021-12-01 20:25:28 +00:00
|
|
|
let cl: Ref<Class<T>> | undefined = _class
|
2021-08-03 16:55:52 +00:00
|
|
|
while (cl !== undefined) {
|
|
|
|
if (cl === from) return true
|
2021-12-01 20:25:28 +00:00
|
|
|
cl = this.getClass(cl).extends
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-12-30 09:04:32 +00:00
|
|
|
/**
|
|
|
|
* Return first non interface/mixin parent
|
|
|
|
*/
|
|
|
|
getBaseClass<T extends Doc>(_class: Ref<Mixin<T>>): Ref<Class<T>> {
|
|
|
|
let cl: Ref<Class<T>> | undefined = _class
|
|
|
|
while (cl !== undefined) {
|
|
|
|
const clz = this.getClass(cl)
|
|
|
|
if (this.isClass(clz)) return cl
|
|
|
|
cl = clz.extends
|
|
|
|
}
|
|
|
|
return core.class.Doc
|
|
|
|
}
|
|
|
|
|
2021-12-01 20:25:28 +00:00
|
|
|
/**
|
|
|
|
* Check if passed _class implements passed interfaces `from`.
|
|
|
|
* It will check for class parents and they interfaces.
|
|
|
|
*/
|
|
|
|
isImplements<T extends Doc>(_class: Ref<Class<T>>, from: Ref<Interface<T>>): boolean {
|
|
|
|
let cl: Ref<Class<T>> | undefined = _class
|
|
|
|
while (cl !== undefined) {
|
|
|
|
const klazz = this.getClass(cl)
|
|
|
|
if (this.isExtends(klazz.implements ?? [], from)) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
cl = klazz.extends
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if interface is extends passed interface.
|
|
|
|
*/
|
|
|
|
private isExtends<T extends Doc>(extendsOrImplements: Ref<Interface<Doc>>[], from: Ref<Interface<T>>): boolean {
|
|
|
|
const result: Ref<Interface<Doc>>[] = []
|
2021-12-02 13:02:18 +00:00
|
|
|
const toVisit = [...extendsOrImplements]
|
2021-12-01 20:25:28 +00:00
|
|
|
while (toVisit.length > 0) {
|
|
|
|
const ref = toVisit.shift() as Ref<Interface<Doc>>
|
|
|
|
if (ref === from) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
addIf(result, ref)
|
|
|
|
toVisit.push(...this.ancestorsOf(ref))
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
getDescendants<T extends Obj>(_class: Ref<Class<T>>): Ref<Class<Obj>>[] {
|
|
|
|
const data = this.descendants.get(_class)
|
|
|
|
if (data === undefined) {
|
|
|
|
throw new Error('descendants not found: ' + _class)
|
|
|
|
}
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
2022-08-05 07:48:31 +00:00
|
|
|
private updateDescendant (_class: Ref<Classifier>, add = true): void {
|
2021-08-03 16:55:52 +00:00
|
|
|
const hierarchy = this.getAncestors(_class)
|
|
|
|
for (const cls of hierarchy) {
|
|
|
|
const list = this.descendants.get(cls)
|
|
|
|
if (list === undefined) {
|
2022-08-05 07:48:31 +00:00
|
|
|
if (add) {
|
|
|
|
this.descendants.set(cls, [_class])
|
|
|
|
}
|
2021-08-03 16:55:52 +00:00
|
|
|
} else {
|
2022-08-05 07:48:31 +00:00
|
|
|
if (add) {
|
|
|
|
list.push(_class)
|
|
|
|
} else {
|
|
|
|
const pos = list.indexOf(_class)
|
|
|
|
if (pos !== -1) {
|
|
|
|
list.splice(pos, 1)
|
|
|
|
}
|
|
|
|
}
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 07:48:31 +00:00
|
|
|
private updateAncestors (_class: Ref<Classifier>, add = true): void {
|
2021-12-01 20:25:28 +00:00
|
|
|
const cl: Ref<Classifier>[] = [_class]
|
|
|
|
const visited = new Set<Ref<Classifier>>()
|
|
|
|
while (cl.length > 0) {
|
|
|
|
const classifier = cl.shift() as Ref<Classifier>
|
|
|
|
if (addNew(visited, classifier)) {
|
|
|
|
const list = this.ancestors.get(_class)
|
|
|
|
if (list === undefined) {
|
2022-08-05 07:48:31 +00:00
|
|
|
if (add) {
|
|
|
|
this.ancestors.set(_class, [classifier])
|
|
|
|
}
|
2021-12-01 20:25:28 +00:00
|
|
|
} else {
|
2022-08-05 07:48:31 +00:00
|
|
|
if (add) {
|
|
|
|
addIf(list, classifier)
|
|
|
|
} else {
|
|
|
|
const pos = list.indexOf(classifier)
|
|
|
|
if (pos !== -1) {
|
|
|
|
list.splice(pos, 1)
|
|
|
|
}
|
|
|
|
}
|
2021-12-01 20:25:28 +00:00
|
|
|
}
|
|
|
|
cl.push(...this.ancestorsOf(classifier))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return extends and implemnets as combined list of references
|
|
|
|
*/
|
|
|
|
private ancestorsOf (classifier: Ref<Classifier>): Ref<Classifier>[] {
|
|
|
|
const attrs = this.classifiers.get(classifier)
|
|
|
|
const result: Ref<Classifier>[] = []
|
2021-12-30 09:04:32 +00:00
|
|
|
if (this.isClass(attrs) || this._isMixin(attrs)) {
|
2021-12-01 20:25:28 +00:00
|
|
|
const cls = attrs as Class<Doc>
|
|
|
|
if (cls.extends !== undefined) {
|
|
|
|
result.push(cls.extends)
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
2021-12-01 20:25:28 +00:00
|
|
|
result.push(...(cls.implements ?? []))
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
2021-12-01 20:25:28 +00:00
|
|
|
if (this.isInterface(attrs)) {
|
|
|
|
result.push(...((attrs as Interface<Doc>).extends ?? []))
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
private isClass (attrs?: Classifier): boolean {
|
|
|
|
return attrs?.kind === ClassifierKind.CLASS
|
|
|
|
}
|
|
|
|
|
2021-12-30 09:04:32 +00:00
|
|
|
private _isMixin (attrs?: Classifier): boolean {
|
|
|
|
return attrs?.kind === ClassifierKind.MIXIN
|
|
|
|
}
|
|
|
|
|
2021-12-01 20:25:28 +00:00
|
|
|
private isInterface (attrs?: Classifier): boolean {
|
|
|
|
return attrs?.kind === ClassifierKind.INTERFACE
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private addAttribute (attribute: AnyAttribute): void {
|
|
|
|
const _class = attribute.attributeOf
|
|
|
|
let attributes = this.attributes.get(_class)
|
|
|
|
if (attributes === undefined) {
|
|
|
|
attributes = new Map<string, AnyAttribute>()
|
|
|
|
this.attributes.set(_class, attributes)
|
|
|
|
}
|
|
|
|
attributes.set(attribute.name, attribute)
|
2022-05-28 04:05:36 +00:00
|
|
|
this.attributesById.set(attribute._id, attribute)
|
2023-01-04 17:58:54 +00:00
|
|
|
this.classifierProperties.delete(attribute.attributeOf)
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
|
|
|
|
2021-12-30 09:04:32 +00:00
|
|
|
getAllAttributes (clazz: Ref<Classifier>, to?: Ref<Classifier>): Map<string, AnyAttribute> {
|
2021-08-27 09:17:18 +00:00
|
|
|
const result = new Map<string, AnyAttribute>()
|
2021-12-30 09:04:32 +00:00
|
|
|
let ancestors = this.getAncestors(clazz)
|
|
|
|
if (to !== undefined) {
|
2022-01-11 09:05:53 +00:00
|
|
|
const toAncestors = this.getAncestors(to)
|
|
|
|
for (const uto of toAncestors) {
|
|
|
|
if (ancestors.includes(uto)) {
|
|
|
|
to = uto
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2022-05-28 04:05:36 +00:00
|
|
|
ancestors = ancestors.filter(
|
|
|
|
(c) => c !== to && (this.isInterface(this.classifiers.get(c)) || this.isDerived(c, to as Ref<Class<Doc>>))
|
|
|
|
)
|
2021-12-30 09:04:32 +00:00
|
|
|
}
|
2021-08-27 09:17:18 +00:00
|
|
|
|
2022-05-28 04:05:36 +00:00
|
|
|
for (let index = ancestors.length - 1; index >= 0; index--) {
|
|
|
|
const cls = ancestors[index]
|
2021-08-27 09:17:18 +00:00
|
|
|
const attributes = this.attributes.get(cls)
|
|
|
|
if (attributes !== undefined) {
|
|
|
|
for (const [name, attr] of attributes) {
|
|
|
|
result.set(name, attr)
|
|
|
|
}
|
|
|
|
}
|
2021-08-26 17:59:08 +00:00
|
|
|
}
|
2021-08-27 09:17:18 +00:00
|
|
|
|
|
|
|
return result
|
2021-08-26 17:59:08 +00:00
|
|
|
}
|
|
|
|
|
2023-01-31 17:18:07 +00:00
|
|
|
getOwnAttributes (clazz: Ref<Classifier>): Map<string, AnyAttribute> {
|
|
|
|
const result = new Map<string, AnyAttribute>()
|
|
|
|
|
|
|
|
const attributes = this.attributes.get(clazz)
|
|
|
|
if (attributes !== undefined) {
|
|
|
|
for (const [name, attr] of attributes) {
|
|
|
|
result.set(name, attr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
getParentClass (_class: Ref<Class<Obj>>): Ref<Class<Obj>> {
|
|
|
|
const baseDomain = this.getDomain(_class)
|
|
|
|
const ancestors = this.getAncestors(_class)
|
|
|
|
let result: Ref<Class<Obj>> = _class
|
|
|
|
for (const ancestor of ancestors) {
|
|
|
|
try {
|
|
|
|
const domain = this.getClass(ancestor).domain
|
|
|
|
if (domain === baseDomain) {
|
|
|
|
result = ancestor
|
|
|
|
}
|
|
|
|
} catch {}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-12-01 20:25:28 +00:00
|
|
|
getAttribute (classifier: Ref<Classifier>, name: string): AnyAttribute {
|
|
|
|
const attr = this.findAttribute(classifier, name)
|
|
|
|
if (attr === undefined) {
|
|
|
|
throw new Error('attribute not found: ' + name)
|
|
|
|
}
|
|
|
|
return attr
|
|
|
|
}
|
|
|
|
|
2023-01-04 17:58:54 +00:00
|
|
|
public findAttribute (classifier: Ref<Classifier>, name: string): AnyAttribute | undefined {
|
2021-12-01 20:25:28 +00:00
|
|
|
const list = [classifier]
|
|
|
|
const visited = new Set<Ref<Classifier>>()
|
|
|
|
while (list.length > 0) {
|
|
|
|
const cl = list.shift() as Ref<Classifier>
|
|
|
|
if (addNew(visited, cl)) {
|
|
|
|
const attribute = this.attributes.get(cl)?.get(name)
|
|
|
|
if (attribute !== undefined) {
|
|
|
|
return attribute
|
|
|
|
}
|
|
|
|
// Check ancestorsOf
|
|
|
|
list.push(...this.ancestorsOf(cl))
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
|
|
|
}
|
2021-12-01 20:25:28 +00:00
|
|
|
}
|
2022-03-03 09:01:39 +00:00
|
|
|
|
2022-04-29 05:27:17 +00:00
|
|
|
updateLookupMixin<T extends Doc>(
|
|
|
|
_class: Ref<Class<T>>,
|
|
|
|
result: WithLookup<T>,
|
|
|
|
options?: FindOptions<T>
|
|
|
|
): WithLookup<T> {
|
2022-03-03 09:01:39 +00:00
|
|
|
const baseClass = this.getBaseClass(_class)
|
|
|
|
const vResult = baseClass !== _class ? this.as(result, _class) : result
|
|
|
|
const lookup = result.$lookup
|
|
|
|
if (lookup !== undefined) {
|
|
|
|
// We need to check if lookup type is mixin and cast to it if required.
|
|
|
|
const lu = options?.lookup as Lookup<Doc>
|
|
|
|
if (lu?._id !== undefined) {
|
|
|
|
for (const [k, v] of Object.entries(lu._id)) {
|
|
|
|
const _cl = getClass(v as ToClassRefT<T, keyof T>)
|
|
|
|
if (this.isMixin(_cl)) {
|
|
|
|
const mval = (lookup as any)[k]
|
|
|
|
if (mval !== undefined) {
|
|
|
|
if (Array.isArray(mval)) {
|
2022-04-29 05:27:17 +00:00
|
|
|
;(lookup as any)[k] = mval.map((it) => this.as(it, _cl))
|
2022-03-03 09:01:39 +00:00
|
|
|
} else {
|
2022-04-29 05:27:17 +00:00
|
|
|
;(lookup as any)[k] = this.as(mval, _cl)
|
2022-03-03 09:01:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-22 09:10:47 +00:00
|
|
|
for (const [k, v] of Object.entries(lu ?? {})) {
|
2022-03-03 09:01:39 +00:00
|
|
|
if (k === '_id') {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
const _cl = getClass(v as ToClassRefT<T, keyof T>)
|
|
|
|
if (this.isMixin(_cl)) {
|
|
|
|
const mval = (lookup as any)[k]
|
2022-03-04 14:42:02 +00:00
|
|
|
if (mval != null) {
|
2022-04-29 05:27:17 +00:00
|
|
|
;(lookup as any)[k] = this.as(mval, _cl)
|
2022-03-03 09:01:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return vResult
|
|
|
|
}
|
2022-05-04 14:54:32 +00:00
|
|
|
|
|
|
|
clone (obj: any): any {
|
2023-02-15 05:36:49 +00:00
|
|
|
if (typeof obj === 'undefined') {
|
|
|
|
return undefined
|
|
|
|
}
|
2022-05-04 14:54:32 +00:00
|
|
|
if (typeof obj === 'function') {
|
|
|
|
return obj
|
|
|
|
}
|
2023-01-18 05:14:59 +00:00
|
|
|
const isArray = Array.isArray(obj)
|
|
|
|
const result: any = isArray ? [] : Object.assign({}, obj)
|
2022-05-04 14:54:32 +00:00
|
|
|
for (const key in obj) {
|
|
|
|
// include prototype properties
|
|
|
|
const value = obj[key]
|
2023-01-18 05:14:59 +00:00
|
|
|
const type = getTypeOf(value)
|
2022-05-04 14:54:32 +00:00
|
|
|
if (type === 'Array') {
|
|
|
|
result[key] = this.clone(value)
|
2022-05-30 16:00:25 +00:00
|
|
|
} else if (type === 'Object') {
|
2022-05-04 14:54:32 +00:00
|
|
|
const m = Hierarchy.mixinClass(value)
|
|
|
|
const valClone = this.clone(value)
|
|
|
|
result[key] = m !== undefined ? this.as(valClone, m) : valClone
|
|
|
|
} else if (type === 'Date') {
|
|
|
|
result[key] = new Date(value.getTime())
|
|
|
|
} else {
|
2023-01-18 05:14:59 +00:00
|
|
|
if (isArray) {
|
|
|
|
result[key] = value
|
|
|
|
}
|
2022-05-04 14:54:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
2022-05-23 15:53:33 +00:00
|
|
|
|
|
|
|
domains (): Domain[] {
|
|
|
|
const classes = Array.from(this.classifiers.values()).filter(
|
|
|
|
(it) => this.isClass(it) || this._isMixin(it)
|
|
|
|
) as Class<Doc>[]
|
|
|
|
return (classes.map((it) => it.domain).filter((it) => it !== undefined) as Domain[]).filter(
|
|
|
|
(it, idx, array) => array.findIndex((pt) => pt === it) === idx
|
|
|
|
)
|
|
|
|
}
|
2023-01-04 17:58:54 +00:00
|
|
|
|
|
|
|
getClassifierProp (cl: Ref<Class<Doc>>, prop: string): any | undefined {
|
|
|
|
return this.classifierProperties.get(cl)?.[prop]
|
|
|
|
}
|
|
|
|
|
|
|
|
setClassifierProp (cl: Ref<Class<Doc>>, prop: string, value: any): void {
|
|
|
|
const cur = this.classifierProperties.get(cl)
|
|
|
|
this.classifierProperties.set(cl, { ...cur, [prop]: value })
|
|
|
|
}
|
2021-12-01 20:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function addNew<T> (val: Set<T>, value: T): boolean {
|
|
|
|
if (val.has(value)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
val.add(value)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
function addIf<T> (array: T[], value: T): void {
|
|
|
|
if (!array.includes(value)) {
|
|
|
|
array.push(value)
|
2021-08-03 16:55:52 +00:00
|
|
|
}
|
|
|
|
}
|
2022-03-03 09:01:39 +00:00
|
|
|
|
|
|
|
function getClass<T extends Doc> (vvv: ToClassRefT<T, keyof T>): Ref<Class<T>> {
|
|
|
|
if (Array.isArray(vvv)) {
|
|
|
|
return vvv[0]
|
|
|
|
}
|
|
|
|
return vvv
|
|
|
|
}
|