UNPKG

@ontola/memoized-hash-factory

Version:

RDF Factory optimized for memory usage.

192 lines (191 loc) 6.51 kB
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();