import {ICollection, IEachFunction} from './ICollection'; import {IPredicate, IPredicateFunction} from '../predicate/IPredicate'; import {IMapper} from '../mapper/IMapper'; import {IComparator} from '../comparator/IComparator'; import {DefaultMapper} from '../mapper/DefaultMapper'; import {Utils} from '../Utils'; const $$TS_COLLECTIONS_INITIAL_CAPACITY:string = '$$TS_COLLECTIONS_INITIAL_CAPACITY'; const $$TS_COLLECTIONS_CAPACITY_GROW_FACTOR:string = '$$TS_COLLECTIONS_CAPACITY_GROW_FACTOR'; export const INITIAL_CAPACITY:number = Utils.isDefined(window) && window[$$TS_COLLECTIONS_INITIAL_CAPACITY] ? parseInt(window[$$TS_COLLECTIONS_INITIAL_CAPACITY]) : 10000; export const CAPACITY_GROW_FACTOR:number = Utils.isDefined(window) && window[$$TS_COLLECTIONS_CAPACITY_GROW_FACTOR] ? parseInt(window[$$TS_COLLECTIONS_CAPACITY_GROW_FACTOR]) : 0.05; /** * Lodash support. */ function defineIndexProperties(object, start:number, end:number) { const properties:PropertyDescriptorMap = {} as PropertyDescriptorMap; for (let i = start; i < end; i++) { ((index:number) => properties[index.toString()] = { get: function () { return this.get(index); } })(i); } Object.defineProperties(object, properties); } export abstract class AbstractCollection implements ICollection { private currentCapacity:number = INITIAL_CAPACITY; /** * Lodash compatibility * @override */ length:number; /** * Lodash compatibility * @override */ [index:number]:TItem; /** * ES6 iterators compatibility * @override */ [Symbol.iterator] = this.iterator(); /** * @override */ public iterator():()=>Iterator { return () => { return this.getIteratorInstance(); }; } /** * @override */ public insert(position:number, item:TItem):ICollection { this.checkAndGrowUp(); return this; } /** * @override */ public addArray(items:Array):ICollection { this.checkAndGrowUp(); return this; } protected checkAndGrowUp() { if (this.length > this.currentCapacity) { const previousCapacity:number = this.currentCapacity; defineIndexProperties( this, previousCapacity, this.currentCapacity = previousCapacity + Math.round(this.length * CAPACITY_GROW_FACTOR) ); } } abstract getIteratorInstance():Iterator; abstract addAll(items:ICollection):ICollection; abstract get(index:number):TItem; abstract add(item:TItem):ICollection; abstract getSize():number; abstract remove(item:TItem):boolean; abstract removeAll():ICollection; abstract sort(comparator:IComparator):ICollection; /** * Compatible with an array */ abstract filter(predicate:IPredicate|IPredicateFunction):ICollection; abstract iterate(callback:IEachFunction, predicate?:IPredicate); abstract map(mapper:IMapper):Array; abstract toArray():Array; /** * Compatible with an array */ abstract find(predicate:IPredicate|IPredicateFunction):TItem; /** * Compatible with an array */ abstract forEach(callback:IEachFunction); abstract isEmpty():boolean; } export abstract class Collection extends AbstractCollection { constructor() { super(); } /** * @override */ public iterate(callback:IEachFunction, predicate?:IPredicate|IPredicateFunction) { for (let iterator:Iterator = this.iterator()(), iteratorResult:IteratorResult = iterator.next(), index = 0; !iteratorResult.done; iteratorResult = iterator.next()) { const value:TItem = iteratorResult.value; if (!predicate || this.isSuitable(value, predicate)) { if (callback.call(this, value, index++) === false) { return; } } } } /** * Compatible with an array */ public forEach(callback:IEachFunction) { this.iterate(callback); } /** * @override */ public addAll(items:ICollection):ICollection { items.iterate((item:TItem) => this.add(item)); return this; } /** * Compatible with an array * @override */ public filter(predicate:IPredicate|IPredicateFunction):ICollection { const filteredCollection:ICollection = this.newInstance(); this.iterate((o:TItem) => filteredCollection.add(o), predicate); return filteredCollection; } /** * @override */ public map(mapper:IMapper):Array { const ids:Array = []; this.iterate((item:TItem) => ids.push(mapper.map(item))); return ids; } /** * @override */ public toArray():Array { return this.map(new DefaultMapper()); } /** * @override */ public addArray(items:Array):ICollection { items.forEach((item:TItem) => this.add(item)); return this; } /** * Compatible with an array * @override */ public find(predicate:IPredicate|IPredicateFunction):TItem { let result:TItem = null; this.iterate((item:TItem) => { if (this.isSuitable(item, predicate)) { result = item; return false; } return true; }); return result; } /** * @override */ public isEmpty():boolean { return !this.getSize(); } /** * @override */ public get length():number { return this.getSize(); } protected newInstance():ICollection { return Reflect.construct(this.constructor, []); } private isSuitable(value:TItem, predicate:IPredicate|IPredicateFunction):boolean { return Utils.isFunction(predicate) ? (predicate as IPredicateFunction)(value) : (predicate as IPredicate).check(value); } } (() => defineIndexProperties(Collection.prototype, 0, INITIAL_CAPACITY))();