initial commit
This commit is contained in:
commit
4b60ece582
327 changed files with 28286 additions and 0 deletions
7
libjs/wContainer/CMakeLists.txt
Normal file
7
libjs/wContainer/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
cmake_minimum_required(VERSION 2.8.12)
|
||||
|
||||
configure_file(abstractpair.js abstractpair.js)
|
||||
configure_file(abstractmap.js abstractmap.js)
|
||||
configure_file(abstractlist.js abstractlist.js)
|
||||
configure_file(abstractorder.js abstractorder.js)
|
||||
configure_file(abstractset.js abstractset.js)
|
204
libjs/wContainer/abstractlist.js
Normal file
204
libjs/wContainer/abstractlist.js
Normal file
|
@ -0,0 +1,204 @@
|
|||
"use strict";
|
||||
var Class = require("../utils/class");
|
||||
|
||||
var AbstractList = Class.inherit({
|
||||
"className": "AbstractList",
|
||||
"constructor": function(owning) {
|
||||
Class.fn.constructor.call(this);
|
||||
|
||||
if (!this.constructor.dataType) {
|
||||
throw new Error("An attempt to instantiate a list without declared member type");
|
||||
}
|
||||
|
||||
this._owning = owning !== false;
|
||||
this._begin = new ListNode(this);
|
||||
this._end = this._begin;
|
||||
this._size = 0;
|
||||
},
|
||||
"destructor": function() {
|
||||
this.clear();
|
||||
|
||||
Class.fn.destructor.call(this);
|
||||
},
|
||||
"begin": function() {
|
||||
return new ListIterator(this._begin);
|
||||
},
|
||||
"clear": function() {
|
||||
var node = this._begin;
|
||||
while (node !== this._end) {
|
||||
node = node._next;
|
||||
node._prev.destructor();
|
||||
}
|
||||
node._prev = null;
|
||||
|
||||
this._begin = node;
|
||||
this._size = 0;
|
||||
},
|
||||
"end": function() {
|
||||
return new ListIterator(this._end);
|
||||
},
|
||||
"erase": function(begin, end) {
|
||||
if (end === undefined) {
|
||||
end = begin.clone();
|
||||
end["++"]();
|
||||
}
|
||||
|
||||
if (begin._node === this._begin) {
|
||||
this._begin = end._node;
|
||||
end._node._prev = null;
|
||||
} else {
|
||||
begin._node._prev._next = end._node;
|
||||
end._node._prev = begin._node._prev;
|
||||
}
|
||||
|
||||
while(!begin["=="](end)) {
|
||||
|
||||
var node = begin._node;
|
||||
begin["++"]();
|
||||
--this._size;
|
||||
node.destructor();
|
||||
}
|
||||
},
|
||||
"insert": function(data, target) {
|
||||
if (target._node._list !== this) {
|
||||
throw new Error("An attempt to insert to list using iterator from another container");
|
||||
}
|
||||
if (!(data instanceof this.constructor.dataType)) {
|
||||
throw new Error("An attempt to insert wrong data type into list");
|
||||
}
|
||||
var node = new ListNode(this);
|
||||
node._data = data;
|
||||
|
||||
if (target._node === this._begin) {
|
||||
this._begin = node;
|
||||
} else {
|
||||
var bItr = target.clone();
|
||||
bItr["--"]();
|
||||
var before = bItr._node;
|
||||
before._next = node;
|
||||
node._prev = before;
|
||||
bItr.destructor();
|
||||
}
|
||||
|
||||
node._next = target._node;
|
||||
target._node._prev = node;
|
||||
|
||||
++this._size;
|
||||
},
|
||||
"pop_back": function() {
|
||||
if (this._begin === this._end) {
|
||||
throw new Error("An attempt to pop from empty list");
|
||||
}
|
||||
var node = this._end._prev;
|
||||
|
||||
if (node === this._begin) {
|
||||
this._begin = this._end;
|
||||
this._end._prev = null;
|
||||
} else {
|
||||
node._prev._next = this._end;
|
||||
this._end._prev = node._prev;
|
||||
}
|
||||
node.destructor();
|
||||
|
||||
--this._size;
|
||||
},
|
||||
"push_back": function(data) {
|
||||
if (!(data instanceof this.constructor.dataType)) {
|
||||
throw new Error("An attempt to insert wrong data type into list");
|
||||
}
|
||||
|
||||
var node = new ListNode(this);
|
||||
node._data = data;
|
||||
if (this._size === 0) {
|
||||
this._begin = node;
|
||||
} else {
|
||||
this._end._prev._next = node;
|
||||
node._prev = this._end._prev;
|
||||
}
|
||||
|
||||
node._next = this._end;
|
||||
this._end._prev = node;
|
||||
|
||||
this._size++;
|
||||
},
|
||||
"size": function() {
|
||||
return this._size;
|
||||
}
|
||||
});
|
||||
|
||||
var ListNode = Class.inherit({
|
||||
"className": "ListNode",
|
||||
"constructor": function(list) {
|
||||
Class.fn.constructor.call(this);
|
||||
|
||||
this._list = list
|
||||
this._data = null;
|
||||
this._next = null;
|
||||
this._prev = null;
|
||||
this._owning = list._owning !== false;
|
||||
},
|
||||
"destructor": function() {
|
||||
if (this._owning && (this._data instanceof Class)) {
|
||||
this._data.destructor();
|
||||
}
|
||||
delete this._list;
|
||||
|
||||
Class.fn.destructor.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
var ListIterator = Class.inherit({
|
||||
"className": "ListIterator",
|
||||
"constructor": function(node) {
|
||||
Class.fn.constructor.call(this);
|
||||
|
||||
this._node = node;
|
||||
},
|
||||
"++": function() {
|
||||
if (this._node._next === null) {
|
||||
throw new Error("An attempt to increment iterator, pointing at the end of container");
|
||||
}
|
||||
this._node = this._node._next;
|
||||
},
|
||||
"--": function() {
|
||||
if (this._node._prev === null) {
|
||||
throw new Error("An attempt to decrement iterator, pointing at the beginning of container");
|
||||
}
|
||||
this._node = this._node._prev;
|
||||
},
|
||||
"*": function() {
|
||||
if (this._node._data === null) {
|
||||
throw new Error("An attempt to dereference iterator, pointing at the end of container");
|
||||
}
|
||||
return this._node._data;
|
||||
},
|
||||
"==": function(other) {
|
||||
return this._node === other._node;
|
||||
},
|
||||
"clone": function() {
|
||||
return new ListIterator(this._node);
|
||||
}
|
||||
});
|
||||
|
||||
AbstractList.dataType = undefined;
|
||||
AbstractList.iterator = ListIterator;
|
||||
|
||||
AbstractList.template = function(data) {
|
||||
|
||||
if (!(data instanceof Function)) {
|
||||
throw new Error("Wrong argument to template a list");
|
||||
}
|
||||
|
||||
var List = AbstractList.inherit({
|
||||
"className": "List",
|
||||
"constructor": function(owning) {
|
||||
AbstractList.fn.constructor.call(this, owning);
|
||||
}
|
||||
});
|
||||
|
||||
List.dataType = data;
|
||||
|
||||
return List;
|
||||
};
|
||||
|
||||
module.exports = AbstractList;
|
142
libjs/wContainer/abstractmap.js
Normal file
142
libjs/wContainer/abstractmap.js
Normal file
|
@ -0,0 +1,142 @@
|
|||
"use strict";
|
||||
var Class = require("../utils/class");
|
||||
var RBTree = require("bintrees").RBTree;
|
||||
var AbstractPair = require("./abstractpair");
|
||||
|
||||
var AbstractMap = Class.inherit({
|
||||
"className": "AbstractMap",
|
||||
"constructor": function(owning) {
|
||||
Class.fn.constructor.call(this);
|
||||
|
||||
this._owning = owning !== false;
|
||||
this._data = new RBTree(function (a, b) {
|
||||
if (a[">"](b)) {
|
||||
return 1;
|
||||
}
|
||||
if (a["<"](b)) {
|
||||
return -1
|
||||
}
|
||||
if (a["=="](b)) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
},
|
||||
"destructor": function() {
|
||||
this.clear();
|
||||
|
||||
Class.fn.destructor.call(this);
|
||||
},
|
||||
"begin": function() {
|
||||
var itr = this._data.iterator();
|
||||
if (itr.next() !== null) {
|
||||
return new Iterator(itr);
|
||||
}
|
||||
return this.end();
|
||||
},
|
||||
"clear": function() {
|
||||
if (this._owning) {
|
||||
this._data.each(function(data) {
|
||||
data.destructor();
|
||||
});
|
||||
}
|
||||
|
||||
this._data.clear();
|
||||
},
|
||||
"each": function(funct) {
|
||||
this._data.each(funct);
|
||||
},
|
||||
"end": function() {
|
||||
return new Iterator(this._data.iterator());
|
||||
},
|
||||
"erase": function(itr) {
|
||||
var pair = itr["*"]();
|
||||
if (!this._data.remove(pair)) {
|
||||
throw new Error("An attempt to remove non-existing map element");
|
||||
}
|
||||
if (this._owning) {
|
||||
pair.destructor();
|
||||
}
|
||||
},
|
||||
"find": function(key) {
|
||||
var pair = new this.constructor.dataType(key);
|
||||
|
||||
var iter = this._data.findIter(pair);
|
||||
if (iter === null) {
|
||||
return this.end();
|
||||
}
|
||||
return new Iterator(iter);
|
||||
},
|
||||
"insert": function(key, value) {
|
||||
var pair = new this.constructor.dataType(key, value);
|
||||
|
||||
if (!this._data.insert(pair)) {
|
||||
throw new Error("An attempt to insert already existing element into a map");
|
||||
}
|
||||
},
|
||||
"r_each": function(funct) {
|
||||
this._data.reach(funct);
|
||||
},
|
||||
"size": function() {
|
||||
return this._data.size;
|
||||
},
|
||||
"lowerBound": function(key) {
|
||||
var pair = new this.constructor.dataType(key);
|
||||
|
||||
return new Iterator(this._data.lowerBound(pair));
|
||||
},
|
||||
"upperBound": function(key) {
|
||||
var pair = new this.constructor.dataType(key);
|
||||
|
||||
return new Iterator(this._data.upperBound(pair));
|
||||
}
|
||||
});
|
||||
|
||||
var Iterator = Class.inherit({
|
||||
"className": "MapIterator",
|
||||
"constructor": function(rbtree_iterator) {
|
||||
Class.fn.constructor.call(this);
|
||||
|
||||
this._itr = rbtree_iterator;
|
||||
},
|
||||
"++": function() {
|
||||
if ((this._itr._cursor === null)) {
|
||||
throw new Error("An attempt to increment an iterator pointing to the end of the map");
|
||||
}
|
||||
this._itr.next();
|
||||
},
|
||||
"--": function() {
|
||||
this._itr.prev();
|
||||
if ((this._itr._cursor === null)) {
|
||||
throw new Error("An attempt to decrement an iterator pointing to the beginning of the map");
|
||||
}
|
||||
},
|
||||
"==": function(other) {
|
||||
return this._itr.data() === other._itr.data();
|
||||
},
|
||||
"*": function() {
|
||||
if ((this._itr._cursor === null)) {
|
||||
throw new Error("An attempt to dereference an iterator pointing to the end of the map");
|
||||
}
|
||||
return this._itr.data();
|
||||
}
|
||||
});
|
||||
|
||||
AbstractMap.dataType = undefined;
|
||||
AbstractMap.iterator = Iterator;
|
||||
|
||||
AbstractMap.template = function(first, second) {
|
||||
var dt = AbstractPair.template(first, second);
|
||||
|
||||
var Map = AbstractMap.inherit({
|
||||
"className": "Map",
|
||||
"constructor": function(owning) {
|
||||
AbstractMap.fn.constructor.call(this, owning);
|
||||
}
|
||||
});
|
||||
|
||||
Map.dataType = dt;
|
||||
|
||||
return Map;
|
||||
};
|
||||
|
||||
module.exports = AbstractMap;
|
104
libjs/wContainer/abstractorder.js
Normal file
104
libjs/wContainer/abstractorder.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
"use strict";
|
||||
var Class = require("../utils/class");
|
||||
var AbstractMap = require("./abstractmap");
|
||||
var AbstractList = require("./abstractlist");
|
||||
|
||||
var AbstractOrder = Class.inherit({
|
||||
"className": "AbstractOrder",
|
||||
"constructor": function(owning) {
|
||||
Class.fn.constructor.call(this);
|
||||
|
||||
if (!this.constructor.dataType) {
|
||||
throw new Error("An attempt to instantiate an order without declared member type");
|
||||
}
|
||||
|
||||
var Map = AbstractMap.template(this.constructor.dataType, this.constructor.iterator);
|
||||
var List = AbstractList.template(this.constructor.dataType);
|
||||
|
||||
this._owning = owning !== false;
|
||||
|
||||
this._rmap = new Map(this._owning);
|
||||
this._order = new List(false);
|
||||
},
|
||||
"destructor": function() {
|
||||
this._rmap.destructor();
|
||||
this._order.destructor();
|
||||
|
||||
Class.fn.destructor.call(this);
|
||||
},
|
||||
"begin": function() {
|
||||
return this._order.begin();
|
||||
},
|
||||
"clear": function() {
|
||||
this._rmap.clear();
|
||||
this._order.clear();
|
||||
},
|
||||
"end": function() {
|
||||
return this._order.end();
|
||||
},
|
||||
"erase": function(element) {
|
||||
var itr = this._rmap.find(element);
|
||||
|
||||
if (itr["=="](this._rmap.end())) {
|
||||
throw new Error("An attempt to erase non existing element from an order");
|
||||
}
|
||||
|
||||
var pair = itr["*"]();
|
||||
|
||||
this._order.erase(pair.second);
|
||||
this._rmap.erase(itr);
|
||||
},
|
||||
"find": function(data) {
|
||||
var itr = this._rmap.find(data);
|
||||
|
||||
if (itr["=="](this._rmap.end())) {
|
||||
return this.end();
|
||||
}
|
||||
return itr["*"]().second;
|
||||
},
|
||||
"insert": function(data, target) {
|
||||
var itr = this._rmap.find(target);
|
||||
|
||||
if (itr["=="](this._rmap.end())) {
|
||||
throw new Error("An attempt to insert element before the non existing one");
|
||||
}
|
||||
|
||||
var pair = itr["*"]();
|
||||
this._order.insert(data, pair.second);
|
||||
|
||||
var pointer = pair.second.clone()["--"]();
|
||||
this._rmap.insert(data, pointer);
|
||||
},
|
||||
"push_back": function(data) {
|
||||
this._order.push_back(data);
|
||||
var itr = this._order.end();
|
||||
itr["--"]();
|
||||
|
||||
this._rmap.insert(data, itr);
|
||||
},
|
||||
"size": function() {
|
||||
return this._order.size();
|
||||
}
|
||||
});
|
||||
|
||||
AbstractOrder.dataType = undefined;
|
||||
AbstractOrder.iterator = AbstractList.iterator;
|
||||
|
||||
AbstractOrder.template = function(data) {
|
||||
if (!(data instanceof Function)) {
|
||||
throw new Error("Wrong argument to template an order");
|
||||
}
|
||||
|
||||
var Order = AbstractOrder.inherit({
|
||||
"className": "Order",
|
||||
"constructor": function(owning) {
|
||||
AbstractOrder.fn.constructor.call(this, owning);
|
||||
}
|
||||
});
|
||||
|
||||
Order.dataType = data;
|
||||
|
||||
return Order;
|
||||
}
|
||||
|
||||
module.exports = AbstractOrder
|
111
libjs/wContainer/abstractpair.js
Normal file
111
libjs/wContainer/abstractpair.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
"use strict";
|
||||
var Class = require("../utils/class");
|
||||
|
||||
var AbstractPair = Class.inherit({
|
||||
"className": "AbstractPair",
|
||||
"constructor": function(first, second) {
|
||||
Class.fn.constructor.call(this);
|
||||
|
||||
if (!this.constructor.firstType || !this.constructor.secondType) {
|
||||
throw new Error("An attempt to instantiate a pair without declared member types");
|
||||
}
|
||||
if (!(first instanceof this.constructor.firstType)) {
|
||||
throw new Error("An attempt to construct a pair from wrong arguments");
|
||||
}
|
||||
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
},
|
||||
"destructor": function() {
|
||||
this.first.destructor();
|
||||
if (this.second) {
|
||||
this.second.destructor();
|
||||
}
|
||||
|
||||
Class.fn.destructor.call(this);
|
||||
},
|
||||
"<": function(other) {
|
||||
if (!(other instanceof this.constructor)) {
|
||||
throw new Error("Can't compare pairs with different content types");
|
||||
}
|
||||
if (this.constructor.complete) {
|
||||
if (this.first["<"](other.first)) {
|
||||
return true;
|
||||
} else if(this.first["=="](other.first)) {
|
||||
this.second["<"](other.second);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return this.first["<"](other.first);
|
||||
}
|
||||
},
|
||||
">": function(other) {
|
||||
if (!(other instanceof this.constructor)) {
|
||||
throw new Error("Can't compare pairs with different content types");
|
||||
}
|
||||
if (this.constructor.complete) {
|
||||
if (this.first[">"](other.first)) {
|
||||
return true;
|
||||
} else if(this.first["=="](other.first)) {
|
||||
this.second[">"](other.second);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return this.first[">"](other.first);
|
||||
}
|
||||
},
|
||||
"==": function(other) {
|
||||
if (!(other instanceof this.constructor)) {
|
||||
throw new Error("Can't compare pairs with different content types");
|
||||
}
|
||||
if (this.constructor.complete) {
|
||||
return this.first["=="](other.first) && this.second["=="](other.second);
|
||||
} else {
|
||||
return this.first["=="](other.first);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AbstractPair.firstType = undefined;
|
||||
AbstractPair.secondType = undefined;
|
||||
AbstractPair.complete = false;
|
||||
|
||||
AbstractPair.template = function(first, second, complete) {
|
||||
if (!(first instanceof Function) || !(second instanceof Function)) {
|
||||
throw new Error("An attempt to create template pair from wrong arguments");
|
||||
}
|
||||
if (
|
||||
!(first.prototype["<"] instanceof Function) ||
|
||||
!(first.prototype[">"] instanceof Function) ||
|
||||
!(first.prototype["=="] instanceof Function)
|
||||
)
|
||||
{
|
||||
throw new Error("Not acceptable first type");
|
||||
}
|
||||
if (
|
||||
complete &&
|
||||
(
|
||||
!(second.prototype["<"] instanceof Function) ||
|
||||
!(second.prototype[">"] instanceof Function) ||
|
||||
!(second.prototype["=="] instanceof Function)
|
||||
)
|
||||
)
|
||||
{
|
||||
throw new Error("Not acceptable second type");
|
||||
}
|
||||
var Pair = AbstractPair.inherit({
|
||||
"className": "Pair",
|
||||
"constructor": function(first, second) {
|
||||
AbstractPair.fn.constructor.call(this, first, second);
|
||||
}
|
||||
});
|
||||
Pair.firstType = first;
|
||||
Pair.secondType = second;
|
||||
Pair.complete = complete === true;
|
||||
|
||||
return Pair;
|
||||
};
|
||||
|
||||
module.exports = AbstractPair;
|
143
libjs/wContainer/abstractset.js
Normal file
143
libjs/wContainer/abstractset.js
Normal file
|
@ -0,0 +1,143 @@
|
|||
"use strict";
|
||||
var Class = require("../utils/class");
|
||||
var RBTree = require("bintrees").RBTree;
|
||||
|
||||
var AbstractSet = Class.inherit({
|
||||
"className": "AbstractSet",
|
||||
"constructor": function(owning) {
|
||||
Class.fn.constructor.call(this);
|
||||
|
||||
this._owning = owning !== false;
|
||||
this._data = new RBTree(function (a, b) {
|
||||
if (a[">"](b)) {
|
||||
return 1;
|
||||
}
|
||||
if (a["<"](b)) {
|
||||
return -1
|
||||
}
|
||||
if (a["=="](b)) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
},
|
||||
"destructor": function() {
|
||||
this.clear();
|
||||
|
||||
Class.fn.destructor.call(this);
|
||||
},
|
||||
"begin": function() {
|
||||
var itr = this._data.iterator();
|
||||
if (itr.next() !== null) {
|
||||
return new Iterator(itr);
|
||||
}
|
||||
return this.end();
|
||||
},
|
||||
"clear": function() {
|
||||
if (this._owning) {
|
||||
this._data.each(function(data) {
|
||||
data.destructor();
|
||||
});
|
||||
}
|
||||
|
||||
this._data.clear();
|
||||
},
|
||||
"each": function(funct) {
|
||||
this._data.each(funct);
|
||||
},
|
||||
"end": function() {
|
||||
return new Iterator(this._data.iterator());
|
||||
},
|
||||
"erase": function(itr) {
|
||||
var value = itr["*"]();
|
||||
if (!this._data.remove(value)) {
|
||||
throw new Error("An attempt to remove non-existing set element");
|
||||
}
|
||||
if (this._owning) {
|
||||
value.destructor();
|
||||
}
|
||||
},
|
||||
"find": function(key) {
|
||||
if (!(key instanceof this.constructor.dataType)) {
|
||||
throw new Error("An attempt to find wrong value type in the set");
|
||||
}
|
||||
var iter = this._data.findIter(key);
|
||||
if (iter === null) {
|
||||
return this.end();
|
||||
}
|
||||
return new Iterator(iter);
|
||||
},
|
||||
"insert": function(value) {
|
||||
if (!(value instanceof this.constructor.dataType)) {
|
||||
throw new Error("An attempt to insert wrong value type to set");
|
||||
}
|
||||
if (!this._data.insert(value)) {
|
||||
throw new Error("An attempt to insert already existing element into a set");
|
||||
}
|
||||
},
|
||||
"r_each": function(funct) {
|
||||
this._data.reach(funct);
|
||||
},
|
||||
"size": function() {
|
||||
return this._data.size;
|
||||
},
|
||||
"lowerBound": function(key) {
|
||||
if (!(key instanceof this.constructor.dataType)) {
|
||||
throw new Error("An attempt to find wrong value type in the set");
|
||||
}
|
||||
return new Iterator(this._data.lowerBound(key));
|
||||
},
|
||||
"upperBound": function(key) {
|
||||
if (!(key instanceof this.constructor.dataType)) {
|
||||
throw new Error("An attempt to find wrong value type in the set");
|
||||
}
|
||||
return new Iterator(this._data.upperBound(key));
|
||||
}
|
||||
});
|
||||
|
||||
var Iterator = Class.inherit({
|
||||
"className": "SetIterator",
|
||||
"constructor": function(rbtree_iterator) {
|
||||
Class.fn.constructor.call(this);
|
||||
|
||||
this._itr = rbtree_iterator;
|
||||
},
|
||||
"++": function() {
|
||||
if ((this._itr._cursor === null)) {
|
||||
throw new Error("An attempt to increment an iterator pointing to the end of the set");
|
||||
}
|
||||
this._itr.next();
|
||||
},
|
||||
"--": function() {
|
||||
this._itr.prev();
|
||||
if ((this._itr._cursor === null)) {
|
||||
throw new Error("An attempt to decrement an iterator pointing to the beginning of the set");
|
||||
}
|
||||
},
|
||||
"==": function(other) {
|
||||
return this._itr.data() === other._itr.data();
|
||||
},
|
||||
"*": function() {
|
||||
if ((this._itr._cursor === null)) {
|
||||
throw new Error("An attempt to dereference an iterator pointing to the end of the set");
|
||||
}
|
||||
return this._itr.data();
|
||||
}
|
||||
});
|
||||
|
||||
AbstractSet.dataType = undefined;
|
||||
AbstractSet.iterator = Iterator;
|
||||
|
||||
AbstractSet.template = function(type) {
|
||||
var Set = AbstractSet.inherit({
|
||||
"className": "Set",
|
||||
"constructor": function(owning) {
|
||||
AbstractSet.fn.constructor.call(this, owning);
|
||||
}
|
||||
});
|
||||
|
||||
Set.dataType = type;
|
||||
|
||||
return Set;
|
||||
};
|
||||
|
||||
module.exports = AbstractSet;
|
Loading…
Add table
Add a link
Reference in a new issue