@ontola/memoized-hash-factory
Version:
RDF Factory optimized for memory usage.
192 lines (191 loc) • 6.51 kB
JavaScript
import { Feature, PlainFactory, TermType, } from "@ontologies/core";
import { murmur3 } from "murmurhash-js";
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.
*/
export class MemoizedHashFactory extends PlainFactory {
constructor(opts = {}) {
super({ supports: MemoizedHashFactory.FactorySupport, ...opts });
this.bnIndex = opts.bnIndex || 1;
this.memoizationMap = opts.memoizationMap || {};
this.seedBase = opts.seedBase || 0;
this.base = rdfBase(this);
}
blankNode(value) {
if (value && typeof value !== "string") {
throw createException('BlankNode', value);
}
const usedValue = value || `_:b${++this.bnIndex}`;
const id = this.id({ termType: "BlankNode", value: usedValue });
if (this.memoizationMap[id]) {
return this.memoizationMap[id];
}
const term = Object.create(this.base);
term.termType = TermType.BlankNode;
term.value = usedValue;
term.id = id;
this.memoizationMap[id] = term;
return term;
}
namedNode(value) {
if (typeof value !== "string") {
throw createException('NamedNode', value);
}
const id = this.id({ termType: "NamedNode", value });
if (this.memoizationMap[id]) {
return this.memoizationMap[id];
}
const term = Object.create(this.base);
term.termType = TermType.NamedNode;
term.value = value;
term.id = id;
this.memoizationMap[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 id = this.id({ termType: "Literal", value, datatype, language });
if (this.memoizationMap[id]) {
return this.memoizationMap[id];
}
const term = Object.create(this.base);
term.termType = TermType.Literal;
term.datatype = datatype;
term.language = language;
term.value = value;
term.id = id;
this.memoizationMap[id] = term;
return term;
}
quad(subject, predicate, object, graph) {
const usedGraph = graph || this.defaultGraph();
const id = murmur3((this.id(subject)
+ this.id(predicate)
+ this.id(object)
+ this.id(usedGraph)).toString(), this.seedBase + 3);
if (this.memoizationMap[id]) {
return this.memoizationMap[id];
}
const quad = Object.create(rdflibQuadPatch);
quad.id = id;
quad.subject = subject;
quad.predicate = predicate;
quad.object = object;
quad.graph = usedGraph;
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;
}
if (this.isQuad(term)) {
return murmur3((this.id(term.subject)
+ this.id(term.predicate)
+ this.id(term.object)
+ (term.graph ? this.id(term.graph) : 0)).toString(), this.seedBase + 3);
}
switch (term.termType) {
case TermType.BlankNode:
return murmur3(term.value, this.seedBase + 1);
case TermType.NamedNode:
return murmur3(term.value, this.seedBase + 2);
case TermType.Literal: {
const langOrDTId = term.language
? murmur3(term.language, this.seedBase)
: this.id(term.datatype);
return murmur3(term.value, this.seedBase + 4 + langOrDTId);
}
default:
return -1;
}
}
}
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,
};
export default new MemoizedHashFactory();