// // 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. // import type { Ref, Class, Obj, Domain, Mixin, Doc, AnyAttribute, Tx, TxCreateDoc, TxMixin } from './classes' import { ClassifierKind } from './classes' import { TxProcessor } from './tx' import core from './component' /** * @public */ export class Hierarchy { private readonly classes = new Map>, Class>() private readonly attributes = new Map>, Map>() private readonly descendants = new Map>, Ref>[]>() private readonly ancestors = new Map>, Ref>[]>() private readonly proxies = new Map>, ProxyHandler>() private createMixinProxyHandler (mixin: Ref>): ProxyHandler { const value = this.getClass(mixin) const ancestor = this.getClass(value.extends as Ref>) const ancestorProxy = ancestor.kind === ClassifierKind.MIXIN ? this.getMixinProxyHandler(ancestor._id) : null return { get (target: any, property: string, receiver: any): any { const value = target[mixin]?.[property] if (value === undefined) { return (ancestorProxy !== null) ? ancestorProxy.get?.(target, property, receiver) : target[property] } return value } } } private getMixinProxyHandler (mixin: Ref>): ProxyHandler { const handler = this.proxies.get(mixin) if (handler === undefined) { const handler = this.createMixinProxyHandler(mixin) this.proxies.set(mixin, handler) return handler } return handler } as(doc: D, mixin: Ref>): M { return new Proxy(doc, this.getMixinProxyHandler(mixin)) as M } getAncestors (_class: Ref>): Ref>[] { const result = this.ancestors.get(_class) if (result === undefined) { throw new Error('ancestors not found: ' + _class) } return result } getClass (_class: Ref>): Class { const data = this.classes.get(_class) if (data === undefined) { throw new Error('class not found: ' + _class) } return data } getDomain (_class: Ref>): Domain { const klazz = this.getClass(_class) if (klazz.domain !== undefined) { return klazz.domain } if (klazz.extends !== undefined) { const domain = this.getDomain(klazz.extends) klazz.domain = domain return domain } throw new Error('domain not found: ' + _class) } tx (tx: Tx): void { if (tx._class !== core.class.TxCreateDoc) { if (tx._class === core.class.TxMixin) { const mixinTx = tx as TxMixin if (tx.objectClass !== core.class.Class) { return } const obj = this.getClass(tx.objectId as Ref>) as any obj[mixinTx.mixin] = mixinTx.attributes } return } const createTx = tx as TxCreateDoc if (createTx.objectClass === core.class.Class) { const createTx = tx as TxCreateDoc> const _id = createTx.objectId this.classes.set(_id, TxProcessor.createDoc2Doc(createTx)) this.addAncestors(_id) this.addDescendant(_id) } else if (createTx.objectClass === core.class.Attribute) { const createTx = tx as TxCreateDoc this.addAttribute(TxProcessor.createDoc2Doc(createTx)) } } isDerived(_class: Ref>, from: Ref>): boolean { let cl: Ref> | undefined = _class while (cl !== undefined) { if (cl === from) return true const attrs = this.classes.get(cl) cl = attrs?.extends } return false } getDescendants(_class: Ref>): Ref>[] { const data = this.descendants.get(_class) if (data === undefined) { throw new Error('descendants not found: ' + _class) } return data } private addDescendant(_class: Ref>): void { const hierarchy = this.getAncestors(_class) for (const cls of hierarchy) { const list = this.descendants.get(cls) if (list === undefined) { this.descendants.set(cls, [_class]) } else { list.push(_class) } } } private addAncestors(_class: Ref>): void { let cl: Ref> | undefined = _class while (cl !== undefined) { const list = this.ancestors.get(_class) if (list === undefined) { this.ancestors.set(_class, [cl]) } else { list.push(cl) } const attrs = this.classes.get(cl) cl = attrs?.extends } } private addAttribute (attribute: AnyAttribute): void { const _class = attribute.attributeOf let attributes = this.attributes.get(_class) if (attributes === undefined) { attributes = new Map() this.attributes.set(_class, attributes) } attributes.set(attribute.name, attribute) } getAttribute (_class: Ref>, name: string): AnyAttribute { const attribute = this.attributes.get(_class)?.get(name) if (attribute === undefined) { const clazz = this.getClass(_class) if (clazz.extends !== undefined) { return this.getAttribute(clazz.extends, name) } else { throw new Error('attribute not found: ' + name) } } return attribute } }