@tripsnek/tmf
Version:
TypeScript Modeling Framework - A TypeScript port of the Eclipse Modeling Framework (EMF)
266 lines • 8.54 kB
JavaScript
/**
* 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