// // 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 { AnyAttribute, Class, Classifier, Doc, Domain, Interface, Mixin, Obj, Ref } from './classes' import { ClassifierKind } from './classes' import core from './component' import type { Tx, TxCreateDoc, TxMixin } from './tx' import { TxProcessor } from './tx' /** * @public */ export class Hierarchy { private readonly classifiers = new Map, Classifier>() 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.classifiers.get(_class) if (data === undefined || this.isInterface(data)) { throw new Error('class not found: ' + _class) } return data } getInterface (_interface: Ref>): Interface { const data = this.classifiers.get(_interface) if (data === undefined || !this.isInterface(data)) { throw new Error('interface not found: ' + _interface) } return data } getDomain (_class: Ref>): Domain { const klazz = this.getClass(_class) if (klazz.domain !== undefined) { return klazz.domain } klazz.domain = this.findDomain(klazz) return klazz.domain } private findDomain (klazz: Class): Domain { let _klazz = klazz while (_klazz.extends !== undefined) { _klazz = this.getClass(_klazz.extends) if (_klazz.domain !== undefined) { return _klazz.domain } } throw new Error(`domain not found: ${klazz._id} `) } tx (tx: Tx): void { switch (tx._class) { case core.class.TxCreateDoc: this.txCreateDoc(tx as TxCreateDoc) return case core.class.TxMixin: this.txMixin(tx as TxMixin) } } private txCreateDoc (tx: TxCreateDoc): void { if (tx.objectClass === core.class.Class || tx.objectClass === core.class.Interface) { const _id = tx.objectId as Ref this.classifiers.set(_id, TxProcessor.createDoc2Doc(tx as TxCreateDoc)) this.addAncestors(_id) this.addDescendant(_id) } else if (tx.objectClass === core.class.Attribute) { const createTx = tx as TxCreateDoc this.addAttribute(TxProcessor.createDoc2Doc(createTx)) } } private txMixin (tx: TxMixin): void { if (tx.objectClass === core.class.Class) { const obj = this.getClass(tx.objectId as Ref>) as any obj[tx.mixin] = tx.attributes } } /** * Check if passed _class is derived from `from` class. * It will iterave over parents. */ isDerived(_class: Ref>, from: Ref>): boolean { let cl: Ref> | undefined = _class while (cl !== undefined) { if (cl === from) return true cl = this.getClass(cl).extends } return false } /** * Check if passed _class implements passed interfaces `from`. * It will check for class parents and they interfaces. */ isImplements(_class: Ref>, from: Ref>): boolean { let cl: Ref> | 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(extendsOrImplements: Ref>[], from: Ref>): boolean { const result: Ref>[] = [] const toVisit = [...extendsOrImplements] while (toVisit.length > 0) { const ref = toVisit.shift() as Ref> if (ref === from) { return true } addIf(result, ref) toVisit.push(...this.ancestorsOf(ref)) } 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 { const cl: Ref[] = [_class] const visited = new Set>() while (cl.length > 0) { const classifier = cl.shift() as Ref if (addNew(visited, classifier)) { const list = this.ancestors.get(_class) if (list === undefined) { this.ancestors.set(_class, [classifier]) } else { addIf(list, classifier) } cl.push(...this.ancestorsOf(classifier)) } } } /** * Return extends and implemnets as combined list of references */ private ancestorsOf (classifier: Ref): Ref[] { const attrs = this.classifiers.get(classifier) const result: Ref[] = [] if (this.isClass(attrs)) { const cls = attrs as Class if (cls.extends !== undefined) { result.push(cls.extends) } result.push(...(cls.implements ?? [])) } if (this.isInterface(attrs)) { result.push(...((attrs as Interface).extends ?? [])) } return result } private isClass (attrs?: Classifier): boolean { return attrs?.kind === ClassifierKind.CLASS } private isInterface (attrs?: Classifier): boolean { return attrs?.kind === ClassifierKind.INTERFACE } 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) } getAllAttributes (clazz: Ref): Map { const result = new Map() const ancestors = this.getAncestors(clazz) for (const cls of ancestors) { const attributes = this.attributes.get(cls) if (attributes !== undefined) { for (const [name, attr] of attributes) { result.set(name, attr) } } } return result } getAttribute (classifier: Ref, name: string): AnyAttribute { const attr = this.findAttribute(classifier, name) if (attr === undefined) { throw new Error('attribute not found: ' + name) } return attr } private findAttribute (classifier: Ref, name: string): AnyAttribute | undefined { const list = [classifier] const visited = new Set>() while (list.length > 0) { const cl = list.shift() as Ref if (addNew(visited, cl)) { const attribute = this.attributes.get(cl)?.get(name) if (attribute !== undefined) { return attribute } // Check ancestorsOf list.push(...this.ancestorsOf(cl)) } } } } function addNew (val: Set, value: T): boolean { if (val.has(value)) { return false } val.add(value) return true } function addIf (array: T[], value: T): void { if (!array.includes(value)) { array.push(value) } }