@ontola/memoized-factory
Version:
RDF Factory with neat memory usage and good cpu performance.
217 lines (214 loc) • 7.33 kB
JavaScript
import { PlainFactory, TermType, Feature } from '@ontologies/core';
const rdflibQuadPatch = {
get why() {
return this.graph;
},
};
const rdfBase = (factory) => ({
equals(other) {
return factory.equals.call(factory, this, other);
},
/* rdflib compat */
/** @deprecated */
toCanonical() {
return this;
},
/** @deprecated */
toNT() {
return factory.toNQ(this);
},
/** @deprecated */
toString() {
return factory.toNQ(this);
},
/** @deprecated */
get uri() {
return this.value;
},
/** @deprecated */
set uri(uri) {
this.value = uri;
},
});
const datatypes = {
boolean: "http://www.w3.org/2001/XMLSchema#boolean",
dateTime: "http://www.w3.org/2001/XMLSchema#dateTime",
decimal: "http://www.w3.org/2001/XMLSchema#decimal",
double: "http://www.w3.org/2001/XMLSchema#double",
integer: "http://www.w3.org/2001/XMLSchema#integer",
langString: "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString",
string: "http://www.w3.org/2001/XMLSchema#string",
};
function createException(type, value) {
const valueType = (value && typeof value === "object") ? value.constructor : typeof value;
return new TypeError(`Value of ${type} has to be type string, was value '${value}' of type '${valueType}'`);
}
/**
* RDF DataFactory which stores every value once at most.
*
* This version uses hashing which might be more CPU consuming but has deterministic id creation.
*/
class MemoizedHashFactory extends PlainFactory {
constructor(opts = {}) {
super({ supports: MemoizedHashFactory.FactorySupport, ...opts });
this.memoizationMap = {};
this.blankNodeMap = {};
this.namedNodeMap = {};
this.literalMap = {};
this.quadMap = {};
this.index = 1;
this.bnIndex = opts.bnIndex || 1;
this.base = rdfBase(this);
}
blankNode(value) {
if (value && typeof value !== "string") {
throw createException("BlankNode", value);
}
const usedValue = value || `_:b${++this.bnIndex}`;
const mapId = this.mapId({ termType: "BlankNode", value: usedValue });
if (mapId && this.blankNodeMap[mapId]) {
return this.blankNodeMap[mapId];
}
const term = Object.create(this.base);
term.termType = TermType.BlankNode;
term.value = usedValue;
term.id = this.index++;
this.blankNodeMap[mapId] = term;
this.memoizationMap[term.id] = term;
return term;
}
namedNode(value) {
if (typeof value !== "string") {
throw createException("NamedNode", value);
}
const mapId = this.mapId({ termType: "NamedNode", value });
if (this.namedNodeMap[mapId]) {
return this.namedNodeMap[mapId];
}
const term = Object.create(this.base);
term.termType = TermType.NamedNode;
term.value = value;
term.id = this.index++;
this.namedNodeMap[mapId] = term;
this.memoizationMap[term.id] = term;
return term;
}
defaultGraph() {
return this.namedNode("rdf:defaultGraph");
}
literal(value, languageOrDatatype) {
if (typeof value !== "string") {
return this.parseLiteral(value);
}
const isLangString = typeof languageOrDatatype === "string";
const datatype = isLangString
? this.namedNode(datatypes.langString)
: (languageOrDatatype || this.namedNode(datatypes.string));
if (datatype === undefined) {
throw Error("datatype must be defined");
}
const language = isLangString ? (languageOrDatatype || "") : "";
const mapId = this.mapId({ termType: "Literal", value, datatype, language });
if (this.literalMap[mapId]) {
return this.literalMap[mapId];
}
const term = Object.create(this.base);
term.termType = TermType.Literal;
term.datatype = datatype;
term.language = language;
term.value = value;
term.id = this.index++;
this.literalMap[mapId] = term;
this.memoizationMap[term.id] = term;
return term;
}
quad(subject, predicate, object, graph) {
const usedGraph = graph || this.defaultGraph();
const quadMapId = `${this.id(subject)},${this.id(predicate)},${this.id(object)},${(graph ? this.id(graph) : 0)}`;
if (this.quadMap[quadMapId]) {
return this.quadMap[quadMapId];
}
const quad = Object.create(rdflibQuadPatch);
quad.id = this.index++;
quad.subject = subject;
quad.predicate = predicate;
quad.object = object;
quad.graph = usedGraph;
this.quadMap[quadMapId] = quad;
this.memoizationMap[quad.id] = quad;
return quad;
}
equals(a, b) {
if (!a || !b) {
return a === b;
}
if (Array.isArray(a) && Array.isArray(b)) {
return this.id(a[0]) === this.id(b[0])
&& this.id(a[1]) === this.id(b[1])
&& this.id(a[2]) === this.id(b[2])
&& this.id(a[3]) === this.id(b[3]);
}
return this.id(a) === this.id(b);
}
fromId(id) {
return this.memoizationMap[id];
}
id(term) {
if (Array.isArray(term) || typeof term === "undefined") {
return -1;
}
if (term.id) {
return term.id;
}
const mapId = this.mapId(term);
if (this.isQuad(term)) {
const mapValue = this.quadMap[mapId];
return mapValue ? mapValue.id : this.index++;
}
switch (term.termType) {
case TermType.BlankNode: {
const mapValue = this.blankNodeMap[mapId];
return mapValue ? mapValue.id : this.index++;
}
case TermType.NamedNode: {
const mapValue = this.namedNodeMap[mapId];
return mapValue ? mapValue.id : this.index++;
}
case TermType.Literal: {
const mapValue = this.literalMap[mapId];
return mapValue ? mapValue.id : this.index++;
}
default:
return -1;
}
}
mapId(term) {
if (this.isQuad(term)) {
return `${this.id(term.subject)},${this.id(term.predicate)},${this.id(term.object)},${(term.graph ? this.id(term.graph) : 0)}`;
}
switch (term.termType) {
case TermType.BlankNode:
case TermType.NamedNode:
return term.value;
case TermType.Literal: {
return `${term.value},${term.language},${term.datatype.value}`;
}
default:
return undefined;
}
}
}
MemoizedHashFactory.FactorySupport = {
[Feature.collections]: false,
[Feature.defaultGraphType]: false,
[Feature.equalsMethod]: false,
[Feature.id]: true,
[Feature.idStamp]: true,
[Feature.identity]: true,
[Feature.reversibleId]: true,
[Feature.variableType]: false,
};
var index = new MemoizedHashFactory();
export default index;
export { MemoizedHashFactory };
//# sourceMappingURL=index.js.map