UNPKG

sprotty

Version:

A next-gen framework for graphical views

160 lines (142 loc) 5.23 kB
/******************************************************************************** * Copyright (c) 2018 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * This Source Code may also be made available under the following Secondary * Licenses when the conditions for such availability set forth in the Eclipse * Public License v. 2.0 are satisfied: GNU General Public License, version 2 * with the GNU Classpath Exception which is available at * https://www.gnu.org/software/classpath/license.html. * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /** * An iterable that allows filtering, mapping values etc. with a fluent API. * Arrays conform to this interface, so an array can be passed at every place where * a FluentIterable is expected. */ export interface FluentIterable<T> extends Iterable<T> { filter(callback: (element: T) => boolean): FluentIterable<T> map<T2>(callback: (element: T) => T2): FluentIterable<T2> forEach(callback: (element: T, index: number) => void): void indexOf(element: any): number } /** * A helper class that allows to easily create fluent iterables. */ export class FluentIterableImpl<S, T> implements FluentIterable<T> { constructor(private readonly startFn: () => S, private readonly nextFn: (state: S) => IteratorResult<T>) {} [Symbol.iterator]() { const iterator = { state: this.startFn(), next: () => this.nextFn(iterator.state), [Symbol.iterator]: () => iterator }; return iterator; } filter(callback: (element: T) => boolean): FluentIterable<T> { return filterIterable(this, callback); } map<T2>(callback: (element: T) => T2): FluentIterable<T2> { return mapIterable(this, callback); } forEach(callback: (element: T, index: number) => void): void { const iterator = this[Symbol.iterator](); let index = 0; let result: IteratorResult<T>; do { result = iterator.next(); if (result.value !== undefined) callback(result.value, index); index++; } while (!result.done); } indexOf(element: any): number { const iterator = this[Symbol.iterator](); let index = 0; let result: IteratorResult<T>; do { result = iterator.next(); if (result.value === element) return index; index++; } while (!result.done); return -1; } } /** * Converts a FluentIterable into an array. If the input is an array, it is returned unchanged. */ export function toArray<T>(input: FluentIterable<T>): T[] { if (input.constructor === Array) { return input as T[]; } const result: T[] = []; input.forEach(element => result.push(element)); return result; } export const DONE_RESULT: IteratorResult<any> = Object.freeze({ done: true, value: undefined }); /** * Create a fluent iterable that filters the content of the given iterable or array. */ export function filterIterable<T>(input: Iterable<T> | ArrayLike<T>, callback: (element: T) => boolean): FluentIterable<T> { return new FluentIterableImpl( () => createIterator(input), (iterator) => { let result: IteratorResult<T>; do { result = iterator.next(); } while (!result.done && !callback(result.value)); return result; } ); } /** * Create a fluent iterable that maps the content of the given iterable or array. */ export function mapIterable<T1, T2>(input: Iterable<T1> | ArrayLike<T1>, callback: (element: T1) => T2): FluentIterable<T2> { return new FluentIterableImpl( () => createIterator(input), (iterator) => { const { done, value } = iterator.next(); if (done) return DONE_RESULT; else return { done: false, value: callback(value) }; } ); } /** * Create an iterator for the given iterable or array. */ function createIterator<T>(collection: Iterable<T> | ArrayLike<T>): Iterator<T> { const method = (collection as Iterable<T>)[Symbol.iterator]; if (typeof method === 'function') { return method.call(collection); } const length = (collection as ArrayLike<T>).length; if (typeof length === 'number' && length >= 0) { return new ArrayIterator(collection as ArrayLike<T>); } return { next: () => DONE_RESULT }; } /** * Iterator implementation for arrays. */ class ArrayIterator<T> implements IterableIterator<T> { constructor(private readonly array: ArrayLike<T>) {} private index = 0; next(): IteratorResult<T> { if (this.index < this.array.length) return { done: false, value: this.array[this.index++] }; else return DONE_RESULT; } [Symbol.iterator]() { return this; } }