UNPKG

@tripsnek/tmf

Version:

TypeScript Modeling Framework - A TypeScript port of the Eclipse Modeling Framework (EMF)

266 lines 8.54 kB
/** * Default implementation for all TMF Lists, and hence all * multi-valued fields. * * This implementation wraps around a simple Javascript Array. */ export class BasicEList { //set when the list belongs to an EObject owner; eFeatureId; //set when a feature on objects in the list points back to the list owningEObject inverseEFeatureID; //the underlying array containing all List elements _elements = []; constructor(elems, owner, featID, inverseFeatID) { this.owner = owner; this.eFeatureId = featID; this.inverseEFeatureID = inverseFeatID; if (elems) this._elements = [...elems]; } add(item, index) { //if there is an inverse reference (container or eopposite), the list must be unique if (!this.hasInverseFeature() || !this.contains(item)) { this.basicAdd(item, index); if (this.hasInverseFeature()) { this.inverseAdd(item); } } } //should only be used for new objects with no existing container (optimization) //may provide modest performance boost in certain cases fastContainmentAddHack(item) { this._elements.push(item); item['_eContainer'] = this.owner; item['_eContainingFeature'] = this.eFeatureId; } basicAdd(item, index) { if (!index && index !== 0) this._elements.push(item); else this._elements.splice(index, 0, item); } inverseAdd(item) { //handle containment inverse references (TODO: Should this be handled by the item's eInverseAdd?) if (this.isContainment()) { item.setEContainer(this.owner, this.eFeatureId); } //handle inverse references (which may also be explicit references to the container) if (this.inverseEFeatureID) { //Remove from existing EOpposite, if it is single-valued (many-many eopposites do not need to be removed) const inverseFeature = item .eClass() .getEStructuralFeature(this.inverseEFeatureID); if (!inverseFeature?.isMany()) { const curOpposite = item.eGet(inverseFeature); if (curOpposite) { curOpposite.eInverseRemove(item, this.eFeatureId); } } item.eInverseAdd(this.owner, this.inverseEFeatureID); } } remove(item) { if (item) { //remove item from the array const removed = this.basicRemove(item); //only proceed with inverse removal if removal was successful if (removed && this.hasInverseFeature()) { this.inverseRemove(item); } } } basicRemove(item) { let removed = false; this._elements.forEach((arrItem, index) => { if (item === arrItem) { this._elements.splice(index, 1); removed = true; } }); return removed; } inverseRemove(item) { //only directly set container to null if there is no inverse feature if (this.isContainment() && !this.inverseEFeatureID) { item.setEContainer(undefined, undefined); } //otherwise, do an inverse remove on the field (whether it is a container or not) else { item.eInverseRemove(this.owner, this.inverseEFeatureID); } } containsAll(c) { throw new Error('Method not implemented.'); } addAll(list) { if (Array.isArray(list)) { for (let i = 0; i < list.length; i++) { this.add(list[i]); } } else { // For other iterables, convert to array first const items = Array.from(list); for (let i = 0; i < items.length; i++) { this.add(items[i]); } } } equals(o) { throw new Error('Method not implemented.'); } size() { return this._elements.length; } isEmpty() { return this._elements.length === 0; } contains(o) { for (const obj of this._elements) { if (o === obj) return true; } return false; } get(index) { return this._elements[index]; } set(index, element) { if (!this.hasInverseFeature() && !this.isContainment()) { this._elements[index] = element; return element; } //TODO: This method MUST handle inverse references else { throw new Error('BasicEList.set(index,element) not implemented for EReferences with containment or inverses.'); } } /** * Indicate whether this list represents a containment relationship * between the list owningEObject and the list contents. */ isContainment() { if (!this.eFeatureId || !this.owner) return false; else { const feature = this.owner .eClass() .getEStructuralFeature(this.eFeatureId); if (!feature) { return false; } //return feature instanceof EReferenceImpl && feature.isContainment(); return feature.isContainment(); } } indexOf(o) { let idx = 0; for (const element of this._elements) { if (element === o) return idx; idx++; } return -1; } lastIndexOf(o) { throw new Error('Method not implemented.'); } removeAll(list) { const startSize = this.size(); if (Array.isArray(list)) { for (let i = 0; i < list.length; i++) { this.remove(list[i]); } } else { // For other iterables, convert to array first const items = Array.from(list); for (let i = 0; i < items.length; i++) { this.remove(items[i]); } } return startSize !== this.size(); } clear() { this.removeAll(Object.assign([], this._elements)); } //implementation of Iterable interface - simply returns the iterator for the wrapped array [Symbol.iterator]() { return this._elements[Symbol.iterator](); } /** * Indicates whether objects in the list have a feature which refers * back to this list's owningEObject. */ hasInverseFeature() { if (this.inverseEFeatureID || this.isContainment()) return true; return false; } //====================================================================== // TMF-specific methods (to work better with TypeScript) /** * Swaps the elements at the given positions, if they exist. * @param idx1 * @param idx2 */ swap(idx1, idx2) { if (this._elements.length > idx1 && this._elements.length > idx2) { const temp = this._elements[idx1]; this._elements[idx1] = this._elements[idx2]; this._elements[idx2] = temp; } } /** * Returns a new native TypeScript Array of the EList's elements */ elements() { const size = this.size(); const result = new Array(size); for (let i = 0; i < size; i++) { result[i] = this.get(i); } return result; } filter(predicate, thisArg) { const result = new BasicEList(); for (const element of this._elements.filter(predicate)) { result.add(element); } return result; } forEach(callbackfn, thisArg) { return this._elements.forEach(callbackfn, thisArg); } find(predicate, thisArg) { return this._elements.find(predicate); } some(arg0) { return this._elements.some(arg0); } map(func, thisArg) { const result = new BasicEList(); for (const element of this._elements.map(func)) { result.add(element); } return result; } last() { if (this._elements.length == 0) return undefined; return this._elements.slice(-1)[0]; } /** * Removes the last element of the List and returns it. */ pop() { if (this.size() === 0) return undefined; const popped = this.get(this.size() - 1); this.remove(popped); return popped; } } //# sourceMappingURL=basicelist.js.map