// @flow // Copyright 2016 Attic Labs, Inc. All rights reserved. // Licensed under the Apache License, version 2.0: // http://www.apache.org/licenses/LICENSE-2.0 import {invariant} from './assert.js'; import Ref from './ref.js'; import type {ValueReader} from './value-store.js'; import type {makeChunkFn} from './sequence-chunker.js'; import type Value from './value.js'; // eslint-disable-line no-unused-vars import type {AsyncIterator} from './async-iterator.js'; import {chunkSequence, chunkSequenceSync} from './sequence-chunker.js'; import Collection from './collection.js'; import {compare, equals} from './compare.js'; import {getTypeOfValue, makeMapType, makeUnionType} from './type.js'; import { OrderedKey, newOrderedMetaSequenceChunkFn, } from './meta-sequence.js'; import { OrderedSequence, OrderedSequenceCursor, OrderedSequenceIterator, } from './ordered-sequence.js'; import diff from './ordered-sequence-diff.js'; import {ValueBase} from './value.js'; import {Kind} from './noms-kind.js'; import type {EqualsFn} from './edit-distance.js'; import RollingValueHasher, {hashValueBytes} from './rolling-value-hasher.js'; export type MapEntry = [K, V]; const KEY = 0; const VALUE = 1; function newMapLeafChunkFn(vr: ?ValueReader): makeChunkFn { return (items: Array>) => { const key = new OrderedKey(items.length > 0 ? items[items.length - 1][KEY] : false); const seq = newMapLeafSequence(vr, items); const nm = Map.fromSequence(seq); return [nm, key, seq.length]; }; } function mapHashValueBytes(entry: MapEntry, rv: RollingValueHasher) { hashValueBytes(entry[KEY], rv); hashValueBytes(entry[VALUE], rv); } export function removeDuplicateFromOrdered(elems: Array, compare: (v1: T, v2: T) => number) : Array { const unique = []; let i = -1; let last = null; elems.forEach((elem: T) => { if (null === elem || undefined === elem || null === last || undefined === last || compare(last, elem) !== 0) { i++; } unique[i] = elem; last = elem; }); return unique; } function compareKeys(v1, v2) { return compare(v1[KEY], v2[KEY]); } function buildMapData( kvs: Array>): Array> { // TODO: Assert k & v are of correct type const entries = kvs.slice(); entries.sort(compareKeys); return removeDuplicateFromOrdered(entries, compareKeys); } export default class Map extends Collection> { constructor(kvs: Array> = []) { const seq = chunkSequenceSync( buildMapData(kvs), newMapLeafChunkFn(null), newOrderedMetaSequenceChunkFn(Kind.Map, null), mapHashValueBytes); invariant(seq instanceof OrderedSequence); super(seq); } async has(key: K): Promise { const cursor = await this.sequence.newCursorAtValue(key); return cursor.valid && equals(cursor.getCurrentKey().value(), key); } async _firstOrLast(last: boolean): Promise> { const cursor = await this.sequence.newCursorAt(null, false, last); if (!cursor.valid) { return undefined; } return cursor.getCurrent(); } first(): Promise> { return this._firstOrLast(false); } last(): Promise> { return this._firstOrLast(true); } async get(key: K): Promise { const cursor = await this.sequence.newCursorAtValue(key); if (!cursor.valid) { return undefined; } const entry = cursor.getCurrent(); return equals(entry[KEY], key) ? entry[VALUE] : undefined; } async forEach(cb: (v: V, k: K) => ?Promise): Promise { const cursor = await this.sequence.newCursorAt(null); const promises = []; return cursor.iter(entry => { promises.push(cb(entry[VALUE], entry[KEY])); return false; }).then(() => Promise.all(promises)).then(() => void 0); } iterator(): AsyncIterator> { return new OrderedSequenceIterator(this.sequence.newCursorAt(null)); } iteratorAt(k: K): AsyncIterator> { return new OrderedSequenceIterator(this.sequence.newCursorAtValue(k)); } _splice(cursor: OrderedSequenceCursor, insert: Array>, remove: number): Promise> { const vr = this.sequence.vr; return chunkSequence(cursor, vr, insert, remove, newMapLeafChunkFn(vr), newOrderedMetaSequenceChunkFn(Kind.Map, vr), mapHashValueBytes).then(s => Map.fromSequence(s)); } async set(key: K, value: V): Promise> { let remove = 0; const cursor = await this.sequence.newCursorAtValue(key, true); if (cursor.valid && equals(cursor.getCurrentKey().value(), key)) { const entry = cursor.getCurrent(); if (equals(entry[VALUE], value)) { return this; } remove = 1; } return this._splice(cursor, [[key, value]], remove); } async delete(key: K): Promise> { const cursor = await this.sequence.newCursorAtValue(key); if (cursor.valid && equals(cursor.getCurrentKey().value(), key)) { return this._splice(cursor, [], 1); } return this; } get size(): number { return this.sequence.numLeaves; } /** * Returns a 3-tuple [added, removed, modified] sorted by keys. */ diff(from: Map): Promise<[Array, Array, Array]> { return diff(from.sequence, this.sequence); } } export class MapLeafSequence extends OrderedSequence> { getKey(idx: number): OrderedKey { return new OrderedKey(this.items[idx][KEY]); } getCompareFn(other: OrderedSequence): EqualsFn { return (idx: number, otherIdx: number) => equals(this.items[idx][KEY], other.items[otherIdx][KEY]) && equals(this.items[idx][VALUE], other.items[otherIdx][VALUE]); } get chunks(): Array> { const chunks = []; for (const entry of this.items) { if (entry[KEY] instanceof ValueBase) { chunks.push(...entry[KEY].chunks); } if (entry[VALUE] instanceof ValueBase) { chunks.push(...entry[VALUE].chunks); } } return chunks; } } export function newMapLeafSequence(vr: ?ValueReader, items: Array>): MapLeafSequence { const kt = []; const vt = []; for (let i = 0; i < items.length; i++) { kt.push(getTypeOfValue(items[i][KEY])); vt.push(getTypeOfValue(items[i][VALUE])); } const t = makeMapType(makeUnionType(kt), makeUnionType(vt)); return new MapLeafSequence(vr, t, items); }